From 94e8b508395b4cf946d6d2830ded9276e7165b9c Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:38:21 +1100 Subject: [PATCH 1/3] feat: sync native models from modelschemas for eight providers Replays PR #1308 onto the rewritten main (no shared history, so a merge or rebase could not apply). Conflict resolutions: - Keep main's reasoning shapes; add the PR's new BytePlus, Groq, and Mistral rows after them. - Add the new rows to main's runtime *_MODEL_INPUT_MODALITIES maps and GROQ_CHAT_MODEL_META, which postdate the generated rows. - Drop reasoningMandatory from the Anthropic options call; main's builder no longer takes it. - Loosen @modelschemas/client from 0.1.0 to ^0.1.0. - Changeset covers only the packages this diff still changes. --- .changeset/sync-models.md | 7 + .github/workflows/sync-models.yml | 8 +- CONTRIBUTING.md | 20 +- package.json | 1 + packages/ai-byteplus/src/model-meta.ts | 72 +- packages/ai-groq/src/model-meta.ts | 33 + packages/ai-mistral/src/model-meta.ts | 339 ++++++++ pnpm-lock.yaml | 8 + scripts/model-sync/catalog.test.ts | 472 +++++++++++ scripts/model-sync/catalog.ts | 461 +++++++++++ scripts/model-sync/ids.ts | 18 +- scripts/model-sync/modelschemas.test.ts | 70 ++ scripts/model-sync/modelschemas.ts | 73 ++ scripts/model-sync/native-insert.test.ts | 75 +- scripts/model-sync/native-insert.ts | 56 +- scripts/model-sync/provider-supports.test.ts | 25 + scripts/model-sync/provider-supports.ts | 72 +- scripts/sync-provider-models.ts | 796 ++++++++----------- 18 files changed, 2109 insertions(+), 497 deletions(-) create mode 100644 .changeset/sync-models.md create mode 100644 scripts/model-sync/catalog.test.ts create mode 100644 scripts/model-sync/catalog.ts create mode 100644 scripts/model-sync/modelschemas.test.ts create mode 100644 scripts/model-sync/modelschemas.ts diff --git a/.changeset/sync-models.md b/.changeset/sync-models.md new file mode 100644 index 0000000000..2203889dfb --- /dev/null +++ b/.changeset/sync-models.md @@ -0,0 +1,7 @@ +--- +'@tanstack/ai-byteplus': patch +'@tanstack/ai-groq': patch +'@tanstack/ai-mistral': patch +--- + +Add new BytePlus, Groq, and Mistral models from the modelschemas catalog. diff --git a/.github/workflows/sync-models.yml b/.github/workflows/sync-models.yml index e67073cbd3..81cd8e797c 100644 --- a/.github/workflows/sync-models.yml +++ b/.github/workflows/sync-models.yml @@ -44,11 +44,15 @@ jobs: id: elevenlabs continue-on-error: true run: pnpm generate:models:elevenlabs + env: + MODELSCHEMAS_API_KEY: ${{ secrets.MODELSCHEMAS_API_KEY }} - name: Sync provider models id: provider continue-on-error: true run: pnpm exec tsx scripts/sync-provider-models.ts + env: + MODELSCHEMAS_API_KEY: ${{ secrets.MODELSCHEMAS_API_KEY }} - name: Format run: pnpm format @@ -87,14 +91,14 @@ jobs: if [ -z "$EXISTING_PR" ] || [ "$EXISTING_PR" = "null" ]; then BODY=$(cat <<'PRBODY' - Automated daily sync of model metadata from OpenRouter, Vercel AI Gateway, Lovable AI Gateway, LLM Gateway, and modelschemas (ElevenLabs text-to-speech, music, sound effects, and voice design). + Automated daily sync of model metadata from OpenRouter, Vercel AI Gateway, Lovable AI Gateway, LLM Gateway, and modelschemas (native provider models, plus ElevenLabs text-to-speech, music, sound effects, and voice design). - Fetches the latest model list from OpenRouter (chat + `GET /api/v1/videos/models`) - Fetches the latest model list from Vercel AI Gateway - Fetches the latest model list from Lovable AI Gateway - Fetches the latest model list from LLM Gateway (`GET /v1/models`) - Converts to the internal adapter format - - Syncs provider-specific model metadata for affected packages + - Inserts new native-provider models from modelschemas (`@modelschemas/client`) - Inserts new ElevenLabs text-to-speech, music, sound-effect, and voice-design models from modelschemas - Creates a patch changeset for all changed packages PRBODY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 10e7f5a23e..4d401a12b6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,20 +53,26 @@ For deeper architecture details (adapter system, isomorphic tools, framework int `pnpm generate:models` is the maintainer command behind the daily **Sync Model Metadata** workflow (branch `automated/sync-models`). It: -1. Fetches OpenRouter, Vercel AI Gateway, and Lovable AI Gateway catalogs. +1. Fetches OpenRouter, Vercel AI Gateway, and Lovable AI Gateway catalogs (OpenRouter adapter + gateway packages). 2. Regenerates `packages/ai-openrouter/src/model-meta.ts` and the Vercel Gateway model list. -3. Inserts **new** native-provider models into `packages/ai-openai`, `ai-anthropic`, `ai-gemini`, and `ai-grok`. -4. Inserts **new** ElevenLabs text-to-speech, music, sound-effect, and voice-design ids from [modelschemas](https://modelschemas.com/v1/schemas/elevenlabs) into `packages/ai-elevenlabs/src/model-meta.ts`. Transcription ids stay hand-maintained. +3. Inserts **new** native-provider models from [modelschemas](https://modelschemas.com) (`@modelschemas/client`) into `ai-openai`, `ai-anthropic`, `ai-gemini`, `ai-grok`, `ai-groq`, `ai-mistral`, `ai-byteplus`, and `ai-elevenlabs`. Native ids, activity, limits, modalities, pricing, and capabilities come from each provider catalog. OpenRouter fills a field only when the native row left it empty. OpenAI, Anthropic, Gemini, and Grok hold an id back until the OpenRouter catalog has a matching row and both prices are known. The log names each held-back id. Groq and Mistral insert even when a price is unknown: the generator leaves that price out (`pricing: {}` when both are unknown) and never writes `0`. BytePlus and ElevenLabs do not write pricing. +4. Inserts **new** ElevenLabs text-to-speech, music, sound-effect, and voice-design ids from [modelschemas](https://modelschemas.com/v1/schemas/elevenlabs) into `packages/ai-elevenlabs/src/model-meta.ts` (`scripts/sync-elevenlabs-models.ts`). Transcription ids stay hand-maintained. 5. Writes a patch changeset for the packages that changed. +The sync stops with an error when modelschemas is down, when a catalog is empty, when no row has a `rawId`, `firstSeenAt`, or `deprecatedAt` key, or when an array or type map it must update is missing from a `model-meta.ts`. + +Reads against modelschemas work without a key (60 req/h per IP). Set `MODELSCHEMAS_API_KEY` on the workflow when you want the higher limit. + Rules the generator follows: - Keep OpenRouter routing aliases (ids that start with `~`) in the OpenRouter catalog. Users can pass `chat({ model: '~anthropic/claude-haiku-latest' })`. The generated constant name maps `~` to `_`. -- Do **not** copy those aliases into native provider files (`ai-openai`, `ai-anthropic`, `ai-gemini`, `ai-grok`). Those adapters only accept the provider's own ids. -- For a new native-provider model, write id, modalities, and pricing. Infer features from OpenRouter `supported_parameters` when that field exists. Do **not** copy another model's tool list (`computer_use`, `google_search`, `x_search`, and similar). -- Anthropic first-party ids use dashes (`claude-fable-5-1`). OpenRouter uses dots (`claude-fable-5.1`). The generator hyphenates Anthropic ids on insert. Do not copy the dotted OpenRouter slug into `ai-anthropic`. +- Do **not** copy those aliases into native provider files (any of the eight packages above). Those adapters only accept the provider's own ids. +- For a new native-provider model, write id, modalities, and (where the provider has a `pricing` field) pricing. Infer features from native catalog capabilities (a list of parameter names, or a feature object, as BytePlus sends). Do **not** copy another model's tool list (`computer_use`, `google_search`, `x_search`, and similar). Do **not** copy OpenRouter flags onto a native row that already listed capabilities. BytePlus, ElevenLabs, Groq, and Mistral insert from the native catalog even when OpenRouter has no row. BytePlus inserts never claim `structured_outputs`: that gate is a live-probed list. +- ElevenLabs is id-literal arrays (`ELEVENLABS_TTS_MODELS`, `_AUDIO_`, `_TRANSCRIPTION_`, `_VOICE_`), not ModelMeta constants. Voice-conversion (`*_sts_*`) ids are skipped. BytePlus video and image duration/size tables stay hand-curated. +- Anthropic first-party ids use dashes (`claude-fable-5-1`). OpenRouter uses dots (`claude-fable-5.1`). The generator takes the modelschemas native id (already hyphenated) and does not copy the dotted OpenRouter slug into `ai-anthropic`. Dated Anthropic snapshots (`claude-haiku-4-5-20251001`) are skipped; the undated alias is the public id. - Write a row in the provider's `*ChatModelToolCapabilitiesByName` map for every new chat model, even when `supports.tools` is still `[]`. Missing that row makes `ResolveToolCapabilities` fall back to `readonly []`. -- For new Anthropic models, infer the provider-options mix from the catalog: no sampling parameters → `AnthropicMaxTokensOptions`; `reasoning.mandatory` → `AnthropicAdaptiveOnlyThinkingOptions` plus `AnthropicOutputConfigOptions`. +- For new Anthropic models, infer the provider-options mix from the catalog: no sampling parameters → `AnthropicMaxTokensOptions`; `reasoning.mandatory` → `AnthropicAdaptiveOnlyThinkingOptions` plus `AnthropicOutputConfigOptions`; reasoning params without sampling → adaptive-or-disabled thinking. Every new Claude model gets `AnthropicCacheControlOptions`. +- `firstSeenAt` is when modelschemas first crawled an id, not its release date. The first sync of a new provider can need a one-off older cutoff in `scripts/.sync-models-last-run`. - Leave curated tools and flags on existing models alone. Edit those by hand after the sync PR opens. Do not rebase or hand-edit `automated/sync-models`. The next scheduled run force-pushes that branch from `main`. Merge generator fixes to `main` first, then let the workflow rebuild the sync PR. diff --git a/package.json b/package.json index 2a12ce279a..b96b9d069e 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@changesets/changelog-github": "^0.7.0", "@changesets/cli": "^2.30.0", "@faker-js/faker": "^10.1.0", + "@modelschemas/client": "^0.1.0", "@stylistic/eslint-plugin": "^5.10.0", "@tanstack/ai": "workspace:*", "@tanstack/ai-grok-build": "workspace:*", diff --git a/packages/ai-byteplus/src/model-meta.ts b/packages/ai-byteplus/src/model-meta.ts index ef3fa5cf11..e490b53036 100644 --- a/packages/ai-byteplus/src/model-meta.ts +++ b/packages/ai-byteplus/src/model-meta.ts @@ -1,10 +1,13 @@ /** * BytePlus ModelArk model metadata. * - * Every Ark model id in this file — chat, video and image — was verified live - * against `https://ark.ap-southeast.bytepluses.com/api/v3` on 2026-07-31. The - * two Seed Speech ids are the exception: they live on the voice host, which - * needs a separate key that was not available, so they are docs-derived. + * Every hand-written Ark model id in this file — chat, video and image — was + * verified live against `https://ark.ap-southeast.bytepluses.com/api/v3` on + * 2026-07-31. Two kinds of id are the exception. The two Seed Speech ids live + * on the voice host, which needs a separate key that was not available, so + * they are docs-derived. Chat ids added by `scripts/sync-provider-models.ts` + * come from the modelschemas catalog and were not live-probed; the sync never + * adds them to the structured-output or thinking-summary lists below. * Capability metadata is a mix of probed and docs-derived facts; anything not * confirmed against the live API is annotated as such at its declaration. * BytePlus @@ -53,6 +56,54 @@ const EFFORT_NONE_MINIMAL_TO_HIGH = { budget: false, } as const +const DEEPSEEK_V4_1_FLASH_260910 = { + name: 'deepseek-v4-1-flash-260910', + context_window: 1_048_576, + max_output_tokens: 393_216, + supports: { + input: ['text'], + output: ['text'], + capabilities: ['reasoning', 'tool_calling'], + tools: [] as const, + }, +} as const satisfies ModelMeta + +const DEEPSEEK_V4_FLASH_GA_260731 = { + name: 'deepseek-v4-flash-ga-260731', + context_window: 1_048_576, + max_output_tokens: 393_216, + supports: { + input: ['text'], + output: ['text'], + capabilities: ['reasoning', 'tool_calling'], + tools: [] as const, + }, +} as const satisfies ModelMeta + +const DEEPSEEK_V4_PRO_GA_260813 = { + name: 'deepseek-v4-pro-ga-260813', + context_window: 1_048_576, + max_output_tokens: 393_216, + supports: { + input: ['text'], + output: ['text'], + capabilities: ['reasoning', 'tool_calling'], + tools: [] as const, + }, +} as const satisfies ModelMeta + +const GLM_5_3_FLASH_260828 = { + name: 'glm-5-3-flash-260828', + context_window: 1_048_576, + max_output_tokens: 131_072, + supports: { + input: ['text'], + output: ['text'], + capabilities: ['reasoning', 'tool_calling'], + tools: [] as const, + }, +} as const satisfies ModelMeta + /** * BytePlus exposes no server-side provider tools (no hosted web search, code * interpreter, …) on the international Ark endpoint, so every chat model @@ -355,6 +406,10 @@ const GPT_OSS_120B_250805 = { * All supported BytePlus chat model identifiers. */ export const BYTEPLUS_CHAT_MODELS = [ + DEEPSEEK_V4_1_FLASH_260910.name, + DEEPSEEK_V4_FLASH_GA_260731.name, + DEEPSEEK_V4_PRO_GA_260813.name, + GLM_5_3_FLASH_260828.name, DOLA_SEED_2_1_TURBO.name, SEED_2_0_LITE_260428.name, SEED_2_0_MINI_260428.name, @@ -501,6 +556,10 @@ export type BytePlusModelInputModalitiesByName = { [DEEPSEEK_V4_FLASH_260425.name]: typeof DEEPSEEK_V4_FLASH_260425.supports.input [DEEPSEEK_V3_2_251201.name]: typeof DEEPSEEK_V3_2_251201.supports.input [GPT_OSS_120B_250805.name]: typeof GPT_OSS_120B_250805.supports.input + [DEEPSEEK_V4_1_FLASH_260910.name]: typeof DEEPSEEK_V4_1_FLASH_260910.supports.input + [DEEPSEEK_V4_FLASH_GA_260731.name]: typeof DEEPSEEK_V4_FLASH_GA_260731.supports.input + [DEEPSEEK_V4_PRO_GA_260813.name]: typeof DEEPSEEK_V4_PRO_GA_260813.supports.input + [GLM_5_3_FLASH_260828.name]: typeof GLM_5_3_FLASH_260828.supports.input } /** @@ -530,6 +589,11 @@ export const BYTEPLUS_MODEL_INPUT_MODALITIES: Readonly< [DEEPSEEK_V4_FLASH_260425.name]: DEEPSEEK_V4_FLASH_260425.supports.input, [DEEPSEEK_V3_2_251201.name]: DEEPSEEK_V3_2_251201.supports.input, [GPT_OSS_120B_250805.name]: GPT_OSS_120B_250805.supports.input, + [DEEPSEEK_V4_1_FLASH_260910.name]: DEEPSEEK_V4_1_FLASH_260910.supports.input, + [DEEPSEEK_V4_FLASH_GA_260731.name]: + DEEPSEEK_V4_FLASH_GA_260731.supports.input, + [DEEPSEEK_V4_PRO_GA_260813.name]: DEEPSEEK_V4_PRO_GA_260813.supports.input, + [GLM_5_3_FLASH_260828.name]: GLM_5_3_FLASH_260828.supports.input, } satisfies BytePlusModelInputModalitiesByName /** diff --git a/packages/ai-groq/src/model-meta.ts b/packages/ai-groq/src/model-meta.ts index 88f9d5da95..7eb01b6ae8 100644 --- a/packages/ai-groq/src/model-meta.ts +++ b/packages/ai-groq/src/model-meta.ts @@ -336,6 +336,34 @@ const QWEN3_32B = { reasoning: EFFORT_NONE_DEFAULT, } as const satisfies ModelMeta +const QWEN_QWEN3_8_27B = { + name: 'qwen/qwen3.8-27b', + context_window: 131_072, + max_completion_tokens: 16_384, + supports: { + input: ['text', 'image'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + tools: [] as const, + }, + pricing: { + input: { + normal: 0.8, + }, + output: { + normal: 4, + }, + }, +} as const satisfies ModelMeta + const GROQ_CHAT_MODEL_META = [ LLAMA_3_1_8B_INSTANT, LLAMA_3_3_70B_VERSATILE, @@ -349,6 +377,7 @@ const GROQ_CHAT_MODEL_META = [ GPT_OSS_SAFEGUARD_20B, KIMI_K2_INSTRUCT_0905, QWEN3_32B, + QWEN_QWEN3_8_27B, ] as const /** @@ -366,6 +395,7 @@ export function groqModelPrefersJsonObject(model: string): boolean { * All supported Groq chat model identifiers. */ export const GROQ_CHAT_MODELS = [ + QWEN_QWEN3_8_27B.name, LLAMA_3_1_8B_INSTANT.name, LLAMA_3_3_70B_VERSATILE.name, LLAMA_4_MAVERICK_17B_128E_INSTRUCT.name, @@ -401,6 +431,7 @@ export type GroqModelInputModalitiesByName = { [GPT_OSS_SAFEGUARD_20B.name]: typeof GPT_OSS_SAFEGUARD_20B.supports.input [KIMI_K2_INSTRUCT_0905.name]: typeof KIMI_K2_INSTRUCT_0905.supports.input [QWEN3_32B.name]: typeof QWEN3_32B.supports.input + [QWEN_QWEN3_8_27B.name]: typeof QWEN_QWEN3_8_27B.supports.input } /** @@ -425,6 +456,7 @@ export const GROQ_MODEL_INPUT_MODALITIES: Readonly< [GPT_OSS_SAFEGUARD_20B.name]: GPT_OSS_SAFEGUARD_20B.supports.input, [KIMI_K2_INSTRUCT_0905.name]: KIMI_K2_INSTRUCT_0905.supports.input, [QWEN3_32B.name]: QWEN3_32B.supports.input, + [QWEN_QWEN3_8_27B.name]: QWEN_QWEN3_8_27B.supports.input, } satisfies GroqModelInputModalitiesByName /** @@ -453,6 +485,7 @@ export type GroqChatModelToolCapabilitiesByName = { [GPT_OSS_SAFEGUARD_20B.name]: typeof GPT_OSS_SAFEGUARD_20B.supports.tools [KIMI_K2_INSTRUCT_0905.name]: typeof KIMI_K2_INSTRUCT_0905.supports.tools [QWEN3_32B.name]: typeof QWEN3_32B.supports.tools + [QWEN_QWEN3_8_27B.name]: typeof QWEN_QWEN3_8_27B.supports.tools } /** diff --git a/packages/ai-mistral/src/model-meta.ts b/packages/ai-mistral/src/model-meta.ts index deeccbb1ea..57f78ce97d 100644 --- a/packages/ai-mistral/src/model-meta.ts +++ b/packages/ai-mistral/src/model-meta.ts @@ -17,6 +17,297 @@ const EFFORT_OFF_HIGH = { budget: false, } as const +const CODESTRAL_2508 = { + name: 'codestral-2508', + context_window: 256_000, + max_completion_tokens: 204_800, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: { + input: { + normal: 0.3, + }, + output: { + normal: 0.9, + }, + }, +} as const satisfies ModelMeta + +const LABS_LEANSTRAL_1_5 = { + name: 'labs-leanstral-1-5', + context_window: 262_144, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: {}, +} as const satisfies ModelMeta + +const LABS_LEANSTRAL_1_5_1 = { + name: 'labs-leanstral-1-5-1', + context_window: 262_144, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: {}, +} as const satisfies ModelMeta + +const MINISTRAL_14B_2512 = { + name: 'ministral-14b-2512', + context_window: 262_144, + max_completion_tokens: 209_715, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: { + input: { + normal: 0.2, + }, + output: { + normal: 0.2, + }, + }, +} as const satisfies ModelMeta + +const MINISTRAL_14B_LATEST = { + name: 'ministral-14b-latest', + context_window: 262_144, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: { + input: { + normal: 0.2, + }, + output: { + normal: 0.2, + }, + }, +} as const satisfies ModelMeta + +const MINISTRAL_3B_2512 = { + name: 'ministral-3b-2512', + context_window: 131_072, + max_completion_tokens: 104_857, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: { + input: { + normal: 0.1, + }, + output: { + normal: 0.1, + }, + }, +} as const satisfies ModelMeta + +const MINISTRAL_8B_2512 = { + name: 'ministral-8b-2512', + context_window: 262_144, + max_completion_tokens: 209_715, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: { + input: { + normal: 0.15, + }, + output: { + normal: 0.15, + }, + }, +} as const satisfies ModelMeta + +const MISTRAL_LARGE_2512 = { + name: 'mistral-large-2512', + context_window: 262_144, + max_completion_tokens: 209_715, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: { + input: { + normal: 0.5, + }, + output: { + normal: 1.5, + }, + }, +} as const satisfies ModelMeta + +const MISTRAL_MEDIUM = { + name: 'mistral-medium', + context_window: 262_144, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: {}, +} as const satisfies ModelMeta + +const MISTRAL_MEDIUM_2604 = { + name: 'mistral-medium-2604', + context_window: 262_144, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: {}, +} as const satisfies ModelMeta + +const MISTRAL_MEDIUM_3_5 = { + name: 'mistral-medium-3-5', + context_window: 262_144, + max_completion_tokens: 209_715, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: { + input: { + normal: 1.5, + }, + output: { + normal: 7.5, + }, + }, +} as const satisfies ModelMeta + +const MISTRAL_SMALL_2603 = { + name: 'mistral-small-2603', + context_window: 262_144, + max_completion_tokens: 209_715, + supports: { + input: ['text', 'image', 'audio'], + output: ['text'], + endpoints: ['chat'], + features: [ + 'streaming', + 'tools', + 'json_object', + 'json_schema', + 'reasoning', + 'vision', + ], + }, + pricing: { + input: { + normal: 0.15, + }, + output: { + normal: 0.6, + }, + }, +} as const satisfies ModelMeta + /** Provider options for vision-capable Mistral models (pixtral-*). */ export type MistralVisionProviderOptions = MistralTextProviderOptions @@ -290,6 +581,18 @@ const CODESTRAL_2 = { * All supported Mistral chat model identifiers. */ export const MISTRAL_CHAT_MODELS = [ + CODESTRAL_2508.name, + LABS_LEANSTRAL_1_5.name, + LABS_LEANSTRAL_1_5_1.name, + MINISTRAL_14B_2512.name, + MINISTRAL_14B_LATEST.name, + MINISTRAL_3B_2512.name, + MINISTRAL_8B_2512.name, + MISTRAL_LARGE_2512.name, + MISTRAL_MEDIUM.name, + MISTRAL_MEDIUM_2604.name, + MISTRAL_MEDIUM_3_5.name, + MISTRAL_SMALL_2603.name, MISTRAL_LARGE_LATEST.name, MISTRAL_MEDIUM_LATEST.name, MISTRAL_SMALL_LATEST.name, @@ -340,6 +643,18 @@ export type MistralModelInputModalitiesByName = { [MISTRAL_MEDIUM_3.name]: typeof MISTRAL_MEDIUM_3.supports.input [MISTRAL_SMALL_2503.name]: typeof MISTRAL_SMALL_2503.supports.input [CODESTRAL_2.name]: typeof CODESTRAL_2.supports.input + [CODESTRAL_2508.name]: typeof CODESTRAL_2508.supports.input + [LABS_LEANSTRAL_1_5.name]: typeof LABS_LEANSTRAL_1_5.supports.input + [LABS_LEANSTRAL_1_5_1.name]: typeof LABS_LEANSTRAL_1_5_1.supports.input + [MINISTRAL_14B_2512.name]: typeof MINISTRAL_14B_2512.supports.input + [MINISTRAL_14B_LATEST.name]: typeof MINISTRAL_14B_LATEST.supports.input + [MINISTRAL_3B_2512.name]: typeof MINISTRAL_3B_2512.supports.input + [MINISTRAL_8B_2512.name]: typeof MINISTRAL_8B_2512.supports.input + [MISTRAL_LARGE_2512.name]: typeof MISTRAL_LARGE_2512.supports.input + [MISTRAL_MEDIUM.name]: typeof MISTRAL_MEDIUM.supports.input + [MISTRAL_MEDIUM_2604.name]: typeof MISTRAL_MEDIUM_2604.supports.input + [MISTRAL_MEDIUM_3_5.name]: typeof MISTRAL_MEDIUM_3_5.supports.input + [MISTRAL_SMALL_2603.name]: typeof MISTRAL_SMALL_2603.supports.input } /** @@ -364,6 +679,18 @@ export const MISTRAL_MODEL_INPUT_MODALITIES: Readonly< [MISTRAL_MEDIUM_3.name]: MISTRAL_MEDIUM_3.supports.input, [MISTRAL_SMALL_2503.name]: MISTRAL_SMALL_2503.supports.input, [CODESTRAL_2.name]: CODESTRAL_2.supports.input, + [CODESTRAL_2508.name]: CODESTRAL_2508.supports.input, + [LABS_LEANSTRAL_1_5.name]: LABS_LEANSTRAL_1_5.supports.input, + [LABS_LEANSTRAL_1_5_1.name]: LABS_LEANSTRAL_1_5_1.supports.input, + [MINISTRAL_14B_2512.name]: MINISTRAL_14B_2512.supports.input, + [MINISTRAL_14B_LATEST.name]: MINISTRAL_14B_LATEST.supports.input, + [MINISTRAL_3B_2512.name]: MINISTRAL_3B_2512.supports.input, + [MINISTRAL_8B_2512.name]: MINISTRAL_8B_2512.supports.input, + [MISTRAL_LARGE_2512.name]: MISTRAL_LARGE_2512.supports.input, + [MISTRAL_MEDIUM.name]: MISTRAL_MEDIUM.supports.input, + [MISTRAL_MEDIUM_2604.name]: MISTRAL_MEDIUM_2604.supports.input, + [MISTRAL_MEDIUM_3_5.name]: MISTRAL_MEDIUM_3_5.supports.input, + [MISTRAL_SMALL_2603.name]: MISTRAL_SMALL_2603.supports.input, } satisfies MistralModelInputModalitiesByName /** @@ -384,6 +711,18 @@ export type MistralChatModelProviderOptionsByName = { [MISTRAL_MEDIUM_3.name]: MistralVisionProviderOptions [MISTRAL_SMALL_2503.name]: MistralVisionProviderOptions [CODESTRAL_2.name]: MistralTextProviderOptions + [CODESTRAL_2508.name]: MistralTextProviderOptions + [LABS_LEANSTRAL_1_5.name]: MistralTextProviderOptions + [LABS_LEANSTRAL_1_5_1.name]: MistralTextProviderOptions + [MINISTRAL_14B_2512.name]: MistralTextProviderOptions + [MINISTRAL_14B_LATEST.name]: MistralTextProviderOptions + [MINISTRAL_3B_2512.name]: MistralTextProviderOptions + [MINISTRAL_8B_2512.name]: MistralTextProviderOptions + [MISTRAL_LARGE_2512.name]: MistralTextProviderOptions + [MISTRAL_MEDIUM.name]: MistralTextProviderOptions + [MISTRAL_MEDIUM_2604.name]: MistralTextProviderOptions + [MISTRAL_MEDIUM_3_5.name]: MistralTextProviderOptions + [MISTRAL_SMALL_2603.name]: MistralTextProviderOptions } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 055c02d2d9..e0309aff9c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,9 @@ importers: '@faker-js/faker': specifier: ^10.1.0 version: 10.1.0 + '@modelschemas/client': + specifier: ^0.1.0 + version: 0.1.0 '@stylistic/eslint-plugin': specifier: ^5.10.0 version: 5.10.0(eslint@9.39.4(jiti@2.7.0)) @@ -6722,6 +6725,9 @@ packages: resolution: {integrity: sha512-FdvwZU5N03O77Suh35JqjAqq5R1ZTEPFtwu6N4BgBq28qrQtrCnpbLL5U2pwYCuE194jAw0xnlOVmIqhfoYuUQ==} engines: {node: '>=20'} + '@modelschemas/client@0.1.0': + resolution: {integrity: sha512-MVtC9gsZcx05ITdqh6Krc8G9lMxne7mPO2VfX6cyjQbpGNWOe/dwyZuETVfwohC3Dnsnsrfi3oIRNQNc+xx87Q==} + '@msgpack/msgpack@3.1.3': resolution: {integrity: sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==} engines: {node: '>= 18'} @@ -22959,6 +22965,8 @@ snapshots: '@modelcontextprotocol/core': 2.1.0 zod: 4.3.6 + '@modelschemas/client@0.1.0': {} + '@msgpack/msgpack@3.1.3': {} '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': diff --git a/scripts/model-sync/catalog.test.ts b/scripts/model-sync/catalog.test.ts new file mode 100644 index 0000000000..96b8dfee49 --- /dev/null +++ b/scripts/model-sync/catalog.test.ts @@ -0,0 +1,472 @@ +import { describe, expect, it } from 'vitest' +import { + alreadySynced, + elevenLabsIdArray, + findOpenRouterEnrichment, + hasFullPricing, + hasImageOutput, + openRouterRawIdCandidates, + outputsText, + parseCatalogModels, + pricingLines, + selectElevenLabsInserts, + selectNativeInserts, + skipNativeModelReason, + toSyncModel, +} from './catalog' +import type { CatalogModel } from './catalog' + +function native(overrides: Partial = {}): CatalogModel { + return { + provider: 'anthropic', + rawId: 'claude-sonnet-5', + activity: 'chat', + firstSeenAt: 1_780_000_000, + deprecatedAt: null, + contextWindow: null, + maxOutput: null, + inputModalities: [], + outputModalities: [], + pricing: { + inputPerMillion: undefined, + outputPerMillion: undefined, + }, + capabilities: [], + reasoningMandatory: false, + ...overrides, + } +} + +describe('parseCatalogModels', () => { + it('reads modelschemas listModels payloads and drops rows without rawId', () => { + const models = parseCatalogModels({ + count: 2, + models: [ + { + provider: 'openai', + rawId: 'gpt-5', + activity: 'chat', + contextWindow: 400_000, + maxOutput: 128_000, + modalities: { input: ['text', 'image'], output: ['text'] }, + pricing: { + per: 'token', + inputPerMillion: 1.25, + outputPerMillion: 10, + }, + capabilities: ['tools', 'reasoning'], + firstSeenAt: 1_754_425_777, + deprecatedAt: null, + }, + { provider: 'openai', activity: 'chat' }, + ], + }) + expect(models).toHaveLength(1) + expect(models[0]).toMatchObject({ + rawId: 'gpt-5', + contextWindow: 400_000, + inputModalities: ['text', 'image'], + capabilities: ['tools', 'reasoning'], + pricing: { inputPerMillion: 1.25, outputPerMillion: 10 }, + }) + }) + + it('maps BytePlus object capabilities onto supported_parameters', () => { + const models = parseCatalogModels({ + models: [ + { + rawId: 'deepseek-v4-pro-ga-260813', + activity: 'chat', + firstSeenAt: 1_790_000_000, + deprecatedAt: null, + capabilities: { + tools: { function_calling: true }, + structured_outputs: { json_schema: false, json_object: false }, + maxReasoningTokens: 393216, + }, + }, + ], + }) + expect(models[0]?.capabilities).toEqual([ + 'tools', + 'tool_choice', + 'reasoning', + 'include_reasoning', + ]) + }) +}) + +describe('parseCatalogModels drift', () => { + const row = { rawId: 'm', firstSeenAt: 1, deprecatedAt: null } + + it('reads reasoning.mandatory', () => { + const [model] = parseCatalogModels({ + models: [{ ...row, reasoning: { mode: 'adaptive', mandatory: true } }], + }) + expect(model?.reasoningMandatory).toBe(true) + }) + + it('throws when no row has a rawId', () => { + expect(() => parseCatalogModels({ models: [{ id: 'm' }] })).toThrow( + 'none of 1 rows has a rawId', + ) + }) + + it('throws when firstSeenAt is gone, so the age check cannot pass everything', () => { + expect(() => + parseCatalogModels({ models: [{ rawId: 'm', deprecatedAt: null }] }), + ).toThrow('no row has a firstSeenAt') + }) + + it('throws when deprecatedAt is gone, so deprecated rows cannot pass', () => { + expect(() => + parseCatalogModels({ models: [{ rawId: 'm', firstSeenAt: 1 }] }), + ).toThrow('no row has a deprecatedAt key') + }) + + it('accepts an empty models array', () => { + expect(parseCatalogModels({ models: [] })).toEqual([]) + }) +}) + +describe('openRouterRawIdCandidates', () => { + it('hyphen/dot-maps Anthropic ids and strips dated snapshots', () => { + expect( + openRouterRawIdCandidates( + native({ rawId: 'claude-haiku-4-5-20251001' }), + 'anthropic', + ), + ).toEqual([ + 'anthropic/claude-haiku-4-5-20251001', + 'anthropic/claude-haiku-4-5', + 'anthropic/claude-haiku-4.5-20251001', + 'anthropic/claude-haiku-4.5', + ]) + }) + + it('prefixes Gemini with google/ and Grok with x-ai/', () => { + expect( + openRouterRawIdCandidates( + native({ rawId: 'gemini-3.1-pro', provider: 'gemini' }), + 'gemini', + ), + ).toEqual(['google/gemini-3.1-pro']) + expect( + openRouterRawIdCandidates( + native({ rawId: 'grok-4.6', provider: 'grok' }), + 'grok', + ), + ).toEqual(['x-ai/grok-4.6']) + }) +}) + +describe('findOpenRouterEnrichment / toSyncModel', () => { + it('joins a hyphenated Anthropic id to the dotted OpenRouter row', () => { + const enrich = native({ + provider: 'openrouter', + rawId: 'anthropic/claude-sonnet-4.5', + contextWindow: 200_000, + maxOutput: 64_000, + inputModalities: ['text', 'image'], + outputModalities: ['text'], + pricing: { inputPerMillion: 3, outputPerMillion: 15 }, + capabilities: ['tools', 'temperature'], + }) + const row = native({ rawId: 'claude-sonnet-4-5' }) + const found = findOpenRouterEnrichment(row, 'anthropic', [enrich]) + expect(found?.rawId).toBe('anthropic/claude-sonnet-4.5') + const synced = toSyncModel(row, found, 'anthropic') + expect(synced.nativeId).toBe('claude-sonnet-4-5') + expect(synced.contextWindow).toBe(200_000) + expect(synced.supportedParameters).toEqual(['tools', 'temperature']) + expect(synced.pricing.inputPerMillion).toBe(3) + }) + + it('keeps native capabilities and modalities; OpenRouter only fills empty pricing', () => { + const enrich = native({ + provider: 'openrouter', + rawId: 'anthropic/claude-sonnet-4.5', + contextWindow: 1_000_000, + inputModalities: ['text', 'image', 'file'], + outputModalities: ['text'], + pricing: { inputPerMillion: 3, outputPerMillion: 15 }, + capabilities: ['tools', 'temperature', 'include_reasoning', 'top_k'], + }) + const row = native({ + rawId: 'claude-sonnet-4-5', + contextWindow: 200_000, + inputModalities: ['text', 'image'], + outputModalities: ['text'], + capabilities: ['tools', 'temperature'], + }) + const synced = toSyncModel(row, enrich, 'anthropic') + expect(synced.contextWindow).toBe(200_000) + expect(synced.inputModalities).toEqual(['text', 'image']) + expect(synced.supportedParameters).toEqual(['tools', 'temperature']) + expect(synced.pricing.inputPerMillion).toBe(3) + }) + + it('fills a missing price side from OpenRouter without replacing the known one', () => { + const synced = toSyncModel( + native({ pricing: { inputPerMillion: 2, outputPerMillion: undefined } }), + native({ pricing: { inputPerMillion: 3, outputPerMillion: 15 } }), + 'anthropic', + ) + expect(synced.pricing).toEqual({ inputPerMillion: 2, outputPerMillion: 15 }) + }) +}) + +describe('skipNativeModelReason', () => { + const cutoff = 1_700_000_000 + + it('skips dated Anthropic snapshots, non-chat activity, and old rows', () => { + expect( + skipNativeModelReason( + native({ rawId: 'claude-haiku-4-5-20251001' }), + 'anthropic', + [], + cutoff, + ['chat'], + ), + ).toBe('dated snapshot') + expect( + skipNativeModelReason( + native({ activity: 'image', rawId: 'grok-imagine-image' }), + 'grok', + [], + cutoff, + ['chat'], + ), + ).toBe('activity image') + expect( + skipNativeModelReason( + native({ firstSeenAt: cutoff - 1 }), + 'anthropic', + [], + cutoff, + ['chat'], + ), + ).toBe('too old') + }) + + it('keeps a current chat model', () => { + expect( + skipNativeModelReason(native(), 'anthropic', [], cutoff, ['chat']), + ).toBeNull() + }) + + it('treats transcribe ids as non-chat even when activity is chat', () => { + expect( + skipNativeModelReason( + native({ rawId: 'gemini-3.5-transcribe', provider: 'gemini' }), + 'gemini', + [], + cutoff, + ['chat'], + ), + ).toBe('non-chat family') + }) + + it('keeps audio rows when audio is an accepted activity', () => { + expect( + skipNativeModelReason( + native({ activity: 'audio', rawId: 'eleven_v3' }), + 'elevenlabs', + [], + cutoff, + ['audio'], + ), + ).toBeNull() + }) + + it('keeps Groq rows with no activity', () => { + expect( + skipNativeModelReason( + native({ activity: null, rawId: 'qwen/qwen3.8-27b' }), + 'groq', + [], + cutoff, + [null], + ), + ).toBeNull() + }) +}) + +describe('outputsText', () => { + it('treats empty modalities plus unset activity as chat', () => { + const model = toSyncModel( + native({ activity: null, rawId: 'qwen/qwen3.8-27b' }), + undefined, + 'groq', + ) + expect(outputsText(model, null)).toBe(true) + }) +}) + +describe('elevenLabsIdArray', () => { + it('routes TTS, skips STS, and classifies scribe / music', () => { + expect(elevenLabsIdArray('eleven_v3_conversational')).toBe( + 'ELEVENLABS_TTS_MODELS', + ) + expect(elevenLabsIdArray('eleven_multilingual_sts_v2')).toBeNull() + expect(elevenLabsIdArray('scribe_v2')).toBe( + 'ELEVENLABS_TRANSCRIPTION_MODELS', + ) + expect(elevenLabsIdArray('music_v2_5')).toBe('ELEVENLABS_AUDIO_MODELS') + expect(elevenLabsIdArray('eleven_ttv_v3')).toBe('ELEVENLABS_VOICE_MODELS') + }) +}) + +describe('selectElevenLabsInserts', () => { + const audio = (rawId: string) => + native({ provider: 'elevenlabs', rawId, activity: 'audio' }) + + it('skips ids already in the file and adds a repeated id once', () => { + const byArray = selectElevenLabsInserts( + [ + audio('eleven_v3'), + audio('eleven_v4'), + audio('eleven_v4'), + audio('scribe_v3'), + ], + 0, + new Set(['eleven_v3']), + ) + expect(byArray).toEqual({ + ELEVENLABS_TTS_MODELS: ['eleven_v4'], + ELEVENLABS_AUDIO_MODELS: [], + ELEVENLABS_TRANSCRIPTION_MODELS: ['scribe_v3'], + ELEVENLABS_VOICE_MODELS: [], + }) + }) +}) + +describe('selectNativeInserts', () => { + const priced = { inputPerMillion: 3, outputPerMillion: 15 } + const rules = { + provider: 'anthropic' as const, + skipPatterns: ['claude-legacy-'], + acceptedActivities: ['chat'], + requireOpenRouterEnrich: true, + cutoffTimestamp: 0, + } + const openrouter = [ + native({ rawId: 'anthropic/claude-sonnet-6', pricing: priced }), + native({ rawId: 'anthropic/claude-art-1', pricing: priced }), + ] + + it('inserts a priced model and holds back one OpenRouter has not priced', () => { + const selection = selectNativeInserts( + [native({ rawId: 'claude-sonnet-6' }), native({ rawId: 'claude-new-1' })], + rules, + openrouter, + () => false, + ) + expect(selection.inserts.map(({ model }) => model.nativeId)).toEqual([ + 'claude-sonnet-6', + ]) + expect(selection.inserts[0]?.model.pricing).toEqual(priced) + expect(selection.heldBack).toEqual(['claude-new-1']) + }) + + it('does not hold back a model that is already in the file', () => { + const selection = selectNativeInserts( + [native({ rawId: 'claude-new-1' })], + rules, + openrouter, + () => true, + ) + expect(selection.heldBack).toEqual([]) + expect(selection.skipped).toEqual({ 'already synced': 1 }) + }) + + it('counts every other skip by reason', () => { + const selection = selectNativeInserts( + [ + native({ rawId: 'claude-art-1', outputModalities: ['image'] }), + native({ rawId: 'claude-old-1', deprecatedAt: 1 }), + native({ rawId: 'claude-sonnet-6:thinking' }), + native({ rawId: 'claude-legacy-2' }), + ], + rules, + openrouter, + () => false, + ) + expect(selection.inserts).toEqual([]) + expect(selection.skipped).toEqual({ + 'image output': 1, + deprecated: 1, + 'routing variant': 1, + 'skip pattern': 1, + }) + }) + + it('inserts a Groq model with no price when the price gate is off', () => { + const selection = selectNativeInserts( + [native({ provider: 'groq', rawId: 'qwen/qwen9', activity: null })], + { + ...rules, + provider: 'groq', + acceptedActivities: [null, 'chat'], + requireOpenRouterEnrich: false, + }, + openrouter, + () => false, + ) + expect(selection.inserts).toHaveLength(1) + expect(selection.heldBack).toEqual([]) + }) +}) + +describe('pricingLines / hasFullPricing', () => { + it('leaves an unknown price out instead of writing 0', () => { + const lines = pricingLines({ + inputPerMillion: undefined, + outputPerMillion: undefined, + }) + expect(lines).toEqual([' pricing: {', ' },']) + expect(lines.join('\n')).not.toContain('normal: 0') + }) + + it('writes only the known side, rounded', () => { + const block = pricingLines({ + inputPerMillion: 0.09999999999999999, + outputPerMillion: undefined, + }).join('\n') + expect(block).toContain('normal: 0.1,') + expect(block).not.toContain('output') + }) + + it('needs both prices for providers whose ModelMeta requires them', () => { + expect( + hasFullPricing({ inputPerMillion: 1, outputPerMillion: undefined }), + ).toBe(false) + expect(hasFullPricing({ inputPerMillion: 0, outputPerMillion: 0 })).toBe( + true, + ) + }) +}) + +describe('hasImageOutput / alreadySynced', () => { + it('treats image activity and image output as image models', () => { + expect(hasImageOutput(native({ activity: 'image' }))).toBe(true) + expect( + hasImageOutput(native({ outputModalities: ['image', 'text'] })), + ).toBe(true) + expect(hasImageOutput(native({ outputModalities: ['text'] }))).toBe(false) + }) + + it('matches existing ids with dots or dashes', () => { + expect( + alreadySynced( + 'claude-sonnet-4.5', + new Set(['claude-sonnet-4-5']), + new Set(), + ), + ).toBe(true) + expect( + alreadySynced('gpt-5.6-luna', new Set(), new Set(['GPT_5_6_LUNA'])), + ).toBe(true) + expect(alreadySynced('gpt-5.6-luna', new Set(), new Set())).toBe(false) + }) +}) diff --git a/scripts/model-sync/catalog.ts b/scripts/model-sync/catalog.ts new file mode 100644 index 0000000000..71c020d645 --- /dev/null +++ b/scripts/model-sync/catalog.ts @@ -0,0 +1,461 @@ +/** + * modelschemas catalog rows for native-provider model-meta inserts. + * + * Native catalogs supply ids, activity, limits, modalities, pricing, and + * capabilities. OpenRouter rows on modelschemas fill a field only when the + * native row left it empty. + */ + +import { toModelConstName, toNativeProviderId } from './ids' +import type { ModelMetaProvider, SyncedProvider } from './provider-supports' + +export const OPENROUTER_PREFIX: Partial> = { + openai: 'openai/', + anthropic: 'anthropic/', + gemini: 'google/', + grok: 'x-ai/', + mistral: 'mistralai/', +} + +const NON_CHAT_MODEL_PREFIXES = [ + 'lyria-', + 'veo-', + 'imagen-', + 'sora-', + 'dall-e-', + 'tts-', +] + +const DATED_SNAPSHOT = /-\d{8}$/ + +/** USD per million tokens, as modelschemas reports it. */ +export interface CatalogPricing { + inputPerMillion: number | undefined + outputPerMillion: number | undefined +} + +export interface CatalogModel { + provider: string + rawId: string + activity: string | null + firstSeenAt: number | null + deprecatedAt: number | null + contextWindow: number | null + maxOutput: number | null + inputModalities: Array + outputModalities: Array + pricing: CatalogPricing + capabilities: Array + /** `reasoning.mandatory`: thinking cannot be turned off (Claude Fable). */ + reasoningMandatory: boolean +} + +export interface SyncModel { + nativeId: string + contextWindow: number | null + maxOutput: number | null + inputModalities: Array + outputModalities: Array + pricing: CatalogPricing + supportedParameters: Array + reasoningMandatory: boolean +} + +export function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +function asStringArray(value: unknown): Array { + if (!Array.isArray(value)) return [] + return value.filter((item): item is string => typeof item === 'string') +} + +function asFiniteNumber(value: unknown): number | null { + return typeof value === 'number' && Number.isFinite(value) ? value : null +} + +/** Most providers list parameter names; BytePlus sends a feature object. */ +function asSupportedParameters(value: unknown): Array { + if (Array.isArray(value)) return asStringArray(value) + if (!isRecord(value)) return [] + const params: Array = [] + const tools = isRecord(value.tools) ? value.tools : null + if (tools?.function_calling === true) { + params.push('tools', 'tool_choice') + } + const structured = isRecord(value.structured_outputs) + ? value.structured_outputs + : null + if (structured?.json_schema === true || structured?.json_object === true) { + params.push('structured_outputs', 'response_format') + } + if ( + typeof value.maxReasoningTokens === 'number' && + value.maxReasoningTokens > 0 + ) { + params.push('reasoning', 'include_reasoning') + } + return params +} + +function asPricing(value: unknown): CatalogPricing { + const record = isRecord(value) ? value : {} + return { + inputPerMillion: asFiniteNumber(record.inputPerMillion) ?? undefined, + outputPerMillion: asFiniteNumber(record.outputPerMillion) ?? undefined, + } +} + +export function parseCatalogModel(value: unknown): CatalogModel | null { + if (!isRecord(value)) return null + if (typeof value.rawId !== 'string' || value.rawId.length === 0) return null + const modalities = isRecord(value.modalities) ? value.modalities : null + const reasoning = isRecord(value.reasoning) ? value.reasoning : null + return { + provider: typeof value.provider === 'string' ? value.provider : '', + rawId: value.rawId, + activity: typeof value.activity === 'string' ? value.activity : null, + firstSeenAt: asFiniteNumber(value.firstSeenAt), + deprecatedAt: asFiniteNumber(value.deprecatedAt), + contextWindow: asFiniteNumber(value.contextWindow), + maxOutput: asFiniteNumber(value.maxOutput), + inputModalities: asStringArray(modalities?.input), + outputModalities: asStringArray(modalities?.output), + pricing: asPricing(value.pricing), + capabilities: asSupportedParameters(value.capabilities), + reasoningMandatory: reasoning?.mandatory === true, + } +} + +/** + * Parse a `listModels` payload. A changed payload shape must not look like + * "no new models", and a renamed `firstSeenAt` / `deprecatedAt` must not + * let the whole historical catalog through the age and deprecation checks. + */ +export function parseCatalogModels(data: unknown): Array { + const rows = isRecord(data) ? data.models : undefined + if (!Array.isArray(rows)) { + throw new Error('modelschemas listModels: no models array') + } + const models = rows + .map((row) => parseCatalogModel(row)) + .filter((model): model is CatalogModel => model !== null) + if (rows.length === 0) return models + if (models.length === 0) { + throw new Error( + `modelschemas listModels: none of ${rows.length} rows has a rawId`, + ) + } + if (!models.some((model) => model.firstSeenAt != null)) { + throw new Error('modelschemas listModels: no row has a firstSeenAt') + } + if (!rows.some((row) => isRecord(row) && 'deprecatedAt' in row)) { + throw new Error('modelschemas listModels: no row has a deprecatedAt key') + } + return models +} + +/** OpenRouter dots Anthropic version suffixes (`4.5`); native ids use dashes. */ +function anthropicOpenRouterId(nativeId: string): string { + return nativeId.replace(/(\d)-(\d)/g, '$1.$2') +} + +export function openRouterRawIdCandidates( + native: CatalogModel, + provider: SyncedProvider, +): Array { + const prefix = OPENROUTER_PREFIX[provider] + if (!prefix) return [] + const ids = new Set([native.rawId]) + if (provider === 'anthropic') { + const undated = native.rawId.replace(DATED_SNAPSHOT, '') + ids.add(undated) + ids.add(anthropicOpenRouterId(native.rawId)) + ids.add(anthropicOpenRouterId(undated)) + } + return [...ids].map((id) => prefix + id) +} + +export function findOpenRouterEnrichment( + native: CatalogModel, + provider: SyncedProvider, + openrouter: Array, +): CatalogModel | undefined { + const wanted = new Set(openRouterRawIdCandidates(native, provider)) + return openrouter.find((model) => wanted.has(model.rawId)) +} + +function pickList( + preferred: Array, + fallback: Array = [], +): Array { + return preferred.length > 0 ? preferred : fallback +} + +/** Both prices known. OpenAI/Anthropic/Gemini/Grok ModelMeta require both. */ +export function hasFullPricing(pricing: CatalogPricing): boolean { + return pricing.inputPerMillion != null && pricing.outputPerMillion != null +} + +/** Strips float noise: 0.09999999999999999 → 0.1. */ +function roundPrice(price: number): number { + return Math.round(price * 1e10) / 1e10 +} + +/** + * The `pricing` block of a generated model constant. A price the catalog + * does not have is left out, not written as `0` (which reads as "free"). + */ +// ponytail: modelschemas has no cached-input price, so `cached` is never written. +export function pricingLines(pricing: CatalogPricing): Array { + const lines = [` pricing: {`] + if (pricing.inputPerMillion != null) { + lines.push( + ` input: {`, + ` normal: ${roundPrice(pricing.inputPerMillion)},`, + ` },`, + ) + } + if (pricing.outputPerMillion != null) { + lines.push( + ` output: {`, + ` normal: ${roundPrice(pricing.outputPerMillion)},`, + ` },`, + ) + } + lines.push(` },`) + return lines +} + +export function toSyncModel( + native: CatalogModel, + enrich: CatalogModel | undefined, + provider: SyncedProvider, +): SyncModel { + return { + nativeId: toNativeProviderId(native.rawId, provider), + contextWindow: native.contextWindow ?? enrich?.contextWindow ?? null, + maxOutput: native.maxOutput ?? enrich?.maxOutput ?? null, + inputModalities: pickList(native.inputModalities, enrich?.inputModalities), + outputModalities: pickList( + native.outputModalities, + enrich?.outputModalities, + ), + // Per side, so a native input price does not hide OpenRouter's output price. + pricing: { + inputPerMillion: + native.pricing.inputPerMillion ?? enrich?.pricing.inputPerMillion, + outputPerMillion: + native.pricing.outputPerMillion ?? enrich?.pricing.outputPerMillion, + }, + supportedParameters: pickList(native.capabilities, enrich?.capabilities), + reasoningMandatory: + native.reasoningMandatory || enrich?.reasoningMandatory === true, + } +} + +export function matchesSkipPattern( + rawId: string, + patterns: Array, +): boolean { + return patterns.some((pattern) => rawId.startsWith(pattern)) +} + +export function isNonChatFamily(rawId: string): boolean { + return ( + rawId.includes('transcribe') || + matchesSkipPattern(rawId, NON_CHAT_MODEL_PREFIXES) + ) +} + +export function isDatedAnthropicSnapshot(rawId: string): boolean { + return DATED_SNAPSHOT.test(rawId) +} + +export function hasImageOutput(model: { + activity: string | null + outputModalities: Array +}): boolean { + return model.activity === 'image' || model.outputModalities.includes('image') +} + +export function outputsText( + model: SyncModel, + activity: string | null, +): boolean { + if (model.outputModalities.includes('text')) return true + if ( + model.outputModalities.includes('image') || + model.outputModalities.includes('video') + ) { + return false + } + return ( + model.outputModalities.length === 0 && + (activity === 'chat' || activity === null) + ) +} + +/** + * Why this native row should not be inserted. `null` means keep going. + */ +export function skipNativeModelReason( + model: CatalogModel, + provider: SyncedProvider, + skipPatterns: Array, + cutoffTimestamp: number, + acceptedActivities: Array, +): string | null { + if (model.deprecatedAt != null) return 'deprecated' + if (!acceptedActivities.includes(model.activity)) { + return `activity ${model.activity}` + } + if (model.rawId.includes(':')) return 'routing variant' + if (isNonChatFamily(model.rawId)) return 'non-chat family' + if (matchesSkipPattern(model.rawId, skipPatterns)) return 'skip pattern' + if (provider === 'anthropic' && isDatedAnthropicSnapshot(model.rawId)) { + return 'dated snapshot' + } + if (model.firstSeenAt != null && model.firstSeenAt < cutoffTimestamp) { + return 'too old' + } + return null +} + +export interface NativeInsertRules { + provider: ModelMetaProvider + skipPatterns: Array + acceptedActivities: Array + /** + * Hold a native id back until the modelschemas OpenRouter catalog has a + * matching row and both prices are known (from either row). The + * OpenAI/Anthropic/Gemini/Grok ModelMeta types require both prices. + */ + requireOpenRouterEnrich: boolean + cutoffTimestamp: number +} + +export interface NativeInsertSelection { + inserts: Array<{ model: SyncModel; activity: string | null }> + /** Raw ids skipped only because the price gate is not met yet. */ + heldBack: Array + /** Count of every other skip, by reason. */ + skipped: Record +} + +/** Pick the catalog rows that become new model-meta constants. */ +export function selectNativeInserts( + rows: Array, + rules: NativeInsertRules, + openrouter: Array, + isSynced: (nativeId: string) => boolean, +): NativeInsertSelection { + const selection: NativeInsertSelection = { + inserts: [], + heldBack: [], + skipped: {}, + } + const skip = (reason: string) => { + selection.skipped[reason] = (selection.skipped[reason] ?? 0) + 1 + } + for (const row of rows) { + const reason = skipNativeModelReason( + row, + rules.provider, + rules.skipPatterns, + rules.cutoffTimestamp, + rules.acceptedActivities, + ) + if (reason) { + skip(reason) + continue + } + const enrich = findOpenRouterEnrichment(row, rules.provider, openrouter) + const model = toSyncModel(row, enrich, rules.provider) + if (isSynced(model.nativeId)) { + skip('already synced') + continue + } + if ( + hasImageOutput({ + activity: row.activity, + outputModalities: model.outputModalities, + }) + ) { + skip('image output') + continue + } + if ( + rules.requireOpenRouterEnrich && + !(enrich && hasFullPricing(model.pricing)) + ) { + selection.heldBack.push(row.rawId) + continue + } + selection.inserts.push({ model, activity: row.activity }) + } + return selection +} + +export const ELEVENLABS_ID_ARRAYS = [ + 'ELEVENLABS_TTS_MODELS', + 'ELEVENLABS_AUDIO_MODELS', + 'ELEVENLABS_TRANSCRIPTION_MODELS', + 'ELEVENLABS_VOICE_MODELS', +] as const + +type ElevenLabsIdArray = (typeof ELEVENLABS_ID_ARRAYS)[number] + +/** + * Which ElevenLabs id array a raw id belongs in. STS / voice-conversion ids + * have no TanStack adapter, so they get `null`. + */ +export function elevenLabsIdArray(rawId: string): ElevenLabsIdArray | null { + if (rawId.includes('_sts_')) return null + if (rawId.includes('_ttv_')) return 'ELEVENLABS_VOICE_MODELS' + if (rawId.startsWith('scribe')) return 'ELEVENLABS_TRANSCRIPTION_MODELS' + if (rawId.includes('text_to_sound') || rawId.startsWith('music_')) { + return 'ELEVENLABS_AUDIO_MODELS' + } + if (rawId.startsWith('eleven_')) return 'ELEVENLABS_TTS_MODELS' + return null +} + +/** New ElevenLabs ids per array. An id is added at most once. */ +export function selectElevenLabsInserts( + rows: Array, + cutoffTimestamp: number, + existingIds: Set, +): Record> { + const byArray: Record> = { + ELEVENLABS_TTS_MODELS: [], + ELEVENLABS_AUDIO_MODELS: [], + ELEVENLABS_TRANSCRIPTION_MODELS: [], + ELEVENLABS_VOICE_MODELS: [], + } + const seen = new Set(existingIds) + for (const row of rows) { + if ( + skipNativeModelReason(row, 'elevenlabs', [], cutoffTimestamp, ['audio']) + ) { + continue + } + const arrayName = elevenLabsIdArray(row.rawId) + if (!arrayName || seen.has(row.rawId)) continue + byArray[arrayName].push(row.rawId) + seen.add(row.rawId) + } + return byArray +} + +export function alreadySynced( + nativeId: string, + existingIds: Set, + existingConstNames: Set, +): boolean { + const normalized = nativeId.replaceAll('.', '-') + return ( + existingIds.has(normalized) || + existingConstNames.has(toModelConstName(nativeId)) + ) +} diff --git a/scripts/model-sync/ids.ts b/scripts/model-sync/ids.ts index c3c2bf88e4..fdcfc616e3 100644 --- a/scripts/model-sync/ids.ts +++ b/scripts/model-sync/ids.ts @@ -22,19 +22,17 @@ export function rejectRoutingAliases( } /** - * Native adapters only accept the provider's own ids. OpenRouter uses dots - * in Claude version suffixes (`claude-haiku-4.5`, `claude-fable-5.1`); - * Anthropic's Messages API uses dashes (`claude-haiku-4-5`, - * `claude-fable-5-1`). Other providers keep the OpenRouter stripped id. + * Native adapters only accept the provider's own ids. Anthropic's Messages + * API uses dashes in Claude version suffixes (`claude-haiku-4-5`), and + * OpenRouter uses dots (`claude-haiku-4.5`). The native modelschemas id is + * already dashed; this guards against a dotted one. Other providers keep + * the raw id. */ -export function toNativeProviderId( - strippedId: string, - provider: 'openai' | 'anthropic' | 'gemini' | 'grok', -): string { +export function toNativeProviderId(rawId: string, provider: string): string { if (provider === 'anthropic') { - return strippedId.replaceAll('.', '-') + return rawId.replaceAll('.', '-') } - return strippedId + return rawId } export function toModelConstName(modelId: string): string { diff --git a/scripts/model-sync/modelschemas.test.ts b/scripts/model-sync/modelschemas.test.ts new file mode 100644 index 0000000000..32059a5324 --- /dev/null +++ b/scripts/model-sync/modelschemas.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from 'vitest' +import { createSyncClient, fetchSyncCatalogs } from './modelschemas' + +function clientReturning(respond: (provider: string) => Response) { + return createSyncClient({ + apiKey: '', + fetch: async (input) => { + const url = new URL(input instanceof Request ? input.url : String(input)) + return respond(url.searchParams.get('provider') ?? '') + }, + }) +} + +const row = (rawId: string) => ({ rawId, firstSeenAt: 1, deprecatedAt: null }) + +const json = (body: unknown, status = 200) => + new Response(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json' }, + }) + +describe('fetchSyncCatalogs', () => { + it('fails when modelschemas is down', async () => { + const client = clientReturning(() => + json({ error: { message: 'unavailable' } }, 503), + ) + await expect(fetchSyncCatalogs(client)).rejects.toThrow('unavailable') + }) + + it('fails on an empty openrouter catalog instead of dropping prices', async () => { + const client = clientReturning((provider) => + json({ models: provider === 'openrouter' ? [] : [row('x')] }), + ) + await expect(fetchSyncCatalogs(client)).rejects.toThrow( + 'empty openrouter catalog', + ) + }) + + it('fails on an empty native catalog instead of reporting no new models', async () => { + const client = clientReturning((provider) => + json({ models: provider === 'groq' ? [] : [row('x')] }), + ) + await expect(fetchSyncCatalogs(client)).rejects.toThrow( + 'empty groq catalog', + ) + }) + + it('fails when one native provider errors', async () => { + const client = clientReturning((provider) => + provider === 'mistral' + ? json({ error: { message: 'mistral down' } }, 500) + : json({ models: [row('x')] }), + ) + await expect(fetchSyncCatalogs(client)).rejects.toThrow('mistral down') + }) + + it('fails on a payload with no models array', async () => { + const client = clientReturning(() => json({ data: [] })) + await expect(fetchSyncCatalogs(client)).rejects.toThrow('no models array') + }) + + it('returns each native catalog next to openrouter', async () => { + const client = clientReturning((provider) => + json({ models: [row(`${provider}-model`)] }), + ) + const catalogs = await fetchSyncCatalogs(client) + expect(catalogs.openrouter[0]?.rawId).toBe('openrouter-model') + expect(catalogs.native.mistral[0]?.rawId).toBe('mistral-model') + }) +}) diff --git a/scripts/model-sync/modelschemas.ts b/scripts/model-sync/modelschemas.ts new file mode 100644 index 0000000000..719335bab2 --- /dev/null +++ b/scripts/model-sync/modelschemas.ts @@ -0,0 +1,73 @@ +/** + * Thin wrapper around `@modelschemas/client` for the native model sync. + */ + +import { createModelschemasClient, listModels } from '@modelschemas/client' +import { isRecord, parseCatalogModels } from './catalog' +import type { CatalogModel } from './catalog' +import { SYNCED_PROVIDERS } from './provider-supports' +import type { SyncedProvider } from './provider-supports' + +export function createSyncClient(options?: { + apiKey?: string + fetch?: typeof globalThis.fetch +}): ReturnType { + const apiKey = options?.apiKey ?? process.env.MODELSCHEMAS_API_KEY + return createModelschemasClient({ + baseUrl: 'https://modelschemas.com', + ...(apiKey ? { apiKey } : {}), + ...(options?.fetch ? { fetch: options.fetch } : {}), + }) +} + +type SyncClient = ReturnType + +function errorMessage(error: unknown): string { + if (typeof error === 'string') return error + if (!isRecord(error)) return String(error) + if (isRecord(error.error) && typeof error.error.message === 'string') { + return error.error.message + } + if (typeof error.message === 'string') return error.message + return JSON.stringify(error) +} + +export async function fetchCatalog( + client: SyncClient, + provider: string, +): Promise> { + const result = await listModels({ client, query: { provider } }) + if (result.error !== undefined) { + throw new Error( + `modelschemas listModels provider=${provider}: ${errorMessage(result.error)}`, + ) + } + return parseCatalogModels(result.data) +} + +/** + * Every synced provider's native catalog plus the OpenRouter catalog. + * An empty catalog throws: it would hide every insert for that provider + * (or every price, for OpenRouter) behind a "no new models" exit 0. + */ +export async function fetchSyncCatalogs(client: SyncClient): Promise<{ + native: Record> + openrouter: Array +}> { + const [openrouter, ...nativeLists] = await Promise.all([ + fetchCatalog(client, 'openrouter'), + ...SYNCED_PROVIDERS.map((provider) => fetchCatalog(client, provider)), + ]) + if (openrouter.length === 0) { + throw new Error('modelschemas returned an empty openrouter catalog') + } + const native = {} as Record> + for (const [index, provider] of SYNCED_PROVIDERS.entries()) { + const rows = nativeLists[index] + if (!rows?.length) { + throw new Error(`modelschemas returned an empty ${provider} catalog`) + } + native[provider] = rows + } + return { native, openrouter } +} diff --git a/scripts/model-sync/native-insert.test.ts b/scripts/model-sync/native-insert.test.ts index d8be383028..b6066749f1 100644 --- a/scripts/model-sync/native-insert.test.ts +++ b/scripts/model-sync/native-insert.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from 'vitest' -import { applyChatModelCatalogInserts } from './native-insert' +import { + addToStringLiteralArray, + applyChatModelCatalogInserts, + extractStringLiteralArrayValues, + insertConstants, +} from './native-insert' const STUB = `const FOO = { id: 'claude-foo', @@ -184,4 +189,72 @@ export type OpenAIModelInputModalitiesByName = { [CLAUDE_FABLE_5_1.id]: CLAUDE_FABLE_5_1.supports.input, } satisfies AnthropicModelInputModalitiesByName`) }) + + it('throws when a type map is missing, so no constant is left unreferenced', () => { + expect(() => + applyChatModelCatalogInserts( + STUB.replace('AnthropicModelInputModalitiesByName', 'Renamed'), + ANTHROPIC_CONFIG, + [ + { + constName: 'CLAUDE_NEW', + providerOptionsEntry: 'AnthropicSamplingOptions', + hasMaxOutputTokens: false, + }, + ], + ), + ).toThrow("Could not find type map 'AnthropicModelInputModalitiesByName'") + }) +}) + +describe('insertConstants', () => { + it('keeps the JSDoc of the first export on that export', () => { + const stub = `const OLD = { name: 'old' } + +/** + * All supported chat model identifiers. + */ +export const CHAT_MODELS = [OLD.name] as const +` + const result = insertConstants(stub, [`const NEW = { name: 'new' }`]) + expect(result).toContain( + `const NEW = { name: 'new' }\n\n/**\n * All supported chat model identifiers.\n */\nexport const CHAT_MODELS`, + ) + expect(result.indexOf('const OLD')).toBeLessThan( + result.indexOf('const NEW'), + ) + }) + + it('inserts right before the export when it has no JSDoc', () => { + const result = insertConstants(`const A = 1\n\nexport const B = 2\n`, [ + 'const C = 3', + ]) + expect(result).toBe(`const A = 1\n\nconst C = 3\n\nexport const B = 2\n`) + }) +}) + +describe('string-literal arrays', () => { + const stub = `export const ELEVENLABS_TTS_MODELS = [ + 'eleven_v3', +] as const +` + + it('inserts quoted ids after the opening bracket', () => { + const result = addToStringLiteralArray(stub, 'ELEVENLABS_TTS_MODELS', [ + 'eleven_v3_conversational', + ]) + expect(result).toContain(" 'eleven_v3_conversational',") + expect( + extractStringLiteralArrayValues(result, 'ELEVENLABS_TTS_MODELS'), + ).toEqual(new Set(['eleven_v3_conversational', 'eleven_v3'])) + }) + + it('throws instead of reading a missing array as empty', () => { + expect(() => + extractStringLiteralArrayValues(stub, 'ELEVENLABS_AUDIO_MODELS'), + ).toThrow("Could not find array 'ELEVENLABS_AUDIO_MODELS'") + expect(() => + addToStringLiteralArray(stub, 'ELEVENLABS_AUDIO_MODELS', ['music_v2']), + ).toThrow("Could not find array 'ELEVENLABS_AUDIO_MODELS'") + }) }) diff --git a/scripts/model-sync/native-insert.ts b/scripts/model-sync/native-insert.ts index 33d9cdd031..6cbcdb4f02 100644 --- a/scripts/model-sync/native-insert.ts +++ b/scripts/model-sync/native-insert.ts @@ -2,10 +2,10 @@ * File-mutation helpers for `sync-provider-models.ts`. * * Kept here so the insert path (chat array + type maps) can be unit-tested - * without running the full OpenRouter fetch. + * without running the full modelschemas fetch. */ -type ArrayRef = '.name' | '.id' +export type ArrayRef = '.name' | '.id' export function insertConstants( content: string, @@ -16,13 +16,54 @@ export function insertConstants( if (exportIndex === -1) { return content + block } - return content.slice(0, exportIndex) + block + content.slice(exportIndex) + // Insert above the export's JSDoc, so the comment stays on its export. + const doc = /\n\/\*\*(?:(?!\*\/)[\s\S])*\*\/\s*$/.exec( + content.slice(0, exportIndex), + ) + const at = doc ? doc.index : exportIndex + return content.slice(0, at) + block + content.slice(at) +} + +export function addToStringLiteralArray( + content: string, + arrayName: string, + values: Array, +): string { + if (values.length === 0) return content + return addToArray( + content, + arrayName, + values.map((value) => `'${value}'`), + '', + ) +} + +export function extractStringLiteralArrayValues( + content: string, + arrayName: string, +): Set { + const ids = new Set() + const block = content.match( + new RegExp(`export const ${arrayName} = \\[([\\s\\S]*?)\\] as const`), + ) + if (!block) { + // An empty set here would make every existing id look new. + throw new Error(`Could not find array '${arrayName}' in file`) + } + const quoted = block[1]!.matchAll(/'([^']+)'/g) + for (const match of quoted) { + ids.add(match[1]!) + } + return ids } /** * Insert entries immediately AFTER the opening bracket. Each inserted line * carries its own trailing comma so the existing body does not need a * comma-guess. See the grok-4.5 single-line array breakage. + * + * Every insert helper throws when its anchor is missing: the constants are + * already in the file, and nothing would reference them. */ function addToArray( content: string, @@ -35,8 +76,7 @@ function addToArray( const afterDecl = declIndex === -1 ? -1 : declIndex + decl.length const bracket = afterDecl === -1 ? -1 : content.indexOf('[', afterDecl) if (declIndex === -1 || bracket === -1 || bracket - afterDecl > 40) { - console.warn(` Warning: Could not find array '${arrayName}' in file`) - return content + throw new Error(`Could not find array '${arrayName}' in file`) } const newEntries = entries @@ -56,8 +96,7 @@ function addToTypeMap( ) const match = pattern.exec(content) if (!match) { - console.warn(` Warning: Could not find type map '${typeName}' in file`) - return content + throw new Error(`Could not find type map '${typeName}' in file`) } const newEntries = entries.join('\n') @@ -74,8 +113,7 @@ function addToObjectMap( ) const match = pattern.exec(content) if (!match) { - console.warn(` Warning: Could not find object map '${mapName}' in file`) - return content + throw new Error(`Could not find object map '${mapName}' in file`) } const newEntries = entries.join('\n') diff --git a/scripts/model-sync/provider-supports.test.ts b/scripts/model-sync/provider-supports.test.ts index 1c541d409e..79c4dfb097 100644 --- a/scripts/model-sync/provider-supports.test.ts +++ b/scripts/model-sync/provider-supports.test.ts @@ -71,6 +71,31 @@ describe('buildProviderSupportsBody', () => { expect(body).toContain('thinking') }) + it('writes Groq features without copying hosted tools', () => { + const body = buildProviderSupportsBody({ + provider: 'groq', + inputModalities: ['text'], + supportedParameters: ['tools', 'structured_outputs'], + }) + expect(body).toContain("endpoints: ['chat']") + expect(body).toContain('tools: [] as const') + expect(body).toContain("'json_schema'") + expect(body).not.toContain('browser_search') + }) + + it('writes BytePlus capabilities but leaves structured outputs to the probed list', () => { + const body = buildProviderSupportsBody({ + provider: 'byteplus', + inputModalities: ['text', 'image'], + outputModalities: ['text'], + supportedParameters: ['tools', 'reasoning', 'structured_outputs'], + }) + expect(body).toContain('tool_calling') + expect(body).toContain('reasoning') + expect(body).toContain('tools: [] as const') + expect(body).not.toContain('structured_outputs') + }) + it('lists Grok server tools', () => { const body = buildProviderSupportsBody({ provider: 'grok', diff --git a/scripts/model-sync/provider-supports.ts b/scripts/model-sync/provider-supports.ts index b025193b09..dfbfe0bfb8 100644 --- a/scripts/model-sync/provider-supports.ts +++ b/scripts/model-sync/provider-supports.ts @@ -1,18 +1,34 @@ /** * `supports` blocks for newly synced native-provider models. * - * Input modalities come from OpenRouter. Features come from - * `supported_parameters`. Server-tool lists are the current first-party - * vocabulary for that provider, written only when the catalog lists `tools` - * or `tool_choice`. Anthropic thinking flags follow the same parameter - * split as `buildAnthropicProviderOptionsType`. + * Input modalities and `supported_parameters` come from the native + * modelschemas catalog (OpenRouter fills a field only when the native row + * is empty). Server-tool lists are the current first-party vocabulary for + * openai, anthropic, gemini and grok, written only when the catalog lists + * `tools` or `tool_choice`. Anthropic thinking flags follow the same + * parameter split as `buildAnthropicProviderOptionsType`. */ -export type SyncedProvider = 'openai' | 'anthropic' | 'gemini' | 'grok' +export const SYNCED_PROVIDERS = [ + 'openai', + 'anthropic', + 'gemini', + 'grok', + 'groq', + 'mistral', + 'byteplus', + 'elevenlabs', +] as const + +export type SyncedProvider = (typeof SYNCED_PROVIDERS)[number] + +/** Providers synced as ModelMeta constants. ElevenLabs is id arrays only. */ +export type ModelMetaProvider = Exclude export interface ProviderSupportsInput { - provider: SyncedProvider + provider: ModelMetaProvider inputModalities: Array + outputModalities?: Array supportedParameters?: Array reasoningMandatory?: boolean } @@ -83,9 +99,10 @@ const ANTHROPIC_BASE_OPTIONS = [ /** * Per-model Anthropic provider-options intersection, inferred from the - * OpenRouter catalog. Does not copy another model's tool list. Thinking is - * not here: `chat({ reasoning })` sets it, with the levels of each model's - * `reasoning` field in `model-meta.ts`. + * modelschemas capabilities (OpenRouter's when the native row has none). + * Does not copy another model's tool list. Thinking is not here: + * `chat({ reasoning })` sets it, with the levels of each model's `reasoning` + * field in `model-meta.ts`. * * - sampling listed → sampling options. * - no sampling (Sonnet 5, Opus 4.7+, Fable 5) → `max_tokens` only. @@ -200,5 +217,40 @@ export function buildProviderSupportsBody( lines.push(toolsLine(hasTools, GROK_SERVER_TOOLS)) return lines.join('\n') } + case 'groq': + case 'mistral': { + const features = ['streaming'] + if (hasTools) features.push('tools') + if (hasStructured) { + features.push('json_object', 'json_schema') + } + if (hasReasoning) features.push('reasoning') + if (input.inputModalities.includes('image')) features.push('vision') + const lines = [ + ` input: ${inputList},`, + ` output: ['text'],`, + ` endpoints: ['chat'],`, + ` features: ${quoteList(features)},`, + ] + // Groq hosted tools (browser_search, …) are per-model; never guess them. + if (input.provider === 'groq') lines.push(` tools: [] as const,`) + return lines.join('\n') + } + case 'byteplus': { + // No `structured_outputs`: the runtime gate is the live-probed + // BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS list, which a sync cannot update. + const capabilities: Array = [] + if (hasReasoning) capabilities.push('reasoning') + if (hasTools) capabilities.push('tool_calling') + const output = quoteList( + input.outputModalities?.length ? input.outputModalities : ['text'], + ) + const lines = [` input: ${inputList},`, ` output: ${output},`] + if (capabilities.length > 0) { + lines.push(` capabilities: ${quoteList(capabilities)},`) + } + lines.push(` tools: [] as const,`) + return lines.join('\n') + } } } diff --git a/scripts/sync-provider-models.ts b/scripts/sync-provider-models.ts index a7897d19d7..ae26b2ee86 100644 --- a/scripts/sync-provider-models.ts +++ b/scripts/sync-provider-models.ts @@ -1,79 +1,70 @@ /** - * Syncs OpenRouter models into native provider model-meta.ts files. + * Syncs modelschemas catalogs into native provider model-meta.ts files. * - * For each supported provider (OpenAI, Anthropic, Gemini, Grok), this script: - * 1. Reads the OpenRouter model list - * 2. Filters models matching the provider prefix + * For each synced provider, this script: + * 1. Lists that provider's models from modelschemas (`@modelschemas/client`) + * 2. Uses native limits, modalities, pricing, and capabilities. Fills any + * field the native row left empty from the modelschemas OpenRouter catalog * 3. Identifies models missing from the provider's model-meta.ts * 4. Generates and inserts new model constants, array entries, and type map entries * * Usage: * pnpm tsx scripts/sync-provider-models.ts * - * ## Providers deliberately NOT synced + * Optional: MODELSCHEMAS_API_KEY raises the modelschemas rate limit. * - * The sync is only safe when the provider's own model ids can be derived from - * OpenRouter's. Adding an entry to `PROVIDER_MAP` for a provider where that - * doesn't hold generates ids that 404 at request time, which is worse than no - * automation. These are excluded on purpose: + * ## Providers deliberately NOT synced * - * - **byteplus** (`@tanstack/ai-byteplus`) — OpenRouter lists the same models - * under undated slugs (`bytedance-seed/seed-1.6`), but BytePlus Ark - * addresses them by *date-suffixed* id (`seed-1-6-250615`), and the suffix - * is not derivable from anything OpenRouter publishes. Ark's own `GET - * /models` is the catalog, and it is not exhaustive. On top of that the - * package's capability tables are live-probe-verified specifically because - * the published metadata is wrong in both directions, so a metadata-driven - * sync would overwrite probed facts with worse ones. Use `/gap-analysis - * byteplus` instead; the probe recipe is in that package's `model-meta.ts`. - * - **fal** — media-only provider whose endpoint ids are not OpenRouter - * models. fal image fields have their own generator - * (`scripts/generate-fal-image-field-map.ts`). - * - **elevenlabs** — text-to-speech, music, sound effects, and voice design - * ids come from modelschemas via `scripts/sync-elevenlabs-models.ts`, not - * OpenRouter. Transcription stays hand-maintained. + * - **fal** — 1k+ model-grained endpoints; image field maps have their own + * generator (`scripts/generate-fal-image-field-map.ts`). * - **bedrock** — ids are AWS-region-qualified; see * `scripts/fetch-bedrock-models.ts`. + * - **ollama**, **llmgateway**, **vercel-gateway**, **lovable** — different + * catalogs (local, aggregator, or their own fetch scripts). + * - **claude-code**, **codex**, **opencode**, **grok-build** — harness aliases. + * BytePlus video/image duration and size tables stay hand-curated. */ import { execFileSync } from 'node:child_process' -import { readFile, writeFile } from 'node:fs/promises' +import { readFile, readdir, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { - isRoutingAlias, - toModelConstName, - toNativeProviderId, -} from './model-sync/ids' + ELEVENLABS_ID_ARRAYS, + alreadySynced, + outputsText, + pricingLines, + selectElevenLabsInserts, + selectNativeInserts, +} from './model-sync/catalog' +import type { CatalogModel, SyncModel } from './model-sync/catalog' +import { toModelConstName } from './model-sync/ids' import { + addToStringLiteralArray, applyChatModelCatalogInserts, + extractStringLiteralArrayValues, insertConstants, } from './model-sync/native-insert' +import { createSyncClient, fetchSyncCatalogs } from './model-sync/modelschemas' import { buildAnthropicProviderOptionsType, buildProviderSupportsBody, } from './model-sync/provider-supports' -import type { SyncedProvider } from './model-sync/provider-supports' -import { models } from './openrouter.models' -import type { OpenRouterModel } from './openrouter.models' +import type { ArrayRef } from './model-sync/native-insert' +import type { ModelMetaProvider } from './model-sync/provider-supports' const __dirname = dirname(fileURLToPath(import.meta.url)) const ROOT = resolve(__dirname, '..') -// --------------------------------------------------------------------------- -// Provider configuration -// --------------------------------------------------------------------------- - /** Seconds in 30 days — models older than this before the last sync are skipped */ const MAX_MODEL_AGE_SECONDS = 30 * 24 * 60 * 60 const LAST_RUN_FILE = resolve(ROOT, 'scripts/.sync-models-last-run') interface ProviderConfig { - /** npm package name for changeset */ packageName: string metaFile: string /** How array entries reference the constant, e.g. '.name' or '.id' */ - arrayRef: '.name' | '.id' + arrayRef: ArrayRef /** Which field name is used for context window size */ contextField: 'context_window' | 'max_input_tokens' /** Name of the exported chat model array */ @@ -102,8 +93,6 @@ interface ProviderConfig { * `claude-opus-5-fast` as excluded. */ combinedToolsAndSchemaSetName?: string - /** Provider key for conservative supports generation */ - kind: SyncedProvider /** Valid input modality types for this provider's ModelMeta interface */ validInputModalities: Array /** The satisfies type clause (after 'as const satisfies') */ @@ -114,12 +103,22 @@ interface ProviderConfig { hasBothNameAndId: boolean /** Whether the provider options type is a mapped type (skip insertion) */ providerOptionsIsMappedType: boolean - /** Model ID patterns to always skip (matched against stripped ID) */ + /** Raw-id prefixes to always skip */ skipPatterns: Array + /** Activities to insert. `null` means the catalog left activity unset. */ + acceptedActivities: Array + /** See `NativeInsertRules.requireOpenRouterEnrich`. */ + requireOpenRouterEnrich: boolean + /** + * Write a `pricing` block. An unknown side is left out, never written as + * 0, so Groq/Mistral can insert with `pricing: {}`. BytePlus has none. + */ + includePricing: boolean + outputTokenField: 'max_output_tokens' | 'max_completion_tokens' } -const PROVIDER_MAP: Record = { - 'openai/': { +const PROVIDER_MAP: Record = { + openai: { packageName: '@tanstack/ai-openai', metaFile: resolve(ROOT, 'packages/ai-openai/src/model-meta.ts'), arrayRef: '.name', @@ -129,7 +128,6 @@ const PROVIDER_MAP: Record = { inputModalitiesTypeName: 'OpenAIModelInputModalitiesByName', toolCapabilitiesTypeName: 'OpenAIChatModelToolCapabilitiesByName', validInputModalities: ['text', 'image', 'audio', 'video'], - kind: 'openai', referenceSatisfies: 'ModelMeta', referenceProviderOptionsEntry: @@ -143,8 +141,12 @@ const PROVIDER_MAP: Record = { 'gpt-oss-', // Open-source/experimental models 'chatgpt-', // ChatGPT branded models ], + acceptedActivities: ['chat'], + requireOpenRouterEnrich: true, + includePricing: true, + outputTokenField: 'max_output_tokens', }, - 'anthropic/': { + anthropic: { packageName: '@tanstack/ai-anthropic', metaFile: resolve(ROOT, 'packages/ai-anthropic/src/model-meta.ts'), arrayRef: '.id', @@ -156,7 +158,6 @@ const PROVIDER_MAP: Record = { maxOutputTokensMapName: 'ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS', combinedToolsAndSchemaSetName: 'ANTHROPIC_COMBINED_TOOLS_AND_SCHEMA_MODELS', validInputModalities: ['text', 'image', 'audio', 'video', 'document'], - kind: 'anthropic', referenceSatisfies: 'ModelMeta', referenceProviderOptionsEntry: @@ -164,8 +165,12 @@ const PROVIDER_MAP: Record = { hasBothNameAndId: true, providerOptionsIsMappedType: false, skipPatterns: [], + acceptedActivities: ['chat'], + requireOpenRouterEnrich: true, + includePricing: true, + outputTokenField: 'max_output_tokens', }, - 'google/': { + gemini: { packageName: '@tanstack/ai-gemini', metaFile: resolve(ROOT, 'packages/ai-gemini/src/model-meta.ts'), arrayRef: '.name', @@ -175,7 +180,6 @@ const PROVIDER_MAP: Record = { inputModalitiesTypeName: 'GeminiModelInputModalitiesByName', toolCapabilitiesTypeName: 'GeminiChatModelToolCapabilitiesByName', validInputModalities: ['text', 'image', 'audio', 'video', 'document'], - kind: 'gemini', referenceSatisfies: 'ModelMeta', referenceProviderOptionsEntry: @@ -185,8 +189,12 @@ const PROVIDER_MAP: Record = { skipPatterns: [ 'gemma-', // Gemma open-source models (not Gemini API models) ], + acceptedActivities: ['chat'], + requireOpenRouterEnrich: true, + includePricing: true, + outputTokenField: 'max_output_tokens', }, - 'x-ai/': { + grok: { packageName: '@tanstack/ai-grok', metaFile: resolve(ROOT, 'packages/ai-grok/src/model-meta.ts'), arrayRef: '.name', @@ -196,18 +204,92 @@ const PROVIDER_MAP: Record = { inputModalitiesTypeName: 'GrokModelInputModalitiesByName', toolCapabilitiesTypeName: 'GrokChatModelToolCapabilitiesByName', validInputModalities: ['text', 'image', 'audio', 'video', 'document'], - kind: 'grok', referenceSatisfies: 'ModelMeta', referenceProviderOptionsEntry: 'GrokProviderOptions', hasBothNameAndId: false, providerOptionsIsMappedType: true, skipPatterns: [], + acceptedActivities: ['chat'], + requireOpenRouterEnrich: true, + includePricing: true, + outputTokenField: 'max_output_tokens', + }, + groq: { + packageName: '@tanstack/ai-groq', + metaFile: resolve(ROOT, 'packages/ai-groq/src/model-meta.ts'), + arrayRef: '.name', + contextField: 'context_window', + chatArrayName: 'GROQ_CHAT_MODELS', + providerOptionsTypeName: 'GroqChatModelProviderOptionsByName', + inputModalitiesTypeName: 'GroqModelInputModalitiesByName', + toolCapabilitiesTypeName: 'GroqChatModelToolCapabilitiesByName', + validInputModalities: ['text', 'image', 'audio'], + referenceSatisfies: 'ModelMeta', + referenceProviderOptionsEntry: 'GroqTextProviderOptions', + hasBothNameAndId: false, + providerOptionsIsMappedType: true, + skipPatterns: ['whisper-', 'canopylabs/'], + acceptedActivities: [null, 'chat'], + requireOpenRouterEnrich: false, + includePricing: true, + outputTokenField: 'max_completion_tokens', + }, + mistral: { + packageName: '@tanstack/ai-mistral', + metaFile: resolve(ROOT, 'packages/ai-mistral/src/model-meta.ts'), + arrayRef: '.name', + contextField: 'context_window', + chatArrayName: 'MISTRAL_CHAT_MODELS', + providerOptionsTypeName: 'MistralChatModelProviderOptionsByName', + inputModalitiesTypeName: 'MistralModelInputModalitiesByName', + validInputModalities: ['text', 'image', 'audio', 'document'], + referenceSatisfies: 'ModelMeta', + referenceProviderOptionsEntry: 'MistralTextProviderOptions', + hasBothNameAndId: false, + providerOptionsIsMappedType: false, + skipPatterns: [ + 'mistral-embed', + 'codestral-embed', + 'mistral-ocr', + 'mistral-moderation', + 'voxtral-', + 'mistral-vibe', + 'mistral-code-fim', + 'mistral-code-', + 'glm-', + 'zai-', + ], + acceptedActivities: [null, 'chat'], + requireOpenRouterEnrich: false, + includePricing: true, + outputTokenField: 'max_completion_tokens', + }, + byteplus: { + packageName: '@tanstack/ai-byteplus', + metaFile: resolve(ROOT, 'packages/ai-byteplus/src/model-meta.ts'), + arrayRef: '.name', + contextField: 'context_window', + chatArrayName: 'BYTEPLUS_CHAT_MODELS', + providerOptionsTypeName: 'BytePlusChatModelProviderOptionsByName', + inputModalitiesTypeName: 'BytePlusModelInputModalitiesByName', + validInputModalities: ['text', 'image', 'audio', 'video', 'document'], + referenceSatisfies: 'ModelMeta', + referenceProviderOptionsEntry: 'BytePlusTextProviderOptions', + hasBothNameAndId: false, + providerOptionsIsMappedType: true, + skipPatterns: [], + acceptedActivities: ['chat'], + requireOpenRouterEnrich: false, + includePricing: false, + outputTokenField: 'max_output_tokens', }, } -// --------------------------------------------------------------------------- -// Utility functions -// --------------------------------------------------------------------------- +/** ElevenLabs model-meta is id-literal arrays, not ModelMeta constants. */ +const ELEVENLABS = { + packageName: '@tanstack/ai-elevenlabs', + metaFile: resolve(ROOT, 'packages/ai-elevenlabs/src/model-meta.ts'), +} type InputModality = 'text' | 'image' | 'audio' | 'video' | 'document' @@ -220,324 +302,247 @@ const MODALITY_MAP: Record = { document: 'document', } -/** - * Map OpenRouter input modalities to our standard modality types. - * Same mapping as the existing convert-openrouter-models.ts script. - */ function mapInputModalities(modalities: Array): Array { const mapped = modalities .map((m) => MODALITY_MAP[m.toLowerCase()]) .filter((m): m is InputModality => m !== undefined) - // Ensure at least 'text' is present if (!mapped.includes('text')) { mapped.unshift('text') } return mapped } -/** Strip the provider prefix from an OpenRouter model ID */ -function stripPrefix(prefix: string, modelId: string): string { - return modelId.slice(prefix.length) -} - -/** - * Convert an OpenRouter price string to per-million-token pricing. - * Same logic as the existing convert script. - */ -function convertPrice(priceStr: string | undefined): number { - const price = parseFloat(priceStr ?? '0') - if (isNaN(price)) return 0 - const result = price * 1_000_000 - return Math.round(result * 1e10) / 1e10 -} - -function anthropicOptionsType(model: OpenRouterModel): string { +function anthropicOptionsType(model: SyncModel): string { return buildAnthropicProviderOptionsType({ - supportedParameters: model.supported_parameters, - hasCachedPricing: convertPrice(model.pricing.input_cache_read) > 0, + supportedParameters: model.supportedParameters, + // modelschemas has no cache price, and every current Claude model + // supports prompt caching. + hasCachedPricing: true, }) } function providerOptionsEntryFor( - model: OpenRouterModel, + model: SyncModel, + provider: ModelMetaProvider, config: ProviderConfig, ): string { - if (config.kind === 'anthropic') return anthropicOptionsType(model) + if (provider === 'anthropic') return anthropicOptionsType(model) return config.referenceProviderOptionsEntry } function satisfiesClause( - model: OpenRouterModel, + model: SyncModel, + provider: ModelMetaProvider, config: ProviderConfig, ): string { - if (config.kind === 'anthropic') { + if (provider === 'anthropic') { return `ModelMeta<${anthropicOptionsType(model)}>` } return config.referenceSatisfies } -/** - * Normalize a model ID for comparison. - * Handles cases where the same model uses dots vs dashes - * (e.g., Anthropic's 'claude-3-5-haiku' vs OpenRouter's 'claude-3.5-haiku'). - */ -function normalizeId(id: string): string { - return id.replace(/[.]/g, '-') -} - -/** - * Extract existing model IDs from a model-meta file. - * Matches BOTH name: 'xxx' AND id: 'xxx' lines to get a complete set, - * since providers like Anthropic use different naming between name and id. - * Returns a normalized set for comparison purposes. - */ function extractExistingModelIds(content: string): Set { - const ids = new Set() - // Match both name and id fields inside const blocks - const nameRegex = /^\s+name:\s*'([^']+)'/gm - const idRegex = /^\s+id:\s*'([^']+)'/gm - let match - while ((match = nameRegex.exec(content)) !== null) { - ids.add(normalizeId(match[1]!)) - } - while ((match = idRegex.exec(content)) !== null) { - ids.add(normalizeId(match[1]!)) - } - return ids + return new Set( + Array.from(content.matchAll(/^\s+(?:name|id):\s*'([^']+)'/gm), (m) => + m[1]!.replaceAll('.', '-'), + ), + ) } -/** - * Extract existing constant names from a model-meta file. - * Matches: const UPPER_CASE_NAME = - */ function extractExistingConstNames(content: string): Set { - const names = new Set() - const regex = /^const\s+([A-Z][A-Z0-9_]+)\s*=/gm - let match - while ((match = regex.exec(content)) !== null) { - names.add(match[1]!) - } - return names -} - -/** - * Check if an OpenRouter model outputs text (for chat model array). - */ -function outputsText(model: OpenRouterModel): boolean { - return model.architecture.output_modalities.includes('text') -} - -/** - * Check if an OpenRouter model produces image output. - * - * Image-generation models are skipped entirely by the sync — regardless of - * whether they ALSO output text — because image model arrays require manual - * curation with specialized type maps (sizes, provider options, the native - * image union). This covers text+image "native" image models such as the - * Gemini "Nano Banana" family (`gemini-*-image`, e.g. - * `gemini-3.1-flash-lite-image`), which would otherwise be misclassified as - * chat models (they output text) and inserted with a bogus `output: ['text']`. - */ -function outputsImage(model: OpenRouterModel): boolean { - return model.architecture.output_modalities.includes('image') -} - -/** - * Non-chat model family prefixes to exclude from chat model arrays. - * These are audio/music/video/image generation models that happen to - * include 'text' in their output modalities but are not chat models. - */ -const NON_CHAT_MODEL_PREFIXES = [ - 'lyria-', // Google music generation - 'veo-', // Google video generation - 'imagen-', // Google image generation - 'sora-', // OpenAI video generation - 'dall-e-', // OpenAI image generation - 'tts-', // Text-to-speech models -] - -function isNonChatModel(strippedId: string): boolean { - return NON_CHAT_MODEL_PREFIXES.some((p) => strippedId.startsWith(p)) -} - -/** - * Check if a model should be skipped based on provider-specific patterns. - */ -function matchesSkipPattern( - strippedId: string, - patterns: Array, -): boolean { - return patterns.some((p) => strippedId.startsWith(p)) + return new Set( + Array.from( + content.matchAll(/^const\s+([A-Z][A-Z0-9_]+)\s*=/gm), + (m) => m[1]!, + ), + ) } -/** - * Read the last sync run timestamp. Returns epoch seconds, or null if no previous run. - */ async function readLastRunTimestamp(): Promise { + let content: string try { - const content = await readFile(LAST_RUN_FILE, 'utf-8') - const ts = parseInt(content.trim(), 10) - return isNaN(ts) ? null : ts - } catch { - return null + content = await readFile(LAST_RUN_FILE, 'utf-8') + } catch (error) { + // First run. Any other read error must not silently reset the cutoff. + if (isNodeError(error) && error.code === 'ENOENT') return null + throw error } + const ts = parseInt(content.trim(), 10) + if (isNaN(ts)) throw new Error(`${LAST_RUN_FILE} is not a timestamp`) + return ts +} + +function isNodeError(error: unknown): error is NodeJS.ErrnoException { + return error instanceof Error && 'code' in error } -/** - * Write the current timestamp as the last sync run. - */ async function writeLastRunTimestamp(): Promise { const now = Math.floor(Date.now() / 1000) await writeFile(LAST_RUN_FILE, String(now) + '\n', 'utf-8') } -/** - * Check if a model is too old to sync. Models created more than 30 days - * before the last sync run are considered deprecated/legacy and skipped. - */ -function isModelTooOld( - model: OpenRouterModel, - cutoffTimestamp: number, -): boolean { - if (!model.created) return false // No date = don't skip - return model.created < cutoffTimestamp -} - -// --------------------------------------------------------------------------- -// Model constant generation -// --------------------------------------------------------------------------- - function generateModelConstant( - model: OpenRouterModel, - prefix: string, + model: SyncModel, + provider: ModelMetaProvider, config: ProviderConfig, ): string { - const strippedId = stripPrefix(prefix, model.id) - const nativeId = toNativeProviderId(strippedId, config.kind) - const constName = toModelConstName(nativeId) - - const inputNormal = convertPrice(model.pricing.prompt) - const inputCached = convertPrice(model.pricing.input_cache_read) - const outputNormal = convertPrice(model.pricing.completion) + const constName = toModelConstName(model.nativeId) - // Use actual input modalities from OpenRouter data, filtered to what this provider supports - const inputModalities = mapInputModalities( - model.architecture.input_modalities, - ).filter((m) => config.validInputModalities.includes(m)) + const inputModalities = mapInputModalities(model.inputModalities).filter( + (m) => config.validInputModalities.includes(m), + ) const lines: Array = [] lines.push(`const ${constName} = {`) - - // name field - lines.push(` name: '${nativeId}',`) - - // id field (Anthropic has both name and id, set to same value for new models) + lines.push(` name: '${model.nativeId}',`) if (config.hasBothNameAndId) { - lines.push(` id: '${nativeId}',`) + lines.push(` id: '${model.nativeId}',`) } - - // context / max_input_tokens - if (model.context_length > 0) { + if (isPositive(model.contextWindow)) { lines.push( - ` ${config.contextField}: ${formatNumber(model.context_length)},`, + ` ${config.contextField}: ${formatNumber(model.contextWindow)},`, ) } - - // max_output_tokens - if (model.top_provider.max_completion_tokens) { + if (isPositive(model.maxOutput)) { lines.push( - ` max_output_tokens: ${formatNumber(model.top_provider.max_completion_tokens)},`, + ` ${config.outputTokenField}: ${formatNumber(model.maxOutput)},`, ) } - lines.push(` supports: {`) lines.push( buildProviderSupportsBody({ - provider: config.kind, + provider, inputModalities, - supportedParameters: model.supported_parameters, - reasoningMandatory: model.reasoning?.mandatory === true, + outputModalities: model.outputModalities, + supportedParameters: model.supportedParameters, + reasoningMandatory: model.reasoningMandatory, }), ) lines.push(` },`) - - // pricing - lines.push(` pricing: {`) - lines.push(` input: {`) - lines.push(` normal: ${inputNormal},`) - if (inputCached > 0) { - lines.push(` cached: ${inputCached},`) + if (config.includePricing) { + lines.push(...pricingLines(model.pricing)) } - lines.push(` },`) - lines.push(` output: {`) - lines.push(` normal: ${outputNormal},`) - lines.push(` },`) - lines.push(` },`) - - lines.push(`} as const satisfies ${satisfiesClause(model, config)}`) - + lines.push(`} as const satisfies ${satisfiesClause(model, provider, config)}`) return lines.join('\n') } -/** - * Format a number with underscore separators for readability. - * E.g. 131072 -> '131_072', 200000 -> '200_000' - */ -function formatNumber(n: number): string { - if (n < 1000) return String(n) - const str = String(n) - // Insert underscores every 3 digits from the right - const parts: Array = [] - let remaining = str - while (remaining.length > 3) { - parts.unshift(remaining.slice(-3)) - remaining = remaining.slice(0, -3) - } - parts.unshift(remaining) - return parts.join('_') +function isPositive(n: number | null): n is number { + return n != null && n > 0 } -// --------------------------------------------------------------------------- -// Git-based change detection -// --------------------------------------------------------------------------- +/** 1048576 → 1_048_576 */ +function formatNumber(n: number): string { + return n.toLocaleString('en-US').replaceAll(',', '_') +} -/** - * Detect packages with uncommitted changes by running `git diff`. - * This captures changes from ALL prior pipeline steps (e.g. convert-openrouter-models.ts) - * not just the models added by this script. - */ +/** Packages the fetch scripts changed, so the changeset covers them too. */ function detectChangedPackages(): Set { + const diff = execFileSync( + 'git', + ['diff', 'HEAD', '--name-only', '--', 'packages/'], + { encoding: 'utf-8', cwd: ROOT }, + ) const changed = new Set() - try { - const diff = execFileSync( - 'git', - ['diff', 'HEAD', '--name-only', '--', 'packages/'], - { encoding: 'utf-8', cwd: ROOT }, - ).trim() - if (!diff) return changed - - for (const line of diff.split('\n')) { - // packages/ai-openrouter/... → @tanstack/ai-openrouter - const match = line.match(/^packages\/([\w-]+)\//) - if (match) { - changed.add(`@tanstack/${match[1]}`) - } - } - } catch { - // git not available (e.g. running outside a repo) — fall back to empty set + for (const match of diff.matchAll(/^packages\/([\w-]+)\//gm)) { + changed.add(`@tanstack/${match[1]}`) } return changed } -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- +async function syncModelMeta( + provider: ModelMetaProvider, + config: ProviderConfig, + catalogs: { + native: Record> + openrouter: Array + }, + cutoffTimestamp: number, +): Promise<{ added: number; heldBack: number }> { + console.log(`\nProcessing provider: ${provider}`) + const rows = catalogs.native[provider] + console.log(` Found ${rows.length} modelschemas models for '${provider}'`) + + let content = await readFile(config.metaFile, 'utf-8') + const existingIds = extractExistingModelIds(content) + const existingConstNames = extractExistingConstNames(content) + console.log( + ` Existing models in file: ${existingIds.size} IDs, ${existingConstNames.size} constants`, + ) -async function main() { - let totalAdded = 0 - const changedPackages = new Set() + const { inserts, heldBack, skipped } = selectNativeInserts( + rows, + { ...config, provider, cutoffTimestamp }, + catalogs.openrouter, + (nativeId) => alreadySynced(nativeId, existingIds, existingConstNames), + ) + const skipCounts = Object.entries(skipped) + .map(([reason, count]) => `${reason}=${count}`) + .join(', ') + if (skipCounts) console.log(` Skipped: ${skipCounts}`) + for (const rawId of heldBack) { + console.log(` Waiting for an OpenRouter price: ${rawId}`) + } + + if (inserts.length === 0) { + console.log(' No new models to add.') + return { added: 0, heldBack: heldBack.length } + } + + console.log(` Adding ${inserts.length} new models:`) + for (const { model } of inserts) { + console.log(` - ${model.nativeId} (${toModelConstName(model.nativeId)})`) + } + + content = insertConstants( + content, + inserts.map(({ model }) => generateModelConstant(model, provider, config)), + ) + content = applyChatModelCatalogInserts( + content, + config, + inserts + .filter(({ model, activity }) => outputsText(model, activity)) + .map(({ model }) => ({ + constName: toModelConstName(model.nativeId), + providerOptionsEntry: providerOptionsEntryFor(model, provider, config), + hasMaxOutputTokens: isPositive(model.maxOutput), + acceptsCombinedToolsAndSchema: !model.nativeId.endsWith('-fast'), + })), + ) + + await writeFile(config.metaFile, content, 'utf-8') + console.log(` Wrote updated file: ${config.metaFile}`) + return { added: inserts.length, heldBack: heldBack.length } +} + +async function syncElevenLabs( + rows: Array, + cutoffTimestamp: number, +): Promise { + console.log(`\nProcessing provider: elevenlabs`) + let content = await readFile(ELEVENLABS.metaFile, 'utf-8') + const existingIds = new Set( + ELEVENLABS_ID_ARRAYS.flatMap((name) => [ + ...extractStringLiteralArrayValues(content, name), + ]), + ) + const byArray = selectElevenLabsInserts(rows, cutoffTimestamp, existingIds) + const added = Object.values(byArray).flat() + if (added.length === 0) { + console.log(' No new models to add.') + return 0 + } + console.log(` Adding ${added.length} new models:`) + for (const id of added) console.log(` - ${id}`) + for (const name of ELEVENLABS_ID_ARRAYS) { + content = addToStringLiteralArray(content, name, byArray[name]) + } + await writeFile(ELEVENLABS.metaFile, content, 'utf-8') + console.log(` Wrote updated file: ${ELEVENLABS.metaFile}`) + return added.length +} - // Determine age cutoff: skip models created >30 days before last run +async function main() { const lastRun = await readLastRunTimestamp() const now = Math.floor(Date.now() / 1000) const cutoffTimestamp = (lastRun ?? now) - MAX_MODEL_AGE_SECONDS @@ -548,152 +553,49 @@ async function main() { `Model age cutoff: ${cutoffDate} (skipping models created before this date)`, ) - for (const [prefix, config] of Object.entries(PROVIDER_MAP)) { - console.log(`\nProcessing provider: ${prefix}`) + const catalogs = await fetchSyncCatalogs(createSyncClient()) + const counts = Object.entries(catalogs.native) + .map(([id, rows]) => `${id}=${rows.length}`) + .join(', ') + console.log( + `Fetched modelschemas catalogs: ${counts}, openrouter=${catalogs.openrouter.length}`, + ) - // Filter OpenRouter models for this prefix - const providerModels = models.filter((m) => m.id.startsWith(prefix)) - console.log( - ` Found ${providerModels.length} OpenRouter models with prefix '${prefix}'`, + const changedPackages = new Set() + let totalAdded = 0 + let totalHeldBack = 0 + for (const [provider, config] of Object.entries(PROVIDER_MAP) as Array< + [ModelMetaProvider, ProviderConfig] + >) { + const { added, heldBack } = await syncModelMeta( + provider, + config, + catalogs, + cutoffTimestamp, ) + totalAdded += added + totalHeldBack += heldBack + if (added > 0) changedPackages.add(config.packageName) + } + const elevenLabsAdded = await syncElevenLabs( + catalogs.native.elevenlabs, + cutoffTimestamp, + ) + totalAdded += elevenLabsAdded + if (elevenLabsAdded > 0) changedPackages.add(ELEVENLABS.packageName) - // Read the provider's model-meta.ts - let content: string - try { - content = await readFile(config.metaFile, 'utf-8') - } catch { - console.warn(` Skipping: could not read ${config.metaFile}`) - continue - } - - // Extract existing model IDs (normalized) and constant names - const existingIds = extractExistingModelIds(content) - const existingConstNames = extractExistingConstNames(content) + console.log(`\nDone. Added ${totalAdded} new models total.`) + // Held-back ids still age out after 30 days. Freezing the timestamp + // instead would stall it for good on ids OpenRouter never prices (Live + // API models), so each run names them in its log. + if (totalHeldBack > 0) { console.log( - ` Existing models in file: ${existingIds.size} IDs, ${existingConstNames.size} constants`, + `${totalHeldBack} models are waiting for an OpenRouter price (listed above).`, ) - - // Find new models - const newModels: Array<{ - model: OpenRouterModel - constName: string - strippedId: string - }> = [] - - for (const model of providerModels) { - if (isRoutingAlias(model.id)) { - continue - } - - const strippedId = stripPrefix(prefix, model.id) - const nativeId = toNativeProviderId(strippedId, config.kind) - const constName = toModelConstName(nativeId) - - // Skip models with ':' variants (e.g., 'anthropic/claude-3.7-sonnet:thinking') - // These are routing variants, not separate models - if (strippedId.includes(':')) { - continue - } - - // Skip non-chat model families (audio/music/video/image generation) - if (isNonChatModel(strippedId)) { - continue - } - - // Skip provider-specific patterns (deprecated/legacy model families) - if (matchesSkipPattern(strippedId, config.skipPatterns)) { - continue - } - - // Skip models that are too old (created >30 days before last sync) - if (isModelTooOld(model, cutoffTimestamp)) { - continue - } - - // Normalize for comparison to handle dots-vs-dashes naming differences - if ( - !existingIds.has(normalizeId(strippedId)) && - !existingConstNames.has(constName) - ) { - newModels.push({ model, constName, strippedId: nativeId }) - } - } - - if (newModels.length === 0) { - console.log(' No new models to add.') - continue - } - - console.log(` Adding ${newModels.length} new models:`) - for (const { strippedId, constName } of newModels) { - console.log(` - ${strippedId} (${constName})`) - } - - // Filter out image-generation models (they need manual curation for - // size/provider type maps + the native image union). This includes - // text+image models like the Gemini "Nano Banana" native image family, - // not just image-only models. - const filteredModels = newModels.filter(({ model }) => !outputsImage(model)) - const skippedImageModels = newModels.length - filteredModels.length - if (skippedImageModels > 0) { - console.log( - ` Skipping ${skippedImageModels} image-generation models (require manual curation)`, - ) - } - - if (filteredModels.length === 0) { - console.log(' No eligible models to add after filtering.') - continue - } - - // Generate constants - const constants = filteredModels.map(({ model }) => - generateModelConstant(model, prefix, config), - ) - - // Insert constants before first export - content = insertConstants(content, constants) - - // NOTE: We intentionally do NOT add models to image arrays. - // Image model arrays have specialized type maps (sizes, provider options) - // that require manual curation. - - const chatModels = filteredModels.filter(({ model }) => outputsText(model)) - content = applyChatModelCatalogInserts( - content, - { - chatArrayName: config.chatArrayName, - arrayRef: config.arrayRef, - providerOptionsTypeName: config.providerOptionsTypeName, - inputModalitiesTypeName: config.inputModalitiesTypeName, - toolCapabilitiesTypeName: config.toolCapabilitiesTypeName, - maxOutputTokensMapName: config.maxOutputTokensMapName, - combinedToolsAndSchemaSetName: config.combinedToolsAndSchemaSetName, - providerOptionsIsMappedType: config.providerOptionsIsMappedType, - }, - chatModels.map(({ model, constName, strippedId }) => ({ - constName, - providerOptionsEntry: providerOptionsEntryFor(model, config), - hasMaxOutputTokens: Boolean(model.top_provider.max_completion_tokens), - acceptsCombinedToolsAndSchema: !strippedId.endsWith('-fast'), - })), - ) - - // Write the modified file - await writeFile(config.metaFile, content, 'utf-8') - console.log(` Wrote updated file: ${config.metaFile}`) - totalAdded += filteredModels.length - changedPackages.add(config.packageName) } - - console.log(`\nDone. Added ${totalAdded} new models total.`) - - // Record this run's timestamp for future age-based filtering await writeLastRunTimestamp() - // Detect all packages with uncommitted changes (includes changes from - // convert-openrouter-models.ts which runs before this script) const allChangedPackages = detectChangedPackages() for (const pkg of changedPackages) { allChangedPackages.add(pkg) @@ -704,48 +606,34 @@ async function main() { } } -/** - * Create or update the sync-models changeset file. - * If one already exists, merges the package lists. Otherwise creates a new one. - */ async function createChangeset(changedPackages: Set) { const changesetDir = resolve(ROOT, '.changeset') - const { readdir } = await import('node:fs/promises') - const files = await readdir(changesetDir) - const existing = files.find( + const existing = (await readdir(changesetDir)).find( (f) => f.startsWith('sync-models') && f.endsWith('.md'), ) - + const changesetPath = resolve(changesetDir, existing ?? 'sync-models.md') if (existing) { - const existingPath = resolve(changesetDir, existing) - const existingContent = await readFile(existingPath, 'utf-8') - - // Merge existing packages into the set - const pkgRegex = /'([^']+)':\s*patch/g - let match - while ((match = pkgRegex.exec(existingContent)) !== null) { + const existingContent = await readFile(changesetPath, 'utf-8') + for (const match of existingContent.matchAll(/'([^']+)':\s*patch/g)) { changedPackages.add(match[1]!) } - - const content = buildChangesetContent(changedPackages) - await writeFile(existingPath, content, 'utf-8') - console.log(`\nChangeset updated: ${existingPath}`) - } else { - const changesetFile = resolve(changesetDir, 'sync-models.md') - const content = buildChangesetContent(changedPackages) - await writeFile(changesetFile, content, 'utf-8') - console.log(`\nChangeset created: ${changesetFile}`) } - + await writeFile( + changesetPath, + buildChangesetContent(changedPackages), + 'utf-8', + ) + console.log( + `\nChangeset ${existing ? 'updated' : 'created'}: ${changesetPath}`, + ) console.log(` Packages: ${Array.from(changedPackages).sort().join(', ')}`) } - function buildChangesetContent(packages: Set): string { const packageLines = Array.from(packages) .sort() .map((pkg) => `'${pkg}': patch`) .join('\n') - return `---\n${packageLines}\n---\n\nUpdate model metadata from OpenRouter API\n` + return `---\n${packageLines}\n---\n\nUpdate model metadata from modelschemas\n` } main().catch((err) => { From fac81d4ef7f97457a50c07f1577330faaa69c971 Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:47:14 +1100 Subject: [PATCH 2/3] refactor(model-sync): keep main's ElevenLabs sync and fetch it through the client - Delete the PR's second ElevenLabs path (syncElevenLabs, prefix-based routing, string-literal array helpers, and their tests). Main's scripts/sync-elevenlabs-models.ts reads request-schema enums and rejects unsafe ids, so it stays the one path. - sync-elevenlabs-models.ts now uses @modelschemas/client (listModels, getSchema) instead of raw fetch, so MODELSCHEMAS_API_KEY applies. - Read canDoTextToSpeech from providerMetadata. modelschemas moved it out of capabilities, and the script failed with an empty TTS list. --- CONTRIBUTING.md | 8 +-- scripts/model-sync/catalog.test.ts | 52 ------------------ scripts/model-sync/catalog.ts | 55 +------------------ scripts/model-sync/modelschemas.ts | 38 ++++++++++++-- scripts/model-sync/native-insert.test.ts | 33 +----------- scripts/model-sync/native-insert.ts | 33 ------------ scripts/model-sync/provider-supports.ts | 6 +-- scripts/sync-elevenlabs-models.ts | 67 ++++++++++++------------ scripts/sync-provider-models.ts | 61 ++++----------------- 9 files changed, 84 insertions(+), 269 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4d401a12b6..c28c595169 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,8 +55,8 @@ For deeper architecture details (adapter system, isomorphic tools, framework int 1. Fetches OpenRouter, Vercel AI Gateway, and Lovable AI Gateway catalogs (OpenRouter adapter + gateway packages). 2. Regenerates `packages/ai-openrouter/src/model-meta.ts` and the Vercel Gateway model list. -3. Inserts **new** native-provider models from [modelschemas](https://modelschemas.com) (`@modelschemas/client`) into `ai-openai`, `ai-anthropic`, `ai-gemini`, `ai-grok`, `ai-groq`, `ai-mistral`, `ai-byteplus`, and `ai-elevenlabs`. Native ids, activity, limits, modalities, pricing, and capabilities come from each provider catalog. OpenRouter fills a field only when the native row left it empty. OpenAI, Anthropic, Gemini, and Grok hold an id back until the OpenRouter catalog has a matching row and both prices are known. The log names each held-back id. Groq and Mistral insert even when a price is unknown: the generator leaves that price out (`pricing: {}` when both are unknown) and never writes `0`. BytePlus and ElevenLabs do not write pricing. -4. Inserts **new** ElevenLabs text-to-speech, music, sound-effect, and voice-design ids from [modelschemas](https://modelschemas.com/v1/schemas/elevenlabs) into `packages/ai-elevenlabs/src/model-meta.ts` (`scripts/sync-elevenlabs-models.ts`). Transcription ids stay hand-maintained. +3. Inserts **new** native-provider models from [modelschemas](https://modelschemas.com) (`@modelschemas/client`) into `ai-openai`, `ai-anthropic`, `ai-gemini`, `ai-grok`, `ai-groq`, `ai-mistral`, and `ai-byteplus`. Native ids, activity, limits, modalities, pricing, and capabilities come from each provider catalog. OpenRouter fills a field only when the native row left it empty. OpenAI, Anthropic, Gemini, and Grok hold an id back until the OpenRouter catalog has a matching row and both prices are known. The log names each held-back id. Groq and Mistral insert even when a price is unknown: the generator leaves that price out (`pricing: {}` when both are unknown) and never writes `0`. BytePlus does not write pricing. +4. Inserts **new** ElevenLabs text-to-speech, music, sound-effect, and voice-design ids from [modelschemas](https://modelschemas.com/v1/schemas/elevenlabs) into `packages/ai-elevenlabs/src/model-meta.ts` (`scripts/sync-elevenlabs-models.ts`, also through `@modelschemas/client`). Text-to-speech ids are catalog rows with `providerMetadata.canDoTextToSpeech`. Music, sound-effect, and voice-design ids are the `model_id` enums of their request schemas. Transcription ids stay hand-maintained. 5. Writes a patch changeset for the packages that changed. The sync stops with an error when modelschemas is down, when a catalog is empty, when no row has a `rawId`, `firstSeenAt`, or `deprecatedAt` key, or when an array or type map it must update is missing from a `model-meta.ts`. @@ -67,8 +67,8 @@ Rules the generator follows: - Keep OpenRouter routing aliases (ids that start with `~`) in the OpenRouter catalog. Users can pass `chat({ model: '~anthropic/claude-haiku-latest' })`. The generated constant name maps `~` to `_`. - Do **not** copy those aliases into native provider files (any of the eight packages above). Those adapters only accept the provider's own ids. -- For a new native-provider model, write id, modalities, and (where the provider has a `pricing` field) pricing. Infer features from native catalog capabilities (a list of parameter names, or a feature object, as BytePlus sends). Do **not** copy another model's tool list (`computer_use`, `google_search`, `x_search`, and similar). Do **not** copy OpenRouter flags onto a native row that already listed capabilities. BytePlus, ElevenLabs, Groq, and Mistral insert from the native catalog even when OpenRouter has no row. BytePlus inserts never claim `structured_outputs`: that gate is a live-probed list. -- ElevenLabs is id-literal arrays (`ELEVENLABS_TTS_MODELS`, `_AUDIO_`, `_TRANSCRIPTION_`, `_VOICE_`), not ModelMeta constants. Voice-conversion (`*_sts_*`) ids are skipped. BytePlus video and image duration/size tables stay hand-curated. +- For a new native-provider model, write id, modalities, and (where the provider has a `pricing` field) pricing. Infer features from native catalog capabilities (a list of parameter names, or a feature object, as BytePlus sends). Do **not** copy another model's tool list (`computer_use`, `google_search`, `x_search`, and similar). Do **not** copy OpenRouter flags onto a native row that already listed capabilities. BytePlus, Groq, and Mistral insert from the native catalog even when OpenRouter has no row. BytePlus inserts never claim `structured_outputs`: that gate is a live-probed list. +- ElevenLabs music and voice-design ids are pinned to SDK unions. An id the ElevenLabs SDK does not know yet fails the type-check, so wait for the SDK. BytePlus video and image duration/size tables stay hand-curated. - Anthropic first-party ids use dashes (`claude-fable-5-1`). OpenRouter uses dots (`claude-fable-5.1`). The generator takes the modelschemas native id (already hyphenated) and does not copy the dotted OpenRouter slug into `ai-anthropic`. Dated Anthropic snapshots (`claude-haiku-4-5-20251001`) are skipped; the undated alias is the public id. - Write a row in the provider's `*ChatModelToolCapabilitiesByName` map for every new chat model, even when `supports.tools` is still `[]`. Missing that row makes `ResolveToolCapabilities` fall back to `readonly []`. - For new Anthropic models, infer the provider-options mix from the catalog: no sampling parameters → `AnthropicMaxTokensOptions`; `reasoning.mandatory` → `AnthropicAdaptiveOnlyThinkingOptions` plus `AnthropicOutputConfigOptions`; reasoning params without sampling → adaptive-or-disabled thinking. Every new Claude model gets `AnthropicCacheControlOptions`. diff --git a/scripts/model-sync/catalog.test.ts b/scripts/model-sync/catalog.test.ts index 96b8dfee49..477f1f73da 100644 --- a/scripts/model-sync/catalog.test.ts +++ b/scripts/model-sync/catalog.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest' import { alreadySynced, - elevenLabsIdArray, findOpenRouterEnrichment, hasFullPricing, hasImageOutput, @@ -9,7 +8,6 @@ import { outputsText, parseCatalogModels, pricingLines, - selectElevenLabsInserts, selectNativeInserts, skipNativeModelReason, toSyncModel, @@ -267,18 +265,6 @@ describe('skipNativeModelReason', () => { ).toBe('non-chat family') }) - it('keeps audio rows when audio is an accepted activity', () => { - expect( - skipNativeModelReason( - native({ activity: 'audio', rawId: 'eleven_v3' }), - 'elevenlabs', - [], - cutoff, - ['audio'], - ), - ).toBeNull() - }) - it('keeps Groq rows with no activity', () => { expect( skipNativeModelReason( @@ -303,44 +289,6 @@ describe('outputsText', () => { }) }) -describe('elevenLabsIdArray', () => { - it('routes TTS, skips STS, and classifies scribe / music', () => { - expect(elevenLabsIdArray('eleven_v3_conversational')).toBe( - 'ELEVENLABS_TTS_MODELS', - ) - expect(elevenLabsIdArray('eleven_multilingual_sts_v2')).toBeNull() - expect(elevenLabsIdArray('scribe_v2')).toBe( - 'ELEVENLABS_TRANSCRIPTION_MODELS', - ) - expect(elevenLabsIdArray('music_v2_5')).toBe('ELEVENLABS_AUDIO_MODELS') - expect(elevenLabsIdArray('eleven_ttv_v3')).toBe('ELEVENLABS_VOICE_MODELS') - }) -}) - -describe('selectElevenLabsInserts', () => { - const audio = (rawId: string) => - native({ provider: 'elevenlabs', rawId, activity: 'audio' }) - - it('skips ids already in the file and adds a repeated id once', () => { - const byArray = selectElevenLabsInserts( - [ - audio('eleven_v3'), - audio('eleven_v4'), - audio('eleven_v4'), - audio('scribe_v3'), - ], - 0, - new Set(['eleven_v3']), - ) - expect(byArray).toEqual({ - ELEVENLABS_TTS_MODELS: ['eleven_v4'], - ELEVENLABS_AUDIO_MODELS: [], - ELEVENLABS_TRANSCRIPTION_MODELS: ['scribe_v3'], - ELEVENLABS_VOICE_MODELS: [], - }) - }) -}) - describe('selectNativeInserts', () => { const priced = { inputPerMillion: 3, outputPerMillion: 15 } const rules = { diff --git a/scripts/model-sync/catalog.ts b/scripts/model-sync/catalog.ts index 71c020d645..13b828d81f 100644 --- a/scripts/model-sync/catalog.ts +++ b/scripts/model-sync/catalog.ts @@ -7,7 +7,7 @@ */ import { toModelConstName, toNativeProviderId } from './ids' -import type { ModelMetaProvider, SyncedProvider } from './provider-supports' +import type { SyncedProvider } from './provider-supports' export const OPENROUTER_PREFIX: Partial> = { openai: 'openai/', @@ -323,7 +323,7 @@ export function skipNativeModelReason( } export interface NativeInsertRules { - provider: ModelMetaProvider + provider: SyncedProvider skipPatterns: Array acceptedActivities: Array /** @@ -397,57 +397,6 @@ export function selectNativeInserts( return selection } -export const ELEVENLABS_ID_ARRAYS = [ - 'ELEVENLABS_TTS_MODELS', - 'ELEVENLABS_AUDIO_MODELS', - 'ELEVENLABS_TRANSCRIPTION_MODELS', - 'ELEVENLABS_VOICE_MODELS', -] as const - -type ElevenLabsIdArray = (typeof ELEVENLABS_ID_ARRAYS)[number] - -/** - * Which ElevenLabs id array a raw id belongs in. STS / voice-conversion ids - * have no TanStack adapter, so they get `null`. - */ -export function elevenLabsIdArray(rawId: string): ElevenLabsIdArray | null { - if (rawId.includes('_sts_')) return null - if (rawId.includes('_ttv_')) return 'ELEVENLABS_VOICE_MODELS' - if (rawId.startsWith('scribe')) return 'ELEVENLABS_TRANSCRIPTION_MODELS' - if (rawId.includes('text_to_sound') || rawId.startsWith('music_')) { - return 'ELEVENLABS_AUDIO_MODELS' - } - if (rawId.startsWith('eleven_')) return 'ELEVENLABS_TTS_MODELS' - return null -} - -/** New ElevenLabs ids per array. An id is added at most once. */ -export function selectElevenLabsInserts( - rows: Array, - cutoffTimestamp: number, - existingIds: Set, -): Record> { - const byArray: Record> = { - ELEVENLABS_TTS_MODELS: [], - ELEVENLABS_AUDIO_MODELS: [], - ELEVENLABS_TRANSCRIPTION_MODELS: [], - ELEVENLABS_VOICE_MODELS: [], - } - const seen = new Set(existingIds) - for (const row of rows) { - if ( - skipNativeModelReason(row, 'elevenlabs', [], cutoffTimestamp, ['audio']) - ) { - continue - } - const arrayName = elevenLabsIdArray(row.rawId) - if (!arrayName || seen.has(row.rawId)) continue - byArray[arrayName].push(row.rawId) - seen.add(row.rawId) - } - return byArray -} - export function alreadySynced( nativeId: string, existingIds: Set, diff --git a/scripts/model-sync/modelschemas.ts b/scripts/model-sync/modelschemas.ts index 719335bab2..199c803c7c 100644 --- a/scripts/model-sync/modelschemas.ts +++ b/scripts/model-sync/modelschemas.ts @@ -1,8 +1,13 @@ /** - * Thin wrapper around `@modelschemas/client` for the native model sync. + * Thin wrapper around `@modelschemas/client` for the model sync scripts. */ -import { createModelschemasClient, listModels } from '@modelschemas/client' +import { + createModelschemasClient, + getSchema, + listModels, +} from '@modelschemas/client' +import type { GetSchemaData } from '@modelschemas/client' import { isRecord, parseCatalogModels } from './catalog' import type { CatalogModel } from './catalog' import { SYNCED_PROVIDERS } from './provider-supports' @@ -32,17 +37,40 @@ function errorMessage(error: unknown): string { return JSON.stringify(error) } -export async function fetchCatalog( +/** The raw `listModels` payload for one provider. */ +export async function listRawModels( client: SyncClient, provider: string, -): Promise> { +): Promise { const result = await listModels({ client, query: { provider } }) if (result.error !== undefined) { throw new Error( `modelschemas listModels provider=${provider}: ${errorMessage(result.error)}`, ) } - return parseCatalogModels(result.data) + return result.data +} + +export async function fetchCatalog( + client: SyncClient, + provider: string, +): Promise> { + return parseCatalogModels(await listRawModels(client, provider)) +} + +/** The raw input-schema payload for one endpoint, e.g. `v1/music`. */ +export async function getInputSchema( + client: SyncClient, + path: GetSchemaData['path'], +): Promise { + const result = await getSchema({ client, path, query: { kind: 'input' } }) + if (result.error !== undefined) { + const { provider, activity, endpointId } = path + throw new Error( + `modelschemas getSchema ${provider}/${activity}/${endpointId}: ${errorMessage(result.error)}`, + ) + } + return result.data } /** diff --git a/scripts/model-sync/native-insert.test.ts b/scripts/model-sync/native-insert.test.ts index b6066749f1..4bafe9f9df 100644 --- a/scripts/model-sync/native-insert.test.ts +++ b/scripts/model-sync/native-insert.test.ts @@ -1,10 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - addToStringLiteralArray, - applyChatModelCatalogInserts, - extractStringLiteralArrayValues, - insertConstants, -} from './native-insert' +import { applyChatModelCatalogInserts, insertConstants } from './native-insert' const STUB = `const FOO = { id: 'claude-foo', @@ -232,29 +227,3 @@ export const CHAT_MODELS = [OLD.name] as const expect(result).toBe(`const A = 1\n\nconst C = 3\n\nexport const B = 2\n`) }) }) - -describe('string-literal arrays', () => { - const stub = `export const ELEVENLABS_TTS_MODELS = [ - 'eleven_v3', -] as const -` - - it('inserts quoted ids after the opening bracket', () => { - const result = addToStringLiteralArray(stub, 'ELEVENLABS_TTS_MODELS', [ - 'eleven_v3_conversational', - ]) - expect(result).toContain(" 'eleven_v3_conversational',") - expect( - extractStringLiteralArrayValues(result, 'ELEVENLABS_TTS_MODELS'), - ).toEqual(new Set(['eleven_v3_conversational', 'eleven_v3'])) - }) - - it('throws instead of reading a missing array as empty', () => { - expect(() => - extractStringLiteralArrayValues(stub, 'ELEVENLABS_AUDIO_MODELS'), - ).toThrow("Could not find array 'ELEVENLABS_AUDIO_MODELS'") - expect(() => - addToStringLiteralArray(stub, 'ELEVENLABS_AUDIO_MODELS', ['music_v2']), - ).toThrow("Could not find array 'ELEVENLABS_AUDIO_MODELS'") - }) -}) diff --git a/scripts/model-sync/native-insert.ts b/scripts/model-sync/native-insert.ts index 6cbcdb4f02..fe5ea0a529 100644 --- a/scripts/model-sync/native-insert.ts +++ b/scripts/model-sync/native-insert.ts @@ -24,39 +24,6 @@ export function insertConstants( return content.slice(0, at) + block + content.slice(at) } -export function addToStringLiteralArray( - content: string, - arrayName: string, - values: Array, -): string { - if (values.length === 0) return content - return addToArray( - content, - arrayName, - values.map((value) => `'${value}'`), - '', - ) -} - -export function extractStringLiteralArrayValues( - content: string, - arrayName: string, -): Set { - const ids = new Set() - const block = content.match( - new RegExp(`export const ${arrayName} = \\[([\\s\\S]*?)\\] as const`), - ) - if (!block) { - // An empty set here would make every existing id look new. - throw new Error(`Could not find array '${arrayName}' in file`) - } - const quoted = block[1]!.matchAll(/'([^']+)'/g) - for (const match of quoted) { - ids.add(match[1]!) - } - return ids -} - /** * Insert entries immediately AFTER the opening bracket. Each inserted line * carries its own trailing comma so the existing body does not need a diff --git a/scripts/model-sync/provider-supports.ts b/scripts/model-sync/provider-supports.ts index dfbfe0bfb8..12c41a5952 100644 --- a/scripts/model-sync/provider-supports.ts +++ b/scripts/model-sync/provider-supports.ts @@ -17,16 +17,12 @@ export const SYNCED_PROVIDERS = [ 'groq', 'mistral', 'byteplus', - 'elevenlabs', ] as const export type SyncedProvider = (typeof SYNCED_PROVIDERS)[number] -/** Providers synced as ModelMeta constants. ElevenLabs is id arrays only. */ -export type ModelMetaProvider = Exclude - export interface ProviderSupportsInput { - provider: ModelMetaProvider + provider: SyncedProvider inputModalities: Array outputModalities?: Array supportedParameters?: Array diff --git a/scripts/sync-elevenlabs-models.ts b/scripts/sync-elevenlabs-models.ts index b48b1629ba..f45c8b756a 100644 --- a/scripts/sync-elevenlabs-models.ts +++ b/scripts/sync-elevenlabs-models.ts @@ -2,15 +2,18 @@ * Inserts new ElevenLabs model ids into * `packages/ai-elevenlabs/src/model-meta.ts`. * - * Text-to-speech: https://modelschemas.com/v1/models?provider=elevenlabs - * Music: request schema `v1/music` (compose). That enum is the SDK - * `MusicModelId` set. - * Sound effects: `v1/sound-generation`. - * Voice design: `v1/text-to-voice/design`. + * Reads modelschemas through `@modelschemas/client`: + * - Text-to-speech: `listModels` for provider `elevenlabs`. + * - Music: request schema `v1/music` (compose). That enum is the SDK + * `MusicModelId` set. + * - Sound effects: `v1/sound-generation`. + * - Voice design: `v1/text-to-voice/design`. * * Usage: * pnpm tsx scripts/sync-elevenlabs-models.ts * + * Optional: MODELSCHEMAS_API_KEY raises the modelschemas rate limit. + * * Runs as part of `pnpm generate:models` (the daily Sync Model Metadata * workflow). Only adds missing ids. Transcription stays hand-maintained: * its speech-to-text schema has no `model_id` enum. Music and voice design @@ -26,9 +29,12 @@ import { ttsModelIdsFromCatalog, } from './model-sync/elevenlabs' import type { ElevenLabsCatalogModel } from './model-sync/elevenlabs' +import { + createSyncClient, + getInputSchema, + listRawModels, +} from './model-sync/modelschemas' -const CATALOG_URL = 'https://modelschemas.com/v1/models?provider=elevenlabs' -const SCHEMA_ROOT = 'https://modelschemas.com/v1/schemas/elevenlabs/audio' const __dirname = dirname(fileURLToPath(import.meta.url)) const META_FILE = resolve( __dirname, @@ -40,41 +46,34 @@ interface CatalogResponse { models?: Array } -function schemaUrl(endpointId: string): string { - return `${SCHEMA_ROOT}/${endpointId}?kind=input` -} - -async function readJson(url: string): Promise { - const response = await fetch(url) - if (!response.ok) { - throw new Error( - `modelschemas ${response.status} ${response.statusText} for ${url}`, - ) - } - return response.json() -} - -function unwrapSchema(payload: unknown, url: string): unknown { +function unwrapSchema(payload: unknown, endpointId: string): unknown { if ( typeof payload !== 'object' || payload === null || !('schema' in payload) ) { - throw new Error(`modelschemas response has no schema for ${url}`) + throw new Error(`modelschemas response has no schema for ${endpointId}`) } return payload.schema } async function main() { - const musicUrl = schemaUrl('v1/music') - const sfxUrl = schemaUrl('v1/sound-generation') - const voiceUrl = schemaUrl('v1/text-to-voice/design') - const [catalogPayload, musicPayload, sfxPayload, voicePayload] = + const client = createSyncClient() + const schema = async (endpointId: string) => + unwrapSchema( + await getInputSchema(client, { + provider: 'elevenlabs', + activity: 'audio', + endpointId, + }), + endpointId, + ) + const [catalogPayload, musicSchema, sfxSchema, voiceSchema] = await Promise.all([ - readJson(CATALOG_URL), - readJson(musicUrl), - readJson(sfxUrl), - readJson(voiceUrl), + listRawModels(client, 'elevenlabs'), + schema('v1/music'), + schema('v1/sound-generation'), + schema('v1/text-to-voice/design'), ]) const body = catalogPayload as CatalogResponse @@ -94,14 +93,14 @@ async function main() { 'ELEVENLABS_AUDIO_MODELS', 'audio', [ - ...modelIdsFromRequestSchema(unwrapSchema(musicPayload, musicUrl)), - ...modelIdsFromRequestSchema(unwrapSchema(sfxPayload, sfxUrl)), + ...modelIdsFromRequestSchema(musicSchema), + ...modelIdsFromRequestSchema(sfxSchema), ], ], [ 'ELEVENLABS_VOICE_MODELS', 'voice', - modelIdsFromRequestSchema(unwrapSchema(voicePayload, voiceUrl)), + modelIdsFromRequestSchema(voiceSchema), ], ] if (groups.some(([, , ids]) => ids.length === 0)) { diff --git a/scripts/sync-provider-models.ts b/scripts/sync-provider-models.ts index ae26b2ee86..4664272fd0 100644 --- a/scripts/sync-provider-models.ts +++ b/scripts/sync-provider-models.ts @@ -17,6 +17,8 @@ * * - **fal** — 1k+ model-grained endpoints; image field maps have their own * generator (`scripts/generate-fal-image-field-map.ts`). + * - **elevenlabs** — id arrays, not ModelMeta constants; see + * `scripts/sync-elevenlabs-models.ts`. * - **bedrock** — ids are AWS-region-qualified; see * `scripts/fetch-bedrock-models.ts`. * - **ollama**, **llmgateway**, **vercel-gateway**, **lovable** — different @@ -30,19 +32,15 @@ import { readFile, readdir, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { - ELEVENLABS_ID_ARRAYS, alreadySynced, outputsText, pricingLines, - selectElevenLabsInserts, selectNativeInserts, } from './model-sync/catalog' import type { CatalogModel, SyncModel } from './model-sync/catalog' import { toModelConstName } from './model-sync/ids' import { - addToStringLiteralArray, applyChatModelCatalogInserts, - extractStringLiteralArrayValues, insertConstants, } from './model-sync/native-insert' import { createSyncClient, fetchSyncCatalogs } from './model-sync/modelschemas' @@ -51,7 +49,7 @@ import { buildProviderSupportsBody, } from './model-sync/provider-supports' import type { ArrayRef } from './model-sync/native-insert' -import type { ModelMetaProvider } from './model-sync/provider-supports' +import type { SyncedProvider } from './model-sync/provider-supports' const __dirname = dirname(fileURLToPath(import.meta.url)) const ROOT = resolve(__dirname, '..') @@ -117,7 +115,7 @@ interface ProviderConfig { outputTokenField: 'max_output_tokens' | 'max_completion_tokens' } -const PROVIDER_MAP: Record = { +const PROVIDER_MAP: Record = { openai: { packageName: '@tanstack/ai-openai', metaFile: resolve(ROOT, 'packages/ai-openai/src/model-meta.ts'), @@ -285,12 +283,6 @@ const PROVIDER_MAP: Record = { }, } -/** ElevenLabs model-meta is id-literal arrays, not ModelMeta constants. */ -const ELEVENLABS = { - packageName: '@tanstack/ai-elevenlabs', - metaFile: resolve(ROOT, 'packages/ai-elevenlabs/src/model-meta.ts'), -} - type InputModality = 'text' | 'image' | 'audio' | 'video' | 'document' const MODALITY_MAP: Record = { @@ -323,7 +315,7 @@ function anthropicOptionsType(model: SyncModel): string { function providerOptionsEntryFor( model: SyncModel, - provider: ModelMetaProvider, + provider: SyncedProvider, config: ProviderConfig, ): string { if (provider === 'anthropic') return anthropicOptionsType(model) @@ -332,7 +324,7 @@ function providerOptionsEntryFor( function satisfiesClause( model: SyncModel, - provider: ModelMetaProvider, + provider: SyncedProvider, config: ProviderConfig, ): string { if (provider === 'anthropic') { @@ -383,7 +375,7 @@ async function writeLastRunTimestamp(): Promise { function generateModelConstant( model: SyncModel, - provider: ModelMetaProvider, + provider: SyncedProvider, config: ProviderConfig, ): string { const constName = toModelConstName(model.nativeId) @@ -450,10 +442,10 @@ function detectChangedPackages(): Set { } async function syncModelMeta( - provider: ModelMetaProvider, + provider: SyncedProvider, config: ProviderConfig, catalogs: { - native: Record> + native: Record> openrouter: Array }, cutoffTimestamp: number, @@ -515,33 +507,6 @@ async function syncModelMeta( return { added: inserts.length, heldBack: heldBack.length } } -async function syncElevenLabs( - rows: Array, - cutoffTimestamp: number, -): Promise { - console.log(`\nProcessing provider: elevenlabs`) - let content = await readFile(ELEVENLABS.metaFile, 'utf-8') - const existingIds = new Set( - ELEVENLABS_ID_ARRAYS.flatMap((name) => [ - ...extractStringLiteralArrayValues(content, name), - ]), - ) - const byArray = selectElevenLabsInserts(rows, cutoffTimestamp, existingIds) - const added = Object.values(byArray).flat() - if (added.length === 0) { - console.log(' No new models to add.') - return 0 - } - console.log(` Adding ${added.length} new models:`) - for (const id of added) console.log(` - ${id}`) - for (const name of ELEVENLABS_ID_ARRAYS) { - content = addToStringLiteralArray(content, name, byArray[name]) - } - await writeFile(ELEVENLABS.metaFile, content, 'utf-8') - console.log(` Wrote updated file: ${ELEVENLABS.metaFile}`) - return added.length -} - async function main() { const lastRun = await readLastRunTimestamp() const now = Math.floor(Date.now() / 1000) @@ -565,7 +530,7 @@ async function main() { let totalAdded = 0 let totalHeldBack = 0 for (const [provider, config] of Object.entries(PROVIDER_MAP) as Array< - [ModelMetaProvider, ProviderConfig] + [SyncedProvider, ProviderConfig] >) { const { added, heldBack } = await syncModelMeta( provider, @@ -577,12 +542,6 @@ async function main() { totalHeldBack += heldBack if (added > 0) changedPackages.add(config.packageName) } - const elevenLabsAdded = await syncElevenLabs( - catalogs.native.elevenlabs, - cutoffTimestamp, - ) - totalAdded += elevenLabsAdded - if (elevenLabsAdded > 0) changedPackages.add(ELEVENLABS.packageName) console.log(`\nDone. Added ${totalAdded} new models total.`) From c8718196f2437cdf3185d7b7fcad863ad80b70a4 Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:07:16 +1100 Subject: [PATCH 3/3] feat(model-sync): sync native models from modelschemas only, with no prices Model metadata no longer carries prices: nothing read them, and they change faster than a release. Removes pricing from the ModelMeta types and data of ai-openai, ai-anthropic, ai-gemini, ai-grok, ai-groq, ai-mistral, and ai-openrouter, and from the OpenRouter and LLM Gateway generators. The native sync no longer reads OpenRouter at all. Every field comes from the provider's own modelschemas catalog. Capabilities are read from modelschemas' flag map (only true keys count), and a row with no modalities is skipped instead of filled. The OpenRouter price gate is gone. Adds docs/advanced/model-prices.md: price a call live with @modelschemas/client and @modelschemas/rate-card. Bumps the client to ^0.2.1. --- .changeset/model-meta-no-prices.md | 11 + CONTRIBUTING.md | 4 +- docs/advanced/model-prices.md | 155 + docs/advanced/prompt-caching.md | 2 + docs/community-adapters/guide.md | 2 +- docs/config.json | 7 +- kiira.config.ts | 2 + package.json | 2 +- packages/ai-anthropic/src/model-meta.ts | 134 +- packages/ai-gemini/src/model-meta.ts | 240 +- packages/ai-grok/src/model-meta.ts | 101 +- packages/ai-groq/src/model-meta.ts | 67 +- packages/ai-mistral/src/model-meta.ts | 119 +- packages/ai-openai/src/model-meta.ts | 641 -- packages/ai-openrouter/src/model-meta.ts | 5520 ------------------ pnpm-lock.yaml | 50 +- scripts/convert-openrouter-models.ts | 37 - scripts/model-sync/catalog.test.ts | 201 +- scripts/model-sync/catalog.ts | 159 +- scripts/model-sync/llmgateway.test.ts | 19 +- scripts/model-sync/llmgateway.ts | 23 - scripts/model-sync/modelschemas.test.ts | 27 +- scripts/model-sync/modelschemas.ts | 24 +- scripts/model-sync/provider-supports.test.ts | 1 - scripts/model-sync/provider-supports.ts | 16 +- scripts/sync-provider-models.ts | 69 +- skills/tanstack-ai/SKILL.md | 2 + tsconfig.docs.json | 6 + 28 files changed, 343 insertions(+), 7298 deletions(-) create mode 100644 .changeset/model-meta-no-prices.md create mode 100644 docs/advanced/model-prices.md diff --git a/.changeset/model-meta-no-prices.md b/.changeset/model-meta-no-prices.md new file mode 100644 index 0000000000..099732387e --- /dev/null +++ b/.changeset/model-meta-no-prices.md @@ -0,0 +1,11 @@ +--- +'@tanstack/ai-anthropic': patch +'@tanstack/ai-gemini': patch +'@tanstack/ai-grok': patch +'@tanstack/ai-groq': patch +'@tanstack/ai-mistral': patch +'@tanstack/ai-openai': patch +'@tanstack/ai-openrouter': patch +--- + +Model metadata no longer carries prices. Nothing read them, and prices change faster than a package release. To price a call, read the live rate card from modelschemas: see the Model Prices guide. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f14c0c483c..ea5dca31c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ For deeper architecture details (adapter system, isomorphic tools, framework int 1. Fetches OpenRouter, Vercel AI Gateway, Lovable AI Gateway, and Neon AI Gateway catalogs. 2. Regenerates `packages/ai-openrouter/src/model-meta.ts` and the Vercel Gateway and Neon model lists. -3. Inserts **new** native-provider models from [modelschemas](https://modelschemas.com) (`@modelschemas/client`) into `ai-openai`, `ai-anthropic`, `ai-gemini`, `ai-grok`, `ai-groq`, `ai-mistral`, and `ai-byteplus`. Native ids, activity, limits, modalities, pricing, and capabilities come from each provider catalog. OpenRouter fills a field only when the native row left it empty. OpenAI, Anthropic, Gemini, and Grok hold an id back until the OpenRouter catalog has a matching row and both prices are known. The log names each held-back id. Groq and Mistral insert even when a price is unknown: the generator leaves that price out (`pricing: {}` when both are unknown) and never writes `0`. BytePlus does not write pricing. +3. Inserts **new** native-provider models from [modelschemas](https://modelschemas.com) (`@modelschemas/client`) into `ai-openai`, `ai-anthropic`, `ai-gemini`, `ai-grok`, `ai-groq`, `ai-mistral`, and `ai-byteplus`. Ids, activity, limits, modalities, and capabilities come from each provider's own catalog, with no second source. A field the catalog leaves empty stays empty, and a row with no modalities is skipped (the log counts it). Model metadata has no prices: they change faster than a release, so apps read them live (`docs/advanced/model-prices.md`). 4. Inserts **new** ElevenLabs text-to-speech, music, sound-effect, and voice-design ids from [modelschemas](https://modelschemas.com/v1/schemas/elevenlabs) into `packages/ai-elevenlabs/src/model-meta.ts` (`scripts/sync-elevenlabs-models.ts`, also through `@modelschemas/client`). Text-to-speech ids are catalog rows with `providerMetadata.canDoTextToSpeech`. Music, sound-effect, and voice-design ids are the `model_id` enums of their request schemas. Transcription ids stay hand-maintained. 5. Writes a patch changeset for the packages that changed. @@ -67,7 +67,7 @@ Rules the generator follows: - Keep OpenRouter routing aliases (ids that start with `~`) in the OpenRouter catalog. Users can pass `chat({ model: '~anthropic/claude-haiku-latest' })`. The generated constant name maps `~` to `_`. - Do **not** copy those aliases into native provider files (any of the eight packages above). Those adapters only accept the provider's own ids. -- For a new native-provider model, write id, modalities, and (where the provider has a `pricing` field) pricing. Infer features from native catalog capabilities (a list of parameter names, or a feature object, as BytePlus sends). Do **not** copy another model's tool list (`computer_use`, `google_search`, `x_search`, and similar). Do **not** copy OpenRouter flags onto a native row that already listed capabilities. BytePlus, Groq, and Mistral insert from the native catalog even when OpenRouter has no row. BytePlus inserts never claim `structured_outputs`: that gate is a live-probed list. +- For a new native-provider model, write id, limits, and modalities. Infer features from the catalog capabilities: a map of flags where only `true` counts (an absent key is unknown), or a feature object, as BytePlus sends. Do **not** copy another model's tool list (`computer_use`, `google_search`, `x_search`, and similar). BytePlus inserts never claim `structured_outputs`: that gate is a live-probed list. - ElevenLabs music and voice-design ids are pinned to SDK unions. An id the ElevenLabs SDK does not know yet fails the type-check, so wait for the SDK. BytePlus video and image duration/size tables stay hand-curated. - Anthropic first-party ids use dashes (`claude-fable-5-1`). OpenRouter uses dots (`claude-fable-5.1`). The generator takes the modelschemas native id (already hyphenated) and does not copy the dotted OpenRouter slug into `ai-anthropic`. Dated Anthropic snapshots (`claude-haiku-4-5-20251001`) are skipped; the undated alias is the public id. - Write a row in the provider's `*ChatModelToolCapabilitiesByName` map for every new chat model, even when `supports.tools` is still `[]`. Missing that row makes `ResolveToolCapabilities` fall back to `readonly []`. diff --git a/docs/advanced/model-prices.md b/docs/advanced/model-prices.md new file mode 100644 index 0000000000..23da43598f --- /dev/null +++ b/docs/advanced/model-prices.md @@ -0,0 +1,155 @@ +--- +title: Model Prices +id: model-prices +description: "Price each model call from its token usage, with live rate cards from modelschemas." +keywords: + - tanstack ai + - model prices + - token cost + - modelschemas + - rate card + - onUsage +--- + +Get a model's rate card from [modelschemas](https://modelschemas.com) with `@modelschemas/client`. Check it with `rateCardSchema`, then price each call with `evaluateCard` from `@modelschemas/rate-card`. Prices change often, so read them live. Do not copy them into your code. + + + +react: @modelschemas/client @modelschemas/rate-card zod +vue: @modelschemas/client @modelschemas/rate-card zod +solid: @modelschemas/client @modelschemas/rate-card zod +svelte: @modelschemas/client @modelschemas/rate-card zod +preact: @modelschemas/client @modelschemas/rate-card zod +angular: @modelschemas/client @modelschemas/rate-card zod +octane: @modelschemas/client @modelschemas/rate-card zod +vanilla: @modelschemas/client @modelschemas/rate-card zod + + + +## Price a chat call + +Price each call in the [`onUsage`](./middleware-advanced#onusage) hook of a middleware, on the server: + +```typescript +import { chat } from "@tanstack/ai"; +import type { ChatMiddleware, TokenUsage } from "@tanstack/ai"; +import { anthropicText } from "@tanstack/ai-anthropic"; +import { createModelschemasClient, getModel } from "@modelschemas/client"; +import { RateCardError, evaluateCard } from "@modelschemas/rate-card/evaluator"; +import type { RateCard } from "@modelschemas/rate-card/evaluator"; +import { rateCardSchema } from "@modelschemas/rate-card/schema"; + +const client = createModelschemasClient({ baseUrl: "https://modelschemas.com" }); + +// Keep each rate card for one hour. +const cards = new Map }>(); + +function rateCard(provider: string, modelId: string) { + const key = `${provider}/${modelId}`; + const hit = cards.get(key); + if (hit && Date.now() - hit.at < 60 * 60 * 1000) return hit.card; + // A model with no rate card, or only a price summary, fails the check. + const card = getModel({ client, path: { provider, modelId } }).then(({ data }) => { + const parsed = rateCardSchema.safeParse(data?.pricing); + return parsed.success ? parsed.data : undefined; + }); + cards.set(key, { at: Date.now(), card }); + return card; +} + +// A rate card counts each kind of input token once. `promptTokens` holds them all. +function cardUsage(usage: TokenUsage) { + const cached = usage.promptTokensDetails?.cachedTokens ?? 0; + const written = usage.promptTokensDetails?.cacheWriteTokens ?? 0; + const written1h = usage.promptTokensDetails?.cacheWrite1hTokens ?? 0; + const cacheCounts = { + cache_read_tokens: cached, + cache_write_tokens: written - written1h, + cache_write_1h_tokens: written1h, + }; + return { + input_tokens: usage.promptTokens - cached - written, + output_tokens: usage.completionTokens, + // A card without a cache price rejects a cache count, even a 0. + ...Object.fromEntries(Object.entries(cacheCounts).filter(([, count]) => count > 0)), + }; +} + +const costLog: ChatMiddleware = { + name: "cost-log", + onUsage: async (ctx, usage) => { + const card = await rateCard("anthropic", ctx.model); + if (!card) return console.log(`${ctx.model}: no price on modelschemas`); + try { + const { amount, currency } = evaluateCard(card, { usage: cardUsage(usage) }); + const price = new Intl.NumberFormat("en", { style: "currency", currency, maximumFractionDigits: 6 }); + console.log(`${ctx.model}: ${price.format(amount)}`); + } catch (error) { + if (!(error instanceof RateCardError)) throw error; + console.log(`${ctx.model}: price not known (${error.message})`); + } + }, +}; + +const stream = chat({ + adapter: anthropicText("claude-sonnet-5-5"), + messages: [{ role: "user", content: "Hello!" }], + threadId: "thread-1", + middleware: [costLog], +}); +``` + +The log shows the cost of each model call, for example `claude-sonnet-5-5: $0.013`. A card uses long-context tiers when the input is long enough. + +## When the price is not known + +The rate card does not guess: + +- `rateCardSchema.safeParse` fails when modelschemas has no rate card for the model. +- `evaluateCard` throws `RateCardError` when the call used tokens that the card has no price for. +- `currency` is the card's own currency, for example `USD` or `CNY`. Do not add amounts in two currencies. + +modelschemas allows 60 calls an hour without a key. With the one-hour cache above, that is one call for each model. For a higher limit, pass `apiKey` to `createModelschemasClient`. + +## Price an image before you make it + +A media card prices the request, so you can show the cost before you call the model: + +```typescript +import { createModelschemasClient, getModel } from "@modelschemas/client"; +import { evaluateCard } from "@modelschemas/rate-card/evaluator"; +import { rateCardSchema } from "@modelschemas/rate-card/schema"; + +const client = createModelschemasClient({ baseUrl: "https://modelschemas.com" }); + +const { data } = await getModel({ + client, + path: { provider: "fal", modelId: "alibaba/qwen-image-3/edit" }, +}); +const parsed = rateCardSchema.safeParse(data?.pricing); +if (parsed.success) { + const { amount, currency } = evaluateCard(parsed.data, { + request: { num_images: 2 }, + }); + console.log(`Two images cost ${amount} ${currency}`); +} +``` + +## Provider ids + +Pass the modelschemas provider id, not the package name: + +| Package | modelschemas provider | +| --- | --- | +| `@tanstack/ai-anthropic` | `anthropic` | +| `@tanstack/ai-openai` | `openai`, or `azure` for Azure OpenAI | +| `@tanstack/ai-gemini` | `gemini` | +| `@tanstack/ai-vertex` | `google-vertex` | +| `@tanstack/ai-grok` | `grok` | +| `@tanstack/ai-groq` | `groq` | +| `@tanstack/ai-mistral` | `mistral` | +| `@tanstack/ai-bedrock` | `amazon-bedrock` | +| `@tanstack/ai-openrouter` | `openrouter` | +| `@tanstack/ai-vercel-gateway` | `vercel` | +| `@tanstack/ai-cloudflare` | `cloudflare-workers-ai` | +| `@tanstack/ai-fal` | `fal` | diff --git a/docs/advanced/prompt-caching.md b/docs/advanced/prompt-caching.md index 13b8006751..b018a6cc64 100644 --- a/docs/advanced/prompt-caching.md +++ b/docs/advanced/prompt-caching.md @@ -76,6 +76,8 @@ const stream = chat({ From the second call of a conversation, most of the input comes from the cache. +To turn this usage into a price, see [Model Prices](./model-prices). + ## How it works - The cache key is the first of these that you set: diff --git a/docs/community-adapters/guide.md b/docs/community-adapters/guide.md index 4eb4ccca9d..189b5ceecd 100644 --- a/docs/community-adapters/guide.md +++ b/docs/community-adapters/guide.md @@ -342,7 +342,7 @@ const EFFORT_NONE_LOW_TO_XHIGH = { const GPT5_2 = { name: 'gpt-5.2', - // ...supports, pricing + // ...supports reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const diff --git a/docs/config.json b/docs/config.json index 2577af99fb..d35733cceb 100644 --- a/docs/config.json +++ b/docs/config.json @@ -1650,7 +1650,12 @@ "label": "Prompt Caching", "to": "advanced/prompt-caching", "addedAt": "2026-10-09", - "updatedAt": "2026-10-22" + "updatedAt": "2026-10-26" + }, + { + "label": "Model Prices", + "to": "advanced/model-prices", + "addedAt": "2026-10-26" }, { "label": "Mid-Conversation Changes", diff --git a/kiira.config.ts b/kiira.config.ts index 041d9f9633..5bdc19d0b6 100644 --- a/kiira.config.ts +++ b/kiira.config.ts @@ -58,6 +58,8 @@ export default defineConfig({ '@cloudflare/workers-types': '^4.20260317.1', '@decartai/tanstack-ai-adapter': '^0.0.2', '@mynthio/tanstack-ai-adapter': '^0.0.14', + // Live model prices (docs/advanced/model-prices.md) + '@modelschemas/rate-card': '^0.2.1', '@cencori/ai-sdk': '^0.4.0', '@soniox/tanstack-ai-adapter': '^0.1.2', '@upstash/agentkit-tanstack-ai': '^0.1.1', diff --git a/package.json b/package.json index a5834d7c28..2edd3b9fe1 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@changesets/changelog-github": "^0.7.0", "@changesets/cli": "^2.30.0", "@faker-js/faker": "^10.1.0", - "@modelschemas/client": "^0.1.0", + "@modelschemas/client": "^0.2.1", "@stylistic/eslint-plugin": "^5.10.0", "@tanstack/ai": "workspace:*", "@tanstack/ai-grok-build": "workspace:*", diff --git a/packages/ai-anthropic/src/model-meta.ts b/packages/ai-anthropic/src/model-meta.ts index 4009f43a50..7e98c5cde5 100644 --- a/packages/ai-anthropic/src/model-meta.ts +++ b/packages/ai-anthropic/src/model-meta.ts @@ -105,15 +105,6 @@ interface ModelMeta< context_window?: number max_output_tokens?: number knowledge_cutoff?: string - pricing: { - input: { - normal: number - cached?: number - } - output: { - normal: number - } - } /** * Type-level description of which provider options this model supports. */ @@ -138,14 +129,6 @@ const CLAUDE_OPUS_4_6 = { context_window: 200_000, max_output_tokens: 128_000, knowledge_cutoff: '2025-05-01', - pricing: { - input: { - normal: 5, - }, - output: { - normal: 25, - }, - }, supports: { input: ['text', 'image', 'document'], extended_thinking: true, @@ -180,14 +163,6 @@ const CLAUDE_OPUS_4_5 = { context_window: 200_000, max_output_tokens: 32_000, knowledge_cutoff: '2025-11-01', - pricing: { - input: { - normal: 15, - }, - output: { - normal: 75, - }, - }, supports: { input: ['text', 'image', 'document'], extended_thinking: true, @@ -223,14 +198,6 @@ const CLAUDE_SONNET_4_6 = { context_window: 1_000_000, max_output_tokens: 64_000, knowledge_cutoff: '2025-08-01', - pricing: { - input: { - normal: 3, - }, - output: { - normal: 15, - }, - }, supports: { input: ['text', 'image', 'document'], extended_thinking: true, @@ -265,14 +232,6 @@ const CLAUDE_SONNET_4_5 = { context_window: 200_000, max_output_tokens: 64_000, knowledge_cutoff: '2025-09-29', - pricing: { - input: { - normal: 3, - }, - output: { - normal: 15, - }, - }, supports: { input: ['text', 'image', 'document'], extended_thinking: true, @@ -306,14 +265,6 @@ const CLAUDE_HAIKU_4_5 = { context_window: 200_000, max_output_tokens: 64_000, knowledge_cutoff: '2025-10-01', - pricing: { - input: { - normal: 1, - }, - output: { - normal: 5, - }, - }, supports: { input: ['text', 'image', 'document'], extended_thinking: true, @@ -365,15 +316,6 @@ const CLAUDE_OPUS_4_7 = { 'memory', ], }, - pricing: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 25, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicCacheControlOptions & @@ -411,15 +353,6 @@ const CLAUDE_OPUS_4_8 = { 'memory', ], }, - pricing: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 25, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicCacheControlOptions & @@ -460,15 +393,6 @@ const CLAUDE_FABLE_5 = { 'memory', ], }, - pricing: { - input: { - normal: 10, - cached: 1, - }, - output: { - normal: 50, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicCacheControlOptions & @@ -486,7 +410,7 @@ const CLAUDE_FABLE_5 = { // runs adaptive); `{type: 'disabled'}` opts out, but the manual // `{type: 'enabled', budget_tokens}` shape and non-default sampling // parameters (`temperature`, `top_p`, `top_k`) are rejected with a 400. -// No Priority Tier. $2/$10 per MTok is the standard price. +// No Priority Tier. const CLAUDE_SONNET_5 = { name: 'claude-sonnet-5', id: 'claude-sonnet-5', @@ -508,15 +432,6 @@ const CLAUDE_SONNET_5 = { 'memory', ], }, - pricing: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicCacheControlOptions & @@ -558,15 +473,6 @@ const CLAUDE_OPUS_5 = { 'memory', ], }, - pricing: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 25, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicContainerOptions & @@ -600,15 +506,6 @@ const CLAUDE_FABLE_5_1 = { 'memory', ], }, - pricing: { - input: { - normal: 10, - cached: 0.25, - }, - output: { - normal: 50, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicCacheControlOptions & @@ -643,15 +540,6 @@ const CLAUDE_OPUS_5_5 = { 'memory', ], }, - pricing: { - input: { - normal: 4, - cached: 0.2, - }, - output: { - normal: 20, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicCacheControlOptions & @@ -692,15 +580,6 @@ const CLAUDE_SONNET_5_5 = { 'memory', ], }, - pricing: { - input: { - normal: 2, - cached: 0.1, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicCacheControlOptions & @@ -715,7 +594,7 @@ const CLAUDE_SONNET_5_5 = { > // Claude Haiku 5.5: `disabled` thinking 400s at xhigh/max (the types cannot -// say so). Pricing is the <=100K-token tier. +// say so). const CLAUDE_HAIKU_5_5 = { name: 'claude-haiku-5-5', id: 'claude-haiku-5-5', @@ -737,15 +616,6 @@ const CLAUDE_HAIKU_5_5 = { 'memory', ], }, - pricing: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.5, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< AnthropicCacheControlOptions & diff --git a/packages/ai-gemini/src/model-meta.ts b/packages/ai-gemini/src/model-meta.ts index e5493443ad..84176b3ed3 100644 --- a/packages/ai-gemini/src/model-meta.ts +++ b/packages/ai-gemini/src/model-meta.ts @@ -56,15 +56,6 @@ interface ModelMeta { max_input_tokens?: number max_output_tokens?: number knowledge_cutoff?: string - pricing?: { - input: { - normal: number - cached?: number - } - output: { - normal: number - } - } /** * Type-level description of which provider options this model supports. */ @@ -90,14 +81,6 @@ const GEMINI_3_1_PRO = { ], tools: ['code_execution', 'file_search', 'google_search', 'url_context'], }, - pricing: { - input: { - normal: 2.5, - }, - output: { - normal: 15, - }, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -124,14 +107,6 @@ const GEMINI_3_FLASH = { ], tools: ['code_execution', 'file_search', 'google_search', 'url_context'], }, - pricing: { - input: { - normal: 0.5, - }, - output: { - normal: 3, - }, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -157,14 +132,6 @@ const GEMINI_3_PRO_IMAGE = { capabilities: ['batch_api', 'structured_output', 'thinking'], tools: ['google_search'], }, - pricing: { - input: { - normal: 2, - }, - output: { - normal: 0.134, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -190,14 +157,6 @@ const GEMINI_3_1_FLASH_IMAGE = { capabilities: ['batch_api', 'thinking'], tools: ['google_search'], }, - pricing: { - input: { - normal: 0.5, - }, - output: { - normal: 3, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -220,14 +179,6 @@ const GEMINI_3_1_FLASH_LITE_IMAGE = { capabilities: ['batch_api', 'structured_output', 'thinking'], tools: ['google_search'], }, - pricing: { - input: { - normal: 0.25, - }, - output: { - normal: 1.5, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -252,14 +203,6 @@ const GEMINI_NANO_BANANA_2_1 = { capabilities: ['batch_api', 'thinking'], tools: ['google_search'], }, - pricing: { - input: { - normal: 1.5, - }, - output: { - normal: 7.5, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -284,14 +227,6 @@ const GEMINI_3_1_FLASH_LITE = { ], tools: ['code_execution', 'file_search', 'google_search', 'url_context'], }, - pricing: { - input: { - normal: 0.25, - }, - output: { - normal: 1.5, - }, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -318,14 +253,6 @@ const GEMINI_3_1_FLASH_LITE_PREVIEW = { ], tools: ['code_execution', 'file_search', 'google_search', 'url_context'], }, - pricing: { - input: { - normal: 0.25, - }, - output: { - normal: 1.5, - }, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -358,14 +285,6 @@ const GEMINI_2_5_PRO = { 'url_context', ], }, - pricing: { - input: { - normal: 2.5, - }, - output: { - normal: 15, - }, - }, reasoning: BUDGET_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -385,14 +304,6 @@ const GEMINI_2_5_PRO_TTS = { output: ['audio'], capabilities: ['audio_generation'], }, - pricing: { - input: { - normal: 2.5, - }, - output: { - normal: 15, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -423,14 +334,6 @@ const GEMINI_2_5_FLASH = { 'url_context', ], }, - pricing: { - input: { - normal: 1, - }, - output: { - normal: 2.5, - }, - }, reasoning: BUDGET_OFF_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -458,15 +361,6 @@ const GEMINI_2_5_FLASH_LIVE = { 'thinking', ], }, - pricing: { - // todo find this info - input: { - normal: 0, - }, - output: { - normal: 0, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -484,14 +378,6 @@ const GEMINI_2_5_FLASH_TTS = { output: ['audio'], capabilities: ['audio_generation', 'batch_api'], }, - pricing: { - input: { - normal: 1, - }, - output: { - normal: 2.5, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -514,14 +400,6 @@ const GEMINI_3_1_FLASH_TTS = { output: ['audio'], capabilities: ['audio_generation', 'batch_api'], }, - pricing: { - input: { - normal: 0.5, - }, - output: { - normal: 10, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -543,14 +421,6 @@ const LYRIA_3_PRO = { output: ['audio'], capabilities: ['audio_generation'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0, - }, - }, } as const satisfies ModelMeta /** @@ -565,14 +435,6 @@ const LYRIA_3_CLIP = { output: ['audio'], capabilities: ['audio_generation'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0, - }, - }, } as const satisfies ModelMeta const GEMINI_2_5_FLASH_LITE = { @@ -592,14 +454,6 @@ const GEMINI_2_5_FLASH_LITE = { ], tools: ['code_execution', 'google_maps', 'google_search', 'url_context'], }, - pricing: { - input: { - normal: 0.1, - }, - output: { - normal: 0.4, - }, - }, reasoning: BUDGET_OFF_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -610,8 +464,7 @@ const GEMINI_2_5_FLASH_LITE = { > /** - * Veo video generation models. Pricing is per second of generated video - * (audio+video rate where the model supports audio). + * Veo video generation models. * @experimental Veo video generation is an experimental feature and may change. */ const VEO_3_1_PREVIEW = { @@ -622,14 +475,6 @@ const VEO_3_1_PREVIEW = { input: ['text', 'image'], output: ['video', 'audio'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.4, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -645,14 +490,6 @@ const VEO_3_1_FAST_PREVIEW = { input: ['text', 'image'], output: ['video', 'audio'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.15, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -668,14 +505,6 @@ const VEO_3_1_LITE_PREVIEW = { input: ['text', 'image'], output: ['video', 'audio'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.05, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -688,8 +517,7 @@ const VEO_3_1_LITE_PREVIEW = { * conversational editing. Serves only the Interactions API * (`generateContent` rejects it), so it routes through the * interactions-based path of the video adapter, not Veo's - * `:predictLongRunning` flow. Pricing is per second of generated video - * ($0.10/sec). 360p / 720p (default) / 1080p / 4k at 24 FPS, 3–10 second + * `:predictLongRunning` flow. 360p / 720p (default) / 1080p / 4k at 24 FPS, 3–10 second * clips (default 10s). * @see https://ai.google.dev/gemini-api/docs/models/gemini-omni-flash * @experimental Omni video generation is an experimental feature and may change. @@ -702,14 +530,6 @@ const GEMINI_OMNI_1_1_FLASH = { input: ['text', 'image', 'video'], output: ['video', 'audio'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.1, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & @@ -742,15 +562,6 @@ const GEMINI_3_8_FLASH = { 'computer_use', ], }, - pricing: { - input: { - normal: 0.75, - cached: 0.075, - }, - output: { - normal: 3.75, - }, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -785,17 +596,6 @@ const GEMINI_3_7_FLASH = { 'computer_use', ], }, - // Currently 50% off through the end of 2026. - // https://ai.google.dev/gemini-api/docs/pricing#gemini-3.7-flash - pricing: { - input: { - normal: 0.75, - cached: 0.075, - }, - output: { - normal: 3.75, - }, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -830,15 +630,6 @@ const GEMINI_3_6_FLASH = { 'computer_use', ], }, - pricing: { - input: { - normal: 1.5, - cached: 0.15, - }, - output: { - normal: 7.5, - }, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -871,15 +662,6 @@ const GEMINI_3_5_FLASH = { 'computer_use', ], }, - pricing: { - input: { - normal: 1.5, - cached: 0.15, - }, - output: { - normal: 9, - }, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -913,15 +695,6 @@ const GEMINI_3_5_FLASH_LITE = { 'url_context', ], }, - pricing: { - input: { - normal: 0.3, - cached: 0.03, - }, - output: { - normal: 2.5, - }, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< GeminiToolConfigOptions & @@ -941,15 +714,6 @@ const GEMINI_4_ARGON = { capabilities: ['function_calling', 'structured_output', 'thinking'], tools: ['code_execution', 'file_search', 'google_search', 'url_context'], }, - pricing: { - input: { - normal: 2, - cached: 0.1, - }, - output: { - normal: 10, - }, - }, } as const satisfies ModelMeta< GeminiToolConfigOptions & GeminiSafetyOptions & diff --git a/packages/ai-grok/src/model-meta.ts b/packages/ai-grok/src/model-meta.ts index 217764cc21..3e5c3152ca 100644 --- a/packages/ai-grok/src/model-meta.ts +++ b/packages/ai-grok/src/model-meta.ts @@ -32,15 +32,6 @@ interface ModelMeta { max_output_tokens?: number context_window?: number knowledge_cutoff?: string - pricing?: { - input: { - normal: number - cached?: number - } - output: { - normal: number - } - } /** The model's data for `chat({ reasoning })`. None: it does not reason. */ reasoning?: ModelReasoning } @@ -54,15 +45,6 @@ const GROK_4_5 = { capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], tools: [], }, - pricing: { - input: { - normal: 2, - cached: 0.3, - }, - output: { - normal: 6, - }, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const satisfies ModelMeta @@ -75,15 +57,6 @@ const GROK_4_6 = { capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], tools: [], }, - pricing: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 6, - }, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const satisfies ModelMeta @@ -97,15 +70,6 @@ const GROK_4_7 = { capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], tools: [], }, - pricing: { - input: { - normal: 1.6, - cached: 0.4, - }, - output: { - normal: 4.8, - }, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const satisfies ModelMeta @@ -122,21 +86,13 @@ const GROK_RESPONSES_TOOLS = [ 'mcp', ] as const satisfies ReadonlyArray -// Imagine API image models. Pricing is per generated image (output only). +// Imagine API image models. const GROK_IMAGINE_IMAGE = { name: 'grok-imagine-image', supports: { input: ['text', 'image'], output: ['image'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.02, - }, - }, } as const satisfies ModelMeta const GROK_IMAGINE_IMAGE_QUALITY = { @@ -145,14 +101,6 @@ const GROK_IMAGINE_IMAGE_QUALITY = { input: ['text', 'image'], output: ['image'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.05, - }, - }, } as const satisfies ModelMeta // xAI's recommended Imagine image model. Supports the 2.0-only `quality` @@ -163,18 +111,9 @@ const GROK_IMAGINE_IMAGE_2_0 = { input: ['text', 'image'], output: ['image'], }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.04, - }, - }, } as const satisfies ModelMeta -// Imagine API video models. Pricing is per second of generated video -// (output only); generated videos carry an audio track. +// Imagine API video models. Generated videos carry an audio track. // // Both models support text-to-video and image-to-video (a starting-frame // image is optional). grok-imagine-video-1.5 is the documented default: it @@ -189,15 +128,6 @@ const GROK_IMAGINE_VIDEO = { input: ['text', 'image', 'video'], output: ['video', 'audio'], }, - pricing: { - input: { - normal: 0, - }, - output: { - // per second of video - normal: 0.05, - }, - }, } as const satisfies ModelMeta const GROK_IMAGINE_VIDEO_1_5 = { @@ -206,15 +136,6 @@ const GROK_IMAGINE_VIDEO_1_5 = { input: ['text', 'image'], output: ['video', 'audio'], }, - pricing: { - input: { - normal: 0, - }, - output: { - // per second of video - normal: 0.08, - }, - }, } as const satisfies ModelMeta const GROK_4_3 = { @@ -226,15 +147,6 @@ const GROK_4_3 = { capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], tools: GROK_RESPONSES_TOOLS, }, - pricing: { - input: { - normal: 1.25, - cached: 0.2, - }, - output: { - normal: 2.5, - }, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const satisfies ModelMeta @@ -247,15 +159,6 @@ const GROK_BUILD_0_1 = { capabilities: ['reasoning', 'structured_outputs', 'tool_calling'], tools: GROK_RESPONSES_TOOLS, }, - pricing: { - input: { - normal: 1, - cached: 0.2, - }, - output: { - normal: 2, - }, - }, } as const satisfies ModelMeta // Vertex Model Garden IDs. These are not the xAI API catalog. diff --git a/packages/ai-groq/src/model-meta.ts b/packages/ai-groq/src/model-meta.ts index e34863d571..c38ace3619 100644 --- a/packages/ai-groq/src/model-meta.ts +++ b/packages/ai-groq/src/model-meta.ts @@ -15,16 +15,12 @@ const EFFORT_NONE_LOW_TO_HIGH = { } as const /** - * Internal metadata structure describing a Groq model's capabilities and pricing. + * Internal metadata structure describing a Groq model's capabilities. */ interface ModelMeta { name: string context_window?: number max_completion_tokens?: number - pricing: { - input?: { normal: number; cached?: number } - output?: { normal: number } - } supports: { input: Array<'text' | 'image' | 'audio'> output: Array<'text' | 'audio'> @@ -55,14 +51,6 @@ const LLAMA_PROMPT_GUARD_2_86M = { name: 'meta-llama/llama-prompt-guard-2-86m', context_window: 512, max_completion_tokens: 512, - pricing: { - input: { - normal: 0.04, - }, - output: { - normal: 0.04, - }, - }, supports: { input: ['text'], output: ['text'], @@ -76,14 +64,6 @@ const LLAMA_PROMPT_GUARD_2_22M = { name: 'meta-llama/llama-prompt-guard-2-22m', context_window: 512, max_completion_tokens: 512, - pricing: { - input: { - normal: 0.03, - }, - output: { - normal: 0.03, - }, - }, supports: { input: ['text'], output: ['text'], @@ -97,15 +77,6 @@ const GPT_OSS_120B = { name: 'openai/gpt-oss-120b', context_window: 131_072, max_completion_tokens: 65_536, - pricing: { - input: { - normal: 0.15, - cached: 0.075, - }, - output: { - normal: 0.6, - }, - }, supports: { input: ['text'], output: ['text'], @@ -128,15 +99,6 @@ const GPT_OSS_SAFEGUARD_20B = { name: 'openai/gpt-oss-safeguard-20b', context_window: 131_072, max_completion_tokens: 65_536, - pricing: { - input: { - normal: 0.075, - cached: 0.037, - }, - output: { - normal: 0.3, - }, - }, supports: { input: ['text'], output: ['text'], @@ -160,15 +122,6 @@ const GPT_OSS_20B = { name: 'openai/gpt-oss-20b', context_window: 131_072, max_completion_tokens: 65_536, - pricing: { - input: { - normal: 0.075, - cached: 0.037, - }, - output: { - normal: 0.3, - }, - }, supports: { input: ['text'], output: ['text'], @@ -191,14 +144,6 @@ const QWEN_QWEN3_8_27B = { name: 'qwen/qwen3.8-27b', context_window: 131_072, max_completion_tokens: 16_384, - pricing: { - input: { - normal: 0.8, - }, - output: { - normal: 4, - }, - }, supports: { input: ['text', 'image'], output: ['text'], @@ -349,11 +294,6 @@ export type GroqTranscriptionModel = (typeof GROQ_TRANSCRIPTION_MODELS)[number] const ORPHEUS_V1_ENGLISH = { name: 'canopylabs/orpheus-v1-english', - pricing: { - input: { - normal: 22, - }, - }, supports: { input: ['text'], output: ['audio'], @@ -364,11 +304,6 @@ const ORPHEUS_V1_ENGLISH = { const ORPHEUS_ARABIC_SAUDI = { name: 'canopylabs/orpheus-arabic-saudi', - pricing: { - input: { - normal: 40, - }, - }, supports: { input: ['text'], output: ['audio'], diff --git a/packages/ai-mistral/src/model-meta.ts b/packages/ai-mistral/src/model-meta.ts index 51a153edc6..47a47010de 100644 --- a/packages/ai-mistral/src/model-meta.ts +++ b/packages/ai-mistral/src/model-meta.ts @@ -34,14 +34,6 @@ const CODESTRAL_2508 = { 'vision', ], }, - pricing: { - input: { - normal: 0.3, - }, - output: { - normal: 0.9, - }, - }, } as const satisfies ModelMeta const LABS_LEANSTRAL_1_5 = { @@ -60,7 +52,6 @@ const LABS_LEANSTRAL_1_5 = { 'vision', ], }, - pricing: {}, } as const satisfies ModelMeta const LABS_LEANSTRAL_1_5_1 = { @@ -79,7 +70,6 @@ const LABS_LEANSTRAL_1_5_1 = { 'vision', ], }, - pricing: {}, } as const satisfies ModelMeta const MINISTRAL_14B_2512 = { @@ -99,14 +89,6 @@ const MINISTRAL_14B_2512 = { 'vision', ], }, - pricing: { - input: { - normal: 0.2, - }, - output: { - normal: 0.2, - }, - }, } as const satisfies ModelMeta const MINISTRAL_14B_LATEST = { @@ -125,14 +107,6 @@ const MINISTRAL_14B_LATEST = { 'vision', ], }, - pricing: { - input: { - normal: 0.2, - }, - output: { - normal: 0.2, - }, - }, } as const satisfies ModelMeta const MINISTRAL_3B_2512 = { @@ -152,14 +126,6 @@ const MINISTRAL_3B_2512 = { 'vision', ], }, - pricing: { - input: { - normal: 0.1, - }, - output: { - normal: 0.1, - }, - }, } as const satisfies ModelMeta const MINISTRAL_8B_2512 = { @@ -179,14 +145,6 @@ const MINISTRAL_8B_2512 = { 'vision', ], }, - pricing: { - input: { - normal: 0.15, - }, - output: { - normal: 0.15, - }, - }, } as const satisfies ModelMeta const MISTRAL_LARGE_2512 = { @@ -206,14 +164,6 @@ const MISTRAL_LARGE_2512 = { 'vision', ], }, - pricing: { - input: { - normal: 0.5, - }, - output: { - normal: 1.5, - }, - }, } as const satisfies ModelMeta const MISTRAL_MEDIUM = { @@ -232,7 +182,6 @@ const MISTRAL_MEDIUM = { 'vision', ], }, - pricing: {}, } as const satisfies ModelMeta const MISTRAL_MEDIUM_2604 = { @@ -251,7 +200,6 @@ const MISTRAL_MEDIUM_2604 = { 'vision', ], }, - pricing: {}, } as const satisfies ModelMeta const MISTRAL_MEDIUM_3_5 = { @@ -271,14 +219,6 @@ const MISTRAL_MEDIUM_3_5 = { 'vision', ], }, - pricing: { - input: { - normal: 1.5, - }, - output: { - normal: 7.5, - }, - }, } as const satisfies ModelMeta const MISTRAL_SMALL_2603 = { @@ -298,14 +238,6 @@ const MISTRAL_SMALL_2603 = { 'vision', ], }, - pricing: { - input: { - normal: 0.15, - }, - output: { - normal: 0.6, - }, - }, } as const satisfies ModelMeta /** Provider options for vision-capable Mistral models. */ @@ -315,17 +247,12 @@ export type MistralVisionProviderOptions = MistralTextProviderOptions export type MistralReasoningProviderOptions = MistralTextProviderOptions /** - * Internal metadata structure describing a Mistral model's capabilities - * and approximate pricing (USD per million tokens). + * Internal metadata structure describing a Mistral model's capabilities. */ interface ModelMeta { name: string context_window?: number max_completion_tokens?: number - pricing: { - input?: { normal: number; cached?: number } - output?: { normal: number } - } supports: { input: Array<'text' | 'image' | 'audio' | 'document'> output: Array<'text'> @@ -350,10 +277,6 @@ const MISTRAL_LARGE_LATEST = { name: 'mistral-large-latest', context_window: 131_072, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.5 }, - output: { normal: 1.5 }, - }, supports: { input: ['text'], output: ['text'], @@ -366,10 +289,6 @@ const MISTRAL_MEDIUM_LATEST = { name: 'mistral-medium-latest', context_window: 131_072, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.4 }, - output: { normal: 2 }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -383,10 +302,6 @@ const MISTRAL_SMALL_LATEST = { name: 'mistral-small-latest', context_window: 131_072, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.1 }, - output: { normal: 0.3 }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -400,10 +315,6 @@ const MINISTRAL_8B_LATEST = { name: 'ministral-8b-latest', context_window: 131_072, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.1 }, - output: { normal: 0.1 }, - }, supports: { input: ['text'], output: ['text'], @@ -416,10 +327,6 @@ const MINISTRAL_3B_LATEST = { name: 'ministral-3b-latest', context_window: 131_072, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.04 }, - output: { normal: 0.04 }, - }, supports: { input: ['text'], output: ['text'], @@ -432,10 +339,6 @@ const CODESTRAL_LATEST = { name: 'codestral-latest', context_window: 256_000, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.3 }, - output: { normal: 0.9 }, - }, supports: { input: ['text'], output: ['text'], @@ -448,10 +351,6 @@ const MAGISTRAL_MEDIUM_LATEST = { name: 'magistral-medium-latest', context_window: 40_000, max_completion_tokens: 40_000, - pricing: { - input: { normal: 2 }, - output: { normal: 5 }, - }, supports: { input: ['text'], output: ['text'], @@ -465,10 +364,6 @@ const MAGISTRAL_SMALL_LATEST = { name: 'magistral-small-latest', context_window: 40_000, max_completion_tokens: 40_000, - pricing: { - input: { normal: 0.5 }, - output: { normal: 1.5 }, - }, supports: { input: ['text'], output: ['text'], @@ -485,10 +380,6 @@ const MISTRAL_MEDIUM_3 = { name: 'mistral-medium-3', context_window: 131_072, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.4 }, - output: { normal: 2 }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -501,10 +392,6 @@ const MISTRAL_SMALL_2503 = { name: 'mistral-small-2503', context_window: 131_072, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.1 }, - output: { normal: 0.3 }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -517,10 +404,6 @@ const CODESTRAL_2 = { name: 'codestral-2', context_window: 131_072, max_completion_tokens: 8_192, - pricing: { - input: { normal: 0.3 }, - output: { normal: 0.9 }, - }, supports: { input: ['text'], output: ['text'], diff --git a/packages/ai-openai/src/model-meta.ts b/packages/ai-openai/src/model-meta.ts index 86c7247315..f79c2a4092 100644 --- a/packages/ai-openai/src/model-meta.ts +++ b/packages/ai-openai/src/model-meta.ts @@ -117,15 +117,6 @@ interface ModelMeta { context_window?: number max_output_tokens?: number knowledge_cutoff?: string - pricing: { - input: { - normal: number - cached?: number - } - output: { - normal: number - } - } /** * Type-level description of which provider options this model supports. */ @@ -163,15 +154,6 @@ const GPT5_2 = { 'apply_patch', ], }, - pricing: { - input: { - normal: 1.75, - cached: 0.175, - }, - output: { - normal: 14, - }, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -204,14 +186,6 @@ const GPT5_2_PRO = { 'apply_patch', ], }, - pricing: { - input: { - normal: 21, - }, - output: { - normal: 168, - }, - }, reasoning: EFFORT_MEDIUM_TO_XHIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -239,15 +213,6 @@ const GPT5_2_CHAT = { 'mcp', ], }, - pricing: { - input: { - normal: 1.75, - cached: 0.175, - }, - output: { - normal: 14, - }, - }, reasoning: EFFORT_MEDIUM, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -285,15 +250,6 @@ const GPT5_1 = { 'apply_patch', ], }, - pricing: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -322,15 +278,6 @@ const GPT5_1_CODEX = { 'apply_patch', ], }, - pricing: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -369,15 +316,6 @@ const GPT5 = { 'apply_patch', ], }, - pricing: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -411,15 +349,6 @@ const GPT5_MINI = { 'apply_patch', ], }, - pricing: { - input: { - normal: 0.25, - cached: 0.025, - }, - output: { - normal: 2, - }, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -434,15 +363,6 @@ const GPT5_NANO = { context_window: 400_000, max_output_tokens: 128_000, knowledge_cutoff: '2024-05-31', - pricing: { - input: { - normal: 0.05, - cached: 0.005, - }, - output: { - normal: 0.4, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -476,14 +396,6 @@ const GPT5_PRO = { context_window: 400_000, max_output_tokens: 272_000, knowledge_cutoff: '2024-09-30', - pricing: { - input: { - normal: 15, - }, - output: { - normal: 120, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -517,15 +429,6 @@ const GPT5_CODEX = { context_window: 400_000, max_output_tokens: 128_000, knowledge_cutoff: '2024-09-30', - pricing: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text', 'image'], @@ -555,15 +458,6 @@ const GPT5_CODEX = { */ const SORA2 = { name: 'sora-2', - pricing: { - input: { - normal: 0, - }, - output: { - // per second of video - normal: 0.1, - }, - }, supports: { input: ['text', 'image'], output: ['video', 'audio'], @@ -580,15 +474,6 @@ const SORA2 = { */ const SORA2_PRO = { name: 'sora-2-pro', - pricing: { - input: { - normal: 0, - }, - output: { - // per second of video - normal: 0.5, - }, - }, supports: { input: ['text', 'image'], output: ['video', 'audio'], @@ -602,15 +487,6 @@ const SORA2_PRO = { const GPT_IMAGE_1 = { name: 'gpt-image-1', // todo fix for images - pricing: { - input: { - normal: 5, - cached: 1.25, - }, - output: { - normal: 0.1, - }, - }, supports: { input: ['text', 'image'], output: ['image'], @@ -625,15 +501,6 @@ const GPT_IMAGE_1 = { const GPT_IMAGE_1_MINI = { name: 'gpt-image-1-mini', // todo fix for images - pricing: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 0.03, - }, - }, supports: { input: ['text', 'image'], output: ['image'], @@ -647,15 +514,6 @@ const GPT_IMAGE_1_MINI = { const GPT_IMAGE_2_5_FLARE = { name: 'gpt-image-2.5-flare', - pricing: { - input: { - normal: 5, - cached: 1.25, - }, - output: { - normal: 30, - }, - }, supports: { input: ['text', 'image'], output: ['image'], @@ -668,15 +526,6 @@ const GPT_IMAGE_2_5_FLARE = { const GPT_IMAGE_2_5_SUNBURST = { name: 'gpt-image-2.5-sunburst', - pricing: { - input: { - normal: 5, - cached: 1.25, - }, - output: { - normal: 30, - }, - }, supports: { input: ['text', 'image'], output: ['image'], @@ -690,15 +539,6 @@ const GPT_IMAGE_2_5_SUNBURST = { const GPT_IMAGE_2 = { name: 'gpt-image-2', knowledge_cutoff: '2026-04-21', - pricing: { - input: { - normal: 5, - cached: 1.25, - }, - output: { - normal: 30, - }, - }, supports: { input: ['text', 'image'], output: ['image'], @@ -714,15 +554,6 @@ const O3_DEEP_RESEARCH = { context_window: 200_000, max_output_tokens: 100_000, knowledge_cutoff: '2024-01-01', - pricing: { - input: { - normal: 10, - cached: 2.5, - }, - output: { - normal: 40, - }, - }, supports: { input: ['text', 'image'], output: ['text'], @@ -751,15 +582,6 @@ const O4_MINI_DEEP_RESEARCH = { context_window: 200_000, max_output_tokens: 100_000, knowledge_cutoff: '2024-01-01', - pricing: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 8, - }, - }, supports: { input: ['text', 'image'], output: ['text'], @@ -788,14 +610,6 @@ const O3_PRO = { context_window: 200_000, max_output_tokens: 100_000, knowledge_cutoff: '2024-01-01', - pricing: { - input: { - normal: 20, - }, - output: { - normal: 80, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -829,15 +643,6 @@ const GPT_AUDIO = { context_window: 128_000, max_output_tokens: 16_384, knowledge_cutoff: '2023-10-01', - pricing: { - // todo add audio tokens to input output - input: { - normal: 2.5, - }, - output: { - normal: 10, - }, - }, supports: { input: ['text', 'audio'], output: ['text', 'audio'], @@ -857,16 +662,6 @@ const GPT_AUDIO = { context_window: 32_000, max_output_tokens: 4_096, knowledge_cutoff: '2023-10-01', - pricing: { - // todo add audio tokens to input output - input: { - normal: 4, - cached: 0.5, - }, - output: { - normal: 16, - }, - }, supports: { input: ['text', 'audio', 'image'], output: ['text', 'audio'], @@ -885,16 +680,6 @@ const GPT_REALTIME_MINI = { context_window: 32_000, max_output_tokens: 4_096, knowledge_cutoff: '2023-10-01', - pricing: { - // todo add audio and image tokens to input output - input: { - normal: 0.6, - cached: 0.06, - }, - output: { - normal: 2.4, - }, - }, supports: { input: ['text', 'audio', 'image'], output: ['text', 'audio'], @@ -913,15 +698,6 @@ const GPT_AUDIO_MINI = { context_window: 128_000, max_output_tokens: 16_384, knowledge_cutoff: '2023-10-01', - pricing: { - // todo add audio tokens to input output - input: { - normal: 0.6, - }, - output: { - normal: 2.4, - }, - }, supports: { input: ['text', 'audio'], output: ['text', 'audio'], @@ -941,15 +717,6 @@ const O3 = { context_window: 200_000, max_output_tokens: 100_000, knowledge_cutoff: '2024-01-01', - pricing: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 8, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -983,15 +750,6 @@ const O4_MINI = { context_window: 200_000, max_output_tokens: 100_000, knowledge_cutoff: '2024-01-01', - pricing: { - input: { - normal: 1.1, - cached: 0.275, - }, - output: { - normal: 4.4, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -1030,15 +788,6 @@ const GPT4_1 = { context_window: 1_047_576, max_output_tokens: 32_768, knowledge_cutoff: '2024-01-01', - pricing: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 8, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -1083,15 +832,6 @@ const GPT4_1_MINI = { context_window: 1_047_576, max_output_tokens: 32_768, knowledge_cutoff: '2024-01-01', - pricing: { - input: { - normal: 0.4, - cached: 0.1, - }, - output: { - normal: 1.6, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -1135,15 +875,6 @@ const GPT4_1_NANO = { context_window: 1_047_576, max_output_tokens: 32_768, knowledge_cutoff: '2024-01-01', - pricing: { - input: { - normal: 0.1, - cached: 0.025, - }, - output: { - normal: 0.4, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -1188,14 +919,6 @@ const O1_PRO = { context_window: 200_000, max_output_tokens: 100_000, knowledge_cutoff: '2023-10-01', - pricing: { - input: { - normal: 150, - }, - output: { - normal: 600, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -1217,14 +940,6 @@ const GPT_4O_MINI_SEARCH_PREVIEW = { context_window: 128_000, max_output_tokens: 16_384, knowledge_cutoff: '2023-10-01', - pricing: { - input: { - normal: 0.15, - }, - output: { - normal: 0.6, - }, - }, supports: { input: ['text'], output: ['text'], @@ -1241,14 +956,6 @@ const GPT_4O_SEARCH_PREVIEW = { context_window: 128_000, max_output_tokens: 16_384, knowledge_cutoff: '2023-10-01', - pricing: { - input: { - normal: 2.5, - }, - output: { - normal: 10, - }, - }, supports: { input: ['text'], output: ['text'], @@ -1265,15 +972,6 @@ const O3_MINI = { context_window: 200_000, max_output_tokens: 100_000, knowledge_cutoff: '2023-10-01', - pricing: { - input: { - normal: 1.1, - cached: 0.55, - }, - output: { - normal: 4.4, - }, - }, supports: { input: ['text'], output: ['text'], @@ -1306,16 +1004,6 @@ const O3_MINI = { context_window: 16_000, max_output_tokens: 4_096, knowledge_cutoff: '2023-10-01', - pricing: { - // todo add audio tokens - input: { - normal: 0.6, - cached: 0.3, - }, - output: { - normal: 2.4, - }, - }, supports: { input: ['text', 'audio'], output: ['text', 'audio'], @@ -1334,15 +1022,6 @@ const O1 = { context_window: 200_000, max_output_tokens: 100_000, knowledge_cutoff: '2023-10-01', - pricing: { - input: { - normal: 15, - cached: 7.5, - }, - output: { - normal: 60, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -1361,14 +1040,6 @@ const O1 = { /* const OMNI_MODERATION = { name: 'omni-moderation', - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0, - }, - }, supports: { input: ['text', 'image'], output: ['text'], @@ -1384,15 +1055,6 @@ const GPT_4O = { context_window: 128_000, max_output_tokens: 16_384, knowledge_cutoff: '2023-10-01', - pricing: { - input: { - normal: 2.5, - cached: 1.25, - }, - output: { - normal: 10, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -1432,15 +1094,6 @@ const GPT_4O_MINI = { context_window: 128_000, max_output_tokens: 16_384, knowledge_cutoff: '2023-10-01', - pricing: { - input: { - normal: 0.15, - cached: 0.075, - }, - output: { - normal: 0.6, - }, - }, supports: { input: ['text', 'image', 'document'], output: ['text'], @@ -1479,16 +1132,6 @@ const GPT_4O_MINI = { context_window: 32_000, max_output_tokens: 4_096, knowledge_cutoff: '2023-10-01', - pricing: { - // todo add audio tokens to input output - input: { - normal: 5, - cached: 2.5, - }, - output: { - normal: 20, - }, - }, supports: { input: ['text', 'audio'], output: ['text', 'audio'], @@ -1507,14 +1150,6 @@ const GPT_4_TURBO = { context_window: 128_000, max_output_tokens: 4_096, knowledge_cutoff: '2023-12-01', - pricing: { - input: { - normal: 10, - }, - output: { - normal: 30, - }, - }, supports: { input: ['text', 'image'], output: ['text'], @@ -1534,15 +1169,6 @@ const GPT_5_1_CODEX_MINI = { context_window: 400_000, max_output_tokens: 128_000, knowledge_cutoff: '2024-09-30', - pricing: { - input: { - normal: 0.25, - cached: 0.025, - }, - output: { - normal: 2, - }, - }, supports: { input: ['text', 'image'], output: ['text', 'image'], @@ -1571,14 +1197,6 @@ const GPT_3_5_TURBO = { context_window: 16_385, max_output_tokens: 4_096, knowledge_cutoff: '2021-09-01', - pricing: { - input: { - normal: 0.5, - }, - output: { - normal: 1.5, - }, - }, supports: { input: ['text'], output: ['text'], @@ -1595,14 +1213,6 @@ const GPT_4 = { context_window: 8_192, max_output_tokens: 8_192, knowledge_cutoff: '2023-12-01', - pricing: { - input: { - normal: 30, - }, - output: { - normal: 60, - }, - }, supports: { input: ['text'], output: ['text'], @@ -1625,15 +1235,6 @@ const GPT_4O_MINI_TRANSCRIBE = { context_window: 16_000, max_output_tokens: 2_000, knowledge_cutoff: '2024-01-01', - pricing: { - // todo audio tokens - input: { - normal: 1.25, - }, - output: { - normal: 5, - }, - }, supports: { input: ['audio', 'text'], output: ['text'], @@ -1646,15 +1247,6 @@ const GPT_4O_MINI_TRANSCRIBE = { const GPT_4O_MINI_TTS = { name: 'gpt-4o-mini-tts', - pricing: { - // todo audio tokens - input: { - normal: 0.6, - }, - output: { - normal: 12, - }, - }, supports: { input: ['text'], output: ['audio'], @@ -1670,15 +1262,6 @@ const GPT_4O_TRANSCRIBE = { context_window: 16_000, max_output_tokens: 2_000, knowledge_cutoff: '2024-06-01', - pricing: { - // todo audio tokens - input: { - normal: 2.5, - }, - output: { - normal: 10, - }, - }, supports: { input: ['audio', 'text'], output: ['text'], @@ -1694,15 +1277,6 @@ const GPT_4O_TRANSCRIBE_DIARIZE = { context_window: 16_000, max_output_tokens: 2_000, knowledge_cutoff: '2024-06-01', - pricing: { - // todo audio tokens - input: { - normal: 2.5, - }, - output: { - normal: 10, - }, - }, supports: { input: ['audio', 'text'], output: ['text'], @@ -1718,15 +1292,6 @@ const GPT_5_1_CHAT = { context_window: 128_000, max_output_tokens: 16_384, knowledge_cutoff: '2024-09-30', - pricing: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, supports: { input: ['text', 'image'], output: ['text'], @@ -1754,15 +1319,6 @@ const GPT_5_CHAT = { context_window: 128_000, max_output_tokens: 16_384, knowledge_cutoff: '2024-09-30', - pricing: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, supports: { input: ['text', 'image'], output: ['text'], @@ -1787,15 +1343,6 @@ const GPT_5_CHAT = { /* const TTS_1 = { name: 'tts-1', - pricing: { - // todo figure out pricing - input: { - normal: 15, - }, - output: { - normal: 15, - }, - }, supports: { input: ['text'], output: ['audio'], @@ -1808,15 +1355,6 @@ const GPT_5_CHAT = { const TTS_1_HD = { name: 'tts-1-hd', - pricing: { - // todo figure out pricing - input: { - normal: 30, - }, - output: { - normal: 30, - }, - }, supports: { input: ['text'], output: ['audio'], @@ -1857,15 +1395,6 @@ const GPT_5_4_MINI = { 'apply_patch', ], }, - pricing: { - input: { - normal: 0.75, - cached: 0.075, - }, - output: { - normal: 4.5, - }, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -1903,15 +1432,6 @@ const GPT_5_4_NANO = { 'apply_patch', ], }, - pricing: { - input: { - normal: 0.2, - cached: 0.02, - }, - output: { - normal: 1.25, - }, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -1949,15 +1469,6 @@ const GPT_5_4_IMAGE_2 = { 'apply_patch', ], }, - pricing: { - input: { - normal: 8, - cached: 2, - }, - output: { - normal: 15, - }, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -1997,15 +1508,6 @@ const GPT_5_6_SOL = { 'apply_patch', ], }, - pricing: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 30, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2045,15 +1547,6 @@ const GPT_5_6_TERRA = { 'apply_patch', ], }, - pricing: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 12, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2093,15 +1586,6 @@ const GPT_5_6_LUNA = { 'apply_patch', ], }, - pricing: { - input: { - normal: 0.2, - cached: 0.02, - }, - output: { - normal: 1.2, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2140,15 +1624,6 @@ const GPT_5_5 = { 'apply_patch', ], }, - pricing: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 30, - }, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2186,14 +1661,6 @@ const GPT_5_5_PRO = { 'apply_patch', ], }, - pricing: { - input: { - normal: 30, - }, - output: { - normal: 180, - }, - }, reasoning: EFFORT_MEDIUM_TO_XHIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2231,15 +1698,6 @@ const GPT_CHAT_LATEST = { 'apply_patch', ], }, - pricing: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 30, - }, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2260,15 +1718,6 @@ const GPT_5_6_LUNA_PRO = { features: ['streaming', 'function_calling', 'structured_outputs'], tools: [], }, - pricing: { - input: { - normal: 0.2, - cached: 0.02, - }, - output: { - normal: 1.2, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2289,15 +1738,6 @@ const GPT_5_6_SOL_PRO = { features: ['streaming', 'function_calling', 'structured_outputs'], tools: [], }, - pricing: { - input: { - normal: 2.5, - cached: 0.25, - }, - output: { - normal: 15, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2318,15 +1758,6 @@ const GPT_5_6_TERRA_PRO = { features: ['streaming', 'function_calling', 'structured_outputs'], tools: [], }, - pricing: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 12, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2358,15 +1789,6 @@ const GPT_6_ASTRA = { 'apply_patch', ], }, - pricing: { - input: { - normal: 10, - cached: 1, - }, - output: { - normal: 50, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2387,15 +1809,6 @@ const GPT_6_ASTRA_PRO = { features: ['streaming', 'function_calling', 'structured_outputs'], tools: [], }, - pricing: { - input: { - normal: 10, - cached: 1, - }, - output: { - normal: 50, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2427,15 +1840,6 @@ const GPT_6_LUNA = { 'apply_patch', ], }, - pricing: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.5, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2456,15 +1860,6 @@ const GPT_6_LUNA_PRO = { features: ['streaming', 'function_calling', 'structured_outputs'], tools: [], }, - pricing: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.5, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2496,15 +1891,6 @@ const GPT_6_SOL = { 'apply_patch', ], }, - pricing: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2525,15 +1911,6 @@ const GPT_6_SOL_PRO = { features: ['streaming', 'function_calling', 'structured_outputs'], tools: [], }, - pricing: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2566,15 +1943,6 @@ const GPT_6_1_SOL = { 'apply_patch', ], }, - pricing: { - input: { - normal: 2, - cached: 0.1, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & @@ -2607,15 +1975,6 @@ const GPT_6_1_SOL_PRO = { 'apply_patch', ], }, - pricing: { - input: { - normal: 2, - cached: 0.1, - }, - output: { - normal: 10, - }, - }, reasoning: EFFORT_LOW_TO_MAX, } as const satisfies ModelMeta< OpenAIBaseOptions & diff --git a/packages/ai-openrouter/src/model-meta.ts b/packages/ai-openrouter/src/model-meta.ts index 74d79bedf7..bc00f61311 100644 --- a/packages/ai-openrouter/src/model-meta.ts +++ b/packages/ai-openrouter/src/model-meta.ts @@ -231,18 +231,6 @@ const _ANTHROPIC_CLAUDE_FABLE_LATEST = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 10, - cached: 12.75, - }, - output: { - normal: 50, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const _ANTHROPIC_CLAUDE_HAIKU_LATEST = { @@ -262,18 +250,6 @@ const _ANTHROPIC_CLAUDE_HAIKU_LATEST = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.135, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const _ANTHROPIC_CLAUDE_OPUS_LATEST = { @@ -293,18 +269,6 @@ const _ANTHROPIC_CLAUDE_OPUS_LATEST = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 4, - cached: 5.2, - }, - output: { - normal: 20, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const _ANTHROPIC_CLAUDE_SONNET_LATEST = { @@ -324,18 +288,6 @@ const _ANTHROPIC_CLAUDE_SONNET_LATEST = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.6, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const _DEEPSEEK_DEEPSEEK_FLASH_LATEST = { @@ -364,18 +316,6 @@ const _DEEPSEEK_DEEPSEEK_FLASH_LATEST = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.07, - cached: 0.008, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const _DEEPSEEK_DEEPSEEK_PRO_LATEST = { @@ -402,18 +342,6 @@ const _DEEPSEEK_DEEPSEEK_PRO_LATEST = { }, context_window: 1048576, max_output_tokens: 393216, - pricing: { - text: { - input: { - normal: 0.2235, - cached: 0.22, - }, - output: { - normal: 8, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const _DEEPSEEK_DEEPSEEK_V4_FLASH_LATEST = { @@ -441,18 +369,6 @@ const _DEEPSEEK_DEEPSEEK_V4_FLASH_LATEST = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.0475, - cached: 0.028, - }, - output: { - normal: 0.1568, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const _GOOGLE_GEMINI_FLASH_LATEST = { @@ -474,18 +390,6 @@ const _GOOGLE_GEMINI_FLASH_LATEST = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.75, - cached: 0.1166666667, - }, - output: { - normal: 3.75, - }, - }, - image: 7.5e-7, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const _GOOGLE_GEMINI_PRO_LATEST = { @@ -507,18 +411,6 @@ const _GOOGLE_GEMINI_PRO_LATEST = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 2, - cached: 0.2875, - }, - output: { - normal: 10, - }, - }, - image: 0.000002, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const _MOONSHOTAI_KIMI_LATEST = { @@ -545,18 +437,6 @@ const _MOONSHOTAI_KIMI_LATEST = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.238, - cached: 0.228, - }, - output: { - normal: 16, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const _OPENAI_GPT_ASTRA_LATEST = { @@ -576,18 +456,6 @@ const _OPENAI_GPT_ASTRA_LATEST = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 10, - cached: 13.5, - }, - output: { - normal: 50, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const _OPENAI_GPT_LUNA_LATEST = { @@ -607,18 +475,6 @@ const _OPENAI_GPT_LUNA_LATEST = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.135, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const _OPENAI_GPT_MINI_LATEST = { @@ -638,18 +494,6 @@ const _OPENAI_GPT_MINI_LATEST = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.75, - cached: 0.075, - }, - output: { - normal: 4.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const _OPENAI_GPT_SOL_LATEST = { @@ -669,18 +513,6 @@ const _OPENAI_GPT_SOL_LATEST = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.6, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const _OPENAI_GPT_TERRA_LATEST = { @@ -700,18 +532,6 @@ const _OPENAI_GPT_TERRA_LATEST = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.7, - }, - output: { - normal: 12, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const _UNBIASED_PARETO_LATEST = { @@ -724,18 +544,6 @@ const _UNBIASED_PARETO_LATEST = { }, context_window: 1048576, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.8, - cached: 0.03, - }, - output: { - normal: 3.2, - }, - }, - image: 0, - }, } as const const _X_AI_GROK_LATEST = { id: '~x-ai/grok-latest', @@ -757,18 +565,6 @@ const _X_AI_GROK_LATEST = { }, context_window: 500000, max_output_tokens: 450000, - pricing: { - text: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const const _Z_AI_GLM_FLASH_LATEST = { @@ -796,18 +592,6 @@ const _Z_AI_GLM_FLASH_LATEST = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.0052, - cached: 0.0052, - }, - output: { - normal: 0.1985, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const _Z_AI_GLM_LATEST = { @@ -835,18 +619,6 @@ const _Z_AI_GLM_LATEST = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.079, - cached: 0.078, - }, - output: { - normal: 1.4, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const AION_LABS_AION_2_0 = { @@ -866,18 +638,6 @@ const AION_LABS_AION_2_0 = { }, context_window: 131072, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.8, - cached: 0.2, - }, - output: { - normal: 1.6, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const AION_LABS_AION_3_0 = { @@ -897,18 +657,6 @@ const AION_LABS_AION_3_0 = { }, context_window: 131072, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 3, - cached: 0.75, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const AION_LABS_AION_3_0_MINI = { @@ -928,18 +676,6 @@ const AION_LABS_AION_3_0_MINI = { }, context_window: 131072, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.7, - cached: 0.18, - }, - output: { - normal: 1.4, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const AION_LABS_AION_3_5 = { @@ -959,18 +695,6 @@ const AION_LABS_AION_3_5 = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 3, - cached: 0.75, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const AION_LABS_AION_3_5_MINI = { @@ -990,18 +714,6 @@ const AION_LABS_AION_3_5_MINI = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.7, - cached: 0.18, - }, - output: { - normal: 1.4, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const AION_LABS_AION_RP_LLAMA_3_1_8B = { @@ -1014,18 +726,6 @@ const AION_LABS_AION_RP_LLAMA_3_1_8B = { }, context_window: 32768, max_output_tokens: 29491, - pricing: { - text: { - input: { - normal: 0.8, - cached: 0, - }, - output: { - normal: 1.6, - }, - }, - image: 0, - }, } as const const AMAZON_NOVA_2_LITE_V1 = { id: 'amazon/nova-2-lite-v1', @@ -1044,18 +744,6 @@ const AMAZON_NOVA_2_LITE_V1 = { }, context_window: 1000000, max_output_tokens: 65535, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const AMAZON_NOVA_LITE_V1 = { @@ -1068,18 +756,6 @@ const AMAZON_NOVA_LITE_V1 = { }, context_window: 300000, max_output_tokens: 5120, - pricing: { - text: { - input: { - normal: 0.06, - cached: 0, - }, - output: { - normal: 0.24, - }, - }, - image: 0, - }, } as const const AMAZON_NOVA_MICRO_V1 = { id: 'amazon/nova-micro-v1', @@ -1091,18 +767,6 @@ const AMAZON_NOVA_MICRO_V1 = { }, context_window: 128000, max_output_tokens: 5120, - pricing: { - text: { - input: { - normal: 0.035, - cached: 0, - }, - output: { - normal: 0.14, - }, - }, - image: 0, - }, } as const const AMAZON_NOVA_PREMIER_V1 = { id: 'amazon/nova-premier-v1', @@ -1114,18 +778,6 @@ const AMAZON_NOVA_PREMIER_V1 = { }, context_window: 1000000, max_output_tokens: 32000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0.625, - }, - output: { - normal: 12.5, - }, - }, - image: 0, - }, } as const const AMAZON_NOVA_PRO_V1 = { id: 'amazon/nova-pro-v1', @@ -1137,18 +789,6 @@ const AMAZON_NOVA_PRO_V1 = { }, context_window: 300000, max_output_tokens: 5120, - pricing: { - text: { - input: { - normal: 0.8, - cached: 0, - }, - output: { - normal: 3.2, - }, - }, - image: 0, - }, } as const const ANTHRACITE_ORG_MAGNUM_V4_72B = { id: 'anthracite-org/magnum-v4-72b', @@ -1172,18 +812,6 @@ const ANTHRACITE_ORG_MAGNUM_V4_72B = { }, context_window: 32768, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, } as const const ANTHROPIC_CLAUDE_FABLE_5 = { id: 'anthropic/claude-fable-5', @@ -1202,18 +830,6 @@ const ANTHROPIC_CLAUDE_FABLE_5 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 10, - cached: 13.5, - }, - output: { - normal: 50, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_FABLE_5_BATCH = { @@ -1232,18 +848,6 @@ const ANTHROPIC_CLAUDE_FABLE_5_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.75, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_FABLE_5_1 = { @@ -1263,18 +867,6 @@ const ANTHROPIC_CLAUDE_FABLE_5_1 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 10, - cached: 12.75, - }, - output: { - normal: 50, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_FABLE_5_1_BATCH = { @@ -1293,18 +885,6 @@ const ANTHROPIC_CLAUDE_FABLE_5_1_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.375, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_HAIKU_4_5 = { @@ -1326,18 +906,6 @@ const ANTHROPIC_CLAUDE_HAIKU_4_5 = { }, context_window: 200000, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 1, - cached: 1.35, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const ANTHROPIC_CLAUDE_HAIKU_4_5_BATCH = { @@ -1358,18 +926,6 @@ const ANTHROPIC_CLAUDE_HAIKU_4_5_BATCH = { }, context_window: 200000, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0.675, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_TO_HIGH, } as const const ANTHROPIC_CLAUDE_HAIKU_5_5 = { @@ -1389,18 +945,6 @@ const ANTHROPIC_CLAUDE_HAIKU_5_5 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.135, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, } as const const ANTHROPIC_CLAUDE_HAIKU_5_5_BATCH = { id: 'anthropic/claude-haiku-5.5:batch', @@ -1418,18 +962,6 @@ const ANTHROPIC_CLAUDE_HAIKU_5_5_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.0675, - }, - output: { - normal: 0.25, - }, - }, - image: 0, - }, } as const const ANTHROPIC_CLAUDE_OPUS_4_1 = { id: 'anthropic/claude-opus-4.1', @@ -1448,18 +980,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_1 = { }, context_window: 200000, max_output_tokens: 32000, - pricing: { - text: { - input: { - normal: 15, - cached: 20.25, - }, - output: { - normal: 75, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const ANTHROPIC_CLAUDE_OPUS_4_1_BATCH = { @@ -1479,18 +999,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_1_BATCH = { }, context_window: 200000, max_output_tokens: 32000, - pricing: { - text: { - input: { - normal: 7.5, - cached: 10.125, - }, - output: { - normal: 37.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_TO_HIGH, } as const const ANTHROPIC_CLAUDE_OPUS_4_5 = { @@ -1511,18 +1019,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_5 = { }, context_window: 200000, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.75, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const ANTHROPIC_CLAUDE_OPUS_4_5_BATCH = { @@ -1542,18 +1038,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_5_BATCH = { }, context_window: 200000, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 3.375, - }, - output: { - normal: 12.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_TO_HIGH, } as const const ANTHROPIC_CLAUDE_OPUS_4_6 = { @@ -1575,18 +1059,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_6 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.75, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: BUDGET_NONE_LOW_TO_HIGH_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_4_6_BATCH = { @@ -1607,18 +1079,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_6_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 3.375, - }, - output: { - normal: 12.5, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_4_7 = { @@ -1638,18 +1098,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_7 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.75, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_4_7_BATCH = { @@ -1668,18 +1116,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_7_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 3.375, - }, - output: { - normal: 12.5, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_4_8 = { @@ -1699,18 +1135,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_8 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.75, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_4_8_BATCH = { @@ -1729,18 +1153,6 @@ const ANTHROPIC_CLAUDE_OPUS_4_8_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 3.375, - }, - output: { - normal: 12.5, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_5 = { @@ -1760,18 +1172,6 @@ const ANTHROPIC_CLAUDE_OPUS_5 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.75, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_5_BATCH = { @@ -1790,18 +1190,6 @@ const ANTHROPIC_CLAUDE_OPUS_5_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 3.375, - }, - output: { - normal: 12.5, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_5_5 = { @@ -1821,18 +1209,6 @@ const ANTHROPIC_CLAUDE_OPUS_5_5 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 4, - cached: 5.2, - }, - output: { - normal: 20, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_OPUS_5_5_BATCH = { @@ -1851,18 +1227,6 @@ const ANTHROPIC_CLAUDE_OPUS_5_5_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.6, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_SONNET_4_5 = { @@ -1884,18 +1248,6 @@ const ANTHROPIC_CLAUDE_SONNET_4_5 = { }, context_window: 1000000, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 3, - cached: 4.05, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const ANTHROPIC_CLAUDE_SONNET_4_5_BATCH = { @@ -1916,18 +1268,6 @@ const ANTHROPIC_CLAUDE_SONNET_4_5_BATCH = { }, context_window: 1000000, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 1.5, - cached: 2.025, - }, - output: { - normal: 7.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_TO_HIGH, } as const const ANTHROPIC_CLAUDE_SONNET_4_6 = { @@ -1949,18 +1289,6 @@ const ANTHROPIC_CLAUDE_SONNET_4_6 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 3, - cached: 4.05, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH_MAX, } as const const ANTHROPIC_CLAUDE_SONNET_4_6_BATCH = { @@ -1981,18 +1309,6 @@ const ANTHROPIC_CLAUDE_SONNET_4_6_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.5, - cached: 2.025, - }, - output: { - normal: 7.5, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH_MAX, } as const const ANTHROPIC_CLAUDE_SONNET_5 = { @@ -2012,18 +1328,6 @@ const ANTHROPIC_CLAUDE_SONNET_5 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.7, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_SONNET_5_BATCH = { @@ -2042,18 +1346,6 @@ const ANTHROPIC_CLAUDE_SONNET_5_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 1.35, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_SONNET_5_5 = { @@ -2073,18 +1365,6 @@ const ANTHROPIC_CLAUDE_SONNET_5_5 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.6, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ANTHROPIC_CLAUDE_SONNET_5_5_BATCH = { @@ -2103,18 +1383,6 @@ const ANTHROPIC_CLAUDE_SONNET_5_5_BATCH = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 1.3, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const ARCEE_AI_TRINITY_LARGE_THINKING = { @@ -2133,18 +1401,6 @@ const ARCEE_AI_TRINITY_LARGE_THINKING = { }, context_window: 262144, max_output_tokens: 80000, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.06, - }, - output: { - normal: 0.8, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const BYTEDANCE_SEED_SEED_1_6 = { @@ -2166,18 +1422,6 @@ const BYTEDANCE_SEED_SEED_1_6 = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const BYTEDANCE_SEED_SEED_1_6_FLASH = { @@ -2199,18 +1443,6 @@ const BYTEDANCE_SEED_SEED_1_6_FLASH = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.075, - cached: 0, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const BYTEDANCE_SEED_SEED_2_1_TURBO = { @@ -2232,18 +1464,6 @@ const BYTEDANCE_SEED_SEED_2_1_TURBO = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const BYTEDANCE_SEED_SEED_2_0_CODE = { @@ -2265,18 +1485,6 @@ const BYTEDANCE_SEED_SEED_2_0_CODE = { }, context_window: 262144, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0, - }, - output: { - normal: 3, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const const BYTEDANCE_SEED_SEED_2_0_LITE = { @@ -2298,18 +1506,6 @@ const BYTEDANCE_SEED_SEED_2_0_LITE = { }, context_window: 262144, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH, } as const const BYTEDANCE_SEED_SEED_2_0_MINI = { @@ -2331,18 +1527,6 @@ const BYTEDANCE_SEED_SEED_2_0_MINI = { }, context_window: 262144, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0, - }, - output: { - normal: 0.4, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH, } as const const BYTEDANCE_UI_TARS_1_5_7B = { @@ -2366,18 +1550,6 @@ const BYTEDANCE_UI_TARS_1_5_7B = { }, context_window: 128000, max_output_tokens: 2048, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.1, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, } as const const COGNITIVECOMPUTATIONS_DOLPHIN_MISTRAL_24B_VENICE_EDITION = { id: 'cognitivecomputations/dolphin-mistral-24b-venice-edition', @@ -2397,18 +1569,6 @@ const COGNITIVECOMPUTATIONS_DOLPHIN_MISTRAL_24B_VENICE_EDITION = { }, context_window: 128000, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0, - }, - output: { - normal: 0.9, - }, - }, - image: 0, - }, } as const const COHERE_COMMAND_A = { id: 'cohere/command-a', @@ -2429,18 +1589,6 @@ const COHERE_COMMAND_A = { }, context_window: 256000, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, } as const const COHERE_COMMAND_A_PLUS = { id: 'cohere/command-a-plus', @@ -2462,18 +1610,6 @@ const COHERE_COMMAND_A_PLUS = { }, context_window: 192000, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.15, - }, - output: { - normal: 1.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const COHERE_COMMAND_R_08_2024 = { @@ -2496,18 +1632,6 @@ const COHERE_COMMAND_R_08_2024 = { }, context_window: 128000, max_output_tokens: 4000, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, } as const const COHERE_COMMAND_R_PLUS_08_2024 = { id: 'cohere/command-r-plus-08-2024', @@ -2529,18 +1653,6 @@ const COHERE_COMMAND_R_PLUS_08_2024 = { }, context_window: 128000, max_output_tokens: 4000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, } as const const COHERE_COMMAND_R7B_12_2024 = { id: 'cohere/command-r7b-12-2024', @@ -2561,18 +1673,6 @@ const COHERE_COMMAND_R7B_12_2024 = { }, context_window: 128000, max_output_tokens: 4000, - pricing: { - text: { - input: { - normal: 0.0375, - cached: 0, - }, - output: { - normal: 0.15, - }, - }, - image: 0, - }, } as const const COHERE_NORTH_MINI_CODE_FREE = { id: 'cohere/north-mini-code:free', @@ -2594,18 +1694,6 @@ const COHERE_NORTH_MINI_CODE_FREE = { }, context_window: 256000, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const DEEPSEEK_DEEPSEEK_CHAT = { @@ -2629,18 +1717,6 @@ const DEEPSEEK_DEEPSEEK_CHAT = { }, context_window: 163840, max_output_tokens: 16000, - pricing: { - text: { - input: { - normal: 0.2574, - cached: 0, - }, - output: { - normal: 1.0287, - }, - }, - image: 0, - }, } as const const DEEPSEEK_DEEPSEEK_CHAT_V3_0324 = { id: 'deepseek/deepseek-chat-v3-0324', @@ -2662,18 +1738,6 @@ const DEEPSEEK_DEEPSEEK_CHAT_V3_0324 = { }, context_window: 163840, max_output_tokens: 115200, - pricing: { - text: { - input: { - normal: 0.29, - cached: 0.11, - }, - output: { - normal: 1.14, - }, - }, - image: 0, - }, } as const const DEEPSEEK_DEEPSEEK_CHAT_V3_1 = { id: 'deepseek/deepseek-chat-v3.1', @@ -2699,18 +1763,6 @@ const DEEPSEEK_DEEPSEEK_CHAT_V3_1 = { }, context_window: 163840, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.13, - }, - output: { - normal: 0.95, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const DEEPSEEK_DEEPSEEK_R1_0528 = { @@ -2737,18 +1789,6 @@ const DEEPSEEK_DEEPSEEK_R1_0528 = { }, context_window: 163840, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0.35, - }, - output: { - normal: 2.15, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const DEEPSEEK_DEEPSEEK_V3_1_TERMINUS = { @@ -2770,18 +1810,6 @@ const DEEPSEEK_DEEPSEEK_V3_1_TERMINUS = { }, context_window: 163840, max_output_tokens: 147456, - pricing: { - text: { - input: { - normal: 0.27, - cached: 0, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const DEEPSEEK_DEEPSEEK_V3_2 = { @@ -2808,18 +1836,6 @@ const DEEPSEEK_DEEPSEEK_V3_2 = { }, context_window: 163840, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.28, - cached: 0.028, - }, - output: { - normal: 0.42, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const DEEPSEEK_DEEPSEEK_V3_2_EXP = { @@ -2840,18 +1856,6 @@ const DEEPSEEK_DEEPSEEK_V3_2_EXP = { }, context_window: 163840, max_output_tokens: 147456, - pricing: { - text: { - input: { - normal: 0.27, - cached: 0, - }, - output: { - normal: 0.41, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const DEEPSEEK_DEEPSEEK_V4_FLASH = { @@ -2879,18 +1883,6 @@ const DEEPSEEK_DEEPSEEK_V4_FLASH = { }, context_window: 1048576, max_output_tokens: 943717, - pricing: { - text: { - input: { - normal: 0.091, - cached: 0.0182, - }, - output: { - normal: 0.182, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_HIGH_XHIGH, } as const const DEEPSEEK_DEEPSEEK_V4_FLASH_0731 = { @@ -2918,18 +1910,6 @@ const DEEPSEEK_DEEPSEEK_V4_FLASH_0731 = { }, context_window: 1048576, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.44, - cached: 0.014, - }, - output: { - normal: 1.32, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const DEEPSEEK_DEEPSEEK_V4_FLASH_VISION_EXP = { @@ -2956,18 +1936,6 @@ const DEEPSEEK_DEEPSEEK_V4_FLASH_VISION_EXP = { }, context_window: 1048576, max_output_tokens: 262144, - pricing: { - text: { - input: { - normal: 0.2156, - cached: 0.00686, - }, - output: { - normal: 0.6468, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const DEEPSEEK_DEEPSEEK_V4_PRO = { @@ -2995,18 +1963,6 @@ const DEEPSEEK_DEEPSEEK_V4_PRO = { }, context_window: 1048576, max_output_tokens: 384000, - pricing: { - text: { - input: { - normal: 0.95526, - cached: 0.079605, - }, - output: { - normal: 1.91052, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_HIGH_XHIGH, } as const const DEEPSEEK_DEEPSEEK_V4_PRO_0813 = { @@ -3033,18 +1989,6 @@ const DEEPSEEK_DEEPSEEK_V4_PRO_0813 = { }, context_window: 1048576, max_output_tokens: 393216, - pricing: { - text: { - input: { - normal: 0.66, - cached: 0.022, - }, - output: { - normal: 1.98, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const DEEPSEEK_DEEPSEEK_V4_1_FLASH = { @@ -3071,18 +2015,6 @@ const DEEPSEEK_DEEPSEEK_V4_1_FLASH = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.006, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const DEEPSEEK_DEEPSEEK_V4_1_FLASH_1017 = { @@ -3111,18 +2043,6 @@ const DEEPSEEK_DEEPSEEK_V4_1_FLASH_1017 = { }, context_window: 1048576, max_output_tokens: 393216, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.003, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, } as const const DEEPSEEK_DEEPSEEK_V4_1_FLASH_BATCH = { id: 'deepseek/deepseek-v4.1-flash:batch', @@ -3148,18 +2068,6 @@ const DEEPSEEK_DEEPSEEK_V4_1_FLASH_BATCH = { }, context_window: 1048576, max_output_tokens: 384000, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.004, - }, - output: { - normal: 0.15, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const DOTS_STUDIO_DOTS_3_NOTE_PREVIEW_FREE = { @@ -3179,18 +2087,6 @@ const DOTS_STUDIO_DOTS_3_NOTE_PREVIEW_FREE = { }, context_window: 512000, max_output_tokens: 460800, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const ELORIAN_GLIMPSE = { @@ -3216,18 +2112,6 @@ const ELORIAN_GLIMPSE = { }, context_window: 262144, max_output_tokens: 256000, - pricing: { - text: { - input: { - normal: 0.6, - cached: 0.06, - }, - output: { - normal: 3, - }, - }, - image: 0, - }, } as const const FIREWORKS_EMBER_1 = { id: 'fireworks/ember-1', @@ -3252,18 +2136,6 @@ const FIREWORKS_EMBER_1 = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 3, - cached: 0.3, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const GOOGLE_GEMINI_2_5_FLASH = { @@ -3285,18 +2157,6 @@ const GOOGLE_GEMINI_2_5_FLASH = { }, context_window: 1048576, max_output_tokens: 65535, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.1133333333, - }, - output: { - normal: 2.5, - }, - }, - image: 3e-7, - }, reasoning: EFFORT_OFF_HIGH, } as const const GOOGLE_GEMINI_2_5_FLASH_IMAGE = { @@ -3316,18 +2176,6 @@ const GOOGLE_GEMINI_2_5_FLASH_IMAGE = { }, context_window: 32768, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.1133333333, - }, - output: { - normal: 2.5, - }, - }, - image: 3e-7, - }, } as const const GOOGLE_GEMINI_2_5_FLASH_LITE = { id: 'google/gemini-2.5-flash-lite', @@ -3348,18 +2196,6 @@ const GOOGLE_GEMINI_2_5_FLASH_LITE = { }, context_window: 1048576, max_output_tokens: 65535, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.0933333333, - }, - output: { - normal: 0.4, - }, - }, - image: 1e-7, - }, reasoning: EFFORT_OFF_HIGH, } as const const GOOGLE_GEMINI_2_5_PRO = { @@ -3380,18 +2216,6 @@ const GOOGLE_GEMINI_2_5_PRO = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.5, - }, - output: { - normal: 10, - }, - }, - image: 0.00000125, - }, reasoning: BUDGET_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_2_5_PRO_PREVIEW = { @@ -3413,18 +2237,6 @@ const GOOGLE_GEMINI_2_5_PRO_PREVIEW = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.5, - }, - output: { - normal: 10, - }, - }, - image: 0.00000125, - }, reasoning: BUDGET_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_FLASH_PREVIEW = { @@ -3446,18 +2258,6 @@ const GOOGLE_GEMINI_3_FLASH_PREVIEW = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0.13333333330000002, - }, - output: { - normal: 3, - }, - }, - image: 5e-7, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_FLASH_PREVIEW_BATCH = { @@ -3479,18 +2279,6 @@ const GOOGLE_GEMINI_3_FLASH_PREVIEW_BATCH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0, - }, - output: { - normal: 1.5, - }, - }, - image: 2.5e-7, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_PRO_IMAGE = { @@ -3512,18 +2300,6 @@ const GOOGLE_GEMINI_3_PRO_IMAGE = { }, context_window: 131072, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 2, - cached: 0.575, - }, - output: { - normal: 12, - }, - }, - image: 0.000002, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_PRO_IMAGE_PREVIEW = { @@ -3544,18 +2320,6 @@ const GOOGLE_GEMINI_3_PRO_IMAGE_PREVIEW = { }, context_window: 65536, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 2, - cached: 0.575, - }, - output: { - normal: 12, - }, - }, - image: 0.000002, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_1_FLASH_IMAGE = { @@ -3575,18 +2339,6 @@ const GOOGLE_GEMINI_3_1_FLASH_IMAGE = { }, context_window: 131072, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0, - }, - output: { - normal: 3, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_HIGH, } as const const GOOGLE_GEMINI_3_1_FLASH_IMAGE_PREVIEW = { @@ -3606,18 +2358,6 @@ const GOOGLE_GEMINI_3_1_FLASH_IMAGE_PREVIEW = { }, context_window: 65536, max_output_tokens: 58982, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0, - }, - output: { - normal: 3, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_HIGH, } as const const GOOGLE_GEMINI_3_1_FLASH_LITE = { @@ -3639,18 +2379,6 @@ const GOOGLE_GEMINI_3_1_FLASH_LITE = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.1083333333, - }, - output: { - normal: 1.5, - }, - }, - image: 2.5e-7, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_1_FLASH_LITE_IMAGE = { @@ -3670,18 +2398,6 @@ const GOOGLE_GEMINI_3_1_FLASH_LITE_IMAGE = { }, context_window: 65536, max_output_tokens: 58982, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0, - }, - output: { - normal: 1.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_HIGH, } as const const GOOGLE_GEMINI_3_1_FLASH_LITE_PREVIEW = { @@ -3702,18 +2418,6 @@ const GOOGLE_GEMINI_3_1_FLASH_LITE_PREVIEW = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.1083333333, - }, - output: { - normal: 1.5, - }, - }, - image: 2.5e-7, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_1_FLASH_LITE_BATCH = { @@ -3735,18 +2439,6 @@ const GOOGLE_GEMINI_3_1_FLASH_LITE_BATCH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.125, - cached: 0.0125, - }, - output: { - normal: 0.75, - }, - }, - image: 1.25e-7, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_1_PRO_PREVIEW = { @@ -3768,18 +2460,6 @@ const GOOGLE_GEMINI_3_1_PRO_PREVIEW = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 2, - cached: 0.575, - }, - output: { - normal: 12, - }, - }, - image: 0.000002, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const GOOGLE_GEMINI_3_1_PRO_PREVIEW_CUSTOMTOOLS = { @@ -3800,18 +2480,6 @@ const GOOGLE_GEMINI_3_1_PRO_PREVIEW_CUSTOMTOOLS = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 2, - cached: 0.575, - }, - output: { - normal: 12, - }, - }, - image: 0.000002, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const GOOGLE_GEMINI_3_1_PRO_PREVIEW_BATCH = { @@ -3833,18 +2501,6 @@ const GOOGLE_GEMINI_3_1_PRO_PREVIEW_BATCH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 1, - cached: 0, - }, - output: { - normal: 6, - }, - }, - image: 0.000001, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const GOOGLE_GEMINI_3_5_FLASH = { @@ -3866,18 +2522,6 @@ const GOOGLE_GEMINI_3_5_FLASH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 1.5, - cached: 0.2333333333, - }, - output: { - normal: 9, - }, - }, - image: 0.0000015, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_5_FLASH_LITE = { @@ -3899,18 +2543,6 @@ const GOOGLE_GEMINI_3_5_FLASH_LITE = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.1133333333, - }, - output: { - normal: 2.5, - }, - }, - image: 3e-7, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_5_FLASH_LITE_BATCH = { @@ -3930,18 +2562,6 @@ const GOOGLE_GEMINI_3_5_FLASH_LITE_BATCH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.015, - }, - output: { - normal: 1.25, - }, - }, - image: 1.5e-7, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_5_FLASH_BATCH = { @@ -3963,18 +2583,6 @@ const GOOGLE_GEMINI_3_5_FLASH_BATCH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.75, - cached: 0.075, - }, - output: { - normal: 4.5, - }, - }, - image: 7.5e-7, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_6_FLASH = { @@ -3996,18 +2604,6 @@ const GOOGLE_GEMINI_3_6_FLASH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.75, - cached: 0.1166666667, - }, - output: { - normal: 3.75, - }, - }, - image: 7.5e-7, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_6_FLASH_BATCH = { @@ -4027,18 +2623,6 @@ const GOOGLE_GEMINI_3_6_FLASH_BATCH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.375, - cached: 0.0791666667, - }, - output: { - normal: 1.875, - }, - }, - image: 3.75e-7, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const GOOGLE_GEMINI_3_7_FLASH = { @@ -4060,18 +2644,6 @@ const GOOGLE_GEMINI_3_7_FLASH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.75, - cached: 0.1166666667, - }, - output: { - normal: 3.75, - }, - }, - image: 7.5e-7, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const GOOGLE_GEMINI_3_7_FLASH_BATCH = { @@ -4091,18 +2663,6 @@ const GOOGLE_GEMINI_3_7_FLASH_BATCH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.375, - cached: 0.0791666667, - }, - output: { - normal: 1.875, - }, - }, - image: 3.75e-7, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const GOOGLE_GEMINI_3_8_FLASH = { @@ -4124,18 +2684,6 @@ const GOOGLE_GEMINI_3_8_FLASH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.825, - cached: 0.12416666670000001, - }, - output: { - normal: 4.125, - }, - }, - image: 8.25e-7, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const GOOGLE_GEMINI_3_8_FLASH_BATCH = { @@ -4155,18 +2703,6 @@ const GOOGLE_GEMINI_3_8_FLASH_BATCH = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.375, - cached: 0.0791666667, - }, - output: { - normal: 1.875, - }, - }, - image: 3.75e-7, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const GOOGLE_GEMINI_4_ARGON = { @@ -4188,18 +2724,6 @@ const GOOGLE_GEMINI_4_ARGON = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 2, - cached: 0.2875, - }, - output: { - normal: 10, - }, - }, - image: 0.000002, - }, } as const const GOOGLE_GEMINI_4_ARGON_BATCH = { id: 'google/gemini-4-argon:batch', @@ -4220,18 +2744,6 @@ const GOOGLE_GEMINI_4_ARGON_BATCH = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 1, - cached: 0.2375, - }, - output: { - normal: 5, - }, - }, - image: 0.000001, - }, } as const const GOOGLE_GEMINI_NANO_BANANA_2_1 = { id: 'google/gemini-nano-banana-2.1', @@ -4251,18 +2763,6 @@ const GOOGLE_GEMINI_NANO_BANANA_2_1 = { }, context_window: 65536, max_output_tokens: 58982, - pricing: { - text: { - input: { - normal: 1.5, - cached: 0, - }, - output: { - normal: 7.5, - }, - }, - image: 0, - }, } as const const GOOGLE_GEMMA_2_27B_IT = { id: 'google/gemma-2-27b-it', @@ -4283,18 +2783,6 @@ const GOOGLE_GEMMA_2_27B_IT = { }, context_window: 8192, max_output_tokens: 2048, - pricing: { - text: { - input: { - normal: 0.65, - cached: 0, - }, - output: { - normal: 0.65, - }, - }, - image: 0, - }, } as const const GOOGLE_GEMMA_3_12B_IT = { id: 'google/gemma-3-12b-it', @@ -4317,18 +2805,6 @@ const GOOGLE_GEMMA_3_12B_IT = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0, - }, - output: { - normal: 0.15, - }, - }, - image: 0, - }, } as const const GOOGLE_GEMMA_3_27B_IT = { id: 'google/gemma-3-27b-it', @@ -4353,18 +2829,6 @@ const GOOGLE_GEMMA_3_27B_IT = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.08, - cached: 0.04, - }, - output: { - normal: 0.35, - }, - }, - image: 0, - }, } as const const GOOGLE_GEMMA_3_4B_IT = { id: 'google/gemma-3-4b-it', @@ -4388,18 +2852,6 @@ const GOOGLE_GEMMA_3_4B_IT = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.04, - cached: 0.02, - }, - output: { - normal: 0.05, - }, - }, - image: 0, - }, } as const const GOOGLE_GEMMA_4_26B_A4B_IT = { id: 'google/gemma-4-26b-a4b-it', @@ -4425,18 +2877,6 @@ const GOOGLE_GEMMA_4_26B_A4B_IT = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.09, - cached: 0.05, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const GOOGLE_GEMMA_4_26B_A4B_IT_FREE = { @@ -4457,18 +2897,6 @@ const GOOGLE_GEMMA_4_26B_A4B_IT_FREE = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const GOOGLE_GEMMA_4_31B_IT = { @@ -4495,18 +2923,6 @@ const GOOGLE_GEMMA_4_31B_IT = { }, context_window: 262144, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0, - }, - output: { - normal: 0.4, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const GOOGLE_GEMMA_4_31B_IT_FREE = { @@ -4527,18 +2943,6 @@ const GOOGLE_GEMMA_4_31B_IT_FREE = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const GOOGLE_LYRIA_3_CLIP_PREVIEW = { @@ -4557,18 +2961,6 @@ const GOOGLE_LYRIA_3_CLIP_PREVIEW = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, } as const const GOOGLE_LYRIA_3_PRO_PREVIEW = { id: 'google/lyria-3-pro-preview', @@ -4586,18 +2978,6 @@ const GOOGLE_LYRIA_3_PRO_PREVIEW = { }, context_window: 1048576, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, } as const const GRYPHE_MYTHOMAX_L2_13B = { id: 'gryphe/mythomax-l2-13b', @@ -4621,18 +3001,6 @@ const GRYPHE_MYTHOMAX_L2_13B = { }, context_window: 8192, max_output_tokens: 3686, - pricing: { - text: { - input: { - normal: 0.08, - cached: 0, - }, - output: { - normal: 0.11, - }, - }, - image: 0, - }, } as const const IBM_GRANITE_GRANITE_4_0_H_MICRO = { id: 'ibm-granite/granite-4.0-h-micro', @@ -4656,18 +3024,6 @@ const IBM_GRANITE_GRANITE_4_0_H_MICRO = { }, context_window: 131000, max_output_tokens: 117900, - pricing: { - text: { - input: { - normal: 0.017, - cached: 0, - }, - output: { - normal: 0.112, - }, - }, - image: 0, - }, } as const const IBM_GRANITE_GRANITE_4_2_8B = { id: 'ibm-granite/granite-4.2-8b', @@ -4693,18 +3049,6 @@ const IBM_GRANITE_GRANITE_4_2_8B = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.06, - cached: 0.015, - }, - output: { - normal: 0.25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH, } as const const INCEPTION_MERCURY_2 = { @@ -4724,18 +3068,6 @@ const INCEPTION_MERCURY_2 = { }, context_window: 128000, max_output_tokens: 50000, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.025, - }, - output: { - normal: 0.75, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const const INCEPTION_MERCURY_2_5 = { @@ -4755,18 +3087,6 @@ const INCEPTION_MERCURY_2_5 = { }, context_window: 260000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.04, - cached: 0.004, - }, - output: { - normal: 0.15, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const const INCEPTION_MERCURY_3 = { @@ -4786,18 +3106,6 @@ const INCEPTION_MERCURY_3 = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.35, - }, - }, - image: 0, - }, } as const const INCLUSIONAI_LING_3_0_FLASH = { id: 'inclusionai/ling-3.0-flash', @@ -4823,18 +3131,6 @@ const INCLUSIONAI_LING_3_0_FLASH = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.021, - cached: 0.0042, - }, - output: { - normal: 0.063, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const INCLUSIONAI_LING_3_0_FLASH_VL = { @@ -4859,18 +3155,6 @@ const INCLUSIONAI_LING_3_0_FLASH_VL = { }, context_window: 131072, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.06, - cached: 0.012, - }, - output: { - normal: 0.18, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const INCLUSIONAI_LING_3_1_FLASH = { @@ -4897,18 +3181,6 @@ const INCLUSIONAI_LING_3_1_FLASH = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.075, - cached: 0.015, - }, - output: { - normal: 0.225, - }, - }, - image: 0, - }, } as const const INFERENCE_NET_SCHEMATRON_V2_SMALL = { id: 'inference-net/schematron-v2-small', @@ -4930,18 +3202,6 @@ const INFERENCE_NET_SCHEMATRON_V2_SMALL = { }, context_window: 128000, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.05, - }, - output: { - normal: 0.23, - }, - }, - image: 0, - }, } as const const INFERENCE_NET_SCHEMATRON_V2_TURBO = { id: 'inference-net/schematron-v2-turbo', @@ -4963,18 +3223,6 @@ const INFERENCE_NET_SCHEMATRON_V2_TURBO = { }, context_window: 128000, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0.03, - cached: 0.03, - }, - output: { - normal: 0.15, - }, - }, - image: 0, - }, } as const const LIQUID_LFM_2_5_2_6B_FREE = { id: 'liquid/lfm-2.5-2.6b:free', @@ -4999,18 +3247,6 @@ const LIQUID_LFM_2_5_2_6B_FREE = { }, context_window: 65536, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const LIQUID_LFM_2_5_24B_A2B = { @@ -5036,18 +3272,6 @@ const LIQUID_LFM_2_5_24B_A2B = { }, context_window: 128000, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.025, - }, - output: { - normal: 0.7, - }, - }, - image: 0, - }, } as const const MANCER_WEAVER = { id: 'mancer/weaver', @@ -5071,18 +3295,6 @@ const MANCER_WEAVER = { }, context_window: 8000, max_output_tokens: 6000, - pricing: { - text: { - input: { - normal: 0.4, - cached: 0, - }, - output: { - normal: 0.75, - }, - }, - image: 0, - }, } as const const MEITUAN_LONGCAT_2_0 = { id: 'meituan/longcat-2.0', @@ -5105,18 +3317,6 @@ const MEITUAN_LONGCAT_2_0 = { }, context_window: 1048756, max_output_tokens: 262144, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.006, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: BUDGET_OFF_HIGH, } as const const META_LLAMA_LLAMA_3_1_70B_INSTRUCT = { @@ -5140,18 +3340,6 @@ const META_LLAMA_LLAMA_3_1_70B_INSTRUCT = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.4, - cached: 0, - }, - output: { - normal: 0.4, - }, - }, - image: 0, - }, } as const const META_LLAMA_LLAMA_3_1_8B_INSTRUCT = { id: 'meta-llama/llama-3.1-8b-instruct', @@ -5176,18 +3364,6 @@ const META_LLAMA_LLAMA_3_1_8B_INSTRUCT = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.025, - }, - output: { - normal: 0.08, - }, - }, - image: 0, - }, } as const const META_LLAMA_LLAMA_3_2_1B_INSTRUCT = { id: 'meta-llama/llama-3.2-1b-instruct', @@ -5208,18 +3384,6 @@ const META_LLAMA_LLAMA_3_2_1B_INSTRUCT = { }, context_window: 60000, max_output_tokens: 54000, - pricing: { - text: { - input: { - normal: 0.027, - cached: 0, - }, - output: { - normal: 0.201, - }, - }, - image: 0, - }, } as const const META_LLAMA_LLAMA_3_2_3B_INSTRUCT = { id: 'meta-llama/llama-3.2-3b-instruct', @@ -5242,18 +3406,6 @@ const META_LLAMA_LLAMA_3_2_3B_INSTRUCT = { }, context_window: 131072, max_output_tokens: 72000, - pricing: { - text: { - input: { - normal: 0.0509, - cached: 0, - }, - output: { - normal: 0.335, - }, - }, - image: 0, - }, } as const const META_LLAMA_LLAMA_3_3_70B_INSTRUCT = { id: 'meta-llama/llama-3.3-70b-instruct', @@ -5278,18 +3430,6 @@ const META_LLAMA_LLAMA_3_3_70B_INSTRUCT = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0, - }, - output: { - normal: 0.32, - }, - }, - image: 0, - }, } as const const META_LLAMA_LLAMA_4_MAVERICK = { id: 'meta-llama/llama-4-maverick', @@ -5314,18 +3454,6 @@ const META_LLAMA_LLAMA_4_MAVERICK = { }, context_window: 524288, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.1875, - cached: 0.05, - }, - output: { - normal: 0.6525, - }, - }, - image: 0, - }, } as const const META_LLAMA_LLAMA_4_SCOUT = { id: 'meta-llama/llama-4-scout', @@ -5348,18 +3476,6 @@ const META_LLAMA_LLAMA_4_SCOUT = { }, context_window: 1310720, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, } as const const META_LLAMA_LLAMA_GUARD_4_12B = { id: 'meta-llama/llama-guard-4-12b', @@ -5380,18 +3496,6 @@ const META_LLAMA_LLAMA_GUARD_4_12B = { }, context_window: 163840, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.18, - cached: 0, - }, - output: { - normal: 0.18, - }, - }, - image: 0, - }, } as const const META_MUSE_GLIMMER_30B = { id: 'meta/muse-glimmer-30b', @@ -5415,18 +3519,6 @@ const META_MUSE_GLIMMER_30B = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.04, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const const META_MUSE_SPARK_1_1 = { @@ -5446,18 +3538,6 @@ const META_MUSE_SPARK_1_1 = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.15, - }, - output: { - normal: 4.25, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_XHIGH, } as const const META_MUSE_SPARK_1_2 = { @@ -5477,18 +3557,6 @@ const META_MUSE_SPARK_1_2 = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.15, - }, - output: { - normal: 4.25, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_XHIGH, } as const const META_MUSE_SPARK_1_2_CONTRIBUTOR = { @@ -5508,18 +3576,6 @@ const META_MUSE_SPARK_1_2_CONTRIBUTOR = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.002, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_XHIGH, } as const const META_MUSE_SPARK_1_3 = { @@ -5539,18 +3595,6 @@ const META_MUSE_SPARK_1_3 = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.15, - }, - output: { - normal: 4.25, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_MAX, } as const const META_MUSE_SPARK_1_3_CONTRIBUTOR = { @@ -5570,18 +3614,6 @@ const META_MUSE_SPARK_1_3_CONTRIBUTOR = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.002, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_MAX, } as const const META_MUSE_SPARK_OPEN = { @@ -5608,18 +3640,6 @@ const META_MUSE_SPARK_OPEN = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.15, - }, - output: { - normal: 4.25, - }, - }, - image: 0, - }, } as const const META_MUSE_WONDER = { id: 'meta/muse-wonder', @@ -5638,18 +3658,6 @@ const META_MUSE_WONDER = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 4, - cached: 0.2, - }, - output: { - normal: 20, - }, - }, - image: 0, - }, } as const const MICROSOFT_PHI_4 = { id: 'microsoft/phi-4', @@ -5671,18 +3679,6 @@ const MICROSOFT_PHI_4 = { }, context_window: 16384, max_output_tokens: 14745, - pricing: { - text: { - input: { - normal: 0.07, - cached: 0, - }, - output: { - normal: 0.14, - }, - }, - image: 0, - }, } as const const MINIMAX_MINIMAX_01 = { id: 'minimax/minimax-01', @@ -5694,18 +3690,6 @@ const MINIMAX_MINIMAX_01 = { }, context_window: 1000192, max_output_tokens: 900172, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0, - }, - output: { - normal: 1.1, - }, - }, - image: 0, - }, } as const const MINIMAX_MINIMAX_M1 = { id: 'minimax/minimax-m1', @@ -5727,18 +3711,6 @@ const MINIMAX_MINIMAX_M1 = { }, context_window: 1000000, max_output_tokens: 40000, - pricing: { - text: { - input: { - normal: 0.55, - cached: 0, - }, - output: { - normal: 2.2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const MINIMAX_MINIMAX_M2 = { @@ -5764,18 +3736,6 @@ const MINIMAX_MINIMAX_M2 = { }, context_window: 204800, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.255, - cached: 0, - }, - output: { - normal: 1.02, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const MINIMAX_MINIMAX_M2_HER = { @@ -5788,18 +3748,6 @@ const MINIMAX_MINIMAX_M2_HER = { }, context_window: 65536, max_output_tokens: 2048, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.03, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, } as const const MINIMAX_MINIMAX_M2_1 = { id: 'minimax/minimax-m2.1', @@ -5818,18 +3766,6 @@ const MINIMAX_MINIMAX_M2_1 = { }, context_window: 204800, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.03, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const MINIMAX_MINIMAX_M2_5 = { @@ -5856,18 +3792,6 @@ const MINIMAX_MINIMAX_M2_5 = { }, context_window: 204800, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.27, - cached: 0.03, - }, - output: { - normal: 0.95, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const MINIMAX_MINIMAX_M2_7 = { @@ -5892,18 +3816,6 @@ const MINIMAX_MINIMAX_M2_7 = { }, context_window: 204800, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.06, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const MINIMAX_MINIMAX_M3 = { @@ -5930,18 +3842,6 @@ const MINIMAX_MINIMAX_M3 = { }, context_window: 1048576, max_output_tokens: 512000, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.06, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const MINIMAX_MINIMAX_M3_1_FLASH_FREE = { @@ -5961,18 +3861,6 @@ const MINIMAX_MINIMAX_M3_1_FLASH_FREE = { }, context_window: 1000000, max_output_tokens: 524288, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, } as const const MISTRALAI_CODESTRAL_2508 = { id: 'mistralai/codestral-2508', @@ -5994,18 +3882,6 @@ const MISTRALAI_CODESTRAL_2508 = { }, context_window: 256000, max_output_tokens: 204800, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.03, - }, - output: { - normal: 0.9, - }, - }, - image: 0, - }, } as const const MISTRALAI_CODESTRAL_2508_BATCH = { id: 'mistralai/codestral-2508:batch', @@ -6027,18 +3903,6 @@ const MISTRALAI_CODESTRAL_2508_BATCH = { }, context_window: 256000, max_output_tokens: 204800, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.015, - }, - output: { - normal: 0.45, - }, - }, - image: 0, - }, } as const const MISTRALAI_DEVSTRAL_2512 = { id: 'mistralai/devstral-2512', @@ -6060,18 +3924,6 @@ const MISTRALAI_DEVSTRAL_2512 = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.4, - cached: 0.04, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, } as const const MISTRALAI_MINISTRAL_14B_2512 = { id: 'mistralai/ministral-14b-2512', @@ -6093,18 +3945,6 @@ const MISTRALAI_MINISTRAL_14B_2512 = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0.02, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, } as const const MISTRALAI_MINISTRAL_3B_2512 = { id: 'mistralai/ministral-3b-2512', @@ -6126,18 +3966,6 @@ const MISTRALAI_MINISTRAL_3B_2512 = { }, context_window: 131072, max_output_tokens: 104857, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.1, - }, - }, - image: 0, - }, } as const const MISTRALAI_MINISTRAL_8B_2512 = { id: 'mistralai/ministral-8b-2512', @@ -6159,18 +3987,6 @@ const MISTRALAI_MINISTRAL_8B_2512 = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.015, - }, - output: { - normal: 0.15, - }, - }, - image: 0, - }, } as const const MISTRALAI_MINISTRAL_8B_2512_BATCH = { id: 'mistralai/ministral-8b-2512:batch', @@ -6192,18 +4008,6 @@ const MISTRALAI_MINISTRAL_8B_2512_BATCH = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.075, - cached: 0.0075, - }, - output: { - normal: 0.075, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_LARGE = { id: 'mistralai/mistral-large', @@ -6225,18 +4029,6 @@ const MISTRALAI_MISTRAL_LARGE = { }, context_window: 128000, max_output_tokens: 102400, - pricing: { - text: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_LARGE_2407 = { id: 'mistralai/mistral-large-2407', @@ -6258,18 +4050,6 @@ const MISTRALAI_MISTRAL_LARGE_2407 = { }, context_window: 131072, max_output_tokens: 104857, - pricing: { - text: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_LARGE_2512 = { id: 'mistralai/mistral-large-2512', @@ -6291,18 +4071,6 @@ const MISTRALAI_MISTRAL_LARGE_2512 = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0.05, - }, - output: { - normal: 1.5, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_LARGE_2512_BATCH = { id: 'mistralai/mistral-large-2512:batch', @@ -6324,18 +4092,6 @@ const MISTRALAI_MISTRAL_LARGE_2512_BATCH = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.025, - }, - output: { - normal: 0.75, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_LARGE_4_0 = { id: 'mistralai/mistral-large-4-0', @@ -6360,18 +4116,6 @@ const MISTRALAI_MISTRAL_LARGE_4_0 = { }, context_window: 1048576, max_output_tokens: 262144, - pricing: { - text: { - input: { - normal: 1.36, - cached: 0.14, - }, - output: { - normal: 4.18, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_MEDIUM_3 = { id: 'mistralai/mistral-medium-3', @@ -6393,18 +4137,6 @@ const MISTRALAI_MISTRAL_MEDIUM_3 = { }, context_window: 131072, max_output_tokens: 104857, - pricing: { - text: { - input: { - normal: 0.4, - cached: 0.04, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_MEDIUM_3_5 = { id: 'mistralai/mistral-medium-3-5', @@ -6427,18 +4159,6 @@ const MISTRALAI_MISTRAL_MEDIUM_3_5 = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 1.5, - cached: 0, - }, - output: { - normal: 7.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_HIGH, } as const const MISTRALAI_MISTRAL_MEDIUM_3_5_BATCH = { @@ -6462,18 +4182,6 @@ const MISTRALAI_MISTRAL_MEDIUM_3_5_BATCH = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.75, - cached: 0, - }, - output: { - normal: 3.75, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_HIGH, } as const const MISTRALAI_MISTRAL_MEDIUM_3_1 = { @@ -6496,18 +4204,6 @@ const MISTRALAI_MISTRAL_MEDIUM_3_1 = { }, context_window: 131072, max_output_tokens: 104857, - pricing: { - text: { - input: { - normal: 0.4, - cached: 0.04, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_MEDIUM_3_1_BATCH = { id: 'mistralai/mistral-medium-3.1:batch', @@ -6529,18 +4225,6 @@ const MISTRALAI_MISTRAL_MEDIUM_3_1_BATCH = { }, context_window: 131072, max_output_tokens: 104857, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0.02, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_NEMO = { id: 'mistralai/mistral-nemo', @@ -6565,18 +4249,6 @@ const MISTRALAI_MISTRAL_NEMO = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.029, - cached: 0, - }, - output: { - normal: 0.03, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_SABA = { id: 'mistralai/mistral-saba', @@ -6598,18 +4270,6 @@ const MISTRALAI_MISTRAL_SABA = { }, context_window: 32768, max_output_tokens: 26214, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0.02, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_SMALL_24B_INSTRUCT_2501 = { id: 'mistralai/mistral-small-24b-instruct-2501', @@ -6631,18 +4291,6 @@ const MISTRALAI_MISTRAL_SMALL_24B_INSTRUCT_2501 = { }, context_window: 32768, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0, - }, - output: { - normal: 0.08, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_SMALL_2603 = { id: 'mistralai/mistral-small-2603', @@ -6665,18 +4313,6 @@ const MISTRALAI_MISTRAL_SMALL_2603 = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.015, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_HIGH, } as const const MISTRALAI_MISTRAL_SMALL_2603_BATCH = { @@ -6700,18 +4336,6 @@ const MISTRALAI_MISTRAL_SMALL_2603_BATCH = { }, context_window: 262144, max_output_tokens: 209715, - pricing: { - text: { - input: { - normal: 0.075, - cached: 0.0075, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_HIGH, } as const const MISTRALAI_MISTRAL_SMALL_3_1_24B_INSTRUCT = { @@ -6736,18 +4360,6 @@ const MISTRALAI_MISTRAL_SMALL_3_1_24B_INSTRUCT = { }, context_window: 128000, max_output_tokens: 102400, - pricing: { - text: { - input: { - normal: 0.351, - cached: 0, - }, - output: { - normal: 0.555, - }, - }, - image: 0, - }, } as const const MISTRALAI_MISTRAL_SMALL_3_2_24B_INSTRUCT = { id: 'mistralai/mistral-small-3.2-24b-instruct', @@ -6772,18 +4384,6 @@ const MISTRALAI_MISTRAL_SMALL_3_2_24B_INSTRUCT = { }, context_window: 256000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.09375, - cached: 0, - }, - output: { - normal: 0.25, - }, - }, - image: 0, - }, } as const const MISTRALAI_MIXTRAL_8X22B_INSTRUCT = { id: 'mistralai/mixtral-8x22b-instruct', @@ -6805,18 +4405,6 @@ const MISTRALAI_MIXTRAL_8X22B_INSTRUCT = { }, context_window: 65536, max_output_tokens: 52428, - pricing: { - text: { - input: { - normal: 2, - cached: 0.2, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, } as const const MISTRALAI_VOXTRAL_SMALL_24B_2507 = { id: 'mistralai/voxtral-small-24b-2507', @@ -6838,18 +4426,6 @@ const MISTRALAI_VOXTRAL_SMALL_24B_2507 = { }, context_window: 32768, max_output_tokens: 26214, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, } as const const MOONDREAM_MOONDREAM3_PREVIEW = { id: 'moondream/moondream3-preview', @@ -6867,18 +4443,6 @@ const MOONDREAM_MOONDREAM3_PREVIEW = { }, context_window: 32000, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, } as const const MOONDREAM_MOONDREAM3_1_9B_A2B = { id: 'moondream/moondream3.1-9b-a2b', @@ -6896,18 +4460,6 @@ const MOONDREAM_MOONDREAM3_1_9B_A2B = { }, context_window: 32000, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, } as const const MOONSHOTAI_KIMI_K2 = { id: 'moonshotai/kimi-k2', @@ -6928,18 +4480,6 @@ const MOONSHOTAI_KIMI_K2 = { }, context_window: 131072, max_output_tokens: 98304, - pricing: { - text: { - input: { - normal: 0.57, - cached: 0, - }, - output: { - normal: 2.3, - }, - }, - image: 0, - }, } as const const MOONSHOTAI_KIMI_K2_0905 = { id: 'moonshotai/kimi-k2-0905', @@ -6961,18 +4501,6 @@ const MOONSHOTAI_KIMI_K2_0905 = { }, context_window: 262144, max_output_tokens: 98304, - pricing: { - text: { - input: { - normal: 0.6, - cached: 0, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, } as const const MOONSHOTAI_KIMI_K2_THINKING = { id: 'moonshotai/kimi-k2-thinking', @@ -6997,18 +4525,6 @@ const MOONSHOTAI_KIMI_K2_THINKING = { }, context_window: 262144, max_output_tokens: 98304, - pricing: { - text: { - input: { - normal: 0.6, - cached: 0.15, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const MOONSHOTAI_KIMI_K2_5 = { @@ -7035,18 +4551,6 @@ const MOONSHOTAI_KIMI_K2_5 = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0.15, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const MOONSHOTAI_KIMI_K2_6 = { @@ -7074,18 +4578,6 @@ const MOONSHOTAI_KIMI_K2_6 = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.65, - cached: 0.15, - }, - output: { - normal: 3.41, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const MOONSHOTAI_KIMI_K2_7_CODE = { @@ -7113,18 +4605,6 @@ const MOONSHOTAI_KIMI_K2_7_CODE = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.6712, - cached: 0.18, - }, - output: { - normal: 3.35, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const MOONSHOTAI_KIMI_K3 = { @@ -7151,18 +4631,6 @@ const MOONSHOTAI_KIMI_K3 = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.239, - cached: 0.21, - }, - output: { - normal: 11.936, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH_MAX, } as const const MOONSHOTAI_KIMI_K3_BATCH = { @@ -7187,18 +4655,6 @@ const MOONSHOTAI_KIMI_K3_BATCH = { }, context_window: 1048576, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 2.28, - cached: 0.228, - }, - output: { - normal: 11.4, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const MORPH_MORPH_V3_FAST = { @@ -7218,18 +4674,6 @@ const MORPH_MORPH_V3_FAST = { }, context_window: 262144, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.8, - cached: 0, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, } as const const MORPH_MORPH_V3_LARGE = { id: 'morph/morph-v3-large', @@ -7248,18 +4692,6 @@ const MORPH_MORPH_V3_LARGE = { }, context_window: 262144, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.9, - cached: 0, - }, - output: { - normal: 1.9, - }, - }, - image: 0, - }, } as const const NEX_AGI_NEX_N2_5_MINI = { id: 'nex-agi/nex-n2.5-mini', @@ -7278,18 +4710,6 @@ const NEX_AGI_NEX_N2_5_MINI = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.025, - cached: 0.0025, - }, - output: { - normal: 0.1, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MEDIUM_HIGH, } as const const NEX_AGI_NEX_N2_5_PRO = { @@ -7310,18 +4730,6 @@ const NEX_AGI_NEX_N2_5_PRO = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.075, - cached: 0.015, - }, - output: { - normal: 0.25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MEDIUM_HIGH, } as const const NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B = { @@ -7344,18 +4752,6 @@ const NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 1, - cached: 0, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, } as const const NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B = { id: 'nousresearch/hermes-3-llama-3.1-70b', @@ -7376,18 +4772,6 @@ const NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.7, - cached: 0, - }, - output: { - normal: 0.7, - }, - }, - image: 0, - }, } as const const NOUSRESEARCH_HERMES_4_405B = { id: 'nousresearch/hermes-4-405b', @@ -7409,18 +4793,6 @@ const NOUSRESEARCH_HERMES_4_405B = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 1, - cached: 0, - }, - output: { - normal: 3, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const NVIDIA_NEMOTRON_3_NANO_30B_A3B = { @@ -7447,18 +4819,6 @@ const NVIDIA_NEMOTRON_3_NANO_30B_A3B = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.06, - cached: 0, - }, - output: { - normal: 0.24, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const NVIDIA_NEMOTRON_3_NANO_OMNI_30B_A3B_REASONING_FREE = { @@ -7478,18 +4838,6 @@ const NVIDIA_NEMOTRON_3_NANO_OMNI_30B_A3B_REASONING_FREE = { }, context_window: 256000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: BUDGET_OFF_HIGH, } as const const NVIDIA_NEMOTRON_3_SUPER_120B_A12B = { @@ -7516,18 +4864,6 @@ const NVIDIA_NEMOTRON_3_SUPER_120B_A12B = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.15, - }, - output: { - normal: 2.4, - }, - }, - image: 0, - }, reasoning: BUDGET_NONE_LOW_MEDIUM, } as const const NVIDIA_NEMOTRON_3_SUPER_120B_A12B_FREE = { @@ -7548,18 +4884,6 @@ const NVIDIA_NEMOTRON_3_SUPER_120B_A12B_FREE = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: BUDGET_NONE_LOW_MEDIUM, } as const const NVIDIA_NEMOTRON_3_ULTRA_550B_A55B = { @@ -7584,18 +4908,6 @@ const NVIDIA_NEMOTRON_3_ULTRA_550B_A55B = { }, context_window: 262144, max_output_tokens: 182520, - pricing: { - text: { - input: { - normal: 0.6, - cached: 0.12, - }, - output: { - normal: 2.4, - }, - }, - image: 0, - }, reasoning: BUDGET_NONE_MEDIUM_HIGH, } as const const NVIDIA_NEMOTRON_3_ULTRA_550B_A55B_FREE = { @@ -7615,18 +4927,6 @@ const NVIDIA_NEMOTRON_3_ULTRA_550B_A55B_FREE = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: BUDGET_NONE_MEDIUM_HIGH, } as const const NVIDIA_NEMOTRON_3_5_CONTENT_SAFETY = { @@ -7649,18 +4949,6 @@ const NVIDIA_NEMOTRON_3_5_CONTENT_SAFETY = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const NVIDIA_NEMOTRON_3_5_CONTENT_SAFETY_FREE = { @@ -7679,18 +4967,6 @@ const NVIDIA_NEMOTRON_3_5_CONTENT_SAFETY_FREE = { }, context_window: 128000, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const NVIDIA_NEMOTRON_3_5_LIGHTNING = { @@ -7717,18 +4993,6 @@ const NVIDIA_NEMOTRON_3_5_LIGHTNING = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.07, - cached: 0.04, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const NVIDIA_NEMOTRON_3_5_LIGHTNING_FREE = { @@ -7748,18 +5012,6 @@ const NVIDIA_NEMOTRON_3_5_LIGHTNING_FREE = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const NVIDIA_NEMOTRON_3_5_SUPER_VL = { @@ -7786,18 +5038,6 @@ const NVIDIA_NEMOTRON_3_5_SUPER_VL = { }, context_window: 1048576, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.02, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, } as const const NVIDIA_NEMOTRON_3_5_SUPER_VL_FREE = { id: 'nvidia/nemotron-3.5-super-vl:free', @@ -7817,18 +5057,6 @@ const NVIDIA_NEMOTRON_3_5_SUPER_VL_FREE = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_3_5_TURBO = { id: 'openai/gpt-3.5-turbo', @@ -7853,18 +5081,6 @@ const OPENAI_GPT_3_5_TURBO = { }, context_window: 16385, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0, - }, - output: { - normal: 1.5, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_3_5_TURBO_0613 = { id: 'openai/gpt-3.5-turbo-0613', @@ -7889,18 +5105,6 @@ const OPENAI_GPT_3_5_TURBO_0613 = { }, context_window: 4095, max_output_tokens: 3685, - pricing: { - text: { - input: { - normal: 1, - cached: 0, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_3_5_TURBO_16K = { id: 'openai/gpt-3.5-turbo-16k', @@ -7926,18 +5130,6 @@ const OPENAI_GPT_3_5_TURBO_16K = { }, context_window: 16385, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 3, - cached: 0, - }, - output: { - normal: 4, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_3_5_TURBO_INSTRUCT = { id: 'openai/gpt-3.5-turbo-instruct', @@ -7961,18 +5153,6 @@ const OPENAI_GPT_3_5_TURBO_INSTRUCT = { }, context_window: 4095, max_output_tokens: 3685, - pricing: { - text: { - input: { - normal: 1.5, - cached: 0, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_3_5_TURBO_BATCH = { id: 'openai/gpt-3.5-turbo:batch', @@ -7997,18 +5177,6 @@ const OPENAI_GPT_3_5_TURBO_BATCH = { }, context_window: 16385, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0, - }, - output: { - normal: 0.75, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4 = { id: 'openai/gpt-4', @@ -8034,18 +5202,6 @@ const OPENAI_GPT_4 = { }, context_window: 8191, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 30, - cached: 0, - }, - output: { - normal: 60, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4_TURBO = { id: 'openai/gpt-4-turbo', @@ -8070,18 +5226,6 @@ const OPENAI_GPT_4_TURBO = { }, context_window: 128000, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 10, - cached: 0, - }, - output: { - normal: 30, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4_TURBO_BATCH = { id: 'openai/gpt-4-turbo:batch', @@ -8106,18 +5250,6 @@ const OPENAI_GPT_4_TURBO_BATCH = { }, context_window: 128000, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 5, - cached: 0, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4_1 = { id: 'openai/gpt-4.1', @@ -8137,18 +5269,6 @@ const OPENAI_GPT_4_1 = { }, context_window: 1047576, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 8, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4_1_MINI = { id: 'openai/gpt-4.1-mini', @@ -8168,18 +5288,6 @@ const OPENAI_GPT_4_1_MINI = { }, context_window: 1047576, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.4, - cached: 0.1, - }, - output: { - normal: 1.6, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4_1_MINI_BATCH = { id: 'openai/gpt-4.1-mini:batch', @@ -8198,18 +5306,6 @@ const OPENAI_GPT_4_1_MINI_BATCH = { }, context_window: 1047576, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0.05, - }, - output: { - normal: 0.8, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4_1_NANO = { id: 'openai/gpt-4.1-nano', @@ -8229,18 +5325,6 @@ const OPENAI_GPT_4_1_NANO = { }, context_window: 1047576, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.025, - }, - output: { - normal: 0.4, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4_1_NANO_BATCH = { id: 'openai/gpt-4.1-nano:batch', @@ -8259,18 +5343,6 @@ const OPENAI_GPT_4_1_NANO_BATCH = { }, context_window: 1047576, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.0125, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4_1_BATCH = { id: 'openai/gpt-4.1:batch', @@ -8289,18 +5361,6 @@ const OPENAI_GPT_4_1_BATCH = { }, context_window: 1047576, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 1, - cached: 0.25, - }, - output: { - normal: 4, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4O = { id: 'openai/gpt-4o', @@ -8326,18 +5386,6 @@ const OPENAI_GPT_4O = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 2.5, - cached: 1.25, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4O_2024_05_13 = { id: 'openai/gpt-4o-2024-05-13', @@ -8363,18 +5411,6 @@ const OPENAI_GPT_4O_2024_05_13 = { }, context_window: 128000, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 5, - cached: 0, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4O_2024_08_06 = { id: 'openai/gpt-4o-2024-08-06', @@ -8400,18 +5436,6 @@ const OPENAI_GPT_4O_2024_08_06 = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 2.5, - cached: 1.25, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4O_2024_11_20 = { id: 'openai/gpt-4o-2024-11-20', @@ -8436,18 +5460,6 @@ const OPENAI_GPT_4O_2024_11_20 = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 2.5, - cached: 1.25, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4O_MINI = { id: 'openai/gpt-4o-mini', @@ -8473,18 +5485,6 @@ const OPENAI_GPT_4O_MINI = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.075, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4O_MINI_2024_07_18 = { id: 'openai/gpt-4o-mini-2024-07-18', @@ -8509,18 +5509,6 @@ const OPENAI_GPT_4O_MINI_2024_07_18 = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.075, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4O_MINI_BATCH = { id: 'openai/gpt-4o-mini:batch', @@ -8545,18 +5533,6 @@ const OPENAI_GPT_4O_MINI_BATCH = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.075, - cached: 0.0375, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_4O_BATCH = { id: 'openai/gpt-4o:batch', @@ -8581,18 +5557,6 @@ const OPENAI_GPT_4O_BATCH = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.625, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_5 = { id: 'openai/gpt-5', @@ -8611,18 +5575,6 @@ const OPENAI_GPT_5 = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_GPT_5_IMAGE = { @@ -8644,18 +5596,6 @@ const OPENAI_GPT_5_IMAGE = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 10, - cached: 1.25, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_GPT_5_IMAGE_MINI = { @@ -8677,18 +5617,6 @@ const OPENAI_GPT_5_IMAGE_MINI = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0.25, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_GPT_5_MINI = { @@ -8708,18 +5636,6 @@ const OPENAI_GPT_5_MINI = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.025, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_GPT_5_MINI_BATCH = { @@ -8738,18 +5654,6 @@ const OPENAI_GPT_5_MINI_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.125, - cached: 0.0125, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_GPT_5_NANO = { @@ -8769,18 +5673,6 @@ const OPENAI_GPT_5_NANO = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.005, - }, - output: { - normal: 0.4, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_GPT_5_NANO_BATCH = { @@ -8799,18 +5691,6 @@ const OPENAI_GPT_5_NANO_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.025, - cached: 0.0025, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_GPT_5_PRO = { @@ -8829,18 +5709,6 @@ const OPENAI_GPT_5_PRO = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 15, - cached: 0, - }, - output: { - normal: 120, - }, - }, - image: 0, - }, reasoning: EFFORT_HIGH, } as const const OPENAI_GPT_5_PRO_BATCH = { @@ -8859,18 +5727,6 @@ const OPENAI_GPT_5_PRO_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 7.5, - cached: 0, - }, - output: { - normal: 60, - }, - }, - image: 0, - }, reasoning: EFFORT_HIGH, } as const const OPENAI_GPT_5_BATCH = { @@ -8889,18 +5745,6 @@ const OPENAI_GPT_5_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.625, - cached: 0.0625, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_GPT_5_1 = { @@ -8920,18 +5764,6 @@ const OPENAI_GPT_5_1 = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const const OPENAI_GPT_5_1_CODEX = { @@ -8950,18 +5782,6 @@ const OPENAI_GPT_5_1_CODEX = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.13, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const OPENAI_GPT_5_1_CODEX_MAX = { @@ -8980,18 +5800,6 @@ const OPENAI_GPT_5_1_CODEX_MAX = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_1_CODEX_MINI = { @@ -9010,18 +5818,6 @@ const OPENAI_GPT_5_1_CODEX_MINI = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.03, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const const OPENAI_GPT_5_1_BATCH = { @@ -9040,18 +5836,6 @@ const OPENAI_GPT_5_1_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.625, - cached: 0.0625, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const const OPENAI_GPT_5_2 = { @@ -9071,18 +5855,6 @@ const OPENAI_GPT_5_2 = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.75, - cached: 0.175, - }, - output: { - normal: 14, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_2_CHAT = { @@ -9095,18 +5867,6 @@ const OPENAI_GPT_5_2_CHAT = { }, context_window: 128000, max_output_tokens: 32000, - pricing: { - text: { - input: { - normal: 1.75, - cached: 0.175, - }, - output: { - normal: 14, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_5_2_CODEX = { id: 'openai/gpt-5.2-codex', @@ -9124,18 +5884,6 @@ const OPENAI_GPT_5_2_CODEX = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.75, - cached: 0.175, - }, - output: { - normal: 14, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_2_PRO = { @@ -9154,18 +5902,6 @@ const OPENAI_GPT_5_2_PRO = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 21, - cached: 0, - }, - output: { - normal: 168, - }, - }, - image: 0, - }, reasoning: EFFORT_MEDIUM_TO_XHIGH, } as const const OPENAI_GPT_5_2_PRO_BATCH = { @@ -9184,18 +5920,6 @@ const OPENAI_GPT_5_2_PRO_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 10.5, - cached: 0, - }, - output: { - normal: 84, - }, - }, - image: 0, - }, reasoning: EFFORT_MEDIUM_TO_XHIGH, } as const const OPENAI_GPT_5_2_BATCH = { @@ -9214,18 +5938,6 @@ const OPENAI_GPT_5_2_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.875, - cached: 0.0875, - }, - output: { - normal: 7, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_3_CODEX = { @@ -9245,18 +5957,6 @@ const OPENAI_GPT_5_3_CODEX = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.75, - cached: 0.175, - }, - output: { - normal: 14, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_4 = { @@ -9276,18 +5976,6 @@ const OPENAI_GPT_5_4 = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0.25, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_4_IMAGE_2 = { @@ -9307,18 +5995,6 @@ const OPENAI_GPT_5_4_IMAGE_2 = { }, context_window: 272000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 8, - cached: 2, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_4_MINI = { @@ -9338,18 +6014,6 @@ const OPENAI_GPT_5_4_MINI = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.75, - cached: 0.075, - }, - output: { - normal: 4.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_4_MINI_BATCH = { @@ -9368,18 +6032,6 @@ const OPENAI_GPT_5_4_MINI_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.375, - cached: 0.0375, - }, - output: { - normal: 2.25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_4_NANO = { @@ -9399,18 +6051,6 @@ const OPENAI_GPT_5_4_NANO = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0.02, - }, - output: { - normal: 1.25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_4_NANO_BATCH = { @@ -9429,18 +6069,6 @@ const OPENAI_GPT_5_4_NANO_BATCH = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.625, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_4_PRO = { @@ -9460,18 +6088,6 @@ const OPENAI_GPT_5_4_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 30, - cached: 0, - }, - output: { - normal: 180, - }, - }, - image: 0, - }, reasoning: EFFORT_MEDIUM_TO_XHIGH, } as const const OPENAI_GPT_5_4_PRO_BATCH = { @@ -9490,18 +6106,6 @@ const OPENAI_GPT_5_4_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 15, - cached: 0, - }, - output: { - normal: 90, - }, - }, - image: 0, - }, reasoning: EFFORT_MEDIUM_TO_XHIGH, } as const const OPENAI_GPT_5_4_BATCH = { @@ -9520,18 +6124,6 @@ const OPENAI_GPT_5_4_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.125, - }, - output: { - normal: 7.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_5 = { @@ -9551,18 +6143,6 @@ const OPENAI_GPT_5_5 = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 30, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_5_PRO = { @@ -9581,18 +6161,6 @@ const OPENAI_GPT_5_5_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 30, - cached: 0, - }, - output: { - normal: 180, - }, - }, - image: 0, - }, reasoning: EFFORT_MEDIUM_TO_XHIGH, } as const const OPENAI_GPT_5_5_PRO_BATCH = { @@ -9611,18 +6179,6 @@ const OPENAI_GPT_5_5_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 15, - cached: 0, - }, - output: { - normal: 90, - }, - }, - image: 0, - }, reasoning: EFFORT_MEDIUM_TO_XHIGH, } as const const OPENAI_GPT_5_5_BATCH = { @@ -9641,18 +6197,6 @@ const OPENAI_GPT_5_5_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0.25, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_XHIGH, } as const const OPENAI_GPT_5_6_LUNA = { @@ -9672,18 +6216,6 @@ const OPENAI_GPT_5_6_LUNA = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0.27, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_LUNA_PRO = { @@ -9703,18 +6235,6 @@ const OPENAI_GPT_5_6_LUNA_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0.27, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_LUNA_PRO_BATCH = { @@ -9733,18 +6253,6 @@ const OPENAI_GPT_5_6_LUNA_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_LUNA_BATCH = { @@ -9763,18 +6271,6 @@ const OPENAI_GPT_5_6_LUNA_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.01, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_SOL = { @@ -9794,18 +6290,6 @@ const OPENAI_GPT_5_6_SOL = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 4, - cached: 5.4, - }, - output: { - normal: 20, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_SOL_PRO = { @@ -9825,18 +6309,6 @@ const OPENAI_GPT_5_6_SOL_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 4, - cached: 5.4, - }, - output: { - normal: 20, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_SOL_PRO_BATCH = { @@ -9855,18 +6327,6 @@ const OPENAI_GPT_5_6_SOL_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.7, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_SOL_BATCH = { @@ -9885,18 +6345,6 @@ const OPENAI_GPT_5_6_SOL_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.7, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_TERRA = { @@ -9916,18 +6364,6 @@ const OPENAI_GPT_5_6_TERRA = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.7, - }, - output: { - normal: 12, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_TERRA_PRO = { @@ -9947,18 +6383,6 @@ const OPENAI_GPT_5_6_TERRA_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.7, - }, - output: { - normal: 12, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_TERRA_PRO_BATCH = { @@ -9977,18 +6401,6 @@ const OPENAI_GPT_5_6_TERRA_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 0.1, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_5_6_TERRA_BATCH = { @@ -10007,18 +6419,6 @@ const OPENAI_GPT_5_6_TERRA_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 0.1, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_ASTRA = { @@ -10038,18 +6438,6 @@ const OPENAI_GPT_6_ASTRA = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 10, - cached: 13.5, - }, - output: { - normal: 50, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const OPENAI_GPT_6_ASTRA_PRO = { @@ -10069,18 +6457,6 @@ const OPENAI_GPT_6_ASTRA_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 10, - cached: 13.5, - }, - output: { - normal: 50, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const OPENAI_GPT_6_ASTRA_PRO_BATCH = { @@ -10099,18 +6475,6 @@ const OPENAI_GPT_6_ASTRA_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.75, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const OPENAI_GPT_6_ASTRA_BATCH = { @@ -10129,18 +6493,6 @@ const OPENAI_GPT_6_ASTRA_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 6.75, - }, - output: { - normal: 25, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const OPENAI_GPT_6_LUNA = { @@ -10160,18 +6512,6 @@ const OPENAI_GPT_6_LUNA = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.135, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_LUNA_PRO = { @@ -10191,18 +6531,6 @@ const OPENAI_GPT_6_LUNA_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.135, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_LUNA_PRO_BATCH = { @@ -10221,18 +6549,6 @@ const OPENAI_GPT_6_LUNA_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.0675, - }, - output: { - normal: 0.25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_LUNA_BATCH = { @@ -10251,18 +6567,6 @@ const OPENAI_GPT_6_LUNA_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.0675, - }, - output: { - normal: 0.25, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_SOL = { @@ -10282,18 +6586,6 @@ const OPENAI_GPT_6_SOL = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.7, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_SOL_PRO = { @@ -10313,18 +6605,6 @@ const OPENAI_GPT_6_SOL_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.7, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_SOL_PRO_BATCH = { @@ -10343,18 +6623,6 @@ const OPENAI_GPT_6_SOL_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 1.35, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_SOL_BATCH = { @@ -10373,18 +6641,6 @@ const OPENAI_GPT_6_SOL_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 1.35, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_MAX, } as const const OPENAI_GPT_6_1_SOL = { @@ -10404,18 +6660,6 @@ const OPENAI_GPT_6_1_SOL = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.6, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const OPENAI_GPT_6_1_SOL_PRO = { @@ -10435,18 +6679,6 @@ const OPENAI_GPT_6_1_SOL_PRO = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 2.6, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_MAX, } as const const OPENAI_GPT_6_1_SOL_PRO_BATCH = { @@ -10465,18 +6697,6 @@ const OPENAI_GPT_6_1_SOL_PRO_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 1.3, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_6_1_SOL_BATCH = { id: 'openai/gpt-6.1-sol:batch', @@ -10494,18 +6714,6 @@ const OPENAI_GPT_6_1_SOL_BATCH = { }, context_window: 1050000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 1.3, - }, - output: { - normal: 5, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_AUDIO = { id: 'openai/gpt-audio', @@ -10530,18 +6738,6 @@ const OPENAI_GPT_AUDIO = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0, - }, - output: { - normal: 10, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_AUDIO_MINI = { id: 'openai/gpt-audio-mini', @@ -10566,18 +6762,6 @@ const OPENAI_GPT_AUDIO_MINI = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.6, - cached: 0, - }, - output: { - normal: 2.4, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_CHAT_LATEST = { id: 'openai/gpt-chat-latest', @@ -10589,18 +6773,6 @@ const OPENAI_GPT_CHAT_LATEST = { }, context_window: 400000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 30, - }, - }, - image: 0, - }, } as const const OPENAI_GPT_OSS_120B = { id: 'openai/gpt-oss-120b', @@ -10626,18 +6798,6 @@ const OPENAI_GPT_OSS_120B = { }, context_window: 131072, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.075, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const OPENAI_GPT_OSS_120B_BATCH = { @@ -10662,18 +6822,6 @@ const OPENAI_GPT_OSS_120B_BATCH = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.0296, - cached: 0, - }, - output: { - normal: 0.136, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const OPENAI_GPT_OSS_20B = { @@ -10700,18 +6848,6 @@ const OPENAI_GPT_OSS_20B = { }, context_window: 131072, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.018, - cached: 0.009, - }, - output: { - normal: 0.09, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const OPENAI_GPT_OSS_20B_BATCH = { @@ -10736,18 +6872,6 @@ const OPENAI_GPT_OSS_20B_BATCH = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.024, - cached: 0, - }, - output: { - normal: 0.112, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const OPENAI_GPT_OSS_SAFEGUARD_20B = { @@ -10769,18 +6893,6 @@ const OPENAI_GPT_OSS_SAFEGUARD_20B = { }, context_window: 131072, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.075, - cached: 0.0375, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const OPENAI_O1 = { @@ -10799,18 +6911,6 @@ const OPENAI_O1 = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 15, - cached: 7.5, - }, - output: { - normal: 60, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const OPENAI_O1_PRO = { @@ -10823,18 +6923,6 @@ const OPENAI_O1_PRO = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 150, - cached: 0, - }, - output: { - normal: 600, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const OPENAI_O3 = { @@ -10853,18 +6941,6 @@ const OPENAI_O3 = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 8, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const OPENAI_O3_MINI = { @@ -10883,18 +6959,6 @@ const OPENAI_O3_MINI = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 1.1, - cached: 0.55, - }, - output: { - normal: 4.4, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const OPENAI_O3_MINI_HIGH = { @@ -10913,18 +6977,6 @@ const OPENAI_O3_MINI_HIGH = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 1.1, - cached: 0.55, - }, - output: { - normal: 4.4, - }, - }, - image: 0, - }, reasoning: EFFORT_HIGH, } as const const OPENAI_O3_MINI_BATCH = { @@ -10943,18 +6995,6 @@ const OPENAI_O3_MINI_BATCH = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 0.55, - cached: 0.275, - }, - output: { - normal: 2.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_O3_PRO = { @@ -10973,18 +7013,6 @@ const OPENAI_O3_PRO = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 20, - cached: 0, - }, - output: { - normal: 80, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const OPENAI_O3_BATCH = { @@ -11003,18 +7031,6 @@ const OPENAI_O3_BATCH = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 1, - cached: 0.25, - }, - output: { - normal: 4, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const OPENAI_O4_MINI = { @@ -11033,18 +7049,6 @@ const OPENAI_O4_MINI = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 1.1, - cached: 0.275, - }, - output: { - normal: 4.4, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const OPENAI_O4_MINI_HIGH = { @@ -11063,18 +7067,6 @@ const OPENAI_O4_MINI_HIGH = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 1.1, - cached: 0.275, - }, - output: { - normal: 4.4, - }, - }, - image: 0, - }, reasoning: EFFORT_HIGH, } as const const OPENAI_O4_MINI_BATCH = { @@ -11093,18 +7085,6 @@ const OPENAI_O4_MINI_BATCH = { }, context_window: 200000, max_output_tokens: 100000, - pricing: { - text: { - input: { - normal: 0.55, - cached: 0.1375, - }, - output: { - normal: 2.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const PERCEPTRON_PERCEPTRON_MK1 = { @@ -11124,18 +7104,6 @@ const PERCEPTRON_PERCEPTRON_MK1 = { }, context_window: 32768, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0, - }, - output: { - normal: 1.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const PERCEPTRON_PERCEPTRON_MK1_5 = { @@ -11156,18 +7124,6 @@ const PERCEPTRON_PERCEPTRON_MK1_5 = { }, context_window: 36864, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0, - }, - output: { - normal: 1.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH, } as const const PERPLEXITY_SONAR = { @@ -11186,18 +7142,6 @@ const PERPLEXITY_SONAR = { }, context_window: 127072, max_output_tokens: 114364, - pricing: { - text: { - input: { - normal: 1, - cached: 0, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, } as const const PERPLEXITY_SONAR_DEEP_RESEARCH = { id: 'perplexity/sonar-deep-research', @@ -11216,18 +7160,6 @@ const PERPLEXITY_SONAR_DEEP_RESEARCH = { }, context_window: 128000, max_output_tokens: 115200, - pricing: { - text: { - input: { - normal: 2, - cached: 0, - }, - output: { - normal: 8, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const PERPLEXITY_SONAR_PRO = { @@ -11246,18 +7178,6 @@ const PERPLEXITY_SONAR_PRO = { }, context_window: 200000, max_output_tokens: 8000, - pricing: { - text: { - input: { - normal: 3, - cached: 0, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, } as const const PERPLEXITY_SONAR_PRO_SEARCH = { id: 'perplexity/sonar-pro-search', @@ -11276,18 +7196,6 @@ const PERPLEXITY_SONAR_PRO_SEARCH = { }, context_window: 200000, max_output_tokens: 8000, - pricing: { - text: { - input: { - normal: 3, - cached: 0, - }, - output: { - normal: 15, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const PERPLEXITY_SONAR_REASONING_PRO = { @@ -11307,18 +7215,6 @@ const PERPLEXITY_SONAR_REASONING_PRO = { }, context_window: 128000, max_output_tokens: 115200, - pricing: { - text: { - input: { - normal: 2, - cached: 0, - }, - output: { - normal: 8, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const POOLSIDE_LAGUNA_S_2_1 = { @@ -11331,18 +7227,6 @@ const POOLSIDE_LAGUNA_S_2_1 = { }, context_window: 1048576, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.09, - cached: 0.009, - }, - output: { - normal: 0.18, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const POOLSIDE_LAGUNA_S_2_1_FREE = { @@ -11355,18 +7239,6 @@ const POOLSIDE_LAGUNA_S_2_1_FREE = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const POOLSIDE_LAGUNA_XS_2_1 = { @@ -11379,18 +7251,6 @@ const POOLSIDE_LAGUNA_XS_2_1 = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.06, - cached: 0.03, - }, - output: { - normal: 0.12, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const POOLSIDE_LAGUNA_XS_2_1_FREE = { @@ -11403,18 +7263,6 @@ const POOLSIDE_LAGUNA_XS_2_1_FREE = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const PRISM_ML_TERNARY_BONSAI_2_27B = { @@ -11440,18 +7288,6 @@ const PRISM_ML_TERNARY_BONSAI_2_27B = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.075, - cached: 0.0375, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MEDIUM_XHIGH, } as const const QWEN_QWEN_2_5_72B_INSTRUCT = { @@ -11475,18 +7311,6 @@ const QWEN_QWEN_2_5_72B_INSTRUCT = { }, context_window: 32768, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.36, - cached: 0, - }, - output: { - normal: 0.4, - }, - }, - image: 0, - }, } as const const QWEN_QWEN_2_5_7B_INSTRUCT = { id: 'qwen/qwen-2.5-7b-instruct', @@ -11508,18 +7332,6 @@ const QWEN_QWEN_2_5_7B_INSTRUCT = { }, context_window: 32768, max_output_tokens: 29491, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, } as const const QWEN_QWEN_2_5_CODER_32B_INSTRUCT = { id: 'qwen/qwen-2.5-coder-32b-instruct', @@ -11541,18 +7353,6 @@ const QWEN_QWEN_2_5_CODER_32B_INSTRUCT = { }, context_window: 32768, max_output_tokens: 29491, - pricing: { - text: { - input: { - normal: 0.66, - cached: 0, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, } as const const QWEN_QWEN_PLUS = { id: 'qwen/qwen-plus', @@ -11576,18 +7376,6 @@ const QWEN_QWEN_PLUS = { }, context_window: 1000000, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.26, - cached: 0.377, - }, - output: { - normal: 0.78, - }, - }, - image: 0, - }, } as const const QWEN_QWEN2_5_VL_72B_INSTRUCT = { id: 'qwen/qwen2.5-vl-72b-instruct', @@ -11611,18 +7399,6 @@ const QWEN_QWEN2_5_VL_72B_INSTRUCT = { }, context_window: 128000, max_output_tokens: 115200, - pricing: { - text: { - input: { - normal: 0.8, - cached: 0.4, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_14B = { id: 'qwen/qwen3-14b', @@ -11648,18 +7424,6 @@ const QWEN_QWEN3_14B = { }, context_window: 40960, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.12, - cached: 0, - }, - output: { - normal: 0.24, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_235B_A22B_2507 = { @@ -11685,18 +7449,6 @@ const QWEN_QWEN3_235B_A22B_2507 = { }, context_window: 262144, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.09, - cached: 0, - }, - output: { - normal: 0.55, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_235B_A22B_THINKING_2507 = { id: 'qwen/qwen3-235b-a22b-thinking-2507', @@ -11717,18 +7469,6 @@ const QWEN_QWEN3_235B_A22B_THINKING_2507 = { }, context_window: 128000, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.45, - cached: 0, - }, - output: { - normal: 3.5, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const QWEN_QWEN3_30B_A3B = { @@ -11753,18 +7493,6 @@ const QWEN_QWEN3_30B_A3B = { }, context_window: 40960, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.12, - cached: 0, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_30B_A3B_INSTRUCT_2507 = { @@ -11790,18 +7518,6 @@ const QWEN_QWEN3_30B_A3B_INSTRUCT_2507 = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.09, - cached: 0, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_32B = { id: 'qwen/qwen3-32b', @@ -11827,18 +7543,6 @@ const QWEN_QWEN3_32B = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.08, - cached: 0, - }, - output: { - normal: 0.28, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_CODER = { @@ -11864,18 +7568,6 @@ const QWEN_QWEN3_CODER = { }, context_window: 262144, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.1, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_CODER_30B_A3B_INSTRUCT = { id: 'qwen/qwen3-coder-30b-a3b-instruct', @@ -11895,18 +7587,6 @@ const QWEN_QWEN3_CODER_30B_A3B_INSTRUCT = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.07, - cached: 0, - }, - output: { - normal: 0.28, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_CODER_FLASH = { id: 'qwen/qwen3-coder-flash', @@ -11930,18 +7610,6 @@ const QWEN_QWEN3_CODER_FLASH = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.195, - cached: 0.28275, - }, - output: { - normal: 0.975, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_CODER_NEXT = { id: 'qwen/qwen3-coder-next', @@ -11966,18 +7634,6 @@ const QWEN_QWEN3_CODER_NEXT = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.12, - cached: 0.07, - }, - output: { - normal: 0.8, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_NEXT_80B_A3B_INSTRUCT = { id: 'qwen/qwen3-next-80b-a3b-instruct', @@ -12002,18 +7658,6 @@ const QWEN_QWEN3_NEXT_80B_A3B_INSTRUCT = { }, context_window: 262144, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.09, - cached: 0, - }, - output: { - normal: 1.1, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_NEXT_80B_A3B_THINKING = { id: 'qwen/qwen3-next-80b-a3b-thinking', @@ -12038,18 +7682,6 @@ const QWEN_QWEN3_NEXT_80B_A3B_THINKING = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const QWEN_QWEN3_VL_235B_A22B_INSTRUCT = { @@ -12075,18 +7707,6 @@ const QWEN_QWEN3_VL_235B_A22B_INSTRUCT = { }, context_window: 262144, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.21, - cached: 0.1, - }, - output: { - normal: 1.9, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_VL_30B_A3B_INSTRUCT = { id: 'qwen/qwen3-vl-30b-a3b-instruct', @@ -12111,18 +7731,6 @@ const QWEN_QWEN3_VL_30B_A3B_INSTRUCT = { }, context_window: 262144, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_VL_30B_A3B_THINKING = { id: 'qwen/qwen3-vl-30b-a3b-thinking', @@ -12142,18 +7750,6 @@ const QWEN_QWEN3_VL_30B_A3B_THINKING = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.29, - cached: 0, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const QWEN_QWEN3_VL_8B_INSTRUCT = { @@ -12179,18 +7775,6 @@ const QWEN_QWEN3_VL_8B_INSTRUCT = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.12, - }, - output: { - normal: 0.75, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_5_122B_A10B = { id: 'qwen/qwen3.5-122b-a10b', @@ -12216,18 +7800,6 @@ const QWEN_QWEN3_5_122B_A10B = { }, context_window: 262144, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.4, - cached: 0, - }, - output: { - normal: 3.2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_5_27B = { @@ -12254,18 +7826,6 @@ const QWEN_QWEN3_5_27B = { }, context_window: 262144, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.27, - cached: 0.27, - }, - output: { - normal: 2.16, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_5_35B_A3B = { @@ -12292,18 +7852,6 @@ const QWEN_QWEN3_5_35B_A3B = { }, context_window: 262144, max_output_tokens: 81920, - pricing: { - text: { - input: { - normal: 0.14, - cached: 0.05, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_5_397B_A17B = { @@ -12330,18 +7878,6 @@ const QWEN_QWEN3_5_397B_A17B = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.5, - cached: 0.3, - }, - output: { - normal: 3.6, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_5_9B = { @@ -12368,18 +7904,6 @@ const QWEN_QWEN3_5_9B = { }, context_window: 262144, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.08, - cached: 0.04, - }, - output: { - normal: 0.13, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_5_FLASH_02_23 = { @@ -12403,18 +7927,6 @@ const QWEN_QWEN3_5_FLASH_02_23 = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.065, - cached: 0, - }, - output: { - normal: 0.26, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_5_PLUS_02_15 = { @@ -12440,18 +7952,6 @@ const QWEN_QWEN3_5_PLUS_02_15 = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.26, - cached: 0, - }, - output: { - normal: 1.56, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_5_PLUS_20260420 = { @@ -12477,18 +7977,6 @@ const QWEN_QWEN3_5_PLUS_20260420 = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.375, - }, - output: { - normal: 1.8, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_6_27B = { @@ -12515,18 +8003,6 @@ const QWEN_QWEN3_6_27B = { }, context_window: 262144, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.25, - cached: 0.025, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_6_35B_A3B = { @@ -12553,18 +8029,6 @@ const QWEN_QWEN3_6_35B_A3B = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0.05, - }, - output: { - normal: 0.95, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_6_FLASH = { @@ -12590,18 +8054,6 @@ const QWEN_QWEN3_6_FLASH = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.1875, - cached: 0.234375, - }, - output: { - normal: 1.125, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_6_PLUS = { @@ -12627,18 +8079,6 @@ const QWEN_QWEN3_6_PLUS = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.325, - cached: 0.40625, - }, - output: { - normal: 1.95, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_7_FLASH = { @@ -12662,18 +8102,6 @@ const QWEN_QWEN3_7_FLASH = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.03, - cached: 0.044, - }, - output: { - normal: 0.13, - }, - }, - image: 0, - }, reasoning: BUDGET_OFF_HIGH, } as const const QWEN_QWEN3_7_MAX = { @@ -12699,18 +8127,6 @@ const QWEN_QWEN3_7_MAX = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 1.475, - cached: 2.13875, - }, - output: { - normal: 4.425, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_7_PLUS = { @@ -12736,18 +8152,6 @@ const QWEN_QWEN3_7_PLUS = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.32, - cached: 0.464, - }, - output: { - normal: 1.28, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const QWEN_QWEN3_7_PLUS_BATCH = { @@ -12773,18 +8177,6 @@ const QWEN_QWEN3_7_PLUS_BATCH = { }, context_window: 262144, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.23, - cached: 0, - }, - output: { - normal: 0.92, - }, - }, - image: 0, - }, } as const const QWEN_QWEN3_8_2_4T_A95B = { id: 'qwen/qwen3.8-2.4t-a95b', @@ -12810,18 +8202,6 @@ const QWEN_QWEN3_8_2_4T_A95B = { }, context_window: 1048576, max_output_tokens: 909000, - pricing: { - text: { - input: { - normal: 2, - cached: 0.25, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_MEDIUM_XHIGH, } as const const QWEN_QWEN3_8_27B = { @@ -12848,18 +8228,6 @@ const QWEN_QWEN3_8_27B = { }, context_window: 1000000, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.04, - cached: 0.018, - }, - output: { - normal: 1.35, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_MEDIUM_XHIGH, } as const const QWEN_QWEN3_8_FLASH = { @@ -12885,18 +8253,6 @@ const QWEN_QWEN3_8_FLASH = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.016, - }, - output: { - normal: 0.47, - }, - }, - image: 0, - }, reasoning: BUDGET_OFF_HIGH, } as const const QWEN_QWEN3_8_MAX_0902 = { @@ -12922,18 +8278,6 @@ const QWEN_QWEN3_8_MAX_0902 = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 2, - cached: 0.25, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_XHIGH, } as const const QWEN_QWEN3_8_MAX_PRIME = { @@ -12959,18 +8303,6 @@ const QWEN_QWEN3_8_MAX_PRIME = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 4, - cached: 0.5, - }, - output: { - normal: 12, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_XHIGH, } as const const QWEN_QWEN3_8_OMNI_FLASH = { @@ -12996,18 +8328,6 @@ const QWEN_QWEN3_8_OMNI_FLASH = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.016, - }, - output: { - normal: 0.47, - }, - }, - image: 0, - }, reasoning: BUDGET_OFF_HIGH, } as const const RECURSIVE_FLINT_V1_0_FREE = { @@ -13030,18 +8350,6 @@ const RECURSIVE_FLINT_V1_0_FREE = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, } as const const REFLECTION_BEAM_1_0_501B_A23B = { id: 'reflection/beam-1.0-501b-a23b', @@ -13067,18 +8375,6 @@ const REFLECTION_BEAM_1_0_501B_A23B = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.04, - }, - output: { - normal: 1, - }, - }, - image: 0, - }, } as const const REKAAI_REKA_EDGE = { id: 'rekaai/reka-edge', @@ -13101,18 +8397,6 @@ const REKAAI_REKA_EDGE = { }, context_window: 16384, max_output_tokens: 14745, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0, - }, - output: { - normal: 0.1, - }, - }, - image: 0, - }, } as const const REKAAI_REKA_FLASH_3 = { id: 'rekaai/reka-flash-3', @@ -13135,18 +8419,6 @@ const REKAAI_REKA_FLASH_3 = { }, context_window: 65536, max_output_tokens: 58982, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const RELACE_RELACE_APPLY_3 = { @@ -13159,18 +8431,6 @@ const RELACE_RELACE_APPLY_3 = { }, context_window: 256000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.85, - cached: 0, - }, - output: { - normal: 1.25, - }, - }, - image: 0, - }, } as const const RELACE_RELACE_SEARCH = { id: 'relace/relace-search', @@ -13190,18 +8450,6 @@ const RELACE_RELACE_SEARCH = { }, context_window: 256000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 1, - cached: 0, - }, - output: { - normal: 3, - }, - }, - image: 0, - }, } as const const SAKANA_FUGU_MAX = { id: 'sakana/fugu-max', @@ -13213,18 +8461,6 @@ const SAKANA_FUGU_MAX = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 2, - cached: 0.25, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_HIGH_TO_MAX, } as const const SAKANA_FUGU_ULTRA = { @@ -13237,18 +8473,6 @@ const SAKANA_FUGU_ULTRA = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 30, - }, - }, - image: 0, - }, reasoning: EFFORT_HIGH_TO_MAX, } as const const SAKANA_FUGU_ULTRA_V2 = { @@ -13261,18 +8485,6 @@ const SAKANA_FUGU_ULTRA_V2 = { }, context_window: 1000000, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 5, - cached: 0.5, - }, - output: { - normal: 30, - }, - }, - image: 0, - }, reasoning: EFFORT_HIGH_TO_MAX, } as const const SAKANA_SAKANA_NAMAZU = { @@ -13285,18 +8497,6 @@ const SAKANA_SAKANA_NAMAZU = { }, context_window: 262144, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.95, - cached: 0.15, - }, - output: { - normal: 4, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_HIGH, } as const const SAO10K_L3_LUNARIS_8B = { @@ -13321,18 +8521,6 @@ const SAO10K_L3_LUNARIS_8B = { }, context_window: 8192, max_output_tokens: 7372, - pricing: { - text: { - input: { - normal: 0.04, - cached: 0, - }, - output: { - normal: 0.05, - }, - }, - image: 0, - }, } as const const SAO10K_L3_1_EURYALE_70B = { id: 'sao10k/l3.1-euryale-70b', @@ -13353,18 +8541,6 @@ const SAO10K_L3_1_EURYALE_70B = { }, context_window: 131072, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.85, - cached: 0, - }, - output: { - normal: 0.85, - }, - }, - image: 0, - }, } as const const SCALEDOWN_EXTRACT = { id: 'scaledown/extract', @@ -13376,18 +8552,6 @@ const SCALEDOWN_EXTRACT = { }, context_window: 1000000, max_output_tokens: 900000, - pricing: { - text: { - input: { - normal: 0.04, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, } as const const SCALEDOWN_SUMMARIZE = { id: 'scaledown/summarize', @@ -13399,18 +8563,6 @@ const SCALEDOWN_SUMMARIZE = { }, context_window: 1000000, max_output_tokens: 4000, - pricing: { - text: { - input: { - normal: 0.04, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, } as const const STEPFUN_STEP_3_5_FLASH = { id: 'stepfun/step-3.5-flash', @@ -13429,18 +8581,6 @@ const STEPFUN_STEP_3_5_FLASH = { }, context_window: 262144, max_output_tokens: 65536, - pricing: { - text: { - input: { - normal: 0.1, - cached: 0, - }, - output: { - normal: 0.3, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const STEPFUN_STEP_3_7_FLASH = { @@ -13466,18 +8606,6 @@ const STEPFUN_STEP_3_7_FLASH = { }, context_window: 262144, max_output_tokens: 230400, - pricing: { - text: { - input: { - normal: 0.2, - cached: 0.04, - }, - output: { - normal: 1.15, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const STEPFUN_STEP_5_PREVIEW = { @@ -13504,18 +8632,6 @@ const STEPFUN_STEP_5_PREVIEW = { }, context_window: 1048576, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 1, - cached: 0.1, - }, - output: { - normal: 2.7, - }, - }, - image: 0, - }, } as const const TENCENT_HUNYUAN_A13B_INSTRUCT = { id: 'tencent/hunyuan-a13b-instruct', @@ -13534,18 +8650,6 @@ const TENCENT_HUNYUAN_A13B_INSTRUCT = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.14, - cached: 0, - }, - output: { - normal: 0.57, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const TENCENT_HY_MT2_1_8B = { @@ -13563,18 +8667,6 @@ const TENCENT_HY_MT2_1_8B = { }, context_window: 8192, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 0.044, - cached: 0, - }, - output: { - normal: 0.177, - }, - }, - image: 0, - }, } as const const TENCENT_HY_MT2_30B_A3B = { id: 'tencent/hy-mt2-30b-a3b', @@ -13592,18 +8684,6 @@ const TENCENT_HY_MT2_30B_A3B = { }, context_window: 8192, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 0.074, - cached: 0, - }, - output: { - normal: 0.295, - }, - }, - image: 0, - }, } as const const TENCENT_HY_MT2_7B = { id: 'tencent/hy-mt2-7b', @@ -13621,18 +8701,6 @@ const TENCENT_HY_MT2_7B = { }, context_window: 8192, max_output_tokens: 4096, - pricing: { - text: { - input: { - normal: 0.074, - cached: 0, - }, - output: { - normal: 0.295, - }, - }, - image: 0, - }, } as const const TENCENT_HY3 = { id: 'tencent/hy3', @@ -13657,18 +8725,6 @@ const TENCENT_HY3 = { }, context_window: 262144, max_output_tokens: 128000, - pricing: { - text: { - input: { - normal: 0.132, - cached: 0.033, - }, - output: { - normal: 0.528, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH, } as const const TENCENT_HY3_PREVIEW = { @@ -13688,18 +8744,6 @@ const TENCENT_HY3_PREVIEW = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0.18, - cached: 0.06, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH, } as const const TENCENT_HY4_PREVIEW = { @@ -13725,18 +8769,6 @@ const TENCENT_HY4_PREVIEW = { }, context_window: 1048576, max_output_tokens: 64000, - pricing: { - text: { - input: { - normal: 0.834, - cached: 0.042, - }, - output: { - normal: 2.501, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_HIGH, } as const const THEDRUMMER_CYDONIA_24B_V4_1 = { @@ -13761,18 +8793,6 @@ const THEDRUMMER_CYDONIA_24B_V4_1 = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.15, - }, - output: { - normal: 0.5, - }, - }, - image: 0, - }, } as const const THEDRUMMER_SKYFALL_36B_V2 = { id: 'thedrummer/skyfall-36b-v2', @@ -13796,18 +8816,6 @@ const THEDRUMMER_SKYFALL_36B_V2 = { }, context_window: 32768, max_output_tokens: 29491, - pricing: { - text: { - input: { - normal: 0.55, - cached: 0.25, - }, - output: { - normal: 0.8, - }, - }, - image: 0, - }, } as const const THEDRUMMER_UNSLOPNEMO_12B = { id: 'thedrummer/unslopnemo-12b', @@ -13830,18 +8838,6 @@ const THEDRUMMER_UNSLOPNEMO_12B = { }, context_window: 1024000, max_output_tokens: 819200, - pricing: { - text: { - input: { - normal: 0.4, - cached: 0, - }, - output: { - normal: 0.4, - }, - }, - image: 0, - }, } as const const THINKINGMACHINES_INKLING = { id: 'thinkingmachines/inkling', @@ -13864,18 +8860,6 @@ const THINKINGMACHINES_INKLING = { }, context_window: 524288, max_output_tokens: 471859, - pricing: { - text: { - input: { - normal: 1, - cached: 0.17, - }, - output: { - normal: 4.05, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH_MAX, } as const const THINKINGMACHINES_INKLING_SMALL = { @@ -13899,18 +8883,6 @@ const THINKINGMACHINES_INKLING_SMALL = { }, context_window: 524288, max_output_tokens: 262144, - pricing: { - text: { - input: { - normal: 0.45, - cached: 0.1, - }, - output: { - normal: 1.2, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH_MAX, } as const const THINKINGMACHINES_INKLING_SMALL_FREE = { @@ -13932,18 +8904,6 @@ const THINKINGMACHINES_INKLING_SMALL_FREE = { }, context_window: 1048576, max_output_tokens: 262144, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH_MAX, } as const const THINKINGMACHINES_INKLING_FREE = { @@ -13965,18 +8925,6 @@ const THINKINGMACHINES_INKLING_FREE = { }, context_window: 1048576, max_output_tokens: 262144, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH_MAX, } as const const UNBIASED_PARETO_26_10 = { @@ -13989,18 +8937,6 @@ const UNBIASED_PARETO_26_10 = { }, context_window: 1048576, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.8, - cached: 0.03, - }, - output: { - normal: 3.2, - }, - }, - image: 0, - }, } as const const UNBIASED_PARETO_26_9 = { id: 'unbiased/pareto-26.9', @@ -14018,18 +8954,6 @@ const UNBIASED_PARETO_26_9 = { }, context_window: 262144, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 2.5, - cached: 0.25, - }, - output: { - normal: 7.5, - }, - }, - image: 0, - }, } as const const UNDI95_REMM_SLERP_L2_13B = { id: 'undi95/remm-slerp-l2-13b', @@ -14053,18 +8977,6 @@ const UNDI95_REMM_SLERP_L2_13B = { }, context_window: 6144, max_output_tokens: 5529, - pricing: { - text: { - input: { - normal: 0.35, - cached: 0, - }, - output: { - normal: 0.65, - }, - }, - image: 0, - }, } as const const UPSTAGE_SOLAR_MINI4 = { id: 'upstage/solar-mini4', @@ -14086,18 +8998,6 @@ const UPSTAGE_SOLAR_MINI4 = { }, context_window: 524288, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.005, - }, - output: { - normal: 0.2, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_MAX, } as const const UPSTAGE_SOLAR_PRO_3 = { @@ -14120,18 +9020,6 @@ const UPSTAGE_SOLAR_PRO_3 = { }, context_window: 131072, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.015, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_HIGH, } as const const UPSTAGE_SOLAR_PRO4 = { @@ -14154,18 +9042,6 @@ const UPSTAGE_SOLAR_PRO4 = { }, context_window: 524288, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.15, - cached: 0.03, - }, - output: { - normal: 0.6, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_MINIMAL_TO_MAX, } as const const WRITER_PALMYRA_X5 = { @@ -14178,18 +9054,6 @@ const WRITER_PALMYRA_X5 = { }, context_window: 1040000, max_output_tokens: 8192, - pricing: { - text: { - input: { - normal: 0.6, - cached: 0, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, } as const const X_AI_GROK_4_20 = { id: 'x-ai/grok-4.20', @@ -14211,18 +9075,6 @@ const X_AI_GROK_4_20 = { }, context_window: 2000000, max_output_tokens: 1800000, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.2, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const X_AI_GROK_4_20_MULTI_AGENT = { @@ -14244,18 +9096,6 @@ const X_AI_GROK_4_20_MULTI_AGENT = { }, context_window: 2000000, max_output_tokens: 1800000, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.2, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const const X_AI_GROK_4_3 = { @@ -14278,18 +9118,6 @@ const X_AI_GROK_4_3 = { }, context_window: 1000000, max_output_tokens: 900000, - pricing: { - text: { - input: { - normal: 1.25, - cached: 0.2, - }, - output: { - normal: 2.5, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const const X_AI_GROK_4_3_BATCH = { @@ -14312,18 +9140,6 @@ const X_AI_GROK_4_3_BATCH = { }, context_window: 1000000, max_output_tokens: 900000, - pricing: { - text: { - input: { - normal: 1, - cached: 0.16, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_LOW_TO_HIGH, } as const const X_AI_GROK_4_5 = { @@ -14346,18 +9162,6 @@ const X_AI_GROK_4_5 = { }, context_window: 500000, max_output_tokens: 450000, - pricing: { - text: { - input: { - normal: 2, - cached: 0.3, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_HIGH, } as const const X_AI_GROK_4_6 = { @@ -14381,18 +9185,6 @@ const X_AI_GROK_4_6 = { }, context_window: 500000, max_output_tokens: 450000, - pricing: { - text: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const const X_AI_GROK_4_7 = { @@ -14415,18 +9207,6 @@ const X_AI_GROK_4_7 = { }, context_window: 500000, max_output_tokens: 450000, - pricing: { - text: { - input: { - normal: 2, - cached: 0.5, - }, - output: { - normal: 6, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_TO_XHIGH, } as const const X_AI_GROK_BUILD_0_1 = { @@ -14449,18 +9229,6 @@ const X_AI_GROK_BUILD_0_1 = { }, context_window: 256000, max_output_tokens: 230400, - pricing: { - text: { - input: { - normal: 1, - cached: 0.2, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_MINIMAL_TO_HIGH, } as const const XIAOMI_MIMO_V2_5 = { @@ -14484,18 +9252,6 @@ const XIAOMI_MIMO_V2_5 = { }, context_window: 1050000, max_output_tokens: 945000, - pricing: { - text: { - input: { - normal: 0.119, - cached: 0.00255, - }, - output: { - normal: 0.238, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const XIAOMI_MIMO_V2_5_PRO = { @@ -14521,18 +9277,6 @@ const XIAOMI_MIMO_V2_5_PRO = { }, context_window: 1050000, max_output_tokens: 945000, - pricing: { - text: { - input: { - normal: 0.3045, - cached: 0.0028, - }, - output: { - normal: 0.609, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const XIAOMI_MIMO_V2_6_FLASH = { @@ -14559,18 +9303,6 @@ const XIAOMI_MIMO_V2_6_FLASH = { }, context_window: 1050000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.14, - cached: 0.0028, - }, - output: { - normal: 0.28, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const XIAOMI_MIMO_V2_6_PRO = { @@ -14595,18 +9327,6 @@ const XIAOMI_MIMO_V2_6_PRO = { }, context_window: 1050000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.435, - cached: 0.0036, - }, - output: { - normal: 0.87, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const XIAOMI_MIMO_V2_6_PRO_ULTRASPEED = { @@ -14629,18 +9349,6 @@ const XIAOMI_MIMO_V2_6_PRO_ULTRASPEED = { }, context_window: 1048576, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 4.35, - cached: 0.036, - }, - output: { - normal: 8.7, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const XINGCHEN_AGI_XING4_0_29B_A4B_FREE = { @@ -14664,18 +9372,6 @@ const XINGCHEN_AGI_XING4_0_29B_A4B_FREE = { }, context_window: 262144, max_output_tokens: 235929, - pricing: { - text: { - input: { - normal: 0, - cached: 0, - }, - output: { - normal: 0, - }, - }, - image: 0, - }, } as const const Z_AI_GLM_4_5 = { id: 'z-ai/glm-4.5', @@ -14694,18 +9390,6 @@ const Z_AI_GLM_4_5 = { }, context_window: 131072, max_output_tokens: 98304, - pricing: { - text: { - input: { - normal: 0.6, - cached: 0.11, - }, - output: { - normal: 2.2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_4_5_AIR = { @@ -14728,18 +9412,6 @@ const Z_AI_GLM_4_5_AIR = { }, context_window: 131072, max_output_tokens: 98304, - pricing: { - text: { - input: { - normal: 0.13, - cached: 0.025, - }, - output: { - normal: 0.85, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_4_5V = { @@ -14763,18 +9435,6 @@ const Z_AI_GLM_4_5V = { }, context_window: 65536, max_output_tokens: 16384, - pricing: { - text: { - input: { - normal: 0.6, - cached: 0.11, - }, - output: { - normal: 1.8, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_4_6 = { @@ -14799,18 +9459,6 @@ const Z_AI_GLM_4_6 = { }, context_window: 204800, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.55, - cached: 0.11, - }, - output: { - normal: 2.2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_4_6V = { @@ -14834,18 +9482,6 @@ const Z_AI_GLM_4_6V = { }, context_window: 131072, max_output_tokens: 32768, - pricing: { - text: { - input: { - normal: 0.3, - cached: 0.055, - }, - output: { - normal: 0.9, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_4_7 = { @@ -14870,18 +9506,6 @@ const Z_AI_GLM_4_7 = { }, context_window: 204800, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.54, - cached: 0.099, - }, - output: { - normal: 1.98, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_4_7_FLASH = { @@ -14908,18 +9532,6 @@ const Z_AI_GLM_4_7_FLASH = { }, context_window: 200000, max_output_tokens: 117964, - pricing: { - text: { - input: { - normal: 0.0605, - cached: 0, - }, - output: { - normal: 0.4, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_5 = { @@ -14945,18 +9557,6 @@ const Z_AI_GLM_5 = { }, context_window: 204800, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 1, - cached: 0.2, - }, - output: { - normal: 3.2, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_5_TURBO = { @@ -14976,18 +9576,6 @@ const Z_AI_GLM_5_TURBO = { }, context_window: 202752, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 1.2, - cached: 0.24, - }, - output: { - normal: 4, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_5_1 = { @@ -15014,18 +9602,6 @@ const Z_AI_GLM_5_1 = { }, context_window: 204800, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 1.4, - cached: 0.26, - }, - output: { - normal: 4.4, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const const Z_AI_GLM_5_2 = { @@ -15053,18 +9629,6 @@ const Z_AI_GLM_5_2 = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.0341, - cached: 0.033, - }, - output: { - normal: 7, - }, - }, - image: 0, - }, reasoning: EFFORT_NONE_HIGH_XHIGH, } as const const Z_AI_GLM_5_3 = { @@ -15092,18 +9656,6 @@ const Z_AI_GLM_5_3 = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.079, - cached: 0.078, - }, - output: { - normal: 1.4, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const Z_AI_GLM_5_3_FLASH = { @@ -15131,18 +9683,6 @@ const Z_AI_GLM_5_3_FLASH = { }, context_window: 1048576, max_output_tokens: 943718, - pricing: { - text: { - input: { - normal: 0.0052, - cached: 0.0052, - }, - output: { - normal: 0.1985, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const Z_AI_GLM_5_3_FLASH_BATCH = { @@ -15169,18 +9709,6 @@ const Z_AI_GLM_5_3_FLASH_BATCH = { }, context_window: 1048576, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.05, - cached: 0.01, - }, - output: { - normal: 0.18, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const Z_AI_GLM_5_3_FLASHX = { @@ -15200,18 +9728,6 @@ const Z_AI_GLM_5_3_FLASHX = { }, context_window: 1048576, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.37, - cached: 0.09, - }, - output: { - normal: 1.25, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const Z_AI_GLM_5_3_PRIME = { @@ -15237,18 +9753,6 @@ const Z_AI_GLM_5_3_PRIME = { }, context_window: 1000000, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 2.8, - cached: 0.56, - }, - output: { - normal: 8.8, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const Z_AI_GLM_5_3_BATCH = { @@ -15273,18 +9777,6 @@ const Z_AI_GLM_5_3_BATCH = { }, context_window: 1048576, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 0.45, - cached: 0.1, - }, - output: { - normal: 2, - }, - }, - image: 0, - }, reasoning: EFFORT_LOW_HIGH_MAX, } as const const Z_AI_GLM_5V_TURBO = { @@ -15304,18 +9796,6 @@ const Z_AI_GLM_5V_TURBO = { }, context_window: 202752, max_output_tokens: 131072, - pricing: { - text: { - input: { - normal: 1.2, - cached: 0.24, - }, - output: { - normal: 4, - }, - }, - image: 0, - }, reasoning: EFFORT_OFF_HIGH, } as const diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8d7e6f184..4b99c35337 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,8 +29,8 @@ importers: specifier: ^10.1.0 version: 10.1.0 '@modelschemas/client': - specifier: ^0.1.0 - version: 0.1.0 + specifier: ^0.2.1 + version: 0.2.1 '@stylistic/eslint-plugin': specifier: ^5.10.0 version: 5.10.0(eslint@9.39.4(jiti@2.7.0)) @@ -3748,7 +3748,7 @@ importers: devDependencies: '@google/genai': specifier: ^2.10.0 - version: 2.10.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)) + version: 2.10.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.6.5)) '@tanstack/ai': specifier: workspace:* version: link:../ai @@ -6948,8 +6948,8 @@ packages: resolution: {integrity: sha512-FdvwZU5N03O77Suh35JqjAqq5R1ZTEPFtwu6N4BgBq28qrQtrCnpbLL5U2pwYCuE194jAw0xnlOVmIqhfoYuUQ==} engines: {node: '>=20'} - '@modelschemas/client@0.1.0': - resolution: {integrity: sha512-MVtC9gsZcx05ITdqh6Krc8G9lMxne7mPO2VfX6cyjQbpGNWOe/dwyZuETVfwohC3Dnsnsrfi3oIRNQNc+xx87Q==} + '@modelschemas/client@0.2.1': + resolution: {integrity: sha512-y1055MBmvtHS9r3j0YnTAeP13AYNbZPwHvUeywMrNahGdm+mtTgi8FGDu8QFZ3hcF1gLu8h3qWtaGrFT/K7aIg==} '@msgpack/msgpack@3.1.3': resolution: {integrity: sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==} @@ -22726,6 +22726,19 @@ snapshots: - supports-color - utf-8-validate + '@google/genai@2.10.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.6.5))': + dependencies: + google-auth-library: 10.5.0 + p-retry: 4.6.2 + protobufjs: 7.6.4 + ws: 8.21.0 + optionalDependencies: + '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.6.5) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@graphql-typed-document-node/core@3.2.0(graphql@16.14.2)': dependencies: graphql: 16.14.2 @@ -23303,12 +23316,37 @@ snapshots: transitivePeerDependencies: - supports-color + '@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.6.5)': + dependencies: + '@hono/node-server': 1.19.17(hono@4.13.8) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.2.1 + express-rate-limit: 8.7.0(express@5.2.1)(supports-color@7.2.0) + hono: 4.13.8 + jose: 6.2.12 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.6.5 + zod-to-json-schema: 3.25.2(zod@4.6.5) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - supports-color + optional: true + '@modelcontextprotocol/server@2.1.0': dependencies: '@modelcontextprotocol/core': 2.1.0 zod: 4.3.6 - '@modelschemas/client@0.1.0': {} + '@modelschemas/client@0.2.1': {} '@msgpack/msgpack@3.1.3': {} diff --git a/scripts/convert-openrouter-models.ts b/scripts/convert-openrouter-models.ts index 5af4cc99e2..e36591a5de 100644 --- a/scripts/convert-openrouter-models.ts +++ b/scripts/convert-openrouter-models.ts @@ -47,21 +47,6 @@ function mapInputModality(modality: string): InputModality | null { return mapping[modality.toLowerCase()] || null } -function convertPricing(priceStr: string, isImage: boolean = false): number { - // OpenRouter pricing is typically per token (in dollars) - // Convert to number - const price = parseFloat(priceStr) - if (isImage) { - return isNaN(price) ? 0 : price - } - return finalPrice(isNaN(price) ? 0 : price) -} -function finalPrice(price: number): number { - // Ensure price is non-negative and rounded to avoid floating-point errors - const result = price * 1_000_000 - // Round to a reasonable number of decimal places - return Math.round(result * 1e10) / 1e10 -} const perModelProviderOptions: Record = {} const perModelInputModalities: Record = {} @@ -358,16 +343,6 @@ function generateModelMetaString(model: OpenRouterModel): string { if (outputModalities.includes('video')) { videoModels.add(`${constName}.id`) } - const inputPrice = convertPricing(model.pricing.prompt) - const outputPrice = convertPricing(model.pricing.completion) - const imagePrice = convertPricing(model.pricing.image ?? '0', true) - const inputCacheReadPrice = convertPricing( - model.pricing.input_cache_read ?? '0', - ) - const inputCacheWritePrice = convertPricing( - model.pricing.input_cache_write ?? '0', - ) - const paramNameMap: Record = { max_tokens: 'maxCompletionTokens', frequency_penalty: 'frequencyPenalty', @@ -435,18 +410,6 @@ function generateModelMetaString(model: OpenRouterModel): string { ) } - lines.push(` pricing: {`) - lines.push(` text: {`) - lines.push(` input: {`) - lines.push(` normal: ${inputPrice},`) - lines.push(` cached: ${inputCacheReadPrice + inputCacheWritePrice},`) - lines.push(` },`) - lines.push(` output: {`) - lines.push(` normal: ${outputPrice},`) - lines.push(` },`) - lines.push(` },`) - lines.push(` image: ${imagePrice},`) - lines.push(` },`) const reasoning = existingReasoning[model.id] if (reasoning) { lines.push(` reasoning: ${reasoningShapeName(reasoning)},`) diff --git a/scripts/model-sync/catalog.test.ts b/scripts/model-sync/catalog.test.ts index 477f1f73da..79da332d9c 100644 --- a/scripts/model-sync/catalog.test.ts +++ b/scripts/model-sync/catalog.test.ts @@ -1,13 +1,9 @@ import { describe, expect, it } from 'vitest' import { alreadySynced, - findOpenRouterEnrichment, - hasFullPricing, hasImageOutput, - openRouterRawIdCandidates, outputsText, parseCatalogModels, - pricingLines, selectNativeInserts, skipNativeModelReason, toSyncModel, @@ -23,12 +19,8 @@ function native(overrides: Partial = {}): CatalogModel { deprecatedAt: null, contextWindow: null, maxOutput: null, - inputModalities: [], - outputModalities: [], - pricing: { - inputPerMillion: undefined, - outputPerMillion: undefined, - }, + inputModalities: ['text'], + outputModalities: ['text'], capabilities: [], reasoningMandatory: false, ...overrides, @@ -47,12 +39,7 @@ describe('parseCatalogModels', () => { contextWindow: 400_000, maxOutput: 128_000, modalities: { input: ['text', 'image'], output: ['text'] }, - pricing: { - per: 'token', - inputPerMillion: 1.25, - outputPerMillion: 10, - }, - capabilities: ['tools', 'reasoning'], + capabilities: { tools: true, reasoning: true, temperature: false }, firstSeenAt: 1_754_425_777, deprecatedAt: null, }, @@ -65,7 +52,6 @@ describe('parseCatalogModels', () => { contextWindow: 400_000, inputModalities: ['text', 'image'], capabilities: ['tools', 'reasoning'], - pricing: { inputPerMillion: 1.25, outputPerMillion: 10 }, }) }) @@ -127,90 +113,27 @@ describe('parseCatalogModels drift', () => { }) }) -describe('openRouterRawIdCandidates', () => { - it('hyphen/dot-maps Anthropic ids and strips dated snapshots', () => { +describe('toSyncModel', () => { + it('takes every field from the native row, and fills nothing', () => { expect( - openRouterRawIdCandidates( - native({ rawId: 'claude-haiku-4-5-20251001' }), - 'anthropic', - ), - ).toEqual([ - 'anthropic/claude-haiku-4-5-20251001', - 'anthropic/claude-haiku-4-5', - 'anthropic/claude-haiku-4.5-20251001', - 'anthropic/claude-haiku-4.5', - ]) - }) - - it('prefixes Gemini with google/ and Grok with x-ai/', () => { - expect( - openRouterRawIdCandidates( - native({ rawId: 'gemini-3.1-pro', provider: 'gemini' }), - 'gemini', - ), - ).toEqual(['google/gemini-3.1-pro']) - expect( - openRouterRawIdCandidates( - native({ rawId: 'grok-4.6', provider: 'grok' }), + toSyncModel( + native({ + rawId: 'grok-5', + provider: 'grok', + contextWindow: 2_000_000, + capabilities: ['tools'], + }), 'grok', ), - ).toEqual(['x-ai/grok-4.6']) - }) -}) - -describe('findOpenRouterEnrichment / toSyncModel', () => { - it('joins a hyphenated Anthropic id to the dotted OpenRouter row', () => { - const enrich = native({ - provider: 'openrouter', - rawId: 'anthropic/claude-sonnet-4.5', - contextWindow: 200_000, - maxOutput: 64_000, - inputModalities: ['text', 'image'], - outputModalities: ['text'], - pricing: { inputPerMillion: 3, outputPerMillion: 15 }, - capabilities: ['tools', 'temperature'], - }) - const row = native({ rawId: 'claude-sonnet-4-5' }) - const found = findOpenRouterEnrichment(row, 'anthropic', [enrich]) - expect(found?.rawId).toBe('anthropic/claude-sonnet-4.5') - const synced = toSyncModel(row, found, 'anthropic') - expect(synced.nativeId).toBe('claude-sonnet-4-5') - expect(synced.contextWindow).toBe(200_000) - expect(synced.supportedParameters).toEqual(['tools', 'temperature']) - expect(synced.pricing.inputPerMillion).toBe(3) - }) - - it('keeps native capabilities and modalities; OpenRouter only fills empty pricing', () => { - const enrich = native({ - provider: 'openrouter', - rawId: 'anthropic/claude-sonnet-4.5', - contextWindow: 1_000_000, - inputModalities: ['text', 'image', 'file'], + ).toEqual({ + nativeId: 'grok-5', + contextWindow: 2_000_000, + maxOutput: null, + inputModalities: ['text'], outputModalities: ['text'], - pricing: { inputPerMillion: 3, outputPerMillion: 15 }, - capabilities: ['tools', 'temperature', 'include_reasoning', 'top_k'], + supportedParameters: ['tools'], + reasoningMandatory: false, }) - const row = native({ - rawId: 'claude-sonnet-4-5', - contextWindow: 200_000, - inputModalities: ['text', 'image'], - outputModalities: ['text'], - capabilities: ['tools', 'temperature'], - }) - const synced = toSyncModel(row, enrich, 'anthropic') - expect(synced.contextWindow).toBe(200_000) - expect(synced.inputModalities).toEqual(['text', 'image']) - expect(synced.supportedParameters).toEqual(['tools', 'temperature']) - expect(synced.pricing.inputPerMillion).toBe(3) - }) - - it('fills a missing price side from OpenRouter without replacing the known one', () => { - const synced = toSyncModel( - native({ pricing: { inputPerMillion: 2, outputPerMillion: undefined } }), - native({ pricing: { inputPerMillion: 3, outputPerMillion: 15 } }), - 'anthropic', - ) - expect(synced.pricing).toEqual({ inputPerMillion: 2, outputPerMillion: 15 }) }) }) @@ -265,6 +188,18 @@ describe('skipNativeModelReason', () => { ).toBe('non-chat family') }) + it('skips a row with no input modalities instead of guessing them', () => { + expect( + skipNativeModelReason( + native({ inputModalities: [] }), + 'gemini', + [], + cutoff, + ['chat'], + ), + ).toBe('no modalities') + }) + it('keeps Groq rows with no activity', () => { expect( skipNativeModelReason( @@ -281,8 +216,11 @@ describe('skipNativeModelReason', () => { describe('outputsText', () => { it('treats empty modalities plus unset activity as chat', () => { const model = toSyncModel( - native({ activity: null, rawId: 'qwen/qwen3.8-27b' }), - undefined, + native({ + activity: null, + rawId: 'qwen/qwen3.8-27b', + outputModalities: [], + }), 'groq', ) expect(outputsText(model, null)).toBe(true) @@ -290,41 +228,30 @@ describe('outputsText', () => { }) describe('selectNativeInserts', () => { - const priced = { inputPerMillion: 3, outputPerMillion: 15 } const rules = { provider: 'anthropic' as const, skipPatterns: ['claude-legacy-'], acceptedActivities: ['chat'], - requireOpenRouterEnrich: true, cutoffTimestamp: 0, } - const openrouter = [ - native({ rawId: 'anthropic/claude-sonnet-6', pricing: priced }), - native({ rawId: 'anthropic/claude-art-1', pricing: priced }), - ] - it('inserts a priced model and holds back one OpenRouter has not priced', () => { + it('inserts a new model with no price and no second source', () => { const selection = selectNativeInserts( - [native({ rawId: 'claude-sonnet-6' }), native({ rawId: 'claude-new-1' })], + [native({ rawId: 'claude-new-1' })], rules, - openrouter, () => false, ) expect(selection.inserts.map(({ model }) => model.nativeId)).toEqual([ - 'claude-sonnet-6', + 'claude-new-1', ]) - expect(selection.inserts[0]?.model.pricing).toEqual(priced) - expect(selection.heldBack).toEqual(['claude-new-1']) }) - it('does not hold back a model that is already in the file', () => { + it('skips a model that is already in the file', () => { const selection = selectNativeInserts( [native({ rawId: 'claude-new-1' })], rules, - openrouter, () => true, ) - expect(selection.heldBack).toEqual([]) expect(selection.skipped).toEqual({ 'already synced': 1 }) }) @@ -335,9 +262,9 @@ describe('selectNativeInserts', () => { native({ rawId: 'claude-old-1', deprecatedAt: 1 }), native({ rawId: 'claude-sonnet-6:thinking' }), native({ rawId: 'claude-legacy-2' }), + native({ rawId: 'claude-blank-1', inputModalities: [] }), ], rules, - openrouter, () => false, ) expect(selection.inserts).toEqual([]) @@ -346,53 +273,9 @@ describe('selectNativeInserts', () => { deprecated: 1, 'routing variant': 1, 'skip pattern': 1, + 'no modalities': 1, }) }) - - it('inserts a Groq model with no price when the price gate is off', () => { - const selection = selectNativeInserts( - [native({ provider: 'groq', rawId: 'qwen/qwen9', activity: null })], - { - ...rules, - provider: 'groq', - acceptedActivities: [null, 'chat'], - requireOpenRouterEnrich: false, - }, - openrouter, - () => false, - ) - expect(selection.inserts).toHaveLength(1) - expect(selection.heldBack).toEqual([]) - }) -}) - -describe('pricingLines / hasFullPricing', () => { - it('leaves an unknown price out instead of writing 0', () => { - const lines = pricingLines({ - inputPerMillion: undefined, - outputPerMillion: undefined, - }) - expect(lines).toEqual([' pricing: {', ' },']) - expect(lines.join('\n')).not.toContain('normal: 0') - }) - - it('writes only the known side, rounded', () => { - const block = pricingLines({ - inputPerMillion: 0.09999999999999999, - outputPerMillion: undefined, - }).join('\n') - expect(block).toContain('normal: 0.1,') - expect(block).not.toContain('output') - }) - - it('needs both prices for providers whose ModelMeta requires them', () => { - expect( - hasFullPricing({ inputPerMillion: 1, outputPerMillion: undefined }), - ).toBe(false) - expect(hasFullPricing({ inputPerMillion: 0, outputPerMillion: 0 })).toBe( - true, - ) - }) }) describe('hasImageOutput / alreadySynced', () => { diff --git a/scripts/model-sync/catalog.ts b/scripts/model-sync/catalog.ts index 13b828d81f..462806e55e 100644 --- a/scripts/model-sync/catalog.ts +++ b/scripts/model-sync/catalog.ts @@ -1,22 +1,15 @@ /** * modelschemas catalog rows for native-provider model-meta inserts. * - * Native catalogs supply ids, activity, limits, modalities, pricing, and - * capabilities. OpenRouter rows on modelschemas fill a field only when the - * native row left it empty. + * Each provider's own catalog supplies ids, activity, limits, modalities, and + * capabilities. A field the catalog leaves empty stays empty. There is no + * second source, and model-meta carries no prices (they change too often to + * ship in a package). */ import { toModelConstName, toNativeProviderId } from './ids' import type { SyncedProvider } from './provider-supports' -export const OPENROUTER_PREFIX: Partial> = { - openai: 'openai/', - anthropic: 'anthropic/', - gemini: 'google/', - grok: 'x-ai/', - mistral: 'mistralai/', -} - const NON_CHAT_MODEL_PREFIXES = [ 'lyria-', 'veo-', @@ -28,12 +21,6 @@ const NON_CHAT_MODEL_PREFIXES = [ const DATED_SNAPSHOT = /-\d{8}$/ -/** USD per million tokens, as modelschemas reports it. */ -export interface CatalogPricing { - inputPerMillion: number | undefined - outputPerMillion: number | undefined -} - export interface CatalogModel { provider: string rawId: string @@ -44,7 +31,6 @@ export interface CatalogModel { maxOutput: number | null inputModalities: Array outputModalities: Array - pricing: CatalogPricing capabilities: Array /** `reasoning.mandatory`: thinking cannot be turned off (Claude Fable). */ reasoningMandatory: boolean @@ -56,7 +42,6 @@ export interface SyncModel { maxOutput: number | null inputModalities: Array outputModalities: Array - pricing: CatalogPricing supportedParameters: Array reasoningMandatory: boolean } @@ -74,10 +59,18 @@ function asFiniteNumber(value: unknown): number | null { return typeof value === 'number' && Number.isFinite(value) ? value : null } -/** Most providers list parameter names; BytePlus sends a feature object. */ +/** + * The parameter names a model is stated to support. modelschemas sends a map + * of flags (`{ tools: true }`): `true` keys only, because an absent key is + * unknown, not false. BytePlus sends a feature object instead. + */ function asSupportedParameters(value: unknown): Array { if (Array.isArray(value)) return asStringArray(value) if (!isRecord(value)) return [] + const flags = Object.entries(value) + if (flags.every(([, flag]) => typeof flag === 'boolean')) { + return flags.filter(([, flag]) => flag).map(([name]) => name) + } const params: Array = [] const tools = isRecord(value.tools) ? value.tools : null if (tools?.function_calling === true) { @@ -98,14 +91,6 @@ function asSupportedParameters(value: unknown): Array { return params } -function asPricing(value: unknown): CatalogPricing { - const record = isRecord(value) ? value : {} - return { - inputPerMillion: asFiniteNumber(record.inputPerMillion) ?? undefined, - outputPerMillion: asFiniteNumber(record.outputPerMillion) ?? undefined, - } -} - export function parseCatalogModel(value: unknown): CatalogModel | null { if (!isRecord(value)) return null if (typeof value.rawId !== 'string' || value.rawId.length === 0) return null @@ -121,7 +106,6 @@ export function parseCatalogModel(value: unknown): CatalogModel | null { maxOutput: asFiniteNumber(value.maxOutput), inputModalities: asStringArray(modalities?.input), outputModalities: asStringArray(modalities?.output), - pricing: asPricing(value.pricing), capabilities: asSupportedParameters(value.capabilities), reasoningMandatory: reasoning?.mandatory === true, } @@ -155,102 +139,18 @@ export function parseCatalogModels(data: unknown): Array { return models } -/** OpenRouter dots Anthropic version suffixes (`4.5`); native ids use dashes. */ -function anthropicOpenRouterId(nativeId: string): string { - return nativeId.replace(/(\d)-(\d)/g, '$1.$2') -} - -export function openRouterRawIdCandidates( - native: CatalogModel, - provider: SyncedProvider, -): Array { - const prefix = OPENROUTER_PREFIX[provider] - if (!prefix) return [] - const ids = new Set([native.rawId]) - if (provider === 'anthropic') { - const undated = native.rawId.replace(DATED_SNAPSHOT, '') - ids.add(undated) - ids.add(anthropicOpenRouterId(native.rawId)) - ids.add(anthropicOpenRouterId(undated)) - } - return [...ids].map((id) => prefix + id) -} - -export function findOpenRouterEnrichment( - native: CatalogModel, - provider: SyncedProvider, - openrouter: Array, -): CatalogModel | undefined { - const wanted = new Set(openRouterRawIdCandidates(native, provider)) - return openrouter.find((model) => wanted.has(model.rawId)) -} - -function pickList( - preferred: Array, - fallback: Array = [], -): Array { - return preferred.length > 0 ? preferred : fallback -} - -/** Both prices known. OpenAI/Anthropic/Gemini/Grok ModelMeta require both. */ -export function hasFullPricing(pricing: CatalogPricing): boolean { - return pricing.inputPerMillion != null && pricing.outputPerMillion != null -} - -/** Strips float noise: 0.09999999999999999 → 0.1. */ -function roundPrice(price: number): number { - return Math.round(price * 1e10) / 1e10 -} - -/** - * The `pricing` block of a generated model constant. A price the catalog - * does not have is left out, not written as `0` (which reads as "free"). - */ -// ponytail: modelschemas has no cached-input price, so `cached` is never written. -export function pricingLines(pricing: CatalogPricing): Array { - const lines = [` pricing: {`] - if (pricing.inputPerMillion != null) { - lines.push( - ` input: {`, - ` normal: ${roundPrice(pricing.inputPerMillion)},`, - ` },`, - ) - } - if (pricing.outputPerMillion != null) { - lines.push( - ` output: {`, - ` normal: ${roundPrice(pricing.outputPerMillion)},`, - ` },`, - ) - } - lines.push(` },`) - return lines -} - export function toSyncModel( native: CatalogModel, - enrich: CatalogModel | undefined, provider: SyncedProvider, ): SyncModel { return { nativeId: toNativeProviderId(native.rawId, provider), - contextWindow: native.contextWindow ?? enrich?.contextWindow ?? null, - maxOutput: native.maxOutput ?? enrich?.maxOutput ?? null, - inputModalities: pickList(native.inputModalities, enrich?.inputModalities), - outputModalities: pickList( - native.outputModalities, - enrich?.outputModalities, - ), - // Per side, so a native input price does not hide OpenRouter's output price. - pricing: { - inputPerMillion: - native.pricing.inputPerMillion ?? enrich?.pricing.inputPerMillion, - outputPerMillion: - native.pricing.outputPerMillion ?? enrich?.pricing.outputPerMillion, - }, - supportedParameters: pickList(native.capabilities, enrich?.capabilities), - reasoningMandatory: - native.reasoningMandatory || enrich?.reasoningMandatory === true, + contextWindow: native.contextWindow, + maxOutput: native.maxOutput, + inputModalities: native.inputModalities, + outputModalities: native.outputModalities, + supportedParameters: native.capabilities, + reasoningMandatory: native.reasoningMandatory, } } @@ -310,6 +210,7 @@ export function skipNativeModelReason( if (!acceptedActivities.includes(model.activity)) { return `activity ${model.activity}` } + if (model.inputModalities.length === 0) return 'no modalities' if (model.rawId.includes(':')) return 'routing variant' if (isNonChatFamily(model.rawId)) return 'non-chat family' if (matchesSkipPattern(model.rawId, skipPatterns)) return 'skip pattern' @@ -326,19 +227,11 @@ export interface NativeInsertRules { provider: SyncedProvider skipPatterns: Array acceptedActivities: Array - /** - * Hold a native id back until the modelschemas OpenRouter catalog has a - * matching row and both prices are known (from either row). The - * OpenAI/Anthropic/Gemini/Grok ModelMeta types require both prices. - */ - requireOpenRouterEnrich: boolean cutoffTimestamp: number } export interface NativeInsertSelection { inserts: Array<{ model: SyncModel; activity: string | null }> - /** Raw ids skipped only because the price gate is not met yet. */ - heldBack: Array /** Count of every other skip, by reason. */ skipped: Record } @@ -347,12 +240,10 @@ export interface NativeInsertSelection { export function selectNativeInserts( rows: Array, rules: NativeInsertRules, - openrouter: Array, isSynced: (nativeId: string) => boolean, ): NativeInsertSelection { const selection: NativeInsertSelection = { inserts: [], - heldBack: [], skipped: {}, } const skip = (reason: string) => { @@ -370,8 +261,7 @@ export function selectNativeInserts( skip(reason) continue } - const enrich = findOpenRouterEnrichment(row, rules.provider, openrouter) - const model = toSyncModel(row, enrich, rules.provider) + const model = toSyncModel(row, rules.provider) if (isSynced(model.nativeId)) { skip('already synced') continue @@ -385,13 +275,6 @@ export function selectNativeInserts( skip('image output') continue } - if ( - rules.requireOpenRouterEnrich && - !(enrich && hasFullPricing(model.pricing)) - ) { - selection.heldBack.push(row.rawId) - continue - } selection.inserts.push({ model, activity: row.activity }) } return selection diff --git a/scripts/model-sync/llmgateway.test.ts b/scripts/model-sync/llmgateway.test.ts index 95ce095378..2abdd0f5c9 100644 --- a/scripts/model-sync/llmgateway.test.ts +++ b/scripts/model-sync/llmgateway.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { catalogRowToMeta, pricePerMillion } from './llmgateway' +import { catalogRowToMeta } from './llmgateway' const minimax = { id: 'minimax-m3.1-flash', @@ -7,11 +7,6 @@ const minimax = { input_modalities: ['text', 'image'], output_modalities: ['text'], }, - pricing: { - prompt: '0.15e-6', - completion: '0.6e-6', - input_cache_read: '0.015e-6', - }, context_length: 1048576, max_output: 524288, json_output: false, @@ -28,18 +23,8 @@ const minimax = { } describe('LLM Gateway catalog rows', () => { - it('scales per-token prices to USD per million', () => { - expect(pricePerMillion('2.0e-6')).toBe(2) - expect(pricePerMillion('0.15e-6')).toBe(0.15) - expect(pricePerMillion('0')).toBeUndefined() - }) - - it('maps reasoning efforts and prices for a chat row', () => { + it('maps reasoning efforts for a chat row', () => { const meta = catalogRowToMeta(minimax) - expect(meta?.pricing).toEqual({ - input: { normal: 0.15, cached: 0.015 }, - output: { normal: 0.6 }, - }) expect(meta?.reasoning).toEqual({ levels: { low: 'low', diff --git a/scripts/model-sync/llmgateway.ts b/scripts/model-sync/llmgateway.ts index f062732203..3ede4f424f 100644 --- a/scripts/model-sync/llmgateway.ts +++ b/scripts/model-sync/llmgateway.ts @@ -59,7 +59,6 @@ export interface LlmGatewayCatalogRow { input_modalities?: unknown output_modalities?: unknown } - pricing?: Record context_length?: unknown max_output?: unknown supported_parameters?: unknown @@ -72,10 +71,6 @@ export interface LlmGatewayModelMeta { id: string contextWindow?: number maxCompletionTokens?: number - pricing: { - input?: { normal: number; cached?: number } - output?: { normal: number } - } input: Array features: Array reasoning?: ReasoningShape @@ -109,14 +104,6 @@ function positiveInt(value: unknown): number | undefined { return n } -/** Catalog prices are USD per token. Model meta stores USD per million. */ -export function pricePerMillion(value: unknown): number | undefined { - if (typeof value !== 'string' && typeof value !== 'number') return undefined - const perToken = typeof value === 'number' ? value : Number(value) - if (!Number.isFinite(perToken) || perToken <= 0) return undefined - return Math.round(perToken * 1e6 * 1e6) / 1e6 -} - function inputOf(row: LlmGatewayCatalogRow): Array { const mapped = stringList(row.architecture?.input_modalities) .map((modality) => INPUT_ALIASES[modality.toLowerCase()]) @@ -181,9 +168,6 @@ export function catalogRowToMeta( flags.add('vision') } - const normal = pricePerMillion(row.pricing?.prompt) - const cached = pricePerMillion(row.pricing?.input_cache_read) - const output = pricePerMillion(row.pricing?.completion) const maxFromProvider = providers .map((provider) => positiveInt(provider.max_output)) .find((value) => value !== undefined) @@ -192,13 +176,6 @@ export function catalogRowToMeta( id: row.id, contextWindow: positiveInt(row.context_length), maxCompletionTokens: positiveInt(row.max_output) ?? maxFromProvider, - pricing: { - input: - normal === undefined - ? undefined - : { normal, ...(cached === undefined ? {} : { cached }) }, - output: output === undefined ? undefined : { normal: output }, - }, input, features: FEATURE_ORDER.filter((feature) => flags.has(feature)), reasoning, diff --git a/scripts/model-sync/modelschemas.test.ts b/scripts/model-sync/modelschemas.test.ts index 32059a5324..048465529b 100644 --- a/scripts/model-sync/modelschemas.test.ts +++ b/scripts/model-sync/modelschemas.test.ts @@ -27,15 +27,6 @@ describe('fetchSyncCatalogs', () => { await expect(fetchSyncCatalogs(client)).rejects.toThrow('unavailable') }) - it('fails on an empty openrouter catalog instead of dropping prices', async () => { - const client = clientReturning((provider) => - json({ models: provider === 'openrouter' ? [] : [row('x')] }), - ) - await expect(fetchSyncCatalogs(client)).rejects.toThrow( - 'empty openrouter catalog', - ) - }) - it('fails on an empty native catalog instead of reporting no new models', async () => { const client = clientReturning((provider) => json({ models: provider === 'groq' ? [] : [row('x')] }), @@ -56,15 +47,19 @@ describe('fetchSyncCatalogs', () => { it('fails on a payload with no models array', async () => { const client = clientReturning(() => json({ data: [] })) - await expect(fetchSyncCatalogs(client)).rejects.toThrow('no models array') + // From client 0.2, `listModels` maps `models` to brand the ids, so it + // throws before `parseCatalogModels` can name the missing array. + await expect(fetchSyncCatalogs(client)).rejects.toThrow() }) - it('returns each native catalog next to openrouter', async () => { - const client = clientReturning((provider) => - json({ models: [row(`${provider}-model`)] }), - ) + it('returns each native catalog, and does not fetch openrouter', async () => { + const asked: Array = [] + const client = clientReturning((provider) => { + asked.push(provider) + return json({ models: [row(`${provider}-model`)] }) + }) const catalogs = await fetchSyncCatalogs(client) - expect(catalogs.openrouter[0]?.rawId).toBe('openrouter-model') - expect(catalogs.native.mistral[0]?.rawId).toBe('mistral-model') + expect(catalogs.mistral[0]?.rawId).toBe('mistral-model') + expect(asked).not.toContain('openrouter') }) }) diff --git a/scripts/model-sync/modelschemas.ts b/scripts/model-sync/modelschemas.ts index 199c803c7c..392c8aece7 100644 --- a/scripts/model-sync/modelschemas.ts +++ b/scripts/model-sync/modelschemas.ts @@ -74,21 +74,15 @@ export async function getInputSchema( } /** - * Every synced provider's native catalog plus the OpenRouter catalog. - * An empty catalog throws: it would hide every insert for that provider - * (or every price, for OpenRouter) behind a "no new models" exit 0. + * Every synced provider's own catalog. An empty catalog throws: it would + * hide every insert for that provider behind a "no new models" exit 0. */ -export async function fetchSyncCatalogs(client: SyncClient): Promise<{ - native: Record> - openrouter: Array -}> { - const [openrouter, ...nativeLists] = await Promise.all([ - fetchCatalog(client, 'openrouter'), - ...SYNCED_PROVIDERS.map((provider) => fetchCatalog(client, provider)), - ]) - if (openrouter.length === 0) { - throw new Error('modelschemas returned an empty openrouter catalog') - } +export async function fetchSyncCatalogs( + client: SyncClient, +): Promise>> { + const nativeLists = await Promise.all( + SYNCED_PROVIDERS.map((provider) => fetchCatalog(client, provider)), + ) const native = {} as Record> for (const [index, provider] of SYNCED_PROVIDERS.entries()) { const rows = nativeLists[index] @@ -97,5 +91,5 @@ export async function fetchSyncCatalogs(client: SyncClient): Promise<{ } native[provider] = rows } - return { native, openrouter } + return native } diff --git a/scripts/model-sync/provider-supports.test.ts b/scripts/model-sync/provider-supports.test.ts index 79c4dfb097..a3a68114c1 100644 --- a/scripts/model-sync/provider-supports.test.ts +++ b/scripts/model-sync/provider-supports.test.ts @@ -113,7 +113,6 @@ describe('buildAnthropicProviderOptionsType', () => { it('uses max_tokens and no sampling when sampling is not listed', () => { const type = buildAnthropicProviderOptionsType({ supportedParameters: ['max_tokens', 'reasoning', 'tools'], - hasCachedPricing: true, }) expect(type).toContain('AnthropicCacheControlOptions') expect(type).toContain('AnthropicMaxTokensOptions') diff --git a/scripts/model-sync/provider-supports.ts b/scripts/model-sync/provider-supports.ts index 12c41a5952..e63a871343 100644 --- a/scripts/model-sync/provider-supports.ts +++ b/scripts/model-sync/provider-supports.ts @@ -1,9 +1,8 @@ /** * `supports` blocks for newly synced native-provider models. * - * Input modalities and `supported_parameters` come from the native - * modelschemas catalog (OpenRouter fills a field only when the native row - * is empty). Server-tool lists are the current first-party vocabulary for + * Input modalities and `supported_parameters` come from the provider's own + * modelschemas catalog, with no second source. Server-tool lists are the current first-party vocabulary for * openai, anthropic, gemini and grok, written only when the catalog lists * `tools` or `tool_choice`. Anthropic thinking flags follow the same * parameter split as `buildAnthropicProviderOptionsType`. @@ -82,7 +81,6 @@ function quoteList(values: Array): string { interface AnthropicProviderOptionsInput { supportedParameters?: Array - hasCachedPricing?: boolean } const ANTHROPIC_BASE_OPTIONS = [ @@ -95,7 +93,7 @@ const ANTHROPIC_BASE_OPTIONS = [ /** * Per-model Anthropic provider-options intersection, inferred from the - * modelschemas capabilities (OpenRouter's when the native row has none). + * modelschemas capabilities. Every current Claude model takes prompt caching. * Does not copy another model's tool list. Thinking is not here: * `chat({ reasoning })` sets it, with the levels of each model's `reasoning` * field in `model-meta.ts`. @@ -109,10 +107,10 @@ export function buildAnthropicProviderOptionsType( const params = input.supportedParameters ?? [] const hasSampling = hasParam(params, ['temperature', 'top_p', 'top_k']) - const parts: Array = [...ANTHROPIC_BASE_OPTIONS] - if (input.hasCachedPricing) { - parts.unshift('AnthropicCacheControlOptions') - } + const parts: Array = [ + 'AnthropicCacheControlOptions', + ...ANTHROPIC_BASE_OPTIONS, + ] parts.push('AnthropicToolChoiceOptions') parts.push( diff --git a/scripts/sync-provider-models.ts b/scripts/sync-provider-models.ts index 39a1846c69..edc12efe3f 100644 --- a/scripts/sync-provider-models.ts +++ b/scripts/sync-provider-models.ts @@ -3,8 +3,8 @@ * * For each synced provider, this script: * 1. Lists that provider's models from modelschemas (`@modelschemas/client`) - * 2. Uses native limits, modalities, pricing, and capabilities. Fills any - * field the native row left empty from the modelschemas OpenRouter catalog + * 2. Uses the native row's limits, modalities, and capabilities. A field the + * row leaves empty stays empty, and a row with no modalities is skipped * 3. Identifies models missing from the provider's model-meta.ts * 4. Generates and inserts new model constants, array entries, and type map entries * @@ -34,7 +34,6 @@ import { fileURLToPath } from 'node:url' import { alreadySynced, outputsText, - pricingLines, selectNativeInserts, } from './model-sync/catalog' import type { CatalogModel, SyncModel } from './model-sync/catalog' @@ -105,13 +104,6 @@ interface ProviderConfig { skipPatterns: Array /** Activities to insert. `null` means the catalog left activity unset. */ acceptedActivities: Array - /** See `NativeInsertRules.requireOpenRouterEnrich`. */ - requireOpenRouterEnrich: boolean - /** - * Write a `pricing` block. An unknown side is left out, never written as - * 0, so Groq/Mistral can insert with `pricing: {}`. BytePlus has none. - */ - includePricing: boolean outputTokenField: 'max_output_tokens' | 'max_completion_tokens' } @@ -140,8 +132,6 @@ const PROVIDER_MAP: Record = { 'chatgpt-', // ChatGPT branded models ], acceptedActivities: ['chat'], - requireOpenRouterEnrich: true, - includePricing: true, outputTokenField: 'max_output_tokens', }, anthropic: { @@ -164,8 +154,6 @@ const PROVIDER_MAP: Record = { providerOptionsIsMappedType: false, skipPatterns: [], acceptedActivities: ['chat'], - requireOpenRouterEnrich: true, - includePricing: true, outputTokenField: 'max_output_tokens', }, gemini: { @@ -188,8 +176,6 @@ const PROVIDER_MAP: Record = { 'gemma-', // Gemma open-source models (not Gemini API models) ], acceptedActivities: ['chat'], - requireOpenRouterEnrich: true, - includePricing: true, outputTokenField: 'max_output_tokens', }, grok: { @@ -208,8 +194,6 @@ const PROVIDER_MAP: Record = { providerOptionsIsMappedType: true, skipPatterns: [], acceptedActivities: ['chat'], - requireOpenRouterEnrich: true, - includePricing: true, outputTokenField: 'max_output_tokens', }, groq: { @@ -228,8 +212,6 @@ const PROVIDER_MAP: Record = { providerOptionsIsMappedType: true, skipPatterns: ['whisper-', 'canopylabs/'], acceptedActivities: [null, 'chat'], - requireOpenRouterEnrich: false, - includePricing: true, outputTokenField: 'max_completion_tokens', }, mistral: { @@ -258,8 +240,6 @@ const PROVIDER_MAP: Record = { 'zai-', ], acceptedActivities: [null, 'chat'], - requireOpenRouterEnrich: false, - includePricing: true, outputTokenField: 'max_completion_tokens', }, byteplus: { @@ -277,8 +257,6 @@ const PROVIDER_MAP: Record = { providerOptionsIsMappedType: true, skipPatterns: [], acceptedActivities: ['chat'], - requireOpenRouterEnrich: false, - includePricing: false, outputTokenField: 'max_output_tokens', }, } @@ -307,9 +285,6 @@ function mapInputModalities(modalities: Array): Array { function anthropicOptionsType(model: SyncModel): string { return buildAnthropicProviderOptionsType({ supportedParameters: model.supportedParameters, - // modelschemas has no cache price, and every current Claude model - // supports prompt caching. - hasCachedPricing: true, }) } @@ -411,9 +386,6 @@ function generateModelConstant( }), ) lines.push(` },`) - if (config.includePricing) { - lines.push(...pricingLines(model.pricing)) - } lines.push(`} as const satisfies ${satisfiesClause(model, provider, config)}`) return lines.join('\n') } @@ -444,14 +416,11 @@ function detectChangedPackages(): Set { async function syncModelMeta( provider: SyncedProvider, config: ProviderConfig, - catalogs: { - native: Record> - openrouter: Array - }, + catalogs: Record>, cutoffTimestamp: number, -): Promise<{ added: number; heldBack: number }> { +): Promise { console.log(`\nProcessing provider: ${provider}`) - const rows = catalogs.native[provider] + const rows = catalogs[provider] console.log(` Found ${rows.length} modelschemas models for '${provider}'`) let content = await readFile(config.metaFile, 'utf-8') @@ -461,23 +430,19 @@ async function syncModelMeta( ` Existing models in file: ${existingIds.size} IDs, ${existingConstNames.size} constants`, ) - const { inserts, heldBack, skipped } = selectNativeInserts( + const { inserts, skipped } = selectNativeInserts( rows, { ...config, provider, cutoffTimestamp }, - catalogs.openrouter, (nativeId) => alreadySynced(nativeId, existingIds, existingConstNames), ) const skipCounts = Object.entries(skipped) .map(([reason, count]) => `${reason}=${count}`) .join(', ') if (skipCounts) console.log(` Skipped: ${skipCounts}`) - for (const rawId of heldBack) { - console.log(` Waiting for an OpenRouter price: ${rawId}`) - } if (inserts.length === 0) { console.log(' No new models to add.') - return { added: 0, heldBack: heldBack.length } + return 0 } console.log(` Adding ${inserts.length} new models:`) @@ -504,7 +469,7 @@ async function syncModelMeta( await writeFile(config.metaFile, content, 'utf-8') console.log(` Wrote updated file: ${config.metaFile}`) - return { added: inserts.length, heldBack: heldBack.length } + return inserts.length } async function main() { @@ -519,40 +484,28 @@ async function main() { ) const catalogs = await fetchSyncCatalogs(createSyncClient()) - const counts = Object.entries(catalogs.native) + const counts = Object.entries(catalogs) .map(([id, rows]) => `${id}=${rows.length}`) .join(', ') - console.log( - `Fetched modelschemas catalogs: ${counts}, openrouter=${catalogs.openrouter.length}`, - ) + console.log(`Fetched modelschemas catalogs: ${counts}`) const changedPackages = new Set() let totalAdded = 0 - let totalHeldBack = 0 for (const [provider, config] of Object.entries(PROVIDER_MAP) as Array< [SyncedProvider, ProviderConfig] >) { - const { added, heldBack } = await syncModelMeta( + const added = await syncModelMeta( provider, config, catalogs, cutoffTimestamp, ) totalAdded += added - totalHeldBack += heldBack if (added > 0) changedPackages.add(config.packageName) } console.log(`\nDone. Added ${totalAdded} new models total.`) - // Held-back ids still age out after 30 days. Freezing the timestamp - // instead would stall it for good on ids OpenRouter never prices (Live - // API models), so each run names them in its log. - if (totalHeldBack > 0) { - console.log( - `${totalHeldBack} models are waiting for an OpenRouter price (listed above).`, - ) - } await writeLastRunTimestamp() const allChangedPackages = detectChangedPackages() diff --git a/skills/tanstack-ai/SKILL.md b/skills/tanstack-ai/SKILL.md index a443e3c963..10128fe78e 100644 --- a/skills/tanstack-ai/SKILL.md +++ b/skills/tanstack-ai/SKILL.md @@ -90,6 +90,8 @@ lockfile shows which package manager to use. Things that trip agents up: +- Model prices: never from memory and never from `model-meta.ts` (it has + none). Read them live from modelschemas, as in `docs/advanced/model-prices.md`. - Model ids: never from memory. Most adapters ship them in `node_modules/@tanstack/ai-/src/model-meta.ts`; take the newest id with the capability the task needs. diff --git a/tsconfig.docs.json b/tsconfig.docs.json index 46a896715f..a846b65aa7 100644 --- a/tsconfig.docs.json +++ b/tsconfig.docs.json @@ -41,6 +41,12 @@ "./node_modules/.kiira/node_modules/@fal-ai/client/src/realtime/index.d.ts", "./examples/ts-react-media/node_modules/@fal-ai/client/src/realtime/index.d.ts" ], + "@modelschemas/rate-card/evaluator": [ + "./node_modules/.kiira/node_modules/@modelschemas/rate-card/dist/evaluator.d.ts" + ], + "@modelschemas/rate-card/schema": [ + "./node_modules/.kiira/node_modules/@modelschemas/rate-card/dist/rate-card.schema.d.ts" + ], "@upstash/agentkit-tanstack-ai/persistence": [ "./node_modules/.kiira/node_modules/@upstash/agentkit-tanstack-ai/dist/persistence.d.ts" ],