一键导入
add-model
Add or update model/provider metadata, presets, canonical IDs, provider mappings, grouped exports, and related tests for @hebo-ai/gateway.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Add or update model/provider metadata, presets, canonical IDs, provider mappings, grouped exports, and related tests for @hebo-ai/gateway.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | add-model |
| description | Add or update model/provider metadata, presets, canonical IDs, provider mappings, grouped exports, and related tests for @hebo-ai/gateway. |
Comprehensive guide for adding new models or providers to the @hebo-ai/gateway model catalog.
Invoke this skill when:
Gather authoritative metadata from two sources:
Use the per-model endpoints endpoint to look up a single model without downloading the entire catalog:
GET https://openrouter.ai/api/v1/models/{model_id}/endpoints
The {model_id} uses the OpenRouter model ID format (e.g., anthropic/claude-sonnet-4.6, openai/gpt-4.1, meta-llama/llama-4-maverick). If you don't know the exact ID, search the full list with grep:
curl -s 'https://openrouter.ai/api/v1/models' | grep -oi '"id":"[^"]*<SEARCH_TERM>[^"]*"'
Replace <SEARCH_TERM> with the model name you're looking for (e.g., sonnet, gpt-4, llama).
Response structure (data object):
id — OpenRouter model ID (e.g., anthropic/claude-sonnet-4.6)name — display namecreated — Unix timestampdescription — model descriptionarchitecture.input_modalities / output_modalities — e.g., ["text", "image"] / ["text"]endpoints[] — array of provider endpoints, each with:
provider_name — e.g., "Anthropic", "Google", "Amazon Bedrock", "Azure"context_length, max_completion_tokenspricing.prompt, pricing.completion, pricing.input_cache_read, pricing.input_cache_writesupported_parameters[] — e.g., ["reasoning", "tools", "tool_choice", "structured_outputs"]The full api.json (~1.8 MB) is organized as { [provider]: { models: { [model_id]: {...} } } }. Pipe through grep to extract what you need without loading everything into context:
# Find model IDs matching a pattern — replace <SEARCH_TERM> with the model name
curl -s 'https://models.dev/api.json' | grep -oi '"id":"[^"]*<SEARCH_TERM>[^"]*"'
# Extract a specific model entry (grab ~20 lines after the model ID)
curl -s 'https://models.dev/api.json' | grep -A 20 '"<MODEL_ID>"'
Each model entry has:
release_date (YYYY-MM-DD), knowledge (cutoff date YYYY-MM-DD)limit.context (context window), limit.output (max output tokens)reasoning (boolean), tool_call (boolean)modalities.input / modalities.outputcost.input, cost.output, cost.cache_read, cost.cache_write (per million tokens)family — model family namePrefer models.dev for release_date, knowledge cutoff, and capability booleans. Use OpenRouter's per-model endpoint for provider availability and supported parameters. If a model isn't found in models.dev, use WebSearch to find it on the provider's official docs.
Determine these attributes:
| Attribute | Values | Notes |
|---|---|---|
| Type | text-generation or embedding | Determines output modality |
| Input modalities | text, image, file, audio, video, pdf | What the model accepts |
| Output modalities | text, image, audio, video, embedding | What the model produces |
| Capabilities | attachments, reasoning, tool_call, structured_output, temperature | Feature flags |
Canonical IDs follow the format: vendor/model-name
Rules:
anthropic/, openai/, google/, meta/, cohere/, amazon/, minimax/, xai/, voyage/claude-opus-4.7, gpt-5.4-mini, -nano, -lite-pro, -chat, -codex, -reasoningAdd the canonical ID to CANONICAL_MODEL_IDS in src/models/types.ts, grouped under the correct provider comment block. Keep entries sorted by version within each provider section.
src/models/<family>/presets.ts where <family> matches the vendor namespace.
import { presetFor } from "../../utils/preset";
import type { CanonicalModelId } from "../types";
import type { CatalogModel } from "../types";
// Reuse or define a base configuration for the family
const FAMILY_BASE = {
modalities: {
input: ["text", "image", "file"] as const,
output: ["text"] as const,
},
capabilities: ["attachments", "tool_call", "structured_output", "temperature"] as const,
context: 200000,
providers: ["provider-id"] as const,
} satisfies CatalogModel;
export const modelName = presetFor<CanonicalModelId, CatalogModel>()("vendor/model-id" as const, {
...FAMILY_BASE,
name: "Display Name",
created: "YYYY-MM-DD", // From models.dev release_date
knowledge: "YYYY-MM", // From models.dev knowledge
// Override base fields as needed:
// capabilities: [...FAMILY_BASE.capabilities, "reasoning"] as const,
// context: 1000000,
// providers: ["provider1", "provider2"] as const,
} satisfies CatalogModel);
export const embeddingModel = presetFor<CanonicalModelId, CatalogModel>()(
"vendor/embedding-model-id" as const,
{
name: "Embedding Model Name",
created: "YYYY-MM-DD",
context: 8192,
modalities: {
input: ["text"] as const,
output: ["embedding"] as const,
},
providers: ["provider-id"] as const,
} satisfies CatalogModel,
);
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Optional | Human-readable display name |
created | string | Optional | Release date (YYYY-MM-DD format) |
knowledge | string | Optional | Knowledge cutoff (YYYY-MM format) |
modalities.input | readonly string[] | Optional | Supported input types |
modalities.output | readonly string[] | Optional | Output types ("text" or "embedding") |
context | number | Optional | Context window in tokens |
capabilities | readonly string[] | Optional | Feature flags |
providers | readonly string[] | Required | Which providers serve this model |
Each family preset file exports grouped collections. Update these consistently:
const familyAtomic = {
v1: [model1a, model1b],
v2: [model2a, model2b],
} as const;
const familyGroups = {
"v1.x": [...familyAtomic["v1"]],
"v2.x": [...familyAtomic["v2"]],
} as const;
const familyByTier = {
lite: [model1Lite, model2Lite],
pro: [model1Pro, model2Pro],
} as const;
export const family = {
...familyAtomic,
...familyGroups,
latest: [...familyAtomic["v2"]], // Most recent stable version
all: Object.values(familyAtomic).flat(), // Every preset
// Optional special groups:
// embeddings: [embeddingModel1, embeddingModel2],
// preview: [...previewModels],
} as const;
Checklist:
latest updated if this is the newest stable releaseall automatically picks up new entries via Object.values().flat()embeddings, preview, codex, chat, pro, etc.)For each model, determine which providers serve it:
https://openrouter.ai/api/v1/models/{model_id}/endpoints — the endpoints[] array lists every provider with their provider_name and tagproviders array in the preset: primary/official provider first, then secondary providersFor every provider listed in the model's providers array, open src/providers/<provider>/canonical.ts and determine whether the new model needs an explicit mapping entry.
Each provider has a withCanonicalIdsFor* function that transforms canonical IDs to provider-native IDs using a default transformation pipeline and an explicit MAPPING override table.
src/providers/<provider>/canonical.ts.MAPPING object.src/providers/<provider>/canonical.test.ts verifying the mapping resolves correctly.type CanonicalIdsOptions = {
mapping?: Partial<Record<ModelId, string>>; // Explicit canonical → native mappings
options?: {
stripNamespace?: boolean; // Remove "vendor/" prefix (default: true)
normalizeDelimiters?: boolean | string[]; // Convert "." → "-" (default: false)
prefix?: string; // Prepend to transformed ID
template?: Record<string, string>; // Template variable substitution
postfix?: string; // Append to transformed ID
namespaceSeparator?: "/" | "." | ":"; // Namespace delimiter (default: "/")
};
};
Skip the explicit mapping when stripNamespace + normalizeDelimiters produces the correct native ID. For example:
anthropic/claude-opus-4.7 → strip → claude-opus-4.7 → normalize → claude-opus-4-7 ✓openai/gpt-5.4 → strip → gpt-5.4 ✓voyage/voyage-4-lite → strip → voyage-4-lite ✓Add a MAPPING entry when the default transformation does not produce the correct provider-native model ID. Common reasons:
anthropic.claude-haiku-4-5-20251001-v1:0)command-a-03-2025)accounts/fireworks/models/...)meta-llama/Meta-Llama-3.1-8B-Instruct)Middlewares bridge OpenAI-compatible request parameters to provider-native options. They exist at two levels — both must be checked:
src/models/<family>/middleware.ts)Registered via modelMiddlewareMatcher.useForModel() and matched by canonical model ID patterns. These handle model-family-specific parameter translation.
| Category | When needed | Example |
|---|---|---|
| Reasoning | Model supports extended thinking / chain-of-thought. Check if "reasoning" is in capabilities. | claudeReasoningMiddleware maps reasoning.effort → Anthropic thinking / effort options |
| Dimensions | Embedding model supports variable output dimensions. Check if the provider uses a non-standard parameter name. | voyageDimensionsMiddleware maps dimensions → Voyage outputDimension |
| Caching | Model supports prompt caching. Check if cache_read / cache_write pricing exists in models.dev. | claudePromptCachingMiddleware maps cache_control → Anthropic cacheControl provider option |
For each applicable category:
src/models/<family>/middleware.ts already covers the new model's ID pattern (wildcards like anthropic/claude-*4* may already match).modelMiddlewareMatcher.useForModel() call to include it.middleware.ts in src/models/<family>/ following the established pattern.src/models/<family>/middleware.test.ts.src/providers/<provider>/middleware.ts)Registered via modelMiddlewareMatcher.useForProvider() and matched by provider ID. These handle provider-specific parameter translation that applies to all models served by that provider, regardless of family.
| Category | Providers with middleware | What it does |
|---|---|---|
| Service tiers | bedrock, vertex, groq | Maps OpenAI service_tier values to provider-native equivalents (e.g., "scale" → Bedrock "reserved") |
| Reasoning | bedrock, fireworks | Translates reasoning params into provider-native format (e.g., Bedrock reasoningConfig, Fireworks thinking) |
| Caching | bedrock | Maps cache_control → Bedrock cachePoint with provider-specific TTL handling |
When the new model is served by multiple providers (from the providers array set in Step 6), check each provider's middleware.ts:
bedrockPromptCachingMiddleware only applies to nova and claude models).src/providers/<provider>/middleware.ts and register it with modelMiddlewareMatcher.useForProvider().src/providers/<provider>/middleware.test.ts.src/providers/<provider>/canonical.test.ts)test("withCanonicalIdsForProvider > maps model correctly", () => {
const provider = withCanonicalIdsForProvider(baseProvider);
const model = provider.languageModel("vendor/model-id");
expect(model.modelId).toBe("expected-native-id");
});
src/providers/registry.test.ts)test("resolves vendor model through provider", () => {
const provider = resolveProvider({
providers: config.providers,
models: config.models,
modelId: "vendor/model-id",
operation: "chat",
});
expect(provider).toBeDefined();
});
Update README.md when:
The model presets section is at approximately line 189 in README.md. Each family entry follows this format:
- **Family** — `@hebo-ai/gateway/models/<family>`
Export: `exportName` (`group1`, `group2`, ..., `latest`, `all`)
src/models/types.tssrc/models/<family>/presets.tscreated and knowledge dates verified against models.devproviders array set correctly with proper orderingcanonical.ts checked — explicit mapping added where default transform doesn't produce the correct native IDbun run typecheck passesbun run test passes