import {
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);
}