Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
31 kB · 971 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972import { CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL, CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL, CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL, CLOUDFLARE_WORKERS_AI_BASE_URL,} from "./providers/cloudflare.js";import type { AnthropicMessagesCompat, Api, Model, ModelThinkingLevel, OpenAICompletionsCompat } from "./types.js";
export const MODELS_DEV_API_URL = "https://models.dev/api.json";
export interface ModelsDevModel { id?: string; name?: string; tool_call?: boolean; reasoning?: boolean; status?: string; limit?: { context?: number; output?: number; }; cost?: { input?: number; output?: number; cache_read?: number; cache_write?: number; }; modalities?: { input?: string[]; output?: string[]; }; provider?: { npm?: string; };}
export interface ModelsDevProviderData { models?: Record<string, ModelsDevModel>;}
const COPILOT_STATIC_HEADERS = { "User-Agent": "GitHubCopilotChat/0.35.0", "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat",} as const;
const KIMI_STATIC_HEADERS = { "User-Agent": "KimiCLI/1.5",} as const;
const ZAI_TOOL_STREAM_UNSUPPORTED_MODELS = new Set(["glm-4.5", "glm-4.5-air", "glm-4.5-flash", "glm-4.5v"]);
const EAGER_TOOL_INPUT_STREAMING_UNSUPPORTED_ANTHROPIC_MODELS = new Set([ "github-copilot:claude-haiku-4.5", "github-copilot:claude-sonnet-4", "github-copilot:claude-sonnet-4.5",]);
const DEEPSEEK_V4_THINKING_LEVEL_MAP: Partial<Record<ModelThinkingLevel, string | null>> = { minimal: null, low: null, medium: null, high: "high", xhigh: "max", max: null,};
const KIMI_K3_THINKING_LEVEL_MAP: Partial<Record<ModelThinkingLevel, string | null>> = { off: null, minimal: null, low: null, medium: null, high: null, xhigh: null, max: "max",};
const DEEPSEEK_V4_COMPAT: OpenAICompletionsCompat = { requiresReasoningContentOnAssistantMessages: true, thinkingFormat: "deepseek",};
const ZAI_THINKING_COMPAT: OpenAICompletionsCompat = { supportsReasoningEffort: false, thinkingFormat: "zai",};
const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([ "gpt-5.1", "gpt-5.2", "gpt-5.3-codex", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.5", "gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",]);
function getAnthropicMessagesCompat(provider: string, modelId: string): AnthropicMessagesCompat | undefined { return EAGER_TOOL_INPUT_STREAMING_UNSUPPORTED_ANTHROPIC_MODELS.has(`${provider}:${modelId}`) ? { supportsEagerToolInputStreaming: false } : undefined;}
function getBedrockBaseUrl(modelId: string): string { if (modelId.startsWith("eu.")) return "https://bedrock-runtime.eu-central-1.amazonaws.com"; if (modelId.startsWith("au.")) return "https://bedrock-runtime.ap-southeast-2.amazonaws.com"; if (modelId.startsWith("jp.")) return "https://bedrock-runtime.ap-northeast-1.amazonaws.com"; return "https://bedrock-runtime.us-east-1.amazonaws.com";}
function mergeThinkingLevelMap(model: Model<Api>, map: NonNullable<Model<Api>["thinkingLevelMap"]>): void { model.thinkingLevelMap = { ...model.thinkingLevelMap, ...map };}
function supportsOpenAiXhigh(modelId: string): boolean { return ( modelId.includes("gpt-5.2") || modelId.includes("gpt-5.3") || modelId.includes("gpt-5.4") || modelId.includes("gpt-5.5") || modelId.includes("gpt-5.6") || modelId.includes("gpt-6") );}
function isGoogleThinkingApi(model: Model<Api>): boolean { return model.api === "google-generative-ai" || model.api === "google-vertex";}
function isGemini3ProModel(modelId: string): boolean { return /gemini-3(?:\.\d+)?-pro/.test(modelId.toLowerCase());}
function isGemini3FlashModel(modelId: string): boolean { return /gemini-3(?:\.\d+)?-flash/.test(modelId.toLowerCase());}
function isGemma4Model(modelId: string): boolean { return /gemma-?4/.test(modelId.toLowerCase());}
export function applyThinkingLevelMetadata(model: Model<Api>): void { if ( (model.api === "openai-responses" || model.api === "azure-openai-responses") && (model.id.startsWith("gpt-5") || model.id.startsWith("gpt-6")) ) { mergeThinkingLevelMap(model, { off: null }); } if ( model.api === "openai-responses" && model.provider === "openai" && OPENAI_RESPONSES_NONE_REASONING_MODELS.has(model.id) ) { mergeThinkingLevelMap(model, { off: "none" }); } if (supportsOpenAiXhigh(model.id)) { mergeThinkingLevelMap(model, { xhigh: "xhigh" }); } if (model.id.includes("gpt-5.6") || model.id.includes("gpt-6")) { mergeThinkingLevelMap(model, { minimal: null, max: "max" }); } if ( model.id.includes("opus-4-6") || model.id.includes("opus-4.6") || model.id.includes("sonnet-4-6") || model.id.includes("sonnet-4.6") ) { mergeThinkingLevelMap(model, { max: "max" }); } if ( model.id.includes("opus-4-7") || model.id.includes("opus-4.7") || model.id.includes("opus-4-8") || model.id.includes("opus-4.8") || model.id.includes("opus-5") || model.id.includes("sonnet-5") ) { mergeThinkingLevelMap(model, { xhigh: "xhigh", max: "max" }); } if (model.id.includes("fable-5") || model.id.includes("mythos-5")) { mergeThinkingLevelMap(model, { off: null, xhigh: "xhigh", max: "max" }); } if (model.id.includes("mythos-preview")) { mergeThinkingLevelMap(model, { off: null, max: "max" }); } if ( model.api === "openai-completions" && (model.id.includes("deepseek-v4") || model.id === "deepseek-flash" || model.id.endsWith("/deepseek-flash")) ) { mergeThinkingLevelMap(model, DEEPSEEK_V4_THINKING_LEVEL_MAP); } const kimiK3Id = model.id.toLowerCase(); if (!model.thinkingLevelMap && (/^k3(-|$)/.test(kimiK3Id) || /(^|\/)kimi-k3(-|$)/.test(kimiK3Id))) { mergeThinkingLevelMap(model, KIMI_K3_THINKING_LEVEL_MAP); } if (isGoogleThinkingApi(model) && isGemini3ProModel(model.id)) { mergeThinkingLevelMap(model, { off: null, minimal: null, low: "LOW", medium: null, high: "HIGH" }); } if (isGoogleThinkingApi(model) && isGemini3FlashModel(model.id)) { mergeThinkingLevelMap(model, { off: null }); } if (isGoogleThinkingApi(model) && isGemma4Model(model.id)) { mergeThinkingLevelMap(model, { off: null, minimal: "MINIMAL", low: null, medium: null, high: "HIGH" }); }}
export function parseModelsDevData(data: Record<string, unknown>): Model<Api>[] { const models: Model<Api>[] = []; const dataObj = data as Record<string, ModelsDevProviderData | undefined>;
// Amazon Bedrock if (dataObj["amazon-bedrock"]?.models) { for (const [modelId, model] of Object.entries(dataObj["amazon-bedrock"].models)) { if (model.tool_call !== true) continue; if (modelId.startsWith("ai21.jamba") || modelId.startsWith("mistral.mistral-7b-instruct-v0")) continue;
models.push({ id: modelId, name: model.name || modelId, api: "bedrock-converse-stream", provider: "amazon-bedrock", baseUrl: getBedrockBaseUrl(modelId), reasoning: model.reasoning === true, input: (model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"]) as ("text" | "image")[], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Anthropic if (dataObj.anthropic?.models) { for (const [modelId, model] of Object.entries(dataObj.anthropic.models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "anthropic-messages", provider: "anthropic", baseUrl: "https://api.anthropic.com", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Google const googleUnsupportedApiModelPattern = /(^|[-_.])(live|deep-research|computer-use)($|[-_.])/i; if (dataObj.google?.models) { for (const [modelId, model] of Object.entries(dataObj.google.models)) { if (model.tool_call !== true) continue; if (googleUnsupportedApiModelPattern.test(modelId)) continue; if (model.modalities?.output?.includes("image")) continue;
models.push({ id: modelId, name: model.name || modelId, api: "google-generative-ai", provider: "google", baseUrl: "https://generativelanguage.googleapis.com/v1beta", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// OpenAI if (dataObj.openai?.models) { for (const [modelId, model] of Object.entries(dataObj.openai.models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "openai-responses", provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Groq if (dataObj.groq?.models) { for (const [modelId, model] of Object.entries(dataObj.groq.models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "openai-completions", provider: "groq", baseUrl: "https://api.groq.com/openai/v1", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Cerebras if (dataObj.cerebras?.models) { for (const [modelId, model] of Object.entries(dataObj.cerebras.models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "openai-completions", provider: "cerebras", baseUrl: "https://api.cerebras.ai/v1", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Cloudflare Workers AI if (dataObj["cloudflare-workers-ai"]?.models) { for (const [modelId, model] of Object.entries(dataObj["cloudflare-workers-ai"].models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "openai-completions", provider: "cloudflare-workers-ai", baseUrl: CLOUDFLARE_WORKERS_AI_BASE_URL, reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, compat: { sendSessionAffinityHeaders: true }, }); } }
// Cloudflare AI Gateway if (dataObj["cloudflare-ai-gateway"]?.models) { for (const [prefixedId, model] of Object.entries(dataObj["cloudflare-ai-gateway"].models)) { if (model.tool_call !== true) continue;
const slashIdx = prefixedId.indexOf("/"); if (slashIdx === -1) continue; const upstream = prefixedId.slice(0, slashIdx); const nativeId = prefixedId.slice(slashIdx + 1);
let api: "anthropic-messages" | "openai-completions" | "openai-responses"; let baseUrl: string; let id: string; if (upstream === "openai") { api = "openai-responses"; baseUrl = CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL; id = nativeId; } else if (upstream === "anthropic") { api = "anthropic-messages"; baseUrl = CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL; id = nativeId; } else if (upstream === "workers-ai") { api = "openai-completions"; baseUrl = CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL; id = prefixedId; } else { continue; }
const compat = upstream === "workers-ai" ? { sendSessionAffinityHeaders: true } : undefined;
models.push({ id, name: model.name || id, api, provider: "cloudflare-ai-gateway", baseUrl, reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, ...(compat ? { compat } : {}), }); } }
// xAI if (dataObj.xai?.models) { for (const [modelId, model] of Object.entries(dataObj.xai.models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "openai-completions", provider: "xai", baseUrl: "https://api.x.ai/v1", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// zAI if (dataObj["zai-coding-plan"]?.models) { for (const [modelId, model] of Object.entries(dataObj["zai-coding-plan"].models)) { if (model.tool_call !== true) continue; const supportsImage = model.modalities?.input?.includes("image");
models.push({ id: modelId, name: model.name || modelId, api: "openai-completions", provider: "zai", baseUrl: "https://api.z.ai/api/coding/paas/v4", reasoning: model.reasoning === true, input: supportsImage ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, compat: { supportsDeveloperRole: false, thinkingFormat: ZAI_THINKING_COMPAT.thinkingFormat, ...(!ZAI_TOOL_STREAM_UNSUPPORTED_MODELS.has(modelId) ? { zaiToolStream: true } : {}), }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Mistral if (dataObj.mistral?.models) { for (const [modelId, model] of Object.entries(dataObj.mistral.models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "mistral-conversations", provider: "mistral", baseUrl: "https://api.mistral.ai", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Hugging Face if (dataObj.huggingface?.models) { for (const [modelId, model] of Object.entries(dataObj.huggingface.models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "openai-completions", provider: "huggingface", baseUrl: "https://router.huggingface.co/v1", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, compat: { supportsDeveloperRole: false, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Fireworks if (dataObj["fireworks-ai"]?.models) { for (const [modelId, model] of Object.entries(dataObj["fireworks-ai"].models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "anthropic-messages", provider: "fireworks", baseUrl: "https://api.fireworks.ai/inference", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// OpenCode and OpenCode Go const opencodeVariants = [ { key: "opencode", provider: "opencode" as const, basePath: "https://opencode.ai/zen" }, { key: "opencode-go", provider: "opencode-go" as const, basePath: "https://opencode.ai/zen/go" }, ];
for (const variant of opencodeVariants) { if (!dataObj[variant.key]?.models) continue;
for (const [modelId, model] of Object.entries(dataObj[variant.key]!.models!)) { if (model.tool_call !== true) continue; if (model.status === "deprecated") continue; if (modelId === "gpt-5.3-codex-spark") continue;
const npm = model.provider?.npm; let api: Api; let baseUrl: string; let compat: OpenAICompletionsCompat | undefined;
if (npm === "@ai-sdk/openai") { api = "openai-responses"; baseUrl = `${variant.basePath}/v1`; } else if (npm === "@ai-sdk/anthropic") { api = "anthropic-messages"; baseUrl = variant.basePath; } else if (npm === "@ai-sdk/google") { api = "google-generative-ai"; baseUrl = `${variant.basePath}/v1`; } else if (npm === "@ai-sdk/alibaba") { api = "openai-completions"; baseUrl = `${variant.basePath}/v1`; compat = { cacheControlFormat: "anthropic" }; } else { api = "openai-completions"; baseUrl = `${variant.basePath}/v1`; }
if (variant.provider === "opencode-go") { if (modelId === "minimax-m2.7" || (npm === "@ai-sdk/anthropic" && modelId.startsWith("qwen"))) { api = "openai-completions"; baseUrl = `${variant.basePath}/v1`; } if (modelId === "qwen3.5-plus" || modelId === "qwen3.6-plus") { api = "openai-completions"; baseUrl = `${variant.basePath}/v1`; compat = { ...(compat ?? {}), thinkingFormat: "qwen" }; } }
models.push({ id: modelId, name: model.name || modelId, api, provider: variant.provider, baseUrl, reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, ...(compat ? { compat } : {}), contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// GitHub Copilot if (dataObj["github-copilot"]?.models) { for (const [modelId, model] of Object.entries(dataObj["github-copilot"].models)) { if (model.tool_call !== true) continue; if (model.status === "deprecated") continue;
const isCopilotClaude = modelId.startsWith("claude-"); const needsResponsesApi = modelId.startsWith("gpt-5") || modelId.startsWith("oswe");
const api: Api = isCopilotClaude ? "anthropic-messages" : needsResponsesApi ? "openai-responses" : "openai-completions";
const anthropicCompat = api === "anthropic-messages" ? getAnthropicMessagesCompat("github-copilot", modelId) : undefined;
const copilotModel: Model<Api> = { id: modelId, name: model.name || modelId, api, provider: "github-copilot", baseUrl: "https://api.individual.githubcopilot.com", reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 128000, maxTokens: model.limit?.output || 8192, headers: { ...COPILOT_STATIC_HEADERS }, ...(anthropicCompat ? { compat: anthropicCompat } : {}), ...(api === "openai-completions" ? { compat: { supportsStore: false, supportsDeveloperRole: false, supportsReasoningEffort: false, }, } : {}), };
models.push(copilotModel); } }
// MiniMax const minimaxVariants = [ { key: "minimax", provider: "minimax" as const, baseUrl: "https://api.minimax.io/anthropic" }, { key: "minimax-cn", provider: "minimax-cn" as const, baseUrl: "https://api.minimaxi.com/anthropic" }, ];
for (const { key, provider, baseUrl } of minimaxVariants) { if (dataObj[key]?.models) { for (const [modelId, model] of Object.entries(dataObj[key]!.models!)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "anthropic-messages", provider, baseUrl, reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } } }
// Kimi for Coding if (dataObj["kimi-for-coding"]?.models) { const kimiModels = dataObj["kimi-for-coding"].models; const hasCanonicalModel = Object.hasOwn(kimiModels, "kimi-for-coding"); const kimiAliases = new Set(["k2p5", "k2p6"]);
for (const [modelId, model] of Object.entries(kimiModels)) { if (model.tool_call !== true) continue; if (kimiAliases.has(modelId) && hasCanonicalModel) continue;
const normalizedId = kimiAliases.has(modelId) ? "kimi-for-coding" : modelId; const normalizedName = kimiAliases.has(modelId) ? "Kimi For Coding" : model.name || normalizedId;
models.push({ id: normalizedId, name: normalizedName, api: "anthropic-messages", provider: "kimi-coding", baseUrl: "https://api.kimi.com/coding", headers: { ...KIMI_STATIC_HEADERS }, reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } }
// Moonshot AI const moonshotVariants = [ { key: "moonshotai", provider: "moonshotai" as const, baseUrl: "https://api.moonshot.ai/v1" }, { key: "moonshotai-cn", provider: "moonshotai-cn" as const, baseUrl: "https://api.moonshot.cn/v1" }, ]; const moonshotCompat: OpenAICompletionsCompat = { supportsStore: false, supportsDeveloperRole: false, supportsReasoningEffort: false, maxTokensField: "max_tokens", supportsStrictMode: false, };
for (const { key, provider, baseUrl } of moonshotVariants) { if (!dataObj[key]?.models) continue;
for (const [modelId, model] of Object.entries(dataObj[key]!.models!)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "openai-completions", provider, baseUrl, reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, compat: moonshotCompat, }); } }
// Xiaomi MiMo const xiaomiVariants = [ { provider: "xiaomi" as const, baseUrl: "https://api.xiaomimimo.com/anthropic" }, { provider: "xiaomi-token-plan-cn" as const, baseUrl: "https://token-plan-cn.xiaomimimo.com/anthropic" }, { provider: "xiaomi-token-plan-ams" as const, baseUrl: "https://token-plan-ams.xiaomimimo.com/anthropic" }, { provider: "xiaomi-token-plan-sgp" as const, baseUrl: "https://token-plan-sgp.xiaomimimo.com/anthropic" }, ];
if (dataObj.xiaomi?.models) { for (const { provider, baseUrl } of xiaomiVariants) { for (const [modelId, model] of Object.entries(dataObj.xiaomi.models)) { if (model.tool_call !== true) continue;
models.push({ id: modelId, name: model.name || modelId, api: "anthropic-messages", provider, baseUrl, reasoning: model.reasoning === true, input: model.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], cost: { input: model.cost?.input || 0, output: model.cost?.output || 0, cacheRead: model.cost?.cache_read || 0, cacheWrite: model.cost?.cache_write || 0, }, contextWindow: model.limit?.context || 4096, maxTokens: model.limit?.output || 4096, }); } } }
// Apply post-processing and known fixes applyModelsDevOverrides(models);
// Mirror OpenAI responses models to Azure OpenAI const azureOpenAiModels: Model<Api>[] = models .filter((model) => model.provider === "openai" && model.api === "openai-responses") .map((model) => ({ ...model, api: "azure-openai-responses", provider: "azure-openai-responses", baseUrl: "", })); models.push(...azureOpenAiModels);
// Apply thinking level metadata for (const model of models) { applyThinkingLevelMetadata(model); }
return models;}
function applyModelsDevOverrides(models: Model<Api>[]): void { // models.dev prices the Z.AI coding plan at zero, because that plan is billed by // subscription. A zero price is true for the wallet and useless for comparing two // models, so the published per-token prices stand in. Source: artificialanalysis.ai, // retrieved 2026-09-11. const zaiListPrices: Record<string, { input: number; output: number }> = { "glm-5.3-flash": { input: 0.15, output: 0.5 }, "glm-5.3": { input: 1.4, output: 4.4 }, }; for (const candidate of models) { if (candidate.provider !== "zai") continue; const listPrice = zaiListPrices[candidate.id]; if (!listPrice) continue; candidate.cost.input = listPrice.input; candidate.cost.output = listPrice.output; }
// Fix Claude Opus 4.5 cache pricing from models.dev const opus45 = models.find((m) => m.provider === "anthropic" && m.id === "claude-opus-4-5"); if (opus45) { opus45.cost.cacheRead = 0.5; opus45.cost.cacheWrite = 6.25; }
for (const candidate of models) { if (candidate.provider === "amazon-bedrock" && candidate.id.includes("anthropic.claude-opus-4-6-v1")) { candidate.cost.cacheRead = 0.5; candidate.cost.cacheWrite = 6.25; }
if ( (candidate.provider === "anthropic" || candidate.provider === "opencode" || candidate.provider === "opencode-go" || candidate.provider === "github-copilot") && (candidate.id === "claude-opus-4-6" || candidate.id === "claude-sonnet-4-6" || candidate.id === "claude-opus-4.6" || candidate.id === "claude-sonnet-4.6") ) { candidate.contextWindow = 1000000; }
if ( (candidate.provider === "opencode" || candidate.provider === "opencode-go") && (candidate.id === "claude-sonnet-4-5" || candidate.id === "claude-sonnet-4") ) { candidate.contextWindow = 200000; }
if ((candidate.provider === "opencode" || candidate.provider === "opencode-go") && candidate.id === "gpt-5.4") { candidate.contextWindow = 272000; candidate.maxTokens = 128000; }
if (candidate.provider === "openai" && (candidate.id === "gpt-5.4" || candidate.id === "gpt-5.5")) { candidate.contextWindow = 272000; candidate.maxTokens = 128000; }
if ( candidate.api === "openai-completions" && (candidate.id.includes("deepseek-v4") || candidate.id === "deepseek-flash" || candidate.id.endsWith("/deepseek-flash")) ) { candidate.compat = { ...candidate.compat, ...DEEPSEEK_V4_COMPAT, }; mergeThinkingLevelMap(candidate, DEEPSEEK_V4_THINKING_LEVEL_MAP); } }
const minimaxDirectSupportedIds = new Set(["MiniMax-M2.7", "MiniMax-M2.7-highspeed"]);
for (const candidate of models) { if ( (candidate.provider === "minimax" || candidate.provider === "minimax-cn") && minimaxDirectSupportedIds.has(candidate.id) ) { candidate.contextWindow = 204800; candidate.maxTokens = 131072; } }
for (let i = models.length - 1; i >= 0; i--) { const candidate = models[i]; if ( (candidate.provider === "minimax" || candidate.provider === "minimax-cn") && !minimaxDirectSupportedIds.has(candidate.id) ) { models.splice(i, 1); } }}
export async function fetchModelsDevModels( fetchFn: typeof fetch = fetch, url: string = MODELS_DEV_API_URL,): Promise<Model<Api>[]> { const response = await fetchFn(url); if (!response.ok) { throw new Error(`Failed to fetch models from ${url}: ${response.status} ${response.statusText}`); } const data = (await response.json()) as Record<string, unknown>; return parseModelsDevData(data);}