Files
sproutclaw-data/agent/sessions/--shumengya-project-agent-sproutclaw--/2026-06-21T10-22-55-059Z_019ee9b4-6853-78f5-a2ba-630dec3085bf.jsonl
shumengya 3a1404f87a feat: 添加 agent/sessions 会话记录备份
数据备份仓库应包含完整会话历史,便于恢复对话上下文。
2026-06-26 15:52:31 +08:00

53 lines
225 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"type":"session","version":3,"id":"019ee9b4-6853-78f5-a2ba-630dec3085bf","timestamp":"2026-06-21T10:22:55.059Z","cwd":"/shumengya/project/agent/sproutclaw"}
{"type":"model_change","id":"65f513dd","parentId":null,"timestamp":"2026-06-21T10:22:55.334Z","provider":"xiaomimimo-openai","modelId":"mimo-v2.5"}
{"type":"thinking_level_change","id":"86ce5c2a","parentId":"65f513dd","timestamp":"2026-06-21T10:22:55.334Z","thinkingLevel":"high"}
{"type":"model_change","id":"d897b94b","parentId":"86ce5c2a","timestamp":"2026-06-21T10:23:11.081Z","provider":"opencode-go-openai","modelId":"glm-5.1"}
{"type":"thinking_level_change","id":"2f2141b9","parentId":"d897b94b","timestamp":"2026-06-21T10:23:11.082Z","thinkingLevel":"off"}
{"type":"message","id":"740d7249","parentId":"2f2141b9","timestamp":"2026-06-21T10:23:58.074Z","message":{"role":"user","content":[{"type":"text","text":"给我的SproutClaw的models.json添加opencode-go的最新的glm5.2模型"}],"timestamp":1782037438073}}
{"type":"message","id":"a9be4535","parentId":"740d7249","timestamp":"2026-06-21T10:24:00.298Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"The user wants to add the latest GLM 5.2 model from opencode-go to their SproutClaw project's models.json. Let me first find the models.json file and understand its structure.","thinkingSignature":"reasoning_content"},{"type":"toolCall","id":"call_7557","name":"fffind","arguments":{"pattern":"models.json","path":"/shumengya/project/agent/sproutclaw"}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":9411,"output":71,"cacheRead":928,"cacheWrite":0,"totalTokens":10410,"cost":{"input":0.013175399999999999,"output":0.0003124,"cacheRead":0.00024128,"cacheWrite":0,"total":0.013729079999999998}},"stopReason":"toolUse","timestamp":1782037438074,"responseId":"chatcmpl-RDKuMFKhZRht2wIug9xmmc5z","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"fe67188d","parentId":"a9be4535","timestamp":"2026-06-21T10:24:00.303Z","message":{"role":"toolResult","toolCallId":"call_7557","toolName":"fffind","content":[{"type":"text","text":"packages/coding-agent/tsconfig.examples.json\npackages/coding-agent/src/modes/interactive/theme/theme-schema.json\npackages/coding-agent/src/modes/rpc/jsonl.ts\npackages/coding-agent/src/modes/interactive/theme/light.json\npackages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json\npackages/coding-agent/src/modes/interactive/theme/dark.json\npackages/coding-agent/npm-shrinkwrap.json\npackages/coding-agent/tsconfig.build.json\npackages/coding-agent/examples/extensions/dynamic-resources/dynamic.json\npackages/coding-agent/examples/extensions/custom-provider-anthropic/package.json\npackages/coding-agent/examples/extensions/sandbox/package.json"}],"details":{"totalMatched":11,"totalFiles":794,"pageIndex":0,"hasMore":false},"isError":false,"timestamp":1782037440303}}
{"type":"message","id":"72b15114","parentId":"fe67188d","timestamp":"2026-06-21T10:24:02.201Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_34ad","name":"fffind","arguments":{"path":"/shumengya/project/agent/sproutclaw","pattern":"models","exclude":"node_modules,test,.git"}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":149,"output":38,"cacheRead":10400,"cacheWrite":0,"totalTokens":10587,"cost":{"input":0.0002086,"output":0.0001672,"cacheRead":0.002704,"cacheWrite":0,"total":0.0030797999999999997}},"stopReason":"toolUse","timestamp":1782037440303,"responseId":"chatcmpl-REcQtl5TpzPUtCEOyNFM6Q2t","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"3cef41a1","parentId":"72b15114","timestamp":"2026-06-21T10:24:02.208Z","message":{"role":"toolResult","toolCallId":"call_34ad","toolName":"fffind","content":[{"type":"text","text":"packages/ai/src/image-models.generated.ts [modified in git]\npackages/ai/src/models.generated.ts\npackages/ai/scripts/generate-models.ts\npackages/ai/src/image-models.ts\npackages/ai/src/models.ts\npackages/coding-agent/src/cli/list-models.ts\npackages/ai/scripts/generate-image-models.ts\npackages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts\npackages/coding-agent/docs/models.md\npackages/coding-agent/src/modes/interactive/components/model-selector.ts\npackages/coding-agent/examples/extensions/model-status.ts\npackages/coding-agent/examples/sdk/02-custom-model.ts\npackages/coding-agent/src/core/model-resolver.ts\npackages/coding-agent/src/core/model-registry.ts\npackages/coding-agent/examples/extensions/plan-mode/utils.ts\npackages/coding-agent/examples/extensions/minimal-mode.ts\npackages/coding-agent/src/modes/interactive/interactive-mode.ts\npackages/coding-agent/src/modes/rpc/rpc-mode.ts\npackages/coding-agent/src/modes/print-mode.ts\npackages/coding-agent/src/modes/interactive/components/bordered-loader.ts\npackages/coding-agent/src/modes/interactive/components/dynamic-border.ts\npackages/coding-agent/src/modes/interactive/components/oauth-selector.ts\npackages/coding-agent/src/modes/interactive/components/session-selector-search.ts\npackages/coding-agent/src/modes/index.ts\npackages/coding-agent/src/modes/interactive/components/config-selector.ts\npackages/coding-agent/src/modes/interactive/components/extension-selector.ts\npackages/coding-agent/src/modes/interactive/components/index.ts\npackages/coding-agent/src/modes/interactive/components/login-dialog.ts\npackages/coding-agent/src/modes/interactive/components/session-selector.ts\npackages/coding-agent/src/modes/rpc/rpc-types.ts\n\n[348 more matches available. cursor=\"1\" to continue]"}],"details":{"totalMatched":378,"totalFiles":794,"pageIndex":0,"hasMore":true},"isError":false,"timestamp":1782037442208}}
{"type":"message","id":"e59bd6ed","parentId":"3cef41a1","timestamp":"2026-06-21T10:24:03.971Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_2c74","name":"read","arguments":{"path":"/shumengya/project/agent/sproutclaw/packages/ai/scripts/generate-models.ts"}},{"type":"toolCall","id":"call_1d63","name":"read","arguments":{"path":"/shumengya/project/agent/sproutclaw/packages/coding-agent/docs/models.md"}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":415,"output":57,"cacheRead":10560,"cacheWrite":0,"totalTokens":11032,"cost":{"input":0.000581,"output":0.0002508,"cacheRead":0.0027456,"cacheWrite":0,"total":0.0035774}},"stopReason":"toolUse","timestamp":1782037442208,"responseId":"chatcmpl-RXmR4tYyyjpa1a7aucdxy5lW","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"576382d4","parentId":"e59bd6ed","timestamp":"2026-06-21T10:24:03.979Z","message":{"role":"toolResult","toolCallId":"call_2c74","toolName":"read","content":[{"type":"text","text":"#!/usr/bin/env node\n\nimport { writeFileSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport {\n\tCLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL,\n\tCLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL,\n\tCLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL,\n\tCLOUDFLARE_WORKERS_AI_BASE_URL,\n} from \"../src/providers/cloudflare.ts\";\nimport type { AnthropicMessagesCompat, Api, KnownProvider, Model, OpenAICompletionsCompat } from \"../src/types.ts\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageRoot = join(__dirname, \"..\");\n\ninterface ModelsDevModel {\n\tid: string;\n\tname: string;\n\ttool_call?: boolean;\n\treasoning?: boolean;\n\tlimit?: {\n\t\tcontext?: number;\n\t\toutput?: number;\n\t};\n\tcost?: {\n\t\tinput?: number;\n\t\toutput?: number;\n\t\tcache_read?: number;\n\t\tcache_write?: number;\n\t};\n\tmodalities?: {\n\t\tinput?: string[];\n\t};\n\tprovider?: {\n\t\tnpm?: string;\n\t};\n}\n\ninterface AiGatewayModel {\n\tid: string;\n\tname?: string;\n\tcontext_window?: number;\n\tmax_tokens?: number;\n\ttags?: string[];\n\tpricing?: {\n\t\tinput?: string | number;\n\t\toutput?: string | number;\n\t\tinput_cache_read?: string | number;\n\t\tinput_cache_write?: string | number;\n\t};\n}\n\nconst COPILOT_STATIC_HEADERS = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"Copilot-Integration-Id\": \"vscode-chat\",\n} as const;\n\nconst KIMI_STATIC_HEADERS = {\n\t\"User-Agent\": \"KimiCLI/1.5\",\n} as const;\n\nconst TOGETHER_BASE_URL = \"https://api.together.ai/v1\";\nconst TOGETHER_BASE_COMPAT: OpenAICompletionsCompat = {\n\tsupportsStore: false,\n\tsupportsDeveloperRole: false,\n\tsupportsReasoningEffort: false,\n\tmaxTokensField: \"max_tokens\",\n\tsupportsStrictMode: false,\n\tsupportsLongCacheRetention: false,\n};\nconst TOGETHER_TOGGLE_REASONING_COMPAT: OpenAICompletionsCompat = {\n\t...TOGETHER_BASE_COMPAT,\n\tthinkingFormat: \"together\",\n};\nconst TOGETHER_REASONING_EFFORT_COMPAT: OpenAICompletionsCompat = {\n\t...TOGETHER_BASE_COMPAT,\n\tsupportsReasoningEffort: true,\n\tthinkingFormat: \"openai\",\n};\nconst TOGETHER_TOGGLE_REASONING_EFFORT_COMPAT: OpenAICompletionsCompat = {\n\t...TOGETHER_TOGGLE_REASONING_COMPAT,\n\tsupportsReasoningEffort: true,\n};\nconst TOGETHER_REASONING_ONLY_MODELS = new Set([\n\t\"deepseek-ai/DeepSeek-R1\",\n\t\"MiniMaxAI/MiniMax-M2.5\",\n\t\"MiniMaxAI/MiniMax-M2.7\",\n]);\nconst TOGETHER_REASONING_EFFORT_MODELS = new Set([\"openai/gpt-oss-20b\", \"openai/gpt-oss-120b\"]);\nconst TOGETHER_TOGGLE_REASONING_EFFORT_MODELS = new Set([\"deepseek-ai/DeepSeek-V4-Pro\"]);\nconst TOGETHER_FIXED_REASONING_LEVEL_MAP = {\n\toff: null,\n\tminimal: null,\n\tlow: null,\n\tmedium: null,\n} as const;\nconst TOGETHER_REASONING_EFFORT_LEVEL_MAP = {\n\toff: null,\n\tminimal: null,\n} as const;\nconst TOGETHER_DEEPSEEK_V4_THINKING_LEVEL_MAP = {\n\tminimal: null,\n\tlow: null,\n\tmedium: null,\n\thigh: \"high\",\n\txhigh: null,\n} as const;\nconst TOGETHER_TOGGLE_REASONING_LEVEL_MAP = {\n\tminimal: null,\n\tlow: null,\n\tmedium: null,\n} as const;\n\nconst AI_GATEWAY_MODELS_URL = \"https://ai-gateway.vercel.sh/v1\";\nconst AI_GATEWAY_BASE_URL = \"https://ai-gateway.vercel.sh\";\nconst ZAI_TOOL_STREAM_UNSUPPORTED_MODELS = new Set([\"glm-4.5\", \"glm-4.5-air\", \"glm-4.5-flash\", \"glm-4.5v\"]);\nconst EAGER_TOOL_INPUT_STREAMING_UNSUPPORTED_ANTHROPIC_MODELS = new Set([\n\t\"github-copilot:claude-haiku-4.5\",\n\t\"github-copilot:claude-sonnet-4\",\n\t\"github-copilot:claude-sonnet-4.5\",\n]);\n\nconst DEEPSEEK_V4_THINKING_LEVEL_MAP = {\n\tminimal: null,\n\tlow: null,\n\tmedium: null,\n\thigh: \"high\",\n\txhigh: \"max\",\n} as const;\n\nconst OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([\n\t\"gpt-5.1\",\n\t\"gpt-5.2\",\n\t\"gpt-5.3-codex\",\n\t\"gpt-5.4\",\n\t\"gpt-5.4-mini\",\n\t\"gpt-5.4-nano\",\n\t\"gpt-5.5\",\n]);\n\nfunction mergeThinkingLevelMap(model: Model<any>, map: NonNullable<Model<any>[\"thinkingLevelMap\"]>): void {\n\tmodel.thinkingLevelMap = { ...model.thinkingLevelMap, ...map };\n}\n\nfunction getTogetherCompat(modelId: string, reasoning: boolean): OpenAICompletionsCompat {\n\tif (!reasoning) return TOGETHER_BASE_COMPAT;\n\tif (TOGETHER_REASONING_EFFORT_MODELS.has(modelId)) return TOGETHER_REASONING_EFFORT_COMPAT;\n\tif (TOGETHER_TOGGLE_REASONING_EFFORT_MODELS.has(modelId)) return TOGETHER_TOGGLE_REASONING_EFFORT_COMPAT;\n\tif (TOGETHER_REASONING_ONLY_MODELS.has(modelId)) return TOGETHER_BASE_COMPAT;\n\treturn TOGETHER_TOGGLE_REASONING_COMPAT;\n}\n\nfunction getTogetherThinkingLevelMap(\n\tmodelId: string,\n\treasoning: boolean,\n): NonNullable<Model<any>[\"thinkingLevelMap\"]> | undefined {\n\tif (!reasoning) return undefined;\n\tif (TOGETHER_REASONING_EFFORT_MODELS.has(modelId)) return { ...TOGETHER_REASONING_EFFORT_LEVEL_MAP };\n\tif (TOGETHER_TOGGLE_REASONING_EFFORT_MODELS.has(modelId)) return { ...TOGETHER_DEEPSEEK_V4_THINKING_LEVEL_MAP };\n\tif (TOGETHER_REASONING_ONLY_MODELS.has(modelId)) return { ...TOGETHER_FIXED_REASONING_LEVEL_MAP };\n\treturn { ...TOGETHER_TOGGLE_REASONING_LEVEL_MAP };\n}\n\nfunction supportsOpenAiXhigh(modelId: string): boolean {\n\treturn (\n\t\tmodelId.includes(\"gpt-5.2\") ||\n\t\tmodelId.includes(\"gpt-5.3\") ||\n\t\tmodelId.includes(\"gpt-5.4\") ||\n\t\tmodelId.includes(\"gpt-5.5\")\n\t);\n}\n\nfunction isGoogleThinkingApi(model: Model<any>): boolean {\n\treturn model.api === \"google-generative-ai\" || model.api === \"google-vertex\";\n}\n\nfunction isGemini3ProModel(modelId: string): boolean {\n\treturn /gemini-3(?:\\.\\d+)?-pro/.test(modelId.toLowerCase());\n}\n\nfunction isGemini3FlashModel(modelId: string): boolean {\n\treturn /gemini-3(?:\\.\\d+)?-flash/.test(modelId.toLowerCase());\n}\n\nfunction isGemma4Model(modelId: string): boolean {\n\treturn /gemma-?4/.test(modelId.toLowerCase());\n}\n\nfunction applyThinkingLevelMetadata(model: Model<any>): void {\n\tif (\n\t\t(model.api === \"openai-responses\" || model.api === \"azure-openai-responses\") &&\n\t\tmodel.id.startsWith(\"gpt-5\")\n\t) {\n\t\tmergeThinkingLevelMap(model, { off: null });\n\t}\n\tif (model.provider === \"github-copilot\" && model.id.startsWith(\"gpt-5\")) {\n\t\tmergeThinkingLevelMap(model, { minimal: \"low\" });\n\t}\n\tif (\n\t\tmodel.api === \"openai-responses\" &&\n\t\tmodel.provider === \"openai\" &&\n\t\tOPENAI_RESPONSES_NONE_REASONING_MODELS.has(model.id)\n\t) {\n\t\tmergeThinkingLevelMap(model, { off: \"none\" });\n\t}\n\tif (supportsOpenAiXhigh(model.id)) {\n\t\tmergeThinkingLevelMap(model, { xhigh: \"xhigh\" });\n\t}\n\tif (model.id.includes(\"opus-4-6\") || model.id.includes(\"opus-4.6\")) {\n\t\tmergeThinkingLevelMap(model, { xhigh: \"max\" });\n\t}\n\tif (model.id.includes(\"opus-4-7\") || model.id.includes(\"opus-4.7\")) {\n\t\tmergeThinkingLevelMap(model, { xhigh: \"xhigh\" });\n\t}\n\tif (model.api === \"openai-completions\" && model.id.includes(\"deepseek-v4\")) {\n\t\tmergeThinkingLevelMap(model, DEEPSEEK_V4_THINKING_LEVEL_MAP);\n\t}\n\tif (isGoogleThinkingApi(model) && isGemini3ProModel(model.id)) {\n\t\tmergeThinkingLevelMap(model, { off: null, minimal: null, low: \"LOW\", medium: null, high: \"HIGH\" });\n\t}\n\tif (isGoogleThinkingApi(model) && isGemini3FlashModel(model.id)) {\n\t\tmergeThinkingLevelMap(model, { off: null });\n\t}\n\tif (isGoogleThinkingApi(model) && isGemma4Model(model.id)) {\n\t\tmergeThinkingLevelMap(model, { off: null, minimal: \"MINIMAL\", low: null, medium: null, high: \"HIGH\" });\n\t}\n\tif (model.provider === \"groq\" && model.id === \"qwen/qwen3-32b\") {\n\t\tmergeThinkingLevelMap(model, { minimal: null, low: null, medium: null, high: \"default\" });\n\t}\n\tif (model.provider === \"openai-codex\" && supportsOpenAiXhigh(model.id)) {\n\t\tmergeThinkingLevelMap(model, { minimal: \"low\" });\n\t}\n\tif (model.provider === \"openrouter\" && model.id.startsWith(\"inception/mercury-2\")) {\n\t\t// Mercury 2 in instant mode (reasoning_effort: \"none\") disables tool calling.\n\t\t// Mark \"off\" unsupported so the openai-completions provider omits the reasoning param\n\t\t// instead of defaulting to {reasoning:{effort:\"none\"}} (see openai-completions.ts:575).\n\t\t// Pi's low/medium/high pass through verbatim; OpenRouter normalizes to Mercury's vocabulary.\n\t\tmergeThinkingLevelMap(model, { off: null });\n\t}\n}\n\nfunction getAnthropicMessagesCompat(provider: string, modelId: string): AnthropicMessagesCompat | undefined {\n\treturn EAGER_TOOL_INPUT_STREAMING_UNSUPPORTED_ANTHROPIC_MODELS.has(`${provider}:${modelId}`)\n\t\t? { supportsEagerToolInputStreaming: false }\n\t\t: undefined;\n}\n\nfunction getBedrockBaseUrl(modelId: string): string {\n\treturn modelId.startsWith(\"eu.\")\n\t\t? \"https://bedrock-runtime.eu-central-1.amazonaws.com\"\n\t\t: \"https://bedrock-runtime.us-east-1.amazonaws.com\";\n}\n\nasync function fetchOpenRouterModels(): Promise<Model<any>[]> {\n\ttry {\n\t\tconsole.log(\"Fetching models from OpenRouter API...\");\n\t\tconst response = await fetch(\"https://openrouter.ai/api/v1/models\");\n\t\tconst data = await response.json();\n\n\t\tconst models: Model<any>[] = [];\n\n\t\tfor (const model of data.data) {\n\t\t\t// Only include models that support tools\n\t\t\tif (!model.supported_parameters?.includes(\"tools\")) continue;\n\n\t\t\t// Parse provider from model ID\n\t\t\tlet provider: KnownProvider = \"openrouter\";\n\t\t\tlet modelKey = model.id;\n\n\t\t\tmodelKey = model.id; // Keep full ID for OpenRouter\n\n\t\t\t// Parse input modalities\n\t\t\tconst input: (\"text\" | \"image\")[] = [\"text\"];\n\t\t\tif (model.architecture?.modality?.includes(\"image\")) {\n\t\t\t\tinput.push(\"image\");\n\t\t\t}\n\n\t\t\t// Convert pricing from $/token to $/million tokens\n\t\t\tconst inputCost = parseFloat(model.pricing?.prompt || \"0\") * 1_000_000;\n\t\t\tconst outputCost = parseFloat(model.pricing?.completion || \"0\") * 1_000_000;\n\t\t\tconst cacheReadCost = parseFloat(model.pricing?.input_cache_read || \"0\") * 1_000_000;\n\t\t\tconst cacheWriteCost = parseFloat(model.pricing?.input_cache_write || \"0\") * 1_000_000;\n\n\t\t\tconst normalizedModel: Model<any> = {\n\t\t\t\tid: modelKey,\n\t\t\t\tname: model.name,\n\t\t\t\tapi: \"openai-completions\",\n\t\t\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\t\t\tprovider,\n\t\t\t\treasoning: model.supported_parameters?.includes(\"reasoning\") || false,\n\t\t\t\tinput,\n\t\t\t\tcost: {\n\t\t\t\t\tinput: inputCost,\n\t\t\t\t\toutput: outputCost,\n\t\t\t\t\tcacheRead: cacheReadCost,\n\t\t\t\t\tcacheWrite: cacheWriteCost,\n\t\t\t\t},\n\t\t\t\tcontextWindow: model.context_length || 4096,\n\t\t\t\tmaxTokens: model.top_provider?.max_completion_tokens || 4096,\n\t\t\t};\n\t\t\tmodels.push(normalizedModel);\n\t\t}\n\n\t\tconsole.log(`Fetched ${models.length} tool-capable models from OpenRouter`);\n\t\treturn models;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to fetch OpenRouter models:\", error);\n\t\treturn [];\n\t}\n}\n\nasync function fetchAiGatewayModels(): Promise<Model<any>[]> {\n\ttry {\n\t\tconsole.log(\"Fetching models from Vercel AI Gateway API...\");\n\t\tconst response = await fetch(`${AI_GATEWAY_MODELS_URL}/models`);\n\t\tconst data = await response.json();\n\t\tconst models: Model<any>[] = [];\n\n\t\tconst toNumber = (value: string | number | undefined): number => {\n\t\t\tif (typeof value === \"number\") {\n\t\t\t\treturn Number.isFinite(value) ? value : 0;\n\t\t\t}\n\t\t\tconst parsed = parseFloat(value ?? \"0\");\n\t\t\treturn Number.isFinite(parsed) ? parsed : 0;\n\t\t};\n\n\t\tconst items = Array.isArray(data.data) ? (data.data as AiGatewayModel[]) : [];\n\t\tfor (const model of items) {\n\t\t\tconst tags = Array.isArray(model.tags) ? model.tags : [];\n\t\t\t// Only include models that support tools\n\t\t\tif (!tags.includes(\"tool-use\")) continue;\n\n\t\t\tconst input: (\"text\" | \"image\")[] = [\"text\"];\n\t\t\tif (tags.includes(\"vision\")) {\n\t\t\t\tinput.push(\"image\");\n\t\t\t}\n\n\t\t\tconst inputCost = toNumber(model.pricing?.input) * 1_000_000;\n\t\t\tconst outputCost = toNumber(model.pricing?.output) * 1_000_000;\n\t\t\tconst cacheReadCost = toNumber(model.pricing?.input_cache_read) * 1_000_000;\n\t\t\tconst cacheWriteCost = toNumber(model.pricing?.input_cache_write) * 1_000_000;\n\n\t\t\tmodels.push({\n\t\t\t\tid: model.id,\n\t\t\t\tname: model.name || model.id,\n\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\tbaseUrl: AI_GATEWAY_BASE_URL,\n\t\t\t\tprovider: \"vercel-ai-gateway\",\n\t\t\t\treasoning: tags.includes(\"reasoning\"),\n\t\t\t\tinput,\n\t\t\t\tcost: {\n\t\t\t\t\tinput: inputCost,\n\t\t\t\t\toutput: outputCost,\n\t\t\t\t\tcacheRead: cacheReadCost,\n\t\t\t\t\tcacheWrite: cacheWriteCost,\n\t\t\t\t},\n\t\t\t\tcontextWindow: model.context_window || 4096,\n\t\t\t\tmaxTokens: model.max_tokens || 4096,\n\t\t\t});\n\t\t}\n\n\t\tconsole.log(`Fetched ${models.length} tool-capable models from Vercel AI Gateway`);\n\t\treturn models;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to fetch Vercel AI Gateway models:\", error);\n\t\treturn [];\n\t}\n}\n\nasync function loadModelsDevData(): Promise<Model<any>[]> {\n\ttry {\n\t\tconsole.log(\"Fetching models from models.dev API...\");\n\t\tconst response = await fetch(\"https://models.dev/api.json\");\n\t\tconst data = await response.json();\n\n\t\tconst models: Model<any>[] = [];\n\n\t\t// Process Amazon Bedrock models\n\t\tif (data[\"amazon-bedrock\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"amazon-bedrock\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tlet id = modelId;\n\n\t\t\t\tif (id.startsWith(\"ai21.jamba\")) {\n\t\t\t\t\t// These models doesn't support tool use in streaming mode\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (id.startsWith(\"mistral.mistral-7b-instruct-v0\")) {\n\t\t\t\t\t// These models doesn't support system messages\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid,\n\t\t\t\t\tname: m.name || id,\n\t\t\t\t\tapi: \"bedrock-converse-stream\" as const,\n\t\t\t\t\tprovider: \"amazon-bedrock\" as const,\n\t\t\t\t\tbaseUrl: getBedrockBaseUrl(id),\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: (m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"]) as (\"text\" | \"image\")[],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Anthropic models\n\t\tif (data.anthropic?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.anthropic.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\tprovider: \"anthropic\",\n\t\t\t\t\tbaseUrl: \"https://api.anthropic.com\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Google models\n\t\tif (data.google?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.google.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"google-generative-ai\",\n\t\t\t\t\tprovider: \"google\",\n\t\t\t\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process OpenAI models\n\t\tif (data.openai?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.openai.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-responses\",\n\t\t\t\t\tprovider: \"openai\",\n\t\t\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Groq models\n\t\tif (data.groq?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.groq.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"groq\",\n\t\t\t\t\tbaseUrl: \"https://api.groq.com/openai/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Cerebras models\n\t\tif (data.cerebras?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.cerebras.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"cerebras\",\n\t\t\t\t\tbaseUrl: \"https://api.cerebras.ai/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Cloudflare Workers AI models\n\t\tif (data[\"cloudflare-workers-ai\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"cloudflare-workers-ai\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"cloudflare-workers-ai\",\n\t\t\t\t\tbaseUrl: CLOUDFLARE_WORKERS_AI_BASE_URL,\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\tcompat: { sendSessionAffinityHeaders: true },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Cloudflare AI Gateway models\n\t\tif (data[\"cloudflare-ai-gateway\"]?.models) {\n\t\t\tfor (const [prefixedId, model] of Object.entries(data[\"cloudflare-ai-gateway\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tconst slashIdx = prefixedId.indexOf(\"/\");\n\t\t\t\tif (slashIdx === -1) continue;\n\t\t\t\tconst upstream = prefixedId.slice(0, slashIdx);\n\t\t\t\tconst nativeId = prefixedId.slice(slashIdx + 1);\n\n\t\t\t\tlet api: \"anthropic-messages\" | \"openai-completions\" | \"openai-responses\";\n\t\t\t\tlet baseUrl: string;\n\t\t\t\tlet id: string;\n\t\t\t\tif (upstream === \"openai\") {\n\t\t\t\t\tapi = \"openai-responses\";\n\t\t\t\t\tbaseUrl = CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL;\n\t\t\t\t\tid = nativeId;\n\t\t\t\t} else if (upstream === \"anthropic\") {\n\t\t\t\t\tapi = \"anthropic-messages\";\n\t\t\t\t\tbaseUrl = CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL;\n\t\t\t\t\tid = nativeId;\n\t\t\t\t} else if (upstream === \"workers-ai\") {\n\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\tbaseUrl = CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL;\n\t\t\t\t\tid = prefixedId;\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// workers-ai/* through the gateway forwards x-session-affinity to\n\t\t\t\t// the underlying Workers AI runtime for prefix-cache routing.\n\t\t\t\tconst compat = upstream === \"workers-ai\" ? { sendSessionAffinityHeaders: true } : undefined;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid,\n\t\t\t\t\tname: m.name || id,\n\t\t\t\t\tapi,\n\t\t\t\t\tprovider: \"cloudflare-ai-gateway\",\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\t...(compat ? { compat } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process xAi models\n\t\tif (data.xai?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.xai.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"xai\",\n\t\t\t\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process zAi models\n\t\tif (data[\"zai-coding-plan\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"zai-coding-plan\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\tconst supportsImage = m.modalities?.input?.includes(\"image\");\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"zai\",\n\t\t\t\t\tbaseUrl: \"https://api.z.ai/api/coding/paas/v4\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: supportsImage ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcompat: {\n\t\t\t\t\t\tsupportsDeveloperRole: false,\n\t\t\t\t\t\tthinkingFormat: \"zai\",\n\t\t\t\t\t\t...(!ZAI_TOOL_STREAM_UNSUPPORTED_MODELS.has(modelId) ? { zaiToolStream: true } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Mistral models\n\t\tif (data.mistral?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.mistral.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"mistral-conversations\",\n\t\t\t\t\tprovider: \"mistral\",\n\t\t\t\t\tbaseUrl: \"https://api.mistral.ai\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Hugging Face models\n\t\tif (data.huggingface?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.huggingface.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"huggingface\",\n\t\t\t\t\tbaseUrl: \"https://router.huggingface.co/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcompat: {\n\t\t\t\t\t\tsupportsDeveloperRole: false,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Fireworks models\n\t\tif (data[\"fireworks-ai\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"fireworks-ai\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\tprovider: \"fireworks\",\n\t\t\t\t\t// Fireworks Anthropic-compatible API - SDK appends /v1/messages\n\t\t\t\t\tbaseUrl: \"https://api.fireworks.ai/inference\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\t// Fireworks prompt caching uses automatic prefix matching + session affinity.\n\t\t\t\t\t// x-session-affinity routes requests to the same replica for cache hits.\n\t\t\t\t\t// cache_control on tools and eager_input_streaming are not supported.\n\t\t\t\t\t// See: https://docs.fireworks.ai/tools-sdks/anthropic-compatibility\n\t\t\t\t\tcompat: {\n\t\t\t\t\t\tsendSessionAffinityHeaders: true,\n\t\t\t\t\t\tsupportsEagerToolInputStreaming: false,\n\t\t\t\t\t\tsupportsCacheControlOnTools: false,\n\t\t\t\t\t\tsupportsLongCacheRetention: false,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Together AI models\n\t\tconst togetherProvider = data.together ?? data.togetherai ?? data[\"together-ai\"];\n\t\tif (togetherProvider?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(togetherProvider.models)) {\n\t\t\t\tconst m = model as ModelsDevModel & { status?: string };\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\tif (m.status === \"deprecated\") continue;\n\n\t\t\t\tconst reasoning = m.reasoning === true;\n\t\t\t\tconst thinkingLevelMap = getTogetherThinkingLevelMap(modelId, reasoning);\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"together\",\n\t\t\t\t\tbaseUrl: TOGETHER_BASE_URL,\n\t\t\t\t\treasoning,\n\t\t\t\t\t...(thinkingLevelMap ? { thinkingLevelMap } : {}),\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcompat: getTogetherCompat(modelId, reasoning),\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process OpenCode models (Zen and Go)\n\t\t// API mapping based on provider.npm field:\n\t\t// - @ai-sdk/openai → openai-responses\n\t\t// - @ai-sdk/anthropic → anthropic-messages\n\t\t// - @ai-sdk/google → google-generative-ai\n\t\t// - null/undefined/@ai-sdk/openai-compatible → openai-completions\n\t\tconst opencodeVariants = [\n\t\t\t{ key: \"opencode\", provider: \"opencode\", basePath: \"https://opencode.ai/zen\" },\n\t\t\t{ key: \"opencode-go\", provider: \"opencode-go\", basePath: \"https://opencode.ai/zen/go\" },\n\t\t] as const;\n\n\t\tfor (const variant of opencodeVariants) {\n\t\t\tif (!data[variant.key]?.models) continue;\n\n\t\t\tfor (const [modelId, model] of Object.entries(data[variant.key].models)) {\n\t\t\t\tconst m = model as ModelsDevModel & { status?: string };\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\tif (m.status === \"deprecated\") continue;\n\n\t\t\t\tconst npm = m.provider?.npm;\n\t\t\t\tlet api: Api;\n\t\t\t\tlet baseUrl: string;\n\t\t\t\tlet compat: OpenAICompletionsCompat | undefined;\n\n\t\t\t\tif (npm === \"@ai-sdk/openai\") {\n\t\t\t\t\tapi = \"openai-responses\";\n\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t} else if (npm === \"@ai-sdk/anthropic\") {\n\t\t\t\t\tapi = \"anthropic-messages\";\n\t\t\t\t\t// Anthropic SDK appends /v1/messages to baseURL\n\t\t\t\t\tbaseUrl = variant.basePath;\n\t\t\t\t} else if (npm === \"@ai-sdk/google\") {\n\t\t\t\t\tapi = \"google-generative-ai\";\n\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t} else if (npm === \"@ai-sdk/alibaba\") {\n\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t\tcompat = { cacheControlFormat: \"anthropic\" };\n\t\t\t\t} else {\n\t\t\t\t\t// null, undefined, or @ai-sdk/openai-compatible\n\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t}\n\n\t\t\t\t// Fix known mismatches between models.dev npm data and actual\n\t\t\t\t// OpenCode Go endpoint behaviour. models.dev reports these models\n\t\t\t\t// as @ai-sdk/anthropic, but the OpenCode Go endpoints either don't\n\t\t\t\t// accept Anthropic SDK auth (MiniMax M2.7) or are served through\n\t\t\t\t// the OpenAI-compatible /v1/chat/completions path (Qwen 3.5/3.6).\n\t\t\t\t// Switch them to openai-completions so requests use Bearer auth\n\t\t\t\t// and the standard /v1/chat/completions endpoint.\n\t\t\t\tif (variant.provider === \"opencode-go\") {\n\t\t\t\t\tif (modelId === \"minimax-m2.7\") {\n\t\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t\t}\n\t\t\t\t\tif (modelId === \"qwen3.5-plus\" || modelId === \"qwen3.6-plus\") {\n\t\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t\t\t// Qwen/DashScope uses enable_thinking at the top level.\n\t\t\t\t\t\tcompat = { ...(compat ?? {}), thinkingFormat: \"qwen\" };\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi,\n\t\t\t\t\tprovider: variant.provider,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\t...(compat ? { compat } : {}),\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process GitHub Copilot models\n\t\tif (data[\"github-copilot\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"github-copilot\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel & { status?: string };\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\tif (m.status === \"deprecated\") continue;\n\n\t\t\t\t// Claude 4.x models route to Anthropic Messages API\n\t\t\t\tconst isCopilotClaude4 = /^claude-(haiku|sonnet|opus)-4([.\\-]|$)/.test(modelId);\n\t\t\t\t// gpt-5 models require responses API, others use completions\n\t\t\t\tconst needsResponsesApi = modelId.startsWith(\"gpt-5\") || modelId.startsWith(\"oswe\");\n\n\t\t\t\tconst api: Api = isCopilotClaude4\n\t\t\t\t\t? \"anthropic-messages\"\n\t\t\t\t\t: needsResponsesApi\n\t\t\t\t\t\t? \"openai-responses\"\n\t\t\t\t\t\t: \"openai-completions\";\n\n\t\t\t\tconst anthropicCompat =\n\t\t\t\t\tapi === \"anthropic-messages\" ? getAnthropicMessagesCompat(\"github-copilot\", modelId) : undefined;\n\n\t\t\t\tconst copilotModel: Model<any> = {\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi,\n\t\t\t\t\tprovider: \"github-copilot\",\n\t\t\t\t\tbaseUrl: \"https://api.individual.githubcopilot.com\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 128000,\n\t\t\t\t\tmaxTokens: m.limit?.output || 8192,\n\t\t\t\t\theaders: { ...COPILOT_STATIC_HEADERS },\n\t\t\t\t\t...(anthropicCompat ? { compat: anthropicCompat } : {}),\n\t\t\t\t\t// compat only applies to openai-completions\n\t\t\t\t\t...(api === \"openai-completions\" ? {\n\t\t\t\t\t\tcompat: {\n\t\t\t\t\t\t\tsupportsStore: false,\n\t\t\t\t\t\t\tsupportsDeveloperRole: false,\n\t\t\t\t\t\t\tsupportsReasoningEffort: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t} : {}),\n\t\t\t\t};\n\n\t\t\t\tmodels.push(copilotModel);\n\t\t\t}\n\t\t}\n\n\t\t// Process MiniMax models\n\t\tconst minimaxVariants = [\n\t\t\t{ key: \"minimax\", provider: \"minimax\", baseUrl: \"https://api.minimax.io/anthropic\" },\n\t\t\t{ key: \"minimax-cn\", provider: \"minimax-cn\", baseUrl: \"https://api.minimaxi.com/anthropic\" },\n\t\t] as const;\n\n\t\tfor (const { key, provider, baseUrl } of minimaxVariants) {\n\t\t\tif (data[key]?.models) {\n\t\t\t\tfor (const [modelId, model] of Object.entries(data[key].models)) {\n\t\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\t\tmodels.push({\n\t\t\t\t\t\tid: modelId,\n\t\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\t\tprovider,\n\t\t\t\t\t\t// MiniMax's Anthropic-compatible API - SDK appends /v1/messages\n\t\t\t\t\t\tbaseUrl,\n\t\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\t\tcost: {\n\t\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Process Kimi For Coding models\n\t\tif (data[\"kimi-for-coding\"]?.models) {\n\t\t\tconst kimiModels = data[\"kimi-for-coding\"].models as Record<string, ModelsDevModel>;\n\t\t\tconst hasCanonicalModel = Object.prototype.hasOwnProperty.call(kimiModels, \"kimi-for-coding\");\n\n\t\t\tconst kimiAliases = new Set([\"k2p5\", \"k2p6\"]);\n\n\t\t\tfor (const [modelId, model] of Object.entries(kimiModels)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\t// models.dev may expose versioned aliases (e.g. k2p5/k2p6).\n\t\t\t\t// Normalize aliases to the canonical model id and drop duplicates when canonical exists.\n\t\t\t\tif (kimiAliases.has(modelId) && hasCanonicalModel) continue;\n\n\t\t\t\tconst normalizedId = kimiAliases.has(modelId) ? \"kimi-for-coding\" : modelId;\n\t\t\t\tconst normalizedName = kimiAliases.has(modelId) ? \"Kimi For Coding\" : m.name || normalizedId;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: normalizedId,\n\t\t\t\t\tname: normalizedName,\n\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\tprovider: \"kimi-coding\",\n\t\t\t\t\t// Kimi For Coding's Anthropic-compatible API - SDK appends /v1/messages\n\t\t\t\t\tbaseUrl: \"https://api.kimi.com/coding\",\n\t\t\t\t\theaders: { ...KIMI_STATIC_HEADERS },\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Moonshot AI models\n\t\tconst moonshotVariants = [\n\t\t\t{ key: \"moonshotai\", provider: \"moonshotai\", baseUrl: \"https://api.moonshot.ai/v1\" },\n\t\t\t{ key: \"moonshotai-cn\", provider: \"moonshotai-cn\", baseUrl: \"https://api.moonshot.cn/v1\" },\n\t\t] as const;\n\t\tconst moonshotCompat: OpenAICompletionsCompat = {\n\t\t\tsupportsStore: false,\n\t\t\tsupportsDeveloperRole: false,\n\t\t\tsupportsReasoningEffort: false,\n\t\t\tmaxTokensField: \"max_tokens\",\n\t\t\tsupportsStrictMode: false,\n\t\t};\n\n\t\tfor (const { key, provider, baseUrl } of moonshotVariants) {\n\t\t\tif (!data[key]?.models) continue;\n\n\t\t\tfor (const [modelId, model] of Object.entries(data[key].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\tcompat: moonshotCompat,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Xiaomi MiMo models\n\t\t// Built-in `xiaomi` targets the API billing endpoint (single stable URL,\n\t\t// keys from platform.xiaomimimo.com). The three `xiaomi-token-plan-*`\n\t\t// providers cover prepaid Token Plan endpoints in cn / ams / sgp.\n\t\tconst xiaomiCompat: OpenAICompletionsCompat = {\n\t\t\trequiresReasoningContentOnAssistantMessages: true,\n\t\t\tthinkingFormat: \"deepseek\",\n\t\t};\n\t\tconst xiaomiVariants = [\n\t\t\t{ provider: \"xiaomi\", baseUrl: \"https://api.xiaomimimo.com/v1\" },\n\t\t\t{ provider: \"xiaomi-token-plan-cn\", baseUrl: \"https://token-plan-cn.xiaomimimo.com/v1\" },\n\t\t\t{ provider: \"xiaomi-token-plan-ams\", baseUrl: \"https://token-plan-ams.xiaomimimo.com/v1\" },\n\t\t\t{ provider: \"xiaomi-token-plan-sgp\", baseUrl: \"https://token-plan-sgp.xiaomimimo.com/v1\" },\n\t\t] as const;\n\n\t\tif (data.xiaomi?.models) {\n\t\t\tfor (const { provider, baseUrl } of xiaomiVariants) {\n\t\t\t\tfor (const [modelId, model] of Object.entries(data.xiaomi.models)) {\n\t\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\t\tmodels.push({\n\t\t\t\t\t\tid: modelId,\n\t\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\t\tprovider,\n\t\t\t\t\t\tbaseUrl,\n\t\t\t\t\t\tcompat: xiaomiCompat,\n\t\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\t\tcost: {\n\t\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(`Loaded ${models.length} tool-capable models from models.dev`);\n\t\treturn models;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to load models.dev data:\", error);\n\t\treturn [];\n\t}\n}\n\nasync function generateModels() {\n\t// Fetch models from both sources\n\t// models.dev: Anthropic, Google, OpenAI, Groq, Cerebras\n\t// OpenRouter: xAI and other providers (excluding Anthropic, Google, OpenAI)\n\t// AI Gateway: OpenAI-compatible catalog with tool-capable models\n\tconst modelsDevModels = await loadModelsDevData();\n\tconst openRouterModels = await fetchOpenRouterModels();\n\tconst aiGatewayModels = await fetchAiGatewayModels();\n\n\t// Combine models (models.dev has priority)\n\tconst allModels = [...modelsDevModels, ...openRouterModels, ...aiGatewayModels].filter(\n\t\t(model) =>\n\t\t\t!((model.provider === \"opencode\" || model.provider === \"opencode-go\") && model.id === \"gpt-5.3-codex-spark\"),\n\t);\n\n\t// Fix incorrect cache pricing for Claude Opus 4.5 from models.dev\n\t// models.dev has 3x the correct pricing (1.5/18.75 instead of 0.5/6.25)\n\tconst opus45 = allModels.find(m => m.provider === \"anthropic\" && m.id === \"claude-opus-4-5\");\n\tif (opus45) {\n\t\topus45.cost.cacheRead = 0.5;\n\t\topus45.cost.cacheWrite = 6.25;\n\t}\n\n\t// Temporary overrides until upstream model metadata is corrected.\n\tfor (const candidate of allModels) {\n\t\tif (candidate.provider === \"amazon-bedrock\" && candidate.id.includes(\"anthropic.claude-opus-4-6-v1\")) {\n\t\t\tcandidate.cost.cacheRead = 0.5;\n\t\t\tcandidate.cost.cacheWrite = 6.25;\n\t\t}\n\t\tif (\n\t\t\t(candidate.provider === \"anthropic\" ||\n\t\t\t\tcandidate.provider === \"opencode\" ||\n\t\t\t\tcandidate.provider === \"opencode-go\" ||\n\t\t\t\tcandidate.provider === \"github-copilot\") &&\n\t\t\t(candidate.id === \"claude-opus-4-6\" ||\n\t\t\t\tcandidate.id === \"claude-sonnet-4-6\" ||\n\t\t\t\tcandidate.id === \"claude-opus-4.6\" ||\n\t\t\t\tcandidate.id === \"claude-sonnet-4.6\")\n\t\t) {\n\t\t\tcandidate.contextWindow = 1000000;\n\t\t}\n\n\t\t// OpenCode variants list Claude Sonnet 4/4.5 with 1M context, actual limit is 200K\n\t\tif (\n\t\t\t(candidate.provider === \"opencode\" || candidate.provider === \"opencode-go\") &&\n\t\t\t(candidate.id === \"claude-sonnet-4-5\" || candidate.id === \"claude-sonnet-4\")\n\t\t) {\n\t\t\tcandidate.contextWindow = 200000;\n\t\t}\n\t\tif ((candidate.provider === \"opencode\" || candidate.provider === \"opencode-go\") && candidate.id === \"gpt-5.4\") {\n\t\t\tcandidate.contextWindow = 272000;\n\t\t\tcandidate.maxTokens = 128000;\n\t\t}\n\t\tif (candidate.provider === \"openai\" && (candidate.id === \"gpt-5.4\" || candidate.id === \"gpt-5.5\")) {\n\t\t\tcandidate.contextWindow = 272000;\n\t\t\tcandidate.maxTokens = 128000;\n\t\t}\n\t\t// Keep selected OpenRouter model metadata stable until upstream settles.\n\t\tif (candidate.provider === \"openrouter\" && candidate.id === \"moonshotai/kimi-k2.5\") {\n\t\t\tcandidate.cost.input = 0.41;\n\t\t\tcandidate.cost.output = 2.06;\n\t\t\tcandidate.cost.cacheRead = 0.07;\n\t\t\tcandidate.maxTokens = 4096;\n\t\t}\n\t\tif (candidate.provider === \"openrouter\" && candidate.id === \"z-ai/glm-5\") {\n\t\t\tcandidate.cost.input = 0.6;\n\t\t\tcandidate.cost.output = 1.9;\n\t\t\tcandidate.cost.cacheRead = 0.119;\n\t\t}\n\n\t}\n\n\n\t// Add missing EU Opus 4.6 profile\n\tif (!allModels.some((m) => m.provider === \"amazon-bedrock\" && m.id === \"eu.anthropic.claude-opus-4-6-v1\")) {\n\t\tallModels.push({\n\t\t\tid: \"eu.anthropic.claude-opus-4-6-v1\",\n\t\t\tname: \"Claude Opus 4.6 (EU)\",\n\t\t\tapi: \"bedrock-converse-stream\",\n\t\t\tprovider: \"amazon-bedrock\",\n\t\t\tbaseUrl: getBedrockBaseUrl(\"eu.anthropic.claude-opus-4-6-v1\"),\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 5,\n\t\t\t\toutput: 25,\n\t\t\t\tcacheRead: 0.5,\n\t\t\t\tcacheWrite: 6.25,\n\t\t\t},\n\t\t\tcontextWindow: 200000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\t// Add missing Claude Opus 4.6\n\tif (!allModels.some(m => m.provider === \"anthropic\" && m.id === \"claude-opus-4-6\")) {\n\t\tallModels.push({\n\t\t\tid: \"claude-opus-4-6\",\n\t\t\tname: \"Claude Opus 4.6\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tbaseUrl: \"https://api.anthropic.com\",\n\t\t\tprovider: \"anthropic\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 5,\n\t\t\t\toutput: 25,\n\t\t\t\tcacheRead: 0.5,\n\t\t\t\tcacheWrite: 6.25,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\t// Add missing Claude Opus 4.7\n\tif (!allModels.some(m => m.provider === \"anthropic\" && m.id === \"claude-opus-4-7\")) {\n\t\tallModels.push({\n\t\t\tid: \"claude-opus-4-7\",\n\t\t\tname: \"Claude Opus 4.7\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tbaseUrl: \"https://api.anthropic.com\",\n\t\t\tprovider: \"anthropic\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 5,\n\t\t\t\toutput: 25,\n\t\t\t\tcacheRead: 0.5,\n\t\t\t\tcacheWrite: 6.25,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\t// Add missing Claude Sonnet 4.6\n\tif (!allModels.some(m => m.provider === \"anthropic\" && m.id === \"claude-sonnet-4-6\")) {\n\t\tallModels.push({\n\t\t\tid: \"claude-sonnet-4-6\",\n\t\t\tname: \"Claude Sonnet 4.6\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tbaseUrl: \"https://api.anthropic.com\",\n\t\t\tprovider: \"anthropic\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 3,\n\t\t\t\toutput: 15,\n\t\t\t\tcacheRead: 0.3,\n\t\t\t\tcacheWrite: 3.75,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 64000,\n\t\t});\n\t}\n\n\t// Add missing Gemini 3.1 Flash Lite Preview until models.dev includes it.\n\tif (!allModels.some((m) => m.provider === \"google\" && m.id === \"gemini-3.1-flash-lite-preview\")) {\n\t\tallModels.push({\n\t\t\tid: \"gemini-3.1-flash-lite-preview\",\n\t\t\tname: \"Gemini 3.1 Flash Lite Preview\",\n\t\t\tapi: \"google-generative-ai\",\n\t\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta\",\n\t\t\tprovider: \"google\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t});\n\t}\n\n\t// Add missing gpt models\n\tif (!allModels.some(m => m.provider === \"openai\" && m.id === \"gpt-5-chat-latest\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5-chat-latest\",\n\t\t\tname: \"GPT-5 Chat Latest\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.25,\n\t\t\t\toutput: 10,\n\t\t\t\tcacheRead: 0.125,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 128000,\n\t\t\tmaxTokens: 16384,\n\t\t});\n\t}\n\n\tif (!allModels.some(m => m.provider === \"openai\" && m.id === \"gpt-5.1-codex\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5.1-codex\",\n\t\t\tname: \"GPT-5.1 Codex\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.25,\n\t\t\t\toutput: 5,\n\t\t\t\tcacheRead: 0.125,\n\t\t\t\tcacheWrite: 1.25,\n\t\t\t},\n\t\t\tcontextWindow: 400000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\tif (!allModels.some(m => m.provider === \"openai\" && m.id === \"gpt-5.1-codex-max\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5.1-codex-max\",\n\t\t\tname: \"GPT-5.1 Codex Max\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.25,\n\t\t\t\toutput: 10,\n\t\t\t\tcacheRead: 0.125,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 400000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\tif (!allModels.some(m => m.provider === \"openai\" && m.id === \"gpt-5.3-codex-spark\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5.3-codex-spark\",\n\t\t\tname: \"GPT-5.3 Codex Spark\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 128000,\n\t\t\tmaxTokens: 16384,\n\t\t});\n\t}\n\n\t// Add missing GitHub Copilot GPT-5.3 models until models.dev includes them.\n\tconst copilotBaseModel = allModels.find(\n\t\t(m) => m.provider === \"github-copilot\" && m.id === \"gpt-5.2-codex\",\n\t);\n\tif (copilotBaseModel) {\n\t\tif (!allModels.some((m) => m.provider === \"github-copilot\" && m.id === \"gpt-5.3-codex\")) {\n\t\t\tallModels.push({\n\t\t\t\t...copilotBaseModel,\n\t\t\t\tid: \"gpt-5.3-codex\",\n\t\t\t\tname: \"GPT-5.3 Codex\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (!allModels.some((m) => m.provider === \"openai\" && m.id === \"gpt-5.4\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5.4\",\n\t\t\tname: \"GPT-5.4\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 2.5,\n\t\t\t\toutput: 15,\n\t\t\t\tcacheRead: 0.25,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 272000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\tconst deepseekCompat: OpenAICompletionsCompat = {\n\t\trequiresReasoningContentOnAssistantMessages: true,\n\t\tthinkingFormat: \"deepseek\",\n\t};\n\tconst deepseekV4Models: Model<\"openai-completions\">[] = [\n\t\t{\n\t\t\tid: \"deepseek-v4-flash\",\n\t\t\tname: \"DeepSeek V4 Flash\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\t\tprovider: \"deepseek\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.14,\n\t\t\t\toutput: 0.28,\n\t\t\t\tcacheRead: 0.0028,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 384000,\n\t\t\tcompat: deepseekCompat,\n\t\t},\n\t\t{\n\t\t\tid: \"deepseek-v4-pro\",\n\t\t\tname: \"DeepSeek V4 Pro\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\t\tprovider: \"deepseek\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.435,\n\t\t\t\toutput: 0.87,\n\t\t\t\tcacheRead: 0.003625,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 384000,\n\t\t\tcompat: deepseekCompat,\n\t\t},\n\t];\n\tallModels.push(...deepseekV4Models);\n\n\tfor (const candidate of allModels) {\n\t\tif (candidate.api === \"openai-completions\" && candidate.id.includes(\"deepseek-v4\")) {\n\t\t\tcandidate.compat = {\n\t\t\t\t...candidate.compat,\n\t\t\t\t...(candidate.provider === \"openrouter\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\trequiresReasoningContentOnAssistantMessages:\n\t\t\t\t\t\t\t\tdeepseekCompat.requiresReasoningContentOnAssistantMessages,\n\t\t\t\t\t\t\tthinkingFormat: deepseekCompat.thinkingFormat,\n\t\t\t\t\t\t}\n\t\t\t\t\t: deepseekCompat),\n\t\t\t};\n\t\t\tmergeThinkingLevelMap(candidate, DEEPSEEK_V4_THINKING_LEVEL_MAP);\n\t\t}\n\t}\n\n\tconst minimaxDirectSupportedIds = new Set([\"MiniMax-M2.7\", \"MiniMax-M2.7-highspeed\"]);\n\n\tfor (const candidate of allModels) {\n\t\tif (\n\t\t\t(candidate.provider === \"minimax\" || candidate.provider === \"minimax-cn\") &&\n\t\t\tminimaxDirectSupportedIds.has(candidate.id)\n\t\t) {\n\t\t\tcandidate.contextWindow = 204800;\n\t\t\tcandidate.maxTokens = 131072;\n\t\t}\n\t}\n\n\tfor (let i = allModels.length - 1; i >= 0; i--) {\n\t\tconst candidate = allModels[i];\n\t\tif (\n\t\t\t(candidate.provider === \"minimax\" || candidate.provider === \"minimax-cn\") &&\n\t\t\t!minimaxDirectSupportedIds.has(candidate.id)\n\t\t) {\n\t\t\tallModels.splice(i, 1);\n\t\t}\n\t}\n\n\t// OpenAI Codex (ChatGPT OAuth) models\n\t// NOTE: These are not fetched from models.dev; we keep a small, explicit list to avoid aliases.\n\t// Context window is based on observed server limits (400s above ~272k), not marketing numbers.\n\tconst CODEX_BASE_URL = \"https://chatgpt.com/backend-api\";\n\tconst CODEX_CONTEXT = 272000;\n\tconst CODEX_MAX_TOKENS = 128000;\n\tconst codexModels: Model<\"openai-codex-responses\">[] = [\n\t\t{\n\t\t\tid: \"gpt-5.2\",\n\t\t\tname: \"GPT-5.2\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.3-codex\",\n\t\t\tname: \"GPT-5.3 Codex\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.3-codex-spark\",\n\t\t\tname: \"GPT-5.3 Codex Spark\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.4\",\n\t\t\tname: \"GPT-5.4\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.4-mini\",\n\t\t\tname: \"GPT-5.4 mini\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.5\",\n\t\t\tname: \"GPT-5.5\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t];\n\tallModels.push(...codexModels);\n\n\t// Add missing Grok models\n\tconst missingGrokModels: Model<\"openai-completions\">[] = [\n\t\t{\n\t\t\tid: \"grok-3\",\n\t\t\tname: \"Grok 3\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\t\tprovider: \"xai\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: { input: 3, output: 15, cacheRead: 0.75, cacheWrite: 0 },\n\t\t\tcontextWindow: 131072,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t\t{\n\t\t\tid: \"grok-3-fast\",\n\t\t\tname: \"Grok 3 Fast\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\t\tprovider: \"xai\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: { input: 5, output: 25, cacheRead: 1.25, cacheWrite: 0 },\n\t\t\tcontextWindow: 131072,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t\t{\n\t\t\tid: \"grok-code-fast-1\",\n\t\t\tname: \"Grok Code Fast 1\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\t\tprovider: \"xai\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.2,\n\t\t\t\toutput: 1.5,\n\t\t\t\tcacheRead: 0.02,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 32768,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t];\n\tfor (const model of missingGrokModels) {\n\t\tif (!allModels.some(m => m.provider === model.provider && m.id === model.id)) {\n\t\t\tallModels.push(model);\n\t\t}\n\t}\n\n\t// Add missing Mistral Medium 3.5 model until models.dev includes it\n\tif (!allModels.some(m => m.provider === \"mistral\" && m.id === \"mistral-medium-3.5\")) {\n\t\tallModels.push({\n\t\t\tid: \"mistral-medium-3.5\",\n\t\t\tname: \"Mistral Medium 3.5\",\n\t\t\tapi: \"mistral-conversations\",\n\t\t\tprovider: \"mistral\",\n\t\t\tbaseUrl: \"https://api.mistral.ai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.5,\n\t\t\t\toutput: 7.5,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 262144, // 256k tokens\n\t\t\tmaxTokens: 262144,\n\t\t});\n\t}\n\n\n[Showing lines 1-1644 of 1930 (50.0KB limit). Use offset=1645 to continue.]"}],"details":{"truncation":{"content":"#!/usr/bin/env node\n\nimport { writeFileSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport {\n\tCLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL,\n\tCLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL,\n\tCLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL,\n\tCLOUDFLARE_WORKERS_AI_BASE_URL,\n} from \"../src/providers/cloudflare.ts\";\nimport type { AnthropicMessagesCompat, Api, KnownProvider, Model, OpenAICompletionsCompat } from \"../src/types.ts\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageRoot = join(__dirname, \"..\");\n\ninterface ModelsDevModel {\n\tid: string;\n\tname: string;\n\ttool_call?: boolean;\n\treasoning?: boolean;\n\tlimit?: {\n\t\tcontext?: number;\n\t\toutput?: number;\n\t};\n\tcost?: {\n\t\tinput?: number;\n\t\toutput?: number;\n\t\tcache_read?: number;\n\t\tcache_write?: number;\n\t};\n\tmodalities?: {\n\t\tinput?: string[];\n\t};\n\tprovider?: {\n\t\tnpm?: string;\n\t};\n}\n\ninterface AiGatewayModel {\n\tid: string;\n\tname?: string;\n\tcontext_window?: number;\n\tmax_tokens?: number;\n\ttags?: string[];\n\tpricing?: {\n\t\tinput?: string | number;\n\t\toutput?: string | number;\n\t\tinput_cache_read?: string | number;\n\t\tinput_cache_write?: string | number;\n\t};\n}\n\nconst COPILOT_STATIC_HEADERS = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"Copilot-Integration-Id\": \"vscode-chat\",\n} as const;\n\nconst KIMI_STATIC_HEADERS = {\n\t\"User-Agent\": \"KimiCLI/1.5\",\n} as const;\n\nconst TOGETHER_BASE_URL = \"https://api.together.ai/v1\";\nconst TOGETHER_BASE_COMPAT: OpenAICompletionsCompat = {\n\tsupportsStore: false,\n\tsupportsDeveloperRole: false,\n\tsupportsReasoningEffort: false,\n\tmaxTokensField: \"max_tokens\",\n\tsupportsStrictMode: false,\n\tsupportsLongCacheRetention: false,\n};\nconst TOGETHER_TOGGLE_REASONING_COMPAT: OpenAICompletionsCompat = {\n\t...TOGETHER_BASE_COMPAT,\n\tthinkingFormat: \"together\",\n};\nconst TOGETHER_REASONING_EFFORT_COMPAT: OpenAICompletionsCompat = {\n\t...TOGETHER_BASE_COMPAT,\n\tsupportsReasoningEffort: true,\n\tthinkingFormat: \"openai\",\n};\nconst TOGETHER_TOGGLE_REASONING_EFFORT_COMPAT: OpenAICompletionsCompat = {\n\t...TOGETHER_TOGGLE_REASONING_COMPAT,\n\tsupportsReasoningEffort: true,\n};\nconst TOGETHER_REASONING_ONLY_MODELS = new Set([\n\t\"deepseek-ai/DeepSeek-R1\",\n\t\"MiniMaxAI/MiniMax-M2.5\",\n\t\"MiniMaxAI/MiniMax-M2.7\",\n]);\nconst TOGETHER_REASONING_EFFORT_MODELS = new Set([\"openai/gpt-oss-20b\", \"openai/gpt-oss-120b\"]);\nconst TOGETHER_TOGGLE_REASONING_EFFORT_MODELS = new Set([\"deepseek-ai/DeepSeek-V4-Pro\"]);\nconst TOGETHER_FIXED_REASONING_LEVEL_MAP = {\n\toff: null,\n\tminimal: null,\n\tlow: null,\n\tmedium: null,\n} as const;\nconst TOGETHER_REASONING_EFFORT_LEVEL_MAP = {\n\toff: null,\n\tminimal: null,\n} as const;\nconst TOGETHER_DEEPSEEK_V4_THINKING_LEVEL_MAP = {\n\tminimal: null,\n\tlow: null,\n\tmedium: null,\n\thigh: \"high\",\n\txhigh: null,\n} as const;\nconst TOGETHER_TOGGLE_REASONING_LEVEL_MAP = {\n\tminimal: null,\n\tlow: null,\n\tmedium: null,\n} as const;\n\nconst AI_GATEWAY_MODELS_URL = \"https://ai-gateway.vercel.sh/v1\";\nconst AI_GATEWAY_BASE_URL = \"https://ai-gateway.vercel.sh\";\nconst ZAI_TOOL_STREAM_UNSUPPORTED_MODELS = new Set([\"glm-4.5\", \"glm-4.5-air\", \"glm-4.5-flash\", \"glm-4.5v\"]);\nconst EAGER_TOOL_INPUT_STREAMING_UNSUPPORTED_ANTHROPIC_MODELS = new Set([\n\t\"github-copilot:claude-haiku-4.5\",\n\t\"github-copilot:claude-sonnet-4\",\n\t\"github-copilot:claude-sonnet-4.5\",\n]);\n\nconst DEEPSEEK_V4_THINKING_LEVEL_MAP = {\n\tminimal: null,\n\tlow: null,\n\tmedium: null,\n\thigh: \"high\",\n\txhigh: \"max\",\n} as const;\n\nconst OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([\n\t\"gpt-5.1\",\n\t\"gpt-5.2\",\n\t\"gpt-5.3-codex\",\n\t\"gpt-5.4\",\n\t\"gpt-5.4-mini\",\n\t\"gpt-5.4-nano\",\n\t\"gpt-5.5\",\n]);\n\nfunction mergeThinkingLevelMap(model: Model<any>, map: NonNullable<Model<any>[\"thinkingLevelMap\"]>): void {\n\tmodel.thinkingLevelMap = { ...model.thinkingLevelMap, ...map };\n}\n\nfunction getTogetherCompat(modelId: string, reasoning: boolean): OpenAICompletionsCompat {\n\tif (!reasoning) return TOGETHER_BASE_COMPAT;\n\tif (TOGETHER_REASONING_EFFORT_MODELS.has(modelId)) return TOGETHER_REASONING_EFFORT_COMPAT;\n\tif (TOGETHER_TOGGLE_REASONING_EFFORT_MODELS.has(modelId)) return TOGETHER_TOGGLE_REASONING_EFFORT_COMPAT;\n\tif (TOGETHER_REASONING_ONLY_MODELS.has(modelId)) return TOGETHER_BASE_COMPAT;\n\treturn TOGETHER_TOGGLE_REASONING_COMPAT;\n}\n\nfunction getTogetherThinkingLevelMap(\n\tmodelId: string,\n\treasoning: boolean,\n): NonNullable<Model<any>[\"thinkingLevelMap\"]> | undefined {\n\tif (!reasoning) return undefined;\n\tif (TOGETHER_REASONING_EFFORT_MODELS.has(modelId)) return { ...TOGETHER_REASONING_EFFORT_LEVEL_MAP };\n\tif (TOGETHER_TOGGLE_REASONING_EFFORT_MODELS.has(modelId)) return { ...TOGETHER_DEEPSEEK_V4_THINKING_LEVEL_MAP };\n\tif (TOGETHER_REASONING_ONLY_MODELS.has(modelId)) return { ...TOGETHER_FIXED_REASONING_LEVEL_MAP };\n\treturn { ...TOGETHER_TOGGLE_REASONING_LEVEL_MAP };\n}\n\nfunction supportsOpenAiXhigh(modelId: string): boolean {\n\treturn (\n\t\tmodelId.includes(\"gpt-5.2\") ||\n\t\tmodelId.includes(\"gpt-5.3\") ||\n\t\tmodelId.includes(\"gpt-5.4\") ||\n\t\tmodelId.includes(\"gpt-5.5\")\n\t);\n}\n\nfunction isGoogleThinkingApi(model: Model<any>): boolean {\n\treturn model.api === \"google-generative-ai\" || model.api === \"google-vertex\";\n}\n\nfunction isGemini3ProModel(modelId: string): boolean {\n\treturn /gemini-3(?:\\.\\d+)?-pro/.test(modelId.toLowerCase());\n}\n\nfunction isGemini3FlashModel(modelId: string): boolean {\n\treturn /gemini-3(?:\\.\\d+)?-flash/.test(modelId.toLowerCase());\n}\n\nfunction isGemma4Model(modelId: string): boolean {\n\treturn /gemma-?4/.test(modelId.toLowerCase());\n}\n\nfunction applyThinkingLevelMetadata(model: Model<any>): void {\n\tif (\n\t\t(model.api === \"openai-responses\" || model.api === \"azure-openai-responses\") &&\n\t\tmodel.id.startsWith(\"gpt-5\")\n\t) {\n\t\tmergeThinkingLevelMap(model, { off: null });\n\t}\n\tif (model.provider === \"github-copilot\" && model.id.startsWith(\"gpt-5\")) {\n\t\tmergeThinkingLevelMap(model, { minimal: \"low\" });\n\t}\n\tif (\n\t\tmodel.api === \"openai-responses\" &&\n\t\tmodel.provider === \"openai\" &&\n\t\tOPENAI_RESPONSES_NONE_REASONING_MODELS.has(model.id)\n\t) {\n\t\tmergeThinkingLevelMap(model, { off: \"none\" });\n\t}\n\tif (supportsOpenAiXhigh(model.id)) {\n\t\tmergeThinkingLevelMap(model, { xhigh: \"xhigh\" });\n\t}\n\tif (model.id.includes(\"opus-4-6\") || model.id.includes(\"opus-4.6\")) {\n\t\tmergeThinkingLevelMap(model, { xhigh: \"max\" });\n\t}\n\tif (model.id.includes(\"opus-4-7\") || model.id.includes(\"opus-4.7\")) {\n\t\tmergeThinkingLevelMap(model, { xhigh: \"xhigh\" });\n\t}\n\tif (model.api === \"openai-completions\" && model.id.includes(\"deepseek-v4\")) {\n\t\tmergeThinkingLevelMap(model, DEEPSEEK_V4_THINKING_LEVEL_MAP);\n\t}\n\tif (isGoogleThinkingApi(model) && isGemini3ProModel(model.id)) {\n\t\tmergeThinkingLevelMap(model, { off: null, minimal: null, low: \"LOW\", medium: null, high: \"HIGH\" });\n\t}\n\tif (isGoogleThinkingApi(model) && isGemini3FlashModel(model.id)) {\n\t\tmergeThinkingLevelMap(model, { off: null });\n\t}\n\tif (isGoogleThinkingApi(model) && isGemma4Model(model.id)) {\n\t\tmergeThinkingLevelMap(model, { off: null, minimal: \"MINIMAL\", low: null, medium: null, high: \"HIGH\" });\n\t}\n\tif (model.provider === \"groq\" && model.id === \"qwen/qwen3-32b\") {\n\t\tmergeThinkingLevelMap(model, { minimal: null, low: null, medium: null, high: \"default\" });\n\t}\n\tif (model.provider === \"openai-codex\" && supportsOpenAiXhigh(model.id)) {\n\t\tmergeThinkingLevelMap(model, { minimal: \"low\" });\n\t}\n\tif (model.provider === \"openrouter\" && model.id.startsWith(\"inception/mercury-2\")) {\n\t\t// Mercury 2 in instant mode (reasoning_effort: \"none\") disables tool calling.\n\t\t// Mark \"off\" unsupported so the openai-completions provider omits the reasoning param\n\t\t// instead of defaulting to {reasoning:{effort:\"none\"}} (see openai-completions.ts:575).\n\t\t// Pi's low/medium/high pass through verbatim; OpenRouter normalizes to Mercury's vocabulary.\n\t\tmergeThinkingLevelMap(model, { off: null });\n\t}\n}\n\nfunction getAnthropicMessagesCompat(provider: string, modelId: string): AnthropicMessagesCompat | undefined {\n\treturn EAGER_TOOL_INPUT_STREAMING_UNSUPPORTED_ANTHROPIC_MODELS.has(`${provider}:${modelId}`)\n\t\t? { supportsEagerToolInputStreaming: false }\n\t\t: undefined;\n}\n\nfunction getBedrockBaseUrl(modelId: string): string {\n\treturn modelId.startsWith(\"eu.\")\n\t\t? \"https://bedrock-runtime.eu-central-1.amazonaws.com\"\n\t\t: \"https://bedrock-runtime.us-east-1.amazonaws.com\";\n}\n\nasync function fetchOpenRouterModels(): Promise<Model<any>[]> {\n\ttry {\n\t\tconsole.log(\"Fetching models from OpenRouter API...\");\n\t\tconst response = await fetch(\"https://openrouter.ai/api/v1/models\");\n\t\tconst data = await response.json();\n\n\t\tconst models: Model<any>[] = [];\n\n\t\tfor (const model of data.data) {\n\t\t\t// Only include models that support tools\n\t\t\tif (!model.supported_parameters?.includes(\"tools\")) continue;\n\n\t\t\t// Parse provider from model ID\n\t\t\tlet provider: KnownProvider = \"openrouter\";\n\t\t\tlet modelKey = model.id;\n\n\t\t\tmodelKey = model.id; // Keep full ID for OpenRouter\n\n\t\t\t// Parse input modalities\n\t\t\tconst input: (\"text\" | \"image\")[] = [\"text\"];\n\t\t\tif (model.architecture?.modality?.includes(\"image\")) {\n\t\t\t\tinput.push(\"image\");\n\t\t\t}\n\n\t\t\t// Convert pricing from $/token to $/million tokens\n\t\t\tconst inputCost = parseFloat(model.pricing?.prompt || \"0\") * 1_000_000;\n\t\t\tconst outputCost = parseFloat(model.pricing?.completion || \"0\") * 1_000_000;\n\t\t\tconst cacheReadCost = parseFloat(model.pricing?.input_cache_read || \"0\") * 1_000_000;\n\t\t\tconst cacheWriteCost = parseFloat(model.pricing?.input_cache_write || \"0\") * 1_000_000;\n\n\t\t\tconst normalizedModel: Model<any> = {\n\t\t\t\tid: modelKey,\n\t\t\t\tname: model.name,\n\t\t\t\tapi: \"openai-completions\",\n\t\t\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\t\t\tprovider,\n\t\t\t\treasoning: model.supported_parameters?.includes(\"reasoning\") || false,\n\t\t\t\tinput,\n\t\t\t\tcost: {\n\t\t\t\t\tinput: inputCost,\n\t\t\t\t\toutput: outputCost,\n\t\t\t\t\tcacheRead: cacheReadCost,\n\t\t\t\t\tcacheWrite: cacheWriteCost,\n\t\t\t\t},\n\t\t\t\tcontextWindow: model.context_length || 4096,\n\t\t\t\tmaxTokens: model.top_provider?.max_completion_tokens || 4096,\n\t\t\t};\n\t\t\tmodels.push(normalizedModel);\n\t\t}\n\n\t\tconsole.log(`Fetched ${models.length} tool-capable models from OpenRouter`);\n\t\treturn models;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to fetch OpenRouter models:\", error);\n\t\treturn [];\n\t}\n}\n\nasync function fetchAiGatewayModels(): Promise<Model<any>[]> {\n\ttry {\n\t\tconsole.log(\"Fetching models from Vercel AI Gateway API...\");\n\t\tconst response = await fetch(`${AI_GATEWAY_MODELS_URL}/models`);\n\t\tconst data = await response.json();\n\t\tconst models: Model<any>[] = [];\n\n\t\tconst toNumber = (value: string | number | undefined): number => {\n\t\t\tif (typeof value === \"number\") {\n\t\t\t\treturn Number.isFinite(value) ? value : 0;\n\t\t\t}\n\t\t\tconst parsed = parseFloat(value ?? \"0\");\n\t\t\treturn Number.isFinite(parsed) ? parsed : 0;\n\t\t};\n\n\t\tconst items = Array.isArray(data.data) ? (data.data as AiGatewayModel[]) : [];\n\t\tfor (const model of items) {\n\t\t\tconst tags = Array.isArray(model.tags) ? model.tags : [];\n\t\t\t// Only include models that support tools\n\t\t\tif (!tags.includes(\"tool-use\")) continue;\n\n\t\t\tconst input: (\"text\" | \"image\")[] = [\"text\"];\n\t\t\tif (tags.includes(\"vision\")) {\n\t\t\t\tinput.push(\"image\");\n\t\t\t}\n\n\t\t\tconst inputCost = toNumber(model.pricing?.input) * 1_000_000;\n\t\t\tconst outputCost = toNumber(model.pricing?.output) * 1_000_000;\n\t\t\tconst cacheReadCost = toNumber(model.pricing?.input_cache_read) * 1_000_000;\n\t\t\tconst cacheWriteCost = toNumber(model.pricing?.input_cache_write) * 1_000_000;\n\n\t\t\tmodels.push({\n\t\t\t\tid: model.id,\n\t\t\t\tname: model.name || model.id,\n\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\tbaseUrl: AI_GATEWAY_BASE_URL,\n\t\t\t\tprovider: \"vercel-ai-gateway\",\n\t\t\t\treasoning: tags.includes(\"reasoning\"),\n\t\t\t\tinput,\n\t\t\t\tcost: {\n\t\t\t\t\tinput: inputCost,\n\t\t\t\t\toutput: outputCost,\n\t\t\t\t\tcacheRead: cacheReadCost,\n\t\t\t\t\tcacheWrite: cacheWriteCost,\n\t\t\t\t},\n\t\t\t\tcontextWindow: model.context_window || 4096,\n\t\t\t\tmaxTokens: model.max_tokens || 4096,\n\t\t\t});\n\t\t}\n\n\t\tconsole.log(`Fetched ${models.length} tool-capable models from Vercel AI Gateway`);\n\t\treturn models;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to fetch Vercel AI Gateway models:\", error);\n\t\treturn [];\n\t}\n}\n\nasync function loadModelsDevData(): Promise<Model<any>[]> {\n\ttry {\n\t\tconsole.log(\"Fetching models from models.dev API...\");\n\t\tconst response = await fetch(\"https://models.dev/api.json\");\n\t\tconst data = await response.json();\n\n\t\tconst models: Model<any>[] = [];\n\n\t\t// Process Amazon Bedrock models\n\t\tif (data[\"amazon-bedrock\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"amazon-bedrock\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tlet id = modelId;\n\n\t\t\t\tif (id.startsWith(\"ai21.jamba\")) {\n\t\t\t\t\t// These models doesn't support tool use in streaming mode\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (id.startsWith(\"mistral.mistral-7b-instruct-v0\")) {\n\t\t\t\t\t// These models doesn't support system messages\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid,\n\t\t\t\t\tname: m.name || id,\n\t\t\t\t\tapi: \"bedrock-converse-stream\" as const,\n\t\t\t\t\tprovider: \"amazon-bedrock\" as const,\n\t\t\t\t\tbaseUrl: getBedrockBaseUrl(id),\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: (m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"]) as (\"text\" | \"image\")[],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Anthropic models\n\t\tif (data.anthropic?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.anthropic.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\tprovider: \"anthropic\",\n\t\t\t\t\tbaseUrl: \"https://api.anthropic.com\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Google models\n\t\tif (data.google?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.google.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"google-generative-ai\",\n\t\t\t\t\tprovider: \"google\",\n\t\t\t\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process OpenAI models\n\t\tif (data.openai?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.openai.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-responses\",\n\t\t\t\t\tprovider: \"openai\",\n\t\t\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Groq models\n\t\tif (data.groq?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.groq.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"groq\",\n\t\t\t\t\tbaseUrl: \"https://api.groq.com/openai/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Cerebras models\n\t\tif (data.cerebras?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.cerebras.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"cerebras\",\n\t\t\t\t\tbaseUrl: \"https://api.cerebras.ai/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Cloudflare Workers AI models\n\t\tif (data[\"cloudflare-workers-ai\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"cloudflare-workers-ai\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"cloudflare-workers-ai\",\n\t\t\t\t\tbaseUrl: CLOUDFLARE_WORKERS_AI_BASE_URL,\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\tcompat: { sendSessionAffinityHeaders: true },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Cloudflare AI Gateway models\n\t\tif (data[\"cloudflare-ai-gateway\"]?.models) {\n\t\t\tfor (const [prefixedId, model] of Object.entries(data[\"cloudflare-ai-gateway\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tconst slashIdx = prefixedId.indexOf(\"/\");\n\t\t\t\tif (slashIdx === -1) continue;\n\t\t\t\tconst upstream = prefixedId.slice(0, slashIdx);\n\t\t\t\tconst nativeId = prefixedId.slice(slashIdx + 1);\n\n\t\t\t\tlet api: \"anthropic-messages\" | \"openai-completions\" | \"openai-responses\";\n\t\t\t\tlet baseUrl: string;\n\t\t\t\tlet id: string;\n\t\t\t\tif (upstream === \"openai\") {\n\t\t\t\t\tapi = \"openai-responses\";\n\t\t\t\t\tbaseUrl = CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL;\n\t\t\t\t\tid = nativeId;\n\t\t\t\t} else if (upstream === \"anthropic\") {\n\t\t\t\t\tapi = \"anthropic-messages\";\n\t\t\t\t\tbaseUrl = CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL;\n\t\t\t\t\tid = nativeId;\n\t\t\t\t} else if (upstream === \"workers-ai\") {\n\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\tbaseUrl = CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL;\n\t\t\t\t\tid = prefixedId;\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// workers-ai/* through the gateway forwards x-session-affinity to\n\t\t\t\t// the underlying Workers AI runtime for prefix-cache routing.\n\t\t\t\tconst compat = upstream === \"workers-ai\" ? { sendSessionAffinityHeaders: true } : undefined;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid,\n\t\t\t\t\tname: m.name || id,\n\t\t\t\t\tapi,\n\t\t\t\t\tprovider: \"cloudflare-ai-gateway\",\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\t...(compat ? { compat } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process xAi models\n\t\tif (data.xai?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.xai.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"xai\",\n\t\t\t\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process zAi models\n\t\tif (data[\"zai-coding-plan\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"zai-coding-plan\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\tconst supportsImage = m.modalities?.input?.includes(\"image\");\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"zai\",\n\t\t\t\t\tbaseUrl: \"https://api.z.ai/api/coding/paas/v4\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: supportsImage ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcompat: {\n\t\t\t\t\t\tsupportsDeveloperRole: false,\n\t\t\t\t\t\tthinkingFormat: \"zai\",\n\t\t\t\t\t\t...(!ZAI_TOOL_STREAM_UNSUPPORTED_MODELS.has(modelId) ? { zaiToolStream: true } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Mistral models\n\t\tif (data.mistral?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.mistral.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"mistral-conversations\",\n\t\t\t\t\tprovider: \"mistral\",\n\t\t\t\t\tbaseUrl: \"https://api.mistral.ai\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Hugging Face models\n\t\tif (data.huggingface?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data.huggingface.models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"huggingface\",\n\t\t\t\t\tbaseUrl: \"https://router.huggingface.co/v1\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcompat: {\n\t\t\t\t\t\tsupportsDeveloperRole: false,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Fireworks models\n\t\tif (data[\"fireworks-ai\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"fireworks-ai\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\tprovider: \"fireworks\",\n\t\t\t\t\t// Fireworks Anthropic-compatible API - SDK appends /v1/messages\n\t\t\t\t\tbaseUrl: \"https://api.fireworks.ai/inference\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\t// Fireworks prompt caching uses automatic prefix matching + session affinity.\n\t\t\t\t\t// x-session-affinity routes requests to the same replica for cache hits.\n\t\t\t\t\t// cache_control on tools and eager_input_streaming are not supported.\n\t\t\t\t\t// See: https://docs.fireworks.ai/tools-sdks/anthropic-compatibility\n\t\t\t\t\tcompat: {\n\t\t\t\t\t\tsendSessionAffinityHeaders: true,\n\t\t\t\t\t\tsupportsEagerToolInputStreaming: false,\n\t\t\t\t\t\tsupportsCacheControlOnTools: false,\n\t\t\t\t\t\tsupportsLongCacheRetention: false,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Together AI models\n\t\tconst togetherProvider = data.together ?? data.togetherai ?? data[\"together-ai\"];\n\t\tif (togetherProvider?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(togetherProvider.models)) {\n\t\t\t\tconst m = model as ModelsDevModel & { status?: string };\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\tif (m.status === \"deprecated\") continue;\n\n\t\t\t\tconst reasoning = m.reasoning === true;\n\t\t\t\tconst thinkingLevelMap = getTogetherThinkingLevelMap(modelId, reasoning);\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider: \"together\",\n\t\t\t\t\tbaseUrl: TOGETHER_BASE_URL,\n\t\t\t\t\treasoning,\n\t\t\t\t\t...(thinkingLevelMap ? { thinkingLevelMap } : {}),\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcompat: getTogetherCompat(modelId, reasoning),\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process OpenCode models (Zen and Go)\n\t\t// API mapping based on provider.npm field:\n\t\t// - @ai-sdk/openai → openai-responses\n\t\t// - @ai-sdk/anthropic → anthropic-messages\n\t\t// - @ai-sdk/google → google-generative-ai\n\t\t// - null/undefined/@ai-sdk/openai-compatible → openai-completions\n\t\tconst opencodeVariants = [\n\t\t\t{ key: \"opencode\", provider: \"opencode\", basePath: \"https://opencode.ai/zen\" },\n\t\t\t{ key: \"opencode-go\", provider: \"opencode-go\", basePath: \"https://opencode.ai/zen/go\" },\n\t\t] as const;\n\n\t\tfor (const variant of opencodeVariants) {\n\t\t\tif (!data[variant.key]?.models) continue;\n\n\t\t\tfor (const [modelId, model] of Object.entries(data[variant.key].models)) {\n\t\t\t\tconst m = model as ModelsDevModel & { status?: string };\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\tif (m.status === \"deprecated\") continue;\n\n\t\t\t\tconst npm = m.provider?.npm;\n\t\t\t\tlet api: Api;\n\t\t\t\tlet baseUrl: string;\n\t\t\t\tlet compat: OpenAICompletionsCompat | undefined;\n\n\t\t\t\tif (npm === \"@ai-sdk/openai\") {\n\t\t\t\t\tapi = \"openai-responses\";\n\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t} else if (npm === \"@ai-sdk/anthropic\") {\n\t\t\t\t\tapi = \"anthropic-messages\";\n\t\t\t\t\t// Anthropic SDK appends /v1/messages to baseURL\n\t\t\t\t\tbaseUrl = variant.basePath;\n\t\t\t\t} else if (npm === \"@ai-sdk/google\") {\n\t\t\t\t\tapi = \"google-generative-ai\";\n\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t} else if (npm === \"@ai-sdk/alibaba\") {\n\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t\tcompat = { cacheControlFormat: \"anthropic\" };\n\t\t\t\t} else {\n\t\t\t\t\t// null, undefined, or @ai-sdk/openai-compatible\n\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t}\n\n\t\t\t\t// Fix known mismatches between models.dev npm data and actual\n\t\t\t\t// OpenCode Go endpoint behaviour. models.dev reports these models\n\t\t\t\t// as @ai-sdk/anthropic, but the OpenCode Go endpoints either don't\n\t\t\t\t// accept Anthropic SDK auth (MiniMax M2.7) or are served through\n\t\t\t\t// the OpenAI-compatible /v1/chat/completions path (Qwen 3.5/3.6).\n\t\t\t\t// Switch them to openai-completions so requests use Bearer auth\n\t\t\t\t// and the standard /v1/chat/completions endpoint.\n\t\t\t\tif (variant.provider === \"opencode-go\") {\n\t\t\t\t\tif (modelId === \"minimax-m2.7\") {\n\t\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t\t}\n\t\t\t\t\tif (modelId === \"qwen3.5-plus\" || modelId === \"qwen3.6-plus\") {\n\t\t\t\t\t\tapi = \"openai-completions\";\n\t\t\t\t\t\tbaseUrl = `${variant.basePath}/v1`;\n\t\t\t\t\t\t// Qwen/DashScope uses enable_thinking at the top level.\n\t\t\t\t\t\tcompat = { ...(compat ?? {}), thinkingFormat: \"qwen\" };\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi,\n\t\t\t\t\tprovider: variant.provider,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\t...(compat ? { compat } : {}),\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process GitHub Copilot models\n\t\tif (data[\"github-copilot\"]?.models) {\n\t\t\tfor (const [modelId, model] of Object.entries(data[\"github-copilot\"].models)) {\n\t\t\t\tconst m = model as ModelsDevModel & { status?: string };\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\tif (m.status === \"deprecated\") continue;\n\n\t\t\t\t// Claude 4.x models route to Anthropic Messages API\n\t\t\t\tconst isCopilotClaude4 = /^claude-(haiku|sonnet|opus)-4([.\\-]|$)/.test(modelId);\n\t\t\t\t// gpt-5 models require responses API, others use completions\n\t\t\t\tconst needsResponsesApi = modelId.startsWith(\"gpt-5\") || modelId.startsWith(\"oswe\");\n\n\t\t\t\tconst api: Api = isCopilotClaude4\n\t\t\t\t\t? \"anthropic-messages\"\n\t\t\t\t\t: needsResponsesApi\n\t\t\t\t\t\t? \"openai-responses\"\n\t\t\t\t\t\t: \"openai-completions\";\n\n\t\t\t\tconst anthropicCompat =\n\t\t\t\t\tapi === \"anthropic-messages\" ? getAnthropicMessagesCompat(\"github-copilot\", modelId) : undefined;\n\n\t\t\t\tconst copilotModel: Model<any> = {\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi,\n\t\t\t\t\tprovider: \"github-copilot\",\n\t\t\t\t\tbaseUrl: \"https://api.individual.githubcopilot.com\",\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 128000,\n\t\t\t\t\tmaxTokens: m.limit?.output || 8192,\n\t\t\t\t\theaders: { ...COPILOT_STATIC_HEADERS },\n\t\t\t\t\t...(anthropicCompat ? { compat: anthropicCompat } : {}),\n\t\t\t\t\t// compat only applies to openai-completions\n\t\t\t\t\t...(api === \"openai-completions\" ? {\n\t\t\t\t\t\tcompat: {\n\t\t\t\t\t\t\tsupportsStore: false,\n\t\t\t\t\t\t\tsupportsDeveloperRole: false,\n\t\t\t\t\t\t\tsupportsReasoningEffort: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t} : {}),\n\t\t\t\t};\n\n\t\t\t\tmodels.push(copilotModel);\n\t\t\t}\n\t\t}\n\n\t\t// Process MiniMax models\n\t\tconst minimaxVariants = [\n\t\t\t{ key: \"minimax\", provider: \"minimax\", baseUrl: \"https://api.minimax.io/anthropic\" },\n\t\t\t{ key: \"minimax-cn\", provider: \"minimax-cn\", baseUrl: \"https://api.minimaxi.com/anthropic\" },\n\t\t] as const;\n\n\t\tfor (const { key, provider, baseUrl } of minimaxVariants) {\n\t\t\tif (data[key]?.models) {\n\t\t\t\tfor (const [modelId, model] of Object.entries(data[key].models)) {\n\t\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\t\tmodels.push({\n\t\t\t\t\t\tid: modelId,\n\t\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\t\tprovider,\n\t\t\t\t\t\t// MiniMax's Anthropic-compatible API - SDK appends /v1/messages\n\t\t\t\t\t\tbaseUrl,\n\t\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\t\tcost: {\n\t\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Process Kimi For Coding models\n\t\tif (data[\"kimi-for-coding\"]?.models) {\n\t\t\tconst kimiModels = data[\"kimi-for-coding\"].models as Record<string, ModelsDevModel>;\n\t\t\tconst hasCanonicalModel = Object.prototype.hasOwnProperty.call(kimiModels, \"kimi-for-coding\");\n\n\t\t\tconst kimiAliases = new Set([\"k2p5\", \"k2p6\"]);\n\n\t\t\tfor (const [modelId, model] of Object.entries(kimiModels)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\t\t\t\t// models.dev may expose versioned aliases (e.g. k2p5/k2p6).\n\t\t\t\t// Normalize aliases to the canonical model id and drop duplicates when canonical exists.\n\t\t\t\tif (kimiAliases.has(modelId) && hasCanonicalModel) continue;\n\n\t\t\t\tconst normalizedId = kimiAliases.has(modelId) ? \"kimi-for-coding\" : modelId;\n\t\t\t\tconst normalizedName = kimiAliases.has(modelId) ? \"Kimi For Coding\" : m.name || normalizedId;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: normalizedId,\n\t\t\t\t\tname: normalizedName,\n\t\t\t\t\tapi: \"anthropic-messages\",\n\t\t\t\t\tprovider: \"kimi-coding\",\n\t\t\t\t\t// Kimi For Coding's Anthropic-compatible API - SDK appends /v1/messages\n\t\t\t\t\tbaseUrl: \"https://api.kimi.com/coding\",\n\t\t\t\t\theaders: { ...KIMI_STATIC_HEADERS },\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Moonshot AI models\n\t\tconst moonshotVariants = [\n\t\t\t{ key: \"moonshotai\", provider: \"moonshotai\", baseUrl: \"https://api.moonshot.ai/v1\" },\n\t\t\t{ key: \"moonshotai-cn\", provider: \"moonshotai-cn\", baseUrl: \"https://api.moonshot.cn/v1\" },\n\t\t] as const;\n\t\tconst moonshotCompat: OpenAICompletionsCompat = {\n\t\t\tsupportsStore: false,\n\t\t\tsupportsDeveloperRole: false,\n\t\t\tsupportsReasoningEffort: false,\n\t\t\tmaxTokensField: \"max_tokens\",\n\t\t\tsupportsStrictMode: false,\n\t\t};\n\n\t\tfor (const { key, provider, baseUrl } of moonshotVariants) {\n\t\t\tif (!data[key]?.models) continue;\n\n\t\t\tfor (const [modelId, model] of Object.entries(data[key].models)) {\n\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelId,\n\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\tprovider,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t},\n\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\tcompat: moonshotCompat,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Process Xiaomi MiMo models\n\t\t// Built-in `xiaomi` targets the API billing endpoint (single stable URL,\n\t\t// keys from platform.xiaomimimo.com). The three `xiaomi-token-plan-*`\n\t\t// providers cover prepaid Token Plan endpoints in cn / ams / sgp.\n\t\tconst xiaomiCompat: OpenAICompletionsCompat = {\n\t\t\trequiresReasoningContentOnAssistantMessages: true,\n\t\t\tthinkingFormat: \"deepseek\",\n\t\t};\n\t\tconst xiaomiVariants = [\n\t\t\t{ provider: \"xiaomi\", baseUrl: \"https://api.xiaomimimo.com/v1\" },\n\t\t\t{ provider: \"xiaomi-token-plan-cn\", baseUrl: \"https://token-plan-cn.xiaomimimo.com/v1\" },\n\t\t\t{ provider: \"xiaomi-token-plan-ams\", baseUrl: \"https://token-plan-ams.xiaomimimo.com/v1\" },\n\t\t\t{ provider: \"xiaomi-token-plan-sgp\", baseUrl: \"https://token-plan-sgp.xiaomimimo.com/v1\" },\n\t\t] as const;\n\n\t\tif (data.xiaomi?.models) {\n\t\t\tfor (const { provider, baseUrl } of xiaomiVariants) {\n\t\t\t\tfor (const [modelId, model] of Object.entries(data.xiaomi.models)) {\n\t\t\t\t\tconst m = model as ModelsDevModel;\n\t\t\t\t\tif (m.tool_call !== true) continue;\n\n\t\t\t\t\tmodels.push({\n\t\t\t\t\t\tid: modelId,\n\t\t\t\t\t\tname: m.name || modelId,\n\t\t\t\t\t\tapi: \"openai-completions\",\n\t\t\t\t\t\tprovider,\n\t\t\t\t\t\tbaseUrl,\n\t\t\t\t\t\tcompat: xiaomiCompat,\n\t\t\t\t\t\treasoning: m.reasoning === true,\n\t\t\t\t\t\tinput: m.modalities?.input?.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"],\n\t\t\t\t\t\tcost: {\n\t\t\t\t\t\t\tinput: m.cost?.input || 0,\n\t\t\t\t\t\t\toutput: m.cost?.output || 0,\n\t\t\t\t\t\t\tcacheRead: m.cost?.cache_read || 0,\n\t\t\t\t\t\t\tcacheWrite: m.cost?.cache_write || 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcontextWindow: m.limit?.context || 4096,\n\t\t\t\t\t\tmaxTokens: m.limit?.output || 4096,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(`Loaded ${models.length} tool-capable models from models.dev`);\n\t\treturn models;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to load models.dev data:\", error);\n\t\treturn [];\n\t}\n}\n\nasync function generateModels() {\n\t// Fetch models from both sources\n\t// models.dev: Anthropic, Google, OpenAI, Groq, Cerebras\n\t// OpenRouter: xAI and other providers (excluding Anthropic, Google, OpenAI)\n\t// AI Gateway: OpenAI-compatible catalog with tool-capable models\n\tconst modelsDevModels = await loadModelsDevData();\n\tconst openRouterModels = await fetchOpenRouterModels();\n\tconst aiGatewayModels = await fetchAiGatewayModels();\n\n\t// Combine models (models.dev has priority)\n\tconst allModels = [...modelsDevModels, ...openRouterModels, ...aiGatewayModels].filter(\n\t\t(model) =>\n\t\t\t!((model.provider === \"opencode\" || model.provider === \"opencode-go\") && model.id === \"gpt-5.3-codex-spark\"),\n\t);\n\n\t// Fix incorrect cache pricing for Claude Opus 4.5 from models.dev\n\t// models.dev has 3x the correct pricing (1.5/18.75 instead of 0.5/6.25)\n\tconst opus45 = allModels.find(m => m.provider === \"anthropic\" && m.id === \"claude-opus-4-5\");\n\tif (opus45) {\n\t\topus45.cost.cacheRead = 0.5;\n\t\topus45.cost.cacheWrite = 6.25;\n\t}\n\n\t// Temporary overrides until upstream model metadata is corrected.\n\tfor (const candidate of allModels) {\n\t\tif (candidate.provider === \"amazon-bedrock\" && candidate.id.includes(\"anthropic.claude-opus-4-6-v1\")) {\n\t\t\tcandidate.cost.cacheRead = 0.5;\n\t\t\tcandidate.cost.cacheWrite = 6.25;\n\t\t}\n\t\tif (\n\t\t\t(candidate.provider === \"anthropic\" ||\n\t\t\t\tcandidate.provider === \"opencode\" ||\n\t\t\t\tcandidate.provider === \"opencode-go\" ||\n\t\t\t\tcandidate.provider === \"github-copilot\") &&\n\t\t\t(candidate.id === \"claude-opus-4-6\" ||\n\t\t\t\tcandidate.id === \"claude-sonnet-4-6\" ||\n\t\t\t\tcandidate.id === \"claude-opus-4.6\" ||\n\t\t\t\tcandidate.id === \"claude-sonnet-4.6\")\n\t\t) {\n\t\t\tcandidate.contextWindow = 1000000;\n\t\t}\n\n\t\t// OpenCode variants list Claude Sonnet 4/4.5 with 1M context, actual limit is 200K\n\t\tif (\n\t\t\t(candidate.provider === \"opencode\" || candidate.provider === \"opencode-go\") &&\n\t\t\t(candidate.id === \"claude-sonnet-4-5\" || candidate.id === \"claude-sonnet-4\")\n\t\t) {\n\t\t\tcandidate.contextWindow = 200000;\n\t\t}\n\t\tif ((candidate.provider === \"opencode\" || candidate.provider === \"opencode-go\") && candidate.id === \"gpt-5.4\") {\n\t\t\tcandidate.contextWindow = 272000;\n\t\t\tcandidate.maxTokens = 128000;\n\t\t}\n\t\tif (candidate.provider === \"openai\" && (candidate.id === \"gpt-5.4\" || candidate.id === \"gpt-5.5\")) {\n\t\t\tcandidate.contextWindow = 272000;\n\t\t\tcandidate.maxTokens = 128000;\n\t\t}\n\t\t// Keep selected OpenRouter model metadata stable until upstream settles.\n\t\tif (candidate.provider === \"openrouter\" && candidate.id === \"moonshotai/kimi-k2.5\") {\n\t\t\tcandidate.cost.input = 0.41;\n\t\t\tcandidate.cost.output = 2.06;\n\t\t\tcandidate.cost.cacheRead = 0.07;\n\t\t\tcandidate.maxTokens = 4096;\n\t\t}\n\t\tif (candidate.provider === \"openrouter\" && candidate.id === \"z-ai/glm-5\") {\n\t\t\tcandidate.cost.input = 0.6;\n\t\t\tcandidate.cost.output = 1.9;\n\t\t\tcandidate.cost.cacheRead = 0.119;\n\t\t}\n\n\t}\n\n\n\t// Add missing EU Opus 4.6 profile\n\tif (!allModels.some((m) => m.provider === \"amazon-bedrock\" && m.id === \"eu.anthropic.claude-opus-4-6-v1\")) {\n\t\tallModels.push({\n\t\t\tid: \"eu.anthropic.claude-opus-4-6-v1\",\n\t\t\tname: \"Claude Opus 4.6 (EU)\",\n\t\t\tapi: \"bedrock-converse-stream\",\n\t\t\tprovider: \"amazon-bedrock\",\n\t\t\tbaseUrl: getBedrockBaseUrl(\"eu.anthropic.claude-opus-4-6-v1\"),\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 5,\n\t\t\t\toutput: 25,\n\t\t\t\tcacheRead: 0.5,\n\t\t\t\tcacheWrite: 6.25,\n\t\t\t},\n\t\t\tcontextWindow: 200000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\t// Add missing Claude Opus 4.6\n\tif (!allModels.some(m => m.provider === \"anthropic\" && m.id === \"claude-opus-4-6\")) {\n\t\tallModels.push({\n\t\t\tid: \"claude-opus-4-6\",\n\t\t\tname: \"Claude Opus 4.6\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tbaseUrl: \"https://api.anthropic.com\",\n\t\t\tprovider: \"anthropic\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 5,\n\t\t\t\toutput: 25,\n\t\t\t\tcacheRead: 0.5,\n\t\t\t\tcacheWrite: 6.25,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\t// Add missing Claude Opus 4.7\n\tif (!allModels.some(m => m.provider === \"anthropic\" && m.id === \"claude-opus-4-7\")) {\n\t\tallModels.push({\n\t\t\tid: \"claude-opus-4-7\",\n\t\t\tname: \"Claude Opus 4.7\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tbaseUrl: \"https://api.anthropic.com\",\n\t\t\tprovider: \"anthropic\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 5,\n\t\t\t\toutput: 25,\n\t\t\t\tcacheRead: 0.5,\n\t\t\t\tcacheWrite: 6.25,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\t// Add missing Claude Sonnet 4.6\n\tif (!allModels.some(m => m.provider === \"anthropic\" && m.id === \"claude-sonnet-4-6\")) {\n\t\tallModels.push({\n\t\t\tid: \"claude-sonnet-4-6\",\n\t\t\tname: \"Claude Sonnet 4.6\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tbaseUrl: \"https://api.anthropic.com\",\n\t\t\tprovider: \"anthropic\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 3,\n\t\t\t\toutput: 15,\n\t\t\t\tcacheRead: 0.3,\n\t\t\t\tcacheWrite: 3.75,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 64000,\n\t\t});\n\t}\n\n\t// Add missing Gemini 3.1 Flash Lite Preview until models.dev includes it.\n\tif (!allModels.some((m) => m.provider === \"google\" && m.id === \"gemini-3.1-flash-lite-preview\")) {\n\t\tallModels.push({\n\t\t\tid: \"gemini-3.1-flash-lite-preview\",\n\t\t\tname: \"Gemini 3.1 Flash Lite Preview\",\n\t\t\tapi: \"google-generative-ai\",\n\t\t\tbaseUrl: \"https://generativelanguage.googleapis.com/v1beta\",\n\t\t\tprovider: \"google\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t});\n\t}\n\n\t// Add missing gpt models\n\tif (!allModels.some(m => m.provider === \"openai\" && m.id === \"gpt-5-chat-latest\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5-chat-latest\",\n\t\t\tname: \"GPT-5 Chat Latest\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.25,\n\t\t\t\toutput: 10,\n\t\t\t\tcacheRead: 0.125,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 128000,\n\t\t\tmaxTokens: 16384,\n\t\t});\n\t}\n\n\tif (!allModels.some(m => m.provider === \"openai\" && m.id === \"gpt-5.1-codex\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5.1-codex\",\n\t\t\tname: \"GPT-5.1 Codex\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.25,\n\t\t\t\toutput: 5,\n\t\t\t\tcacheRead: 0.125,\n\t\t\t\tcacheWrite: 1.25,\n\t\t\t},\n\t\t\tcontextWindow: 400000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\tif (!allModels.some(m => m.provider === \"openai\" && m.id === \"gpt-5.1-codex-max\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5.1-codex-max\",\n\t\t\tname: \"GPT-5.1 Codex Max\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.25,\n\t\t\t\toutput: 10,\n\t\t\t\tcacheRead: 0.125,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 400000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\tif (!allModels.some(m => m.provider === \"openai\" && m.id === \"gpt-5.3-codex-spark\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5.3-codex-spark\",\n\t\t\tname: \"GPT-5.3 Codex Spark\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 128000,\n\t\t\tmaxTokens: 16384,\n\t\t});\n\t}\n\n\t// Add missing GitHub Copilot GPT-5.3 models until models.dev includes them.\n\tconst copilotBaseModel = allModels.find(\n\t\t(m) => m.provider === \"github-copilot\" && m.id === \"gpt-5.2-codex\",\n\t);\n\tif (copilotBaseModel) {\n\t\tif (!allModels.some((m) => m.provider === \"github-copilot\" && m.id === \"gpt-5.3-codex\")) {\n\t\t\tallModels.push({\n\t\t\t\t...copilotBaseModel,\n\t\t\t\tid: \"gpt-5.3-codex\",\n\t\t\t\tname: \"GPT-5.3 Codex\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (!allModels.some((m) => m.provider === \"openai\" && m.id === \"gpt-5.4\")) {\n\t\tallModels.push({\n\t\t\tid: \"gpt-5.4\",\n\t\t\tname: \"GPT-5.4\",\n\t\t\tapi: \"openai-responses\",\n\t\t\tbaseUrl: \"https://api.openai.com/v1\",\n\t\t\tprovider: \"openai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 2.5,\n\t\t\t\toutput: 15,\n\t\t\t\tcacheRead: 0.25,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 272000,\n\t\t\tmaxTokens: 128000,\n\t\t});\n\t}\n\n\tconst deepseekCompat: OpenAICompletionsCompat = {\n\t\trequiresReasoningContentOnAssistantMessages: true,\n\t\tthinkingFormat: \"deepseek\",\n\t};\n\tconst deepseekV4Models: Model<\"openai-completions\">[] = [\n\t\t{\n\t\t\tid: \"deepseek-v4-flash\",\n\t\t\tname: \"DeepSeek V4 Flash\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\t\tprovider: \"deepseek\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.14,\n\t\t\t\toutput: 0.28,\n\t\t\t\tcacheRead: 0.0028,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 384000,\n\t\t\tcompat: deepseekCompat,\n\t\t},\n\t\t{\n\t\t\tid: \"deepseek-v4-pro\",\n\t\t\tname: \"DeepSeek V4 Pro\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.deepseek.com\",\n\t\t\tprovider: \"deepseek\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.435,\n\t\t\t\toutput: 0.87,\n\t\t\t\tcacheRead: 0.003625,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 384000,\n\t\t\tcompat: deepseekCompat,\n\t\t},\n\t];\n\tallModels.push(...deepseekV4Models);\n\n\tfor (const candidate of allModels) {\n\t\tif (candidate.api === \"openai-completions\" && candidate.id.includes(\"deepseek-v4\")) {\n\t\t\tcandidate.compat = {\n\t\t\t\t...candidate.compat,\n\t\t\t\t...(candidate.provider === \"openrouter\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\trequiresReasoningContentOnAssistantMessages:\n\t\t\t\t\t\t\t\tdeepseekCompat.requiresReasoningContentOnAssistantMessages,\n\t\t\t\t\t\t\tthinkingFormat: deepseekCompat.thinkingFormat,\n\t\t\t\t\t\t}\n\t\t\t\t\t: deepseekCompat),\n\t\t\t};\n\t\t\tmergeThinkingLevelMap(candidate, DEEPSEEK_V4_THINKING_LEVEL_MAP);\n\t\t}\n\t}\n\n\tconst minimaxDirectSupportedIds = new Set([\"MiniMax-M2.7\", \"MiniMax-M2.7-highspeed\"]);\n\n\tfor (const candidate of allModels) {\n\t\tif (\n\t\t\t(candidate.provider === \"minimax\" || candidate.provider === \"minimax-cn\") &&\n\t\t\tminimaxDirectSupportedIds.has(candidate.id)\n\t\t) {\n\t\t\tcandidate.contextWindow = 204800;\n\t\t\tcandidate.maxTokens = 131072;\n\t\t}\n\t}\n\n\tfor (let i = allModels.length - 1; i >= 0; i--) {\n\t\tconst candidate = allModels[i];\n\t\tif (\n\t\t\t(candidate.provider === \"minimax\" || candidate.provider === \"minimax-cn\") &&\n\t\t\t!minimaxDirectSupportedIds.has(candidate.id)\n\t\t) {\n\t\t\tallModels.splice(i, 1);\n\t\t}\n\t}\n\n\t// OpenAI Codex (ChatGPT OAuth) models\n\t// NOTE: These are not fetched from models.dev; we keep a small, explicit list to avoid aliases.\n\t// Context window is based on observed server limits (400s above ~272k), not marketing numbers.\n\tconst CODEX_BASE_URL = \"https://chatgpt.com/backend-api\";\n\tconst CODEX_CONTEXT = 272000;\n\tconst CODEX_MAX_TOKENS = 128000;\n\tconst codexModels: Model<\"openai-codex-responses\">[] = [\n\t\t{\n\t\t\tid: \"gpt-5.2\",\n\t\t\tname: \"GPT-5.2\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.3-codex\",\n\t\t\tname: \"GPT-5.3 Codex\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.3-codex-spark\",\n\t\t\tname: \"GPT-5.3 Codex Spark\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.4\",\n\t\t\tname: \"GPT-5.4\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.4-mini\",\n\t\t\tname: \"GPT-5.4 mini\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t\t{\n\t\t\tid: \"gpt-5.5\",\n\t\t\tname: \"GPT-5.5\",\n\t\t\tapi: \"openai-codex-responses\",\n\t\t\tprovider: \"openai-codex\",\n\t\t\tbaseUrl: CODEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },\n\t\t\tcontextWindow: CODEX_CONTEXT,\n\t\t\tmaxTokens: CODEX_MAX_TOKENS,\n\t\t},\n\t];\n\tallModels.push(...codexModels);\n\n\t// Add missing Grok models\n\tconst missingGrokModels: Model<\"openai-completions\">[] = [\n\t\t{\n\t\t\tid: \"grok-3\",\n\t\t\tname: \"Grok 3\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\t\tprovider: \"xai\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: { input: 3, output: 15, cacheRead: 0.75, cacheWrite: 0 },\n\t\t\tcontextWindow: 131072,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t\t{\n\t\t\tid: \"grok-3-fast\",\n\t\t\tname: \"Grok 3 Fast\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\t\tprovider: \"xai\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: { input: 5, output: 25, cacheRead: 1.25, cacheWrite: 0 },\n\t\t\tcontextWindow: 131072,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t\t{\n\t\t\tid: \"grok-code-fast-1\",\n\t\t\tname: \"Grok Code Fast 1\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tbaseUrl: \"https://api.x.ai/v1\",\n\t\t\tprovider: \"xai\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.2,\n\t\t\t\toutput: 1.5,\n\t\t\t\tcacheRead: 0.02,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 32768,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t];\n\tfor (const model of missingGrokModels) {\n\t\tif (!allModels.some(m => m.provider === model.provider && m.id === model.id)) {\n\t\t\tallModels.push(model);\n\t\t}\n\t}\n\n\t// Add missing Mistral Medium 3.5 model until models.dev includes it\n\tif (!allModels.some(m => m.provider === \"mistral\" && m.id === \"mistral-medium-3.5\")) {\n\t\tallModels.push({\n\t\t\tid: \"mistral-medium-3.5\",\n\t\t\tname: \"Mistral Medium 3.5\",\n\t\t\tapi: \"mistral-conversations\",\n\t\t\tprovider: \"mistral\",\n\t\t\tbaseUrl: \"https://api.mistral.ai\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.5,\n\t\t\t\toutput: 7.5,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 262144, // 256k tokens\n\t\t\tmaxTokens: 262144,\n\t\t});\n\t}\n","truncated":true,"truncatedBy":"bytes","totalLines":1929,"totalBytes":59721,"outputLines":1644,"outputBytes":51193,"lastLinePartial":false,"firstLineExceedsLimit":false,"maxLines":2000,"maxBytes":51200}},"isError":false,"timestamp":1782037443977}}
{"type":"message","id":"670b0376","parentId":"576382d4","timestamp":"2026-06-21T10:24:03.982Z","message":{"role":"toolResult","toolCallId":"call_1d63","toolName":"read","content":[{"type":"text","text":"# Custom Models\n\nAdd custom providers and models (Ollama, vLLM, LM Studio, proxies) via `~/.pi/agent/models.json`.\n\n## Table of Contents\n\n- [Minimal Example](#minimal-example)\n- [Full Example](#full-example)\n- [Supported APIs](#supported-apis)\n- [Provider Configuration](#provider-configuration)\n- [Model Configuration](#model-configuration)\n- [Overriding Built-in Providers](#overriding-built-in-providers)\n- [Per-model Overrides](#per-model-overrides)\n- [Anthropic Messages Compatibility](#anthropic-messages-compatibility)\n- [OpenAI Compatibility](#openai-compatibility)\n\n## Minimal Example\n\nFor local models (Ollama, LM Studio, vLLM), only `id` is required per model:\n\n```json\n{\n \"providers\": {\n \"ollama\": {\n \"baseUrl\": \"http://localhost:11434/v1\",\n \"api\": \"openai-completions\",\n \"apiKey\": \"ollama\",\n \"models\": [\n { \"id\": \"llama3.1:8b\" },\n { \"id\": \"qwen2.5-coder:7b\" }\n ]\n }\n }\n}\n```\n\nThe `apiKey` is required but Ollama ignores it, so any value works.\n\nSome OpenAI-compatible servers do not understand the `developer` role used for reasoning-capable models. For those providers, set `compat.supportsDeveloperRole` to `false` so pi sends the system prompt as a `system` message instead. If the server also does not support `reasoning_effort`, set `compat.supportsReasoningEffort` to `false` too.\n\nYou can set `compat` at the provider level to apply to all models, or at the model level to override a specific model. This commonly applies to Ollama, vLLM, SGLang, and similar OpenAI-compatible servers.\n\n```json\n{\n \"providers\": {\n \"ollama\": {\n \"baseUrl\": \"http://localhost:11434/v1\",\n \"api\": \"openai-completions\",\n \"apiKey\": \"ollama\",\n \"compat\": {\n \"supportsDeveloperRole\": false,\n \"supportsReasoningEffort\": false\n },\n \"models\": [\n {\n \"id\": \"gpt-oss:20b\",\n \"reasoning\": true\n }\n ]\n }\n }\n}\n```\n\n## Full Example\n\nOverride defaults when you need specific values:\n\n```json\n{\n \"providers\": {\n \"ollama\": {\n \"baseUrl\": \"http://localhost:11434/v1\",\n \"api\": \"openai-completions\",\n \"apiKey\": \"ollama\",\n \"models\": [\n {\n \"id\": \"llama3.1:8b\",\n \"name\": \"Llama 3.1 8B (Local)\",\n \"reasoning\": false,\n \"input\": [\"text\"],\n \"contextWindow\": 128000,\n \"maxTokens\": 32000,\n \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0 }\n }\n ]\n }\n }\n}\n```\n\nThe file reloads each time you open `/model`. Edit during session; no restart needed.\n\n## Google AI Studio Example\n\nUse `google-generative-ai` with a `baseUrl` to add models from Google AI Studio, including custom Gemma 4 entries:\n\n```json\n{\n \"providers\": {\n \"my-google\": {\n \"baseUrl\": \"https://generativelanguage.googleapis.com/v1beta\",\n \"api\": \"google-generative-ai\",\n \"apiKey\": \"GEMINI_API_KEY\",\n \"models\": [\n {\n \"id\": \"gemma-4-31b-it\",\n \"name\": \"Gemma 4 31B\",\n \"input\": [\"text\", \"image\"],\n \"contextWindow\": 262144,\n \"reasoning\": true\n }\n ]\n }\n }\n}\n```\n\nThe `baseUrl` is required when adding custom models to the `google-generative-ai` API type.\n\n## Supported APIs\n\n| API | Description |\n|-----|-------------|\n| `openai-completions` | OpenAI Chat Completions (most compatible) |\n| `openai-responses` | OpenAI Responses API |\n| `anthropic-messages` | Anthropic Messages API |\n| `google-generative-ai` | Google Generative AI |\n\nSet `api` at provider level (default for all models) or model level (override per model).\n\n## Provider Configuration\n\n| Field | Description |\n|-------|-------------|\n| `baseUrl` | API endpoint URL |\n| `api` | API type (see above) |\n| `apiKey` | API key (see value resolution below) |\n| `headers` | Custom headers (see value resolution below) |\n| `authHeader` | Set `true` to add `Authorization: Bearer <apiKey>` automatically |\n| `models` | Array of model configurations |\n| `modelOverrides` | Per-model overrides for built-in models on this provider |\n\n### Value Resolution\n\nThe `apiKey` and `headers` fields support three formats:\n\n- **Shell command:** `\"!command\"` executes and uses stdout\n ```json\n \"apiKey\": \"!security find-generic-password -ws 'anthropic'\"\n \"apiKey\": \"!op read 'op://vault/item/credential'\"\n ```\n- **Environment variable:** Uses the value of the named variable\n ```json\n \"apiKey\": \"MY_API_KEY\"\n ```\n- **Literal value:** Used directly\n ```json\n \"apiKey\": \"sk-...\"\n ```\n\nFor `models.json`, shell commands are resolved at request time. pi intentionally does not apply built-in TTL, stale reuse, or recovery logic for arbitrary commands. Different commands need different caching and failure strategies, and pi cannot infer the right one.\n\nIf your command is slow, expensive, rate-limited, or should keep using a previous value on transient failures, wrap it in your own script or command that implements the caching or TTL behavior you want.\n\n`/model` availability checks use configured auth presence and do not execute shell commands.\n\n### Custom Headers\n\n```json\n{\n \"providers\": {\n \"custom-proxy\": {\n \"baseUrl\": \"https://proxy.example.com/v1\",\n \"apiKey\": \"MY_API_KEY\",\n \"api\": \"anthropic-messages\",\n \"headers\": {\n \"x-portkey-api-key\": \"PORTKEY_API_KEY\",\n \"x-secret\": \"!op read 'op://vault/item/secret'\"\n },\n \"models\": [...]\n }\n }\n}\n```\n\n## Model Configuration\n\n| Field | Required | Default | Description |\n|-------|----------|---------|-------------|\n| `id` | Yes | — | Model identifier (passed to the API) |\n| `name` | No | `id` | Human-readable model label. Used for matching (`--model` patterns) and shown in model details/status text. |\n| `api` | No | provider's `api` | Override provider's API for this model |\n| `reasoning` | No | `false` | Supports extended thinking |\n| `thinkingLevelMap` | No | omitted | Maps pi thinking levels to provider values and marks unsupported levels (see below) |\n| `input` | No | `[\"text\"]` | Input types: `[\"text\"]` or `[\"text\", \"image\"]` |\n| `contextWindow` | No | `128000` | Context window size in tokens |\n| `maxTokens` | No | `16384` | Maximum output tokens |\n| `cost` | No | all zeros | `{\"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0}` (per million tokens) |\n| `compat` | No | provider `compat` | Provider compatibility overrides. Merged with provider-level `compat` when both are set. |\n\nCurrent behavior:\n- `/model` and `--list-models` list entries by model `id`.\n- The configured `name` is used for model matching and detail/status text.\n\n### Thinking Level Map\n\nUse `thinkingLevelMap` on a model to describe model-specific thinking controls. Keys are pi thinking levels: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`.\n\nValues are tristate:\n\n| Value | Meaning |\n|-------|---------|\n| omitted | Level is supported and uses the provider's default mapping |\n| string | Level is supported and this value is sent to the provider |\n| `null` | Level is unsupported and hidden/skipped/clamped away |\n\nExample for a model that only supports off, high, and max reasoning:\n\n```json\n{\n \"id\": \"deepseek-v4-pro\",\n \"reasoning\": true,\n \"thinkingLevelMap\": {\n \"minimal\": null,\n \"low\": null,\n \"medium\": null,\n \"high\": \"high\",\n \"xhigh\": \"max\"\n }\n}\n```\n\nExample for a model where thinking cannot be disabled:\n\n```json\n{\n \"id\": \"always-thinking-model\",\n \"reasoning\": true,\n \"thinkingLevelMap\": {\n \"off\": null\n }\n}\n```\n\nMigration: older configs that used `compat.reasoningEffortMap` should move that mapping to model-level `thinkingLevelMap`. Use `null` for levels that should not appear in the UI.\n\n## Overriding Built-in Providers\n\nRoute a built-in provider through a proxy without redefining models:\n\n```json\n{\n \"providers\": {\n \"anthropic\": {\n \"baseUrl\": \"https://my-proxy.example.com/v1\"\n }\n }\n}\n```\n\nAll built-in Anthropic models remain available. Existing OAuth or API key auth continues to work.\n\nTo merge custom models into a built-in provider, include the `models` array:\n\n```json\n{\n \"providers\": {\n \"anthropic\": {\n \"baseUrl\": \"https://my-proxy.example.com/v1\",\n \"apiKey\": \"ANTHROPIC_API_KEY\",\n \"api\": \"anthropic-messages\",\n \"models\": [...]\n }\n }\n}\n```\n\nMerge semantics:\n- Built-in models are kept.\n- Custom models are upserted by `id` within the provider.\n- If a custom model `id` matches a built-in model `id`, the custom model replaces that built-in model.\n- If a custom model `id` is new, it is added alongside built-in models.\n\n## Per-model Overrides\n\nUse `modelOverrides` to customize specific built-in models without replacing the provider's full model list.\n\n```json\n{\n \"providers\": {\n \"openrouter\": {\n \"modelOverrides\": {\n \"anthropic/claude-sonnet-4\": {\n \"name\": \"Claude Sonnet 4 (Bedrock Route)\",\n \"compat\": {\n \"openRouterRouting\": {\n \"only\": [\"amazon-bedrock\"]\n }\n }\n }\n }\n }\n }\n}\n```\n\n`modelOverrides` supports these fields per model: `name`, `reasoning`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `headers`, `compat`.\n\nBehavior notes:\n- `modelOverrides` are applied to built-in provider models.\n- Unknown model IDs are ignored.\n- You can combine provider-level `baseUrl`/`headers` with `modelOverrides`.\n- If `models` is also defined for a provider, custom models are merged after built-in overrides. A custom model with the same `id` replaces the overridden built-in model entry.\n\n## Anthropic Messages Compatibility\n\nFor providers or proxies using `api: \"anthropic-messages\"`, use `compat.supportsEagerToolInputStreaming` to control Anthropic fine-grained tool streaming compatibility.\n\nBy default pi sends per-tool `eager_input_streaming: true`. If a proxy or Anthropic-compatible backend rejects that field, set `supportsEagerToolInputStreaming` to `false`. Pi will omit `tools[].eager_input_streaming` and send the legacy `fine-grained-tool-streaming-2025-05-14` beta header for tool-enabled requests instead.\n\n```json\n{\n \"providers\": {\n \"anthropic-proxy\": {\n \"baseUrl\": \"https://proxy.example.com\",\n \"api\": \"anthropic-messages\",\n \"apiKey\": \"ANTHROPIC_PROXY_KEY\",\n \"compat\": {\n \"supportsEagerToolInputStreaming\": false,\n \"supportsLongCacheRetention\": true\n },\n \"models\": [\n {\n \"id\": \"claude-opus-4-7\",\n \"reasoning\": true,\n \"input\": [\"text\", \"image\"]\n }\n ]\n }\n }\n}\n```\n\n| Field | Description |\n|-------|-------------|\n| `supportsEagerToolInputStreaming` | Whether the provider accepts per-tool `eager_input_streaming`. Default: `true`. Set to `false` to omit that field and use the legacy fine-grained tool streaming beta header on tool-enabled requests. |\n| `supportsLongCacheRetention` | Whether the provider accepts Anthropic long cache retention (`cache_control.ttl: \"1h\"`) when cache retention is `long`. Default: `true`. |\n\n## OpenAI Compatibility\n\nFor providers with partial OpenAI compatibility, use the `compat` field.\n\n- Provider-level `compat` applies defaults to all models under that provider.\n- Model-level `compat` overrides provider-level values for that model.\n\n```json\n{\n \"providers\": {\n \"local-llm\": {\n \"baseUrl\": \"http://localhost:8080/v1\",\n \"api\": \"openai-completions\",\n \"compat\": {\n \"supportsUsageInStreaming\": false,\n \"maxTokensField\": \"max_tokens\"\n },\n \"models\": [...]\n }\n }\n}\n```\n\n| Field | Description |\n|-------|-------------|\n| `supportsStore` | Provider supports `store` field |\n| `supportsDeveloperRole` | Use `developer` vs `system` role |\n| `supportsReasoningEffort` | Support for `reasoning_effort` parameter |\n| `supportsUsageInStreaming` | Supports `stream_options: { include_usage: true }` (default: `true`) |\n| `maxTokensField` | Use `max_completion_tokens` or `max_tokens` |\n| `requiresToolResultName` | Include `name` on tool result messages |\n| `requiresAssistantAfterToolResult` | Insert an assistant message before a user message after tool results |\n| `requiresThinkingAsText` | Convert thinking blocks to plain text |\n| `requiresReasoningContentOnAssistantMessages` | Include empty `reasoning_content` on all replayed assistant messages when reasoning is enabled |\n| `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, or `qwen-chat-template` thinking parameters |\n| `cacheControlFormat` | Use Anthropic-style `cache_control` markers on the system prompt, last tool definition, and last user/assistant text content. Currently only `anthropic` is supported. |\n| `supportsStrictMode` | Include the `strict` field in tool definitions |\n| `supportsLongCacheRetention` | Whether the provider accepts long cache retention when cache retention is `long`: `prompt_cache_retention: \"24h\"` for OpenAI prompt caching, or `cache_control.ttl: \"1h\"` when `cacheControlFormat` is `anthropic`. Default: `true`. |\n| `openRouterRouting` | OpenRouter provider routing preferences. This object is sent as-is in the `provider` field of the [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection). |\n| `vercelGatewayRouting` | Vercel AI Gateway routing config for provider selection (`only`, `order`) |\n\n`openrouter` uses `reasoning: { effort }`. `together` uses `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking`.\n\n`cacheControlFormat: \"anthropic\"` is for OpenAI-compatible providers that expose Anthropic-style prompt caching through `cache_control` markers on text content and tool definitions.\n\nExample:\n\n```json\n{\n \"providers\": {\n \"openrouter\": {\n \"baseUrl\": \"https://openrouter.ai/api/v1\",\n \"apiKey\": \"OPENROUTER_API_KEY\",\n \"api\": \"openai-completions\",\n \"models\": [\n {\n \"id\": \"openrouter/anthropic/claude-3.5-sonnet\",\n \"name\": \"OpenRouter Claude 3.5 Sonnet\",\n \"compat\": {\n \"openRouterRouting\": {\n \"allow_fallbacks\": true,\n \"require_parameters\": false,\n \"data_collection\": \"deny\",\n \"zdr\": true,\n \"enforce_distillable_text\": false,\n \"order\": [\"anthropic\", \"amazon-bedrock\", \"google-vertex\"],\n \"only\": [\"anthropic\", \"amazon-bedrock\"],\n \"ignore\": [\"gmicloud\", \"friendli\"],\n \"quantizations\": [\"fp16\", \"bf16\"],\n \"sort\": {\n \"by\": \"price\",\n \"partition\": \"model\"\n },\n \"max_price\": {\n \"prompt\": 10,\n \"completion\": 20\n },\n \"preferred_min_throughput\": {\n \"p50\": 100,\n \"p90\": 50\n },\n \"preferred_max_latency\": {\n \"p50\": 1,\n \"p90\": 3,\n \"p99\": 5\n }\n }\n }\n }\n ]\n }\n }\n}\n```\n\nVercel AI Gateway example:\n\n```json\n{\n \"providers\": {\n \"vercel-ai-gateway\": {\n \"baseUrl\": \"https://ai-gateway.vercel.sh/v1\",\n \"apiKey\": \"AI_GATEWAY_API_KEY\",\n \"api\": \"openai-completions\",\n \"models\": [\n {\n \"id\": \"moonshotai/kimi-k2.5\",\n \"name\": \"Kimi K2.5 (Fireworks via Vercel)\",\n \"reasoning\": true,\n \"input\": [\"text\", \"image\"],\n \"cost\": { \"input\": 0.6, \"output\": 3, \"cacheRead\": 0, \"cacheWrite\": 0 },\n \"contextWindow\": 262144,\n \"maxTokens\": 262144,\n \"compat\": {\n \"vercelGatewayRouting\": {\n \"only\": [\"fireworks\", \"novita\"],\n \"order\": [\"fireworks\", \"novita\"]\n }\n }\n }\n ]\n }\n }\n}\n```\n"}],"isError":false,"timestamp":1782037443982}}
{"type":"message","id":"61f81213","parentId":"670b0376","timestamp":"2026-06-21T10:24:06.912Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Now I need to find the opencode-go provider in the generate-models.ts file and look at what GLM models currently exist. Also, I should check what GLM 5.2 model info is available. Let me first check the current GLM models in the generated modelsfile.","thinkingSignature":"reasoning_content"},{"type":"text","text":"让我先查看现有的 GLM 模型和 opencode-go 相关内容:"},{"type":"toolCall","id":"call_a5ad","name":"ffgrep","arguments":{"pattern":"glm","path":"/shumengya/project/agent/sproutclaw/packages/ai/scripts/generate-models.ts","caseSensitive":true}},{"type":"toolCall","id":"call_5735","name":"ffgrep","arguments":{"pattern":"opencode-go","path":"/shumengya/project/agent/sproutclaw/packages/ai/scripts/generate-models.ts"}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":19801,"output":156,"cacheRead":11008,"cacheWrite":0,"totalTokens":30965,"cost":{"input":0.0277214,"output":0.0006864,"cacheRead":0.00286208,"cacheWrite":0,"total":0.03126988}},"stopReason":"toolUse","timestamp":1782037443984,"responseId":"chatcmpl-Rrw5NbQeh1TGgFvBQsgFupPJ","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"c932e53e","parentId":"61f81213","timestamp":"2026-06-21T10:24:06.918Z","message":{"role":"toolResult","toolCallId":"call_a5ad","toolName":"ffgrep","content":[{"type":"text","text":"packages/ai/scripts/generate-models.ts\n 120: const ZAI_TOOL_STREAM_UNSUPPORTED_MODELS = new Set([\"glm-4.5\", \"glm-4.5-air\", \"glm-4.5-flash\", \"glm-4.5v\"]);\n 1181: if (candidate.provider === \"openrouter\" && candidate.id === \"z-ai/glm-5\") {"}],"details":{"totalMatched":2,"totalFiles":794},"isError":false,"timestamp":1782037446918}}
{"type":"message","id":"a84f40e1","parentId":"c932e53e","timestamp":"2026-06-21T10:24:06.919Z","message":{"role":"toolResult","toolCallId":"call_5735","toolName":"ffgrep","content":[{"type":"text","text":"packages/ai/scripts/generate-models.ts\n 822: { key: \"opencode-go\", provider: \"opencode-go\", basePath: \"https://opencode.ai/zen/go\" },\n 865: if (variant.provider === \"opencode-go\") {\n 1129: !((model.provider === \"opencode\" || model.provider === \"opencode-go\") && model.id === \"gpt-5.3-codex-spark\"),\n 1149: candidate.provider === \"opencode-go\" ||\n 1161: (candidate.provider === \"opencode\" || candidate.provider === \"opencode-go\") &&\n 1166: if ((candidate.provider === \"opencode\" || candidate.provider === \"opencode-go\") && candidate.id === \"gpt-5.4\") {"}],"details":{"totalMatched":6,"totalFiles":794},"isError":false,"timestamp":1782037446919}}
{"type":"message","id":"528919c5","parentId":"a84f40e1","timestamp":"2026-06-21T10:24:09.293Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_8f8b","name":"read","arguments":{"path":"/shumengya/project/agent/sproutclaw/packages/ai/scripts/generate-models.ts","offset":1645}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":372,"output":37,"cacheRead":30848,"cacheWrite":0,"totalTokens":31257,"cost":{"input":0.0005208,"output":0.0001628,"cacheRead":0.00802048,"cacheWrite":0,"total":0.00870408}},"stopReason":"toolUse","timestamp":1782037446920,"responseId":"chatcmpl-RDCcUWhPx3iLWtPs3wlDU5Ei","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"42d0497c","parentId":"528919c5","timestamp":"2026-06-21T10:24:09.296Z","message":{"role":"toolResult","toolCallId":"call_8f8b","toolName":"read","content":[{"type":"text","text":"\t// Add \"auto\" alias for openrouter/auto\n\tif (!allModels.some(m => m.provider === \"openrouter\" && m.id === \"auto\")) {\n\t\tallModels.push({\n\t\t\tid: \"auto\",\n\t\t\tname: \"Auto\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"openrouter\",\n\t\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\t// we dont know about the costs because OpenRouter auto routes to different models\n\t\t\t\t// and then charges you for the underlying used model\n\t\t\t\tinput:0,\n\t\t\t\toutput:0,\n\t\t\t\tcacheRead:0,\n\t\t\t\tcacheWrite:0,\n\t\t\t},\n\t\t\tcontextWindow: 2000000,\n\t\t\tmaxTokens: 30000,\n\t\t});\n\t}\n\n\tconst VERTEX_BASE_URL = \"https://{location}-aiplatform.googleapis.com\";\n\tconst vertexModels: Model<\"google-vertex\">[] = [\n\t\t{\n\t\t\tid: \"gemini-3-pro-preview\",\n\t\t\tname: \"Gemini 3 Pro Preview (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 64000,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-3.1-pro-preview\",\n\t\t\tname: \"Gemini 3.1 Pro Preview (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-3.1-pro-preview-customtools\",\n\t\t\tname: \"Gemini 3.1 Pro Preview Custom Tools (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-3-flash-preview\",\n\t\t\tname: \"Gemini 3 Flash Preview (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-2.0-flash\",\n\t\t\tname: \"Gemini 2.0 Flash (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.15, output: 0.6, cacheRead: 0.0375, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-2.0-flash-lite\",\n\t\t\tname: \"Gemini 2.0 Flash Lite (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.075, output: 0.3, cacheRead: 0.01875, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-2.5-pro\",\n\t\t\tname: \"Gemini 2.5 Pro (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-2.5-flash\",\n\t\t\tname: \"Gemini 2.5 Flash (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-2.5-flash-lite-preview-09-2025\",\n\t\t\tname: \"Gemini 2.5 Flash Lite Preview 09-25 (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.1, output: 0.4, cacheRead: 0.01, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-2.5-flash-lite\",\n\t\t\tname: \"Gemini 2.5 Flash Lite (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.1, output: 0.4, cacheRead: 0.01, cacheWrite: 0 },\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 65536,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-1.5-pro\",\n\t\t\tname: \"Gemini 1.5 Pro (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 1.25, output: 5, cacheRead: 0.3125, cacheWrite: 0 },\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-1.5-flash\",\n\t\t\tname: \"Gemini 1.5 Flash (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.075, output: 0.3, cacheRead: 0.01875, cacheWrite: 0 },\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t\t{\n\t\t\tid: \"gemini-1.5-flash-8b\",\n\t\t\tname: \"Gemini 1.5 Flash-8B (Vertex)\",\n\t\t\tapi: \"google-vertex\",\n\t\t\tprovider: \"google-vertex\",\n\t\t\tbaseUrl: VERTEX_BASE_URL,\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: { input: 0.0375, output: 0.15, cacheRead: 0.01, cacheWrite: 0 },\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 8192,\n\t\t},\n\t];\n\tallModels.push(...vertexModels);\n\n\tconst azureOpenAiModels: Model<Api>[] = allModels\n\t\t.filter((model) => model.provider === \"openai\" && model.api === \"openai-responses\")\n\t\t.map((model) => ({\n\t\t\t...model,\n\t\t\tapi: \"azure-openai-responses\",\n\t\t\tprovider: \"azure-openai-responses\",\n\t\t\tbaseUrl: \"\",\n\t\t}));\n\tallModels.push(...azureOpenAiModels);\n\n\tfor (const model of allModels) {\n\t\tapplyThinkingLevelMetadata(model);\n\t}\n\n\t// Group by provider and deduplicate by model ID\n\tconst providers: Record<string, Record<string, Model<any>>> = {};\n\tfor (const model of allModels) {\n\t\tif (!providers[model.provider]) {\n\t\t\tproviders[model.provider] = {};\n\t\t}\n\t\t// Use model ID as key to automatically deduplicate\n\t\t// Only add if not already present (models.dev takes priority over OpenRouter)\n\t\tif (!providers[model.provider][model.id]) {\n\t\t\tproviders[model.provider][model.id] = model;\n\t\t}\n\t}\n\n\t// Generate TypeScript file\n\tlet output = `// This file is auto-generated by scripts/generate-models.ts\n// Do not edit manually - run 'npm run generate-models' to update\n\nimport type { Model } from \"./types.ts\";\n\nexport const MODELS = {\n`;\n\n\t// Generate provider sections (sorted for deterministic output)\n\tconst sortedProviderIds = Object.keys(providers).sort();\n\tfor (const providerId of sortedProviderIds) {\n\t\tconst models = providers[providerId];\n\t\toutput += `\\t${JSON.stringify(providerId)}: {\\n`;\n\n\t\tconst sortedModelIds = Object.keys(models).sort();\n\t\tfor (const modelId of sortedModelIds) {\n\t\t\tconst model = models[modelId];\n\t\t\toutput += `\\t\\t\"${model.id}\": {\\n`;\n\t\t\toutput += `\\t\\t\\tid: \"${model.id}\",\\n`;\n\t\t\toutput += `\\t\\t\\tname: \"${model.name}\",\\n`;\n\t\t\toutput += `\\t\\t\\tapi: \"${model.api}\",\\n`;\n\t\t\toutput += `\\t\\t\\tprovider: \"${model.provider}\",\\n`;\n\t\t\tif (model.baseUrl !== undefined) {\n\t\t\t\toutput += `\\t\\t\\tbaseUrl: \"${model.baseUrl}\",\\n`;\n\t\t\t}\n\t\t\tif (model.headers) {\n\t\t\t\toutput += `\\t\\t\\theaders: ${JSON.stringify(model.headers)},\\n`;\n\t\t\t}\n\t\t\tif (model.compat) {\n\t\t\t\toutput += `\t\t\tcompat: ${JSON.stringify(model.compat)},\n`;\n\t\t\t}\n\t\t\toutput += `\\t\\t\\treasoning: ${model.reasoning},\\n`;\n\t\t\tif (model.thinkingLevelMap) {\n\t\t\t\toutput += `\\t\\t\\tthinkingLevelMap: ${JSON.stringify(model.thinkingLevelMap)},\\n`;\n\t\t\t}\n\t\t\toutput += `\\t\\t\\tinput: [${model.input.map(i => `\"${i}\"`).join(\", \")}],\\n`;\n\t\t\toutput += `\\t\\t\\tcost: {\\n`;\n\t\t\toutput += `\\t\\t\\t\\tinput: ${model.cost.input},\\n`;\n\t\t\toutput += `\\t\\t\\t\\toutput: ${model.cost.output},\\n`;\n\t\t\toutput += `\\t\\t\\t\\tcacheRead: ${model.cost.cacheRead},\\n`;\n\t\t\toutput += `\\t\\t\\t\\tcacheWrite: ${model.cost.cacheWrite},\\n`;\n\t\t\toutput += `\\t\\t\\t},\\n`;\n\t\t\toutput += `\\t\\t\\tcontextWindow: ${model.contextWindow},\\n`;\n\t\t\toutput += `\\t\\t\\tmaxTokens: ${model.maxTokens},\\n`;\n\t\t\toutput += `\\t\\t} satisfies Model<\"${model.api}\">,\\n`;\n\t\t}\n\n\t\toutput += `\\t},\\n`;\n\t}\n\n\toutput += `} as const;\n`;\n\n\t// Write file\n\twriteFileSync(join(packageRoot, \"src/models.generated.ts\"), output);\n\tconsole.log(\"Generated src/models.generated.ts\");\n\n\t// Print statistics\n\tconst totalModels = allModels.length;\n\tconst reasoningModels = allModels.filter(m => m.reasoning).length;\n\n\tconsole.log(`\\nModel Statistics:`);\n\tconsole.log(` Total tool-capable models: ${totalModels}`);\n\tconsole.log(` Reasoning-capable models: ${reasoningModels}`);\n\n\tfor (const [provider, models] of Object.entries(providers)) {\n\t\tconsole.log(` ${provider}: ${Object.keys(models).length} models`);\n\t}\n}\n\n// Run the generator\ngenerateModels().catch(console.error);\n"}],"isError":false,"timestamp":1782037449296}}
{"type":"message","id":"8f4cf19f","parentId":"42d0497c","timestamp":"2026-06-21T10:24:11.921Z","message":{"role":"assistant","content":[{"type":"text","text":"现在让我查看 models.dev API 来获取 opencode-go 下最新的 GLM 5.2 模型信息:"},{"type":"toolCall","id":"call_f798","name":"bash","arguments":{"command":"curl -s 'https://models.dev/api.json' | python3 -c \"\nimport json, sys\ndata = json.load(sys.stdin)\nopencode_go = data.get('opencode-go', {})\nmodels = opencode_go.get('models', {})\nfor mid, m in models.items():\n if 'glm' in mid.lower():\n print(f'{mid}: {json.dumps(m, indent=2)}')\n\"","timeout":30}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":2833,"output":124,"cacheRead":31232,"cacheWrite":0,"totalTokens":34189,"cost":{"input":0.0039662,"output":0.0005456,"cacheRead":0.00812032,"cacheWrite":0,"total":0.01263212}},"stopReason":"toolUse","timestamp":1782037449298,"responseId":"chatcmpl-RaOj6wgeOnZACtbt3XQMe3Jo","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"e0681854","parentId":"8f4cf19f","timestamp":"2026-06-21T10:24:13.246Z","message":{"role":"toolResult","toolCallId":"call_f798","toolName":"bash","content":[{"type":"text","text":"glm-5.1: {\n \"id\": \"glm-5.1\",\n \"name\": \"GLM-5.1\",\n \"family\": \"glm\",\n \"attachment\": false,\n \"reasoning\": true,\n \"reasoning_options\": [],\n \"tool_call\": true,\n \"interleaved\": {\n \"field\": \"reasoning_content\"\n },\n \"temperature\": true,\n \"knowledge\": \"2025-04\",\n \"release_date\": \"2026-04-07\",\n \"last_updated\": \"2026-04-07\",\n \"modalities\": {\n \"input\": [\n \"text\"\n ],\n \"output\": [\n \"text\"\n ]\n },\n \"open_weights\": true,\n \"limit\": {\n \"context\": 202752,\n \"output\": 32768\n },\n \"cost\": {\n \"input\": 1.4,\n \"output\": 4.4,\n \"cache_read\": 0.26\n }\n}\nglm-5.2: {\n \"id\": \"glm-5.2\",\n \"name\": \"GLM-5.2\",\n \"family\": \"glm\",\n \"attachment\": false,\n \"reasoning\": true,\n \"reasoning_options\": [],\n \"tool_call\": true,\n \"interleaved\": {\n \"field\": \"reasoning_content\"\n },\n \"structured_output\": true,\n \"temperature\": true,\n \"release_date\": \"2026-06-13\",\n \"last_updated\": \"2026-06-13\",\n \"modalities\": {\n \"input\": [\n \"text\"\n ],\n \"output\": [\n \"text\"\n ]\n },\n \"open_weights\": true,\n \"limit\": {\n \"context\": 1000000,\n \"output\": 131072\n },\n \"cost\": {\n \"input\": 1.4,\n \"output\": 4.4,\n \"cache_read\": 0.26\n }\n}\nglm-5: {\n \"id\": \"glm-5\",\n \"name\": \"GLM-5\",\n \"family\": \"glm\",\n \"attachment\": false,\n \"reasoning\": true,\n \"reasoning_options\": [],\n \"tool_call\": true,\n \"interleaved\": {\n \"field\": \"reasoning_content\"\n },\n \"temperature\": true,\n \"knowledge\": \"2025-04\",\n \"release_date\": \"2026-02-11\",\n \"last_updated\": \"2026-02-11\",\n \"modalities\": {\n \"input\": [\n \"text\"\n ],\n \"output\": [\n \"text\"\n ]\n },\n \"open_weights\": true,\n \"limit\": {\n \"context\": 202752,\n \"output\": 32768\n },\n \"status\": \"deprecated\",\n \"cost\": {\n \"input\": 1,\n \"output\": 3.2,\n \"cache_read\": 0.2\n }\n}\n"}],"isError":false,"timestamp":1782037453246}}
{"type":"message","id":"f2bdec4f","parentId":"e0681854","timestamp":"2026-06-21T10:24:17.297Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_4f9a","name":"bash","arguments":{"command":"curl -s 'https://models.dev/api.json' | python3 -c \"\nimport json, sys\ndata = json.load(sys.stdin)\nopencode_go = data.get('opencode-go', {})\nmodels = opencode_go.get('models', {})\nfor mid, m in models.items():\n if mid == 'glm-5.2':\n print(f'{mid}: {json.dumps(m, indent=2)}')\n print(f'provider.npm: {m.get(\\\"provider\\\", {}).get(\\\"npm\\\", \\\"N/A\\\")}')\n\"","timeout":15}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":680,"output":126,"cacheRead":34176,"cacheWrite":0,"totalTokens":34982,"cost":{"input":0.0009519999999999999,"output":0.0005544,"cacheRead":0.00888576,"cacheWrite":0,"total":0.01039216}},"stopReason":"toolUse","timestamp":1782037453248,"responseId":"chatcmpl-RicWgf2novicPHKkNN1Ju6s9","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"375feb3b","parentId":"f2bdec4f","timestamp":"2026-06-21T10:24:17.721Z","message":{"role":"toolResult","toolCallId":"call_4f9a","toolName":"bash","content":[{"type":"text","text":"glm-5.2: {\n \"id\": \"glm-5.2\",\n \"name\": \"GLM-5.2\",\n \"family\": \"glm\",\n \"attachment\": false,\n \"reasoning\": true,\n \"reasoning_options\": [],\n \"tool_call\": true,\n \"interleaved\": {\n \"field\": \"reasoning_content\"\n },\n \"structured_output\": true,\n \"temperature\": true,\n \"release_date\": \"2026-06-13\",\n \"last_updated\": \"2026-06-13\",\n \"modalities\": {\n \"input\": [\n \"text\"\n ],\n \"output\": [\n \"text\"\n ]\n },\n \"open_weights\": true,\n \"limit\": {\n \"context\": 1000000,\n \"output\": 131072\n },\n \"cost\": {\n \"input\": 1.4,\n \"output\": 4.4,\n \"cache_read\": 0.26\n }\n}\nprovider.npm: N/A\n"}],"isError":false,"timestamp":1782037457721}}
{"type":"message","id":"b185afa9","parentId":"375feb3b","timestamp":"2026-06-21T10:24:21.358Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"Now I have the info. GLM-5.2 model from opencode-go:\n- id: glm-5.2\n- name: GLM-5.2\n- reasoning: true\n- tool_call: true\n- input: [\"text\"]\n- context: 1000000\n- output: 131072\n- cost: input 1.4, output 4.4, cache_read 0.26\n- provider.npm: undefined (so it will be openai-completions api)\n\nSince `provider.npm` is not set (not @ai-sdk/anthropic, @ai-sdk/openai, or @ai-sdk/google), it will use `openai-completions` api with baseUrl `https://opencode.ai/zen/go/v1`.\n\nNow I need to check if this model already exists in the generated models. Let me check the current models.generated.ts for glm entries.","thinkingSignature":"reasoning_content"},{"type":"text","text":"现在让我检查生成的模型文件里是否已经有 `glm-5.2`"},{"type":"toolCall","id":"call_4a0f","name":"ffgrep","arguments":{"path":"/shumengya/project/agent/sproutclaw/packages/ai/src/models.generated.ts","pattern":"glm","caseSensitive":true}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":237,"output":251,"cacheRead":34976,"cacheWrite":0,"totalTokens":35464,"cost":{"input":0.0003318,"output":0.0011044,"cacheRead":0.00909376,"cacheWrite":0,"total":0.01052996}},"stopReason":"toolUse","timestamp":1782037457722,"responseId":"chatcmpl-Rml1ZRTIxu8XH77CbzFtkrc5","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"41b19e79","parentId":"b185afa9","timestamp":"2026-06-21T10:24:21.364Z","message":{"role":"toolResult","toolCallId":"call_4a0f","toolName":"ffgrep","content":[{"type":"text","text":"packages/ai/src/models.generated.ts\n 1618: \"zai.glm-4.7\": {\n 1619: id: \"zai.glm-4.7\",\n 1635: \"zai.glm-4.7-flash\": {\n 1636: id: \"zai.glm-4.7-flash\",\n 1652: \"zai.glm-5\": {\n 1653: id: \"zai.glm-5\",\n 2857: \"zai-glm-4.7\": {\n 2858: id: \"zai-glm-4.7\",\n 3500: \"workers-ai/@cf/zai-org/glm-4.7-flash\": {\n 3501: id: \"workers-ai/@cf/zai-org/glm-4.7-flash\",\n 3718: \"@cf/zai-org/glm-4.7-flash\": {\n 3719: id: \"@cf/zai-org/glm-4.7-flash\",\n 3814: \"accounts/fireworks/models/glm-5p1\": {\n 3815: id: \"accounts/fireworks/models/glm-5p1\",\n 3958: \"accounts/fireworks/routers/glm-5p1-fast\": {\n 3959: id: \"accounts/fireworks/routers/glm-5p1-fast\",\n 7581: \"glm-5\": {\n 7582: id: \"glm-5\",\n 7598: \"glm-5.1\": {\n 7599: id: \"glm-5.1\","}],"details":{"totalMatched":20,"totalFiles":794},"isError":false,"timestamp":1782037461364}}
{"type":"message","id":"89561faf","parentId":"41b19e79","timestamp":"2026-06-21T10:24:23.609Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_b834","name":"ffgrep","arguments":{"path":"/shumengya/project/agent/sproutclaw/packages/ai/src/models.generated.ts","pattern":"\"opencode-go\"","context":1}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":357,"output":45,"cacheRead":35456,"cacheWrite":0,"totalTokens":35858,"cost":{"input":0.0004998,"output":0.00019800000000000002,"cacheRead":0.00921856,"cacheWrite":0,"total":0.00991636}},"stopReason":"toolUse","timestamp":1782037461365,"responseId":"chatcmpl-RoliBI9AucxodTF4GZUd8vhm","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"a91cf044","parentId":"89561faf","timestamp":"2026-06-21T10:24:23.615Z","message":{"role":"toolResult","toolCallId":"call_b834","toolName":"ffgrep","content":[{"type":"text","text":"packages/ai/src/models.generated.ts\n 8073- },\n 8074: \"opencode-go\": {\n 8075- \"deepseek-v4-flash\": {\n 8078- api: \"openai-completions\",\n 8079: provider: \"opencode-go\",\n 8080- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8097- api: \"openai-completions\",\n 8098: provider: \"opencode-go\",\n 8099- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8116- api: \"openai-completions\",\n 8117: provider: \"opencode-go\",\n 8118- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8133- api: \"openai-completions\",\n 8134: provider: \"opencode-go\",\n 8135- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8150- api: \"openai-completions\",\n 8151: provider: \"opencode-go\",\n 8152- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8167- api: \"openai-completions\",\n 8168: provider: \"opencode-go\",\n 8169- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8184- api: \"openai-completions\",\n 8185: provider: \"opencode-go\",\n 8186- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8201- api: \"openai-completions\",\n 8202: provider: \"opencode-go\",\n 8203- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8218- api: \"openai-completions\",\n 8219: provider: \"opencode-go\",\n 8220- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8235- api: \"anthropic-messages\",\n 8236: provider: \"opencode-go\",\n 8237- baseUrl: \"https://opencode.ai/zen/go\",\n 8252- api: \"openai-completions\",\n 8253: provider: \"opencode-go\",\n 8254- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8270- api: \"anthropic-messages\",\n 8271: provider: \"opencode-go\",\n 8272- baseUrl: \"https://opencode.ai/zen/go\",\n 8287- api: \"anthropic-messages\",\n 8288: provider: \"opencode-go\",\n 8289- baseUrl: \"https://opencode.ai/zen/go\","}],"details":{"totalMatched":14,"totalFiles":794},"isError":false,"timestamp":1782037463615}}
{"type":"message","id":"9b33d061","parentId":"a91cf044","timestamp":"2026-06-21T10:24:25.283Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_217b","name":"read","arguments":{"path":"/shumengya/project/agent/sproutclaw/packages/ai/src/models.generated.ts","offset":8074,"limit":250}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":634,"output":42,"cacheRead":35840,"cacheWrite":0,"totalTokens":36516,"cost":{"input":0.0008876,"output":0.00018480000000000002,"cacheRead":0.0093184,"cacheWrite":0,"total":0.010390799999999999}},"stopReason":"toolUse","timestamp":1782037463616,"responseId":"chatcmpl-RwhHhb6TuCQaMZOaacOLkkcy","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"a6c3813e","parentId":"9b33d061","timestamp":"2026-06-21T10:24:25.287Z","message":{"role":"toolResult","toolCallId":"call_217b","toolName":"read","content":[{"type":"text","text":"\t\"opencode-go\": {\n\t\t\"deepseek-v4-flash\": {\n\t\t\tid: \"deepseek-v4-flash\",\n\t\t\tname: \"DeepSeek V4 Flash\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\tcompat: {\"requiresReasoningContentOnAssistantMessages\":true,\"thinkingFormat\":\"deepseek\"},\n\t\t\treasoning: true,\n\t\t\tthinkingLevelMap: {\"minimal\":null,\"low\":null,\"medium\":null,\"high\":\"high\",\"xhigh\":\"max\"},\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.14,\n\t\t\t\toutput: 0.28,\n\t\t\t\tcacheRead: 0.0028,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 384000,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"deepseek-v4-pro\": {\n\t\t\tid: \"deepseek-v4-pro\",\n\t\t\tname: \"DeepSeek V4 Pro\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\tcompat: {\"requiresReasoningContentOnAssistantMessages\":true,\"thinkingFormat\":\"deepseek\"},\n\t\t\treasoning: true,\n\t\t\tthinkingLevelMap: {\"minimal\":null,\"low\":null,\"medium\":null,\"high\":\"high\",\"xhigh\":\"max\"},\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.74,\n\t\t\t\toutput: 3.48,\n\t\t\t\tcacheRead: 0.0145,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 384000,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"glm-5\": {\n\t\t\tid: \"glm-5\",\n\t\t\tname: \"GLM-5\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1,\n\t\t\t\toutput: 3.2,\n\t\t\t\tcacheRead: 0.2,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 202752,\n\t\t\tmaxTokens: 32768,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"glm-5.1\": {\n\t\t\tid: \"glm-5.1\",\n\t\t\tname: \"GLM-5.1\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.4,\n\t\t\t\toutput: 4.4,\n\t\t\t\tcacheRead: 0.26,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 202752,\n\t\t\tmaxTokens: 32768,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"kimi-k2.6\": {\n\t\t\tid: \"kimi-k2.6\",\n\t\t\tname: \"Kimi K2.6\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.95,\n\t\t\t\toutput: 4,\n\t\t\t\tcacheRead: 0.16,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 262144,\n\t\t\tmaxTokens: 65536,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"kimi-k2.7-code\": {\n\t\t\tid: \"kimi-k2.7-code\",\n\t\t\tname: \"Kimi K2.7 Code\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.95,\n\t\t\t\toutput: 4,\n\t\t\t\tcacheRead: 0.19,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 262144,\n\t\t\tmaxTokens: 262144,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"mimo-v2.5\": {\n\t\t\tid: \"mimo-v2.5\",\n\t\t\tname: \"MiMo V2.5\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.14,\n\t\t\t\toutput: 0.28,\n\t\t\t\tcacheRead: 0.0028,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 128000,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"mimo-v2.5-pro\": {\n\t\t\tid: \"mimo-v2.5-pro\",\n\t\t\tname: \"MiMo V2.5 Pro\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 1.74,\n\t\t\t\toutput: 3.48,\n\t\t\t\tcacheRead: 0.0145,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 1048576,\n\t\t\tmaxTokens: 128000,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"minimax-m2.7\": {\n\t\t\tid: \"minimax-m2.7\",\n\t\t\tname: \"MiniMax M2.7\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.3,\n\t\t\t\toutput: 1.2,\n\t\t\t\tcacheRead: 0.06,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 204800,\n\t\t\tmaxTokens: 131072,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"minimax-m3\": {\n\t\t\tid: \"minimax-m3\",\n\t\t\tname: \"MiniMax M3 (3x usage)\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.1,\n\t\t\t\toutput: 0.4,\n\t\t\t\tcacheRead: 0.02,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 512000,\n\t\t\tmaxTokens: 131072,\n\t\t} satisfies Model<\"anthropic-messages\">,\n\t\t\"qwen3.6-plus\": {\n\t\t\tid: \"qwen3.6-plus\",\n\t\t\tname: \"Qwen3.6 Plus\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go/v1\",\n\t\t\tcompat: {\"thinkingFormat\":\"qwen\"},\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.5,\n\t\t\t\toutput: 3,\n\t\t\t\tcacheRead: 0.05,\n\t\t\t\tcacheWrite: 0.625,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 65536,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"qwen3.7-max\": {\n\t\t\tid: \"qwen3.7-max\",\n\t\t\tname: \"Qwen3.7 Max\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 2.5,\n\t\t\t\toutput: 7.5,\n\t\t\t\tcacheRead: 0.5,\n\t\t\t\tcacheWrite: 3.125,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 65536,\n\t\t} satisfies Model<\"anthropic-messages\">,\n\t\t\"qwen3.7-plus\": {\n\t\t\tid: \"qwen3.7-plus\",\n\t\t\tname: \"Qwen3.7 Plus\",\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tprovider: \"opencode-go\",\n\t\t\tbaseUrl: \"https://opencode.ai/zen/go\",\n\t\t\treasoning: true,\n\t\t\tinput: [\"text\", \"image\"],\n\t\t\tcost: {\n\t\t\t\tinput: 0.4,\n\t\t\t\toutput: 1.6,\n\t\t\t\tcacheRead: 0.04,\n\t\t\t\tcacheWrite: 0.5,\n\t\t\t},\n\t\t\tcontextWindow: 1000000,\n\t\t\tmaxTokens: 65536,\n\t\t} satisfies Model<\"anthropic-messages\">,\n\t},\n\t\"openrouter\": {\n\t\t\"ai21/jamba-large-1.7\": {\n\t\t\tid: \"ai21/jamba-large-1.7\",\n\t\t\tname: \"AI21: Jamba Large 1.7\",\n\t\t\tapi: \"openai-completions\",\n\t\t\tprovider: \"openrouter\",\n\t\t\tbaseUrl: \"https://openrouter.ai/api/v1\",\n\t\t\treasoning: false,\n\t\t\tinput: [\"text\"],\n\t\t\tcost: {\n\t\t\t\tinput: 2,\n\t\t\t\toutput: 8,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t},\n\t\t\tcontextWindow: 256000,\n\t\t\tmaxTokens: 4096,\n\t\t} satisfies Model<\"openai-completions\">,\n\t\t\"amazon/nova-2-lite-v1\": {\n\t\t\tid: \"amazon/nova-2-lite-v1\",\n\t\t\tname: \"Amazon: Nova 2 Lite\",\n\t\t\tapi: \"openai-completions\",\n\n[8171 more lines in file. Use offset=8324 to continue.]"}],"isError":false,"timestamp":1782037465287}}
{"type":"message","id":"a7061039","parentId":"a6c3813e","timestamp":"2026-06-21T10:24:28.198Z","message":{"role":"assistant","content":[{"type":"text","text":"好的现在我了解了完整情况。opencode-go 下目前只有 `glm-5` 和 `glm-5.1`,缺少 `glm-5.2`。这些模型是从 models.dev API 动态拉取的,但由于 glm-5 在 models.dev 上标记为 `deprecated`,同时 glm-5.2 是新增模型。\n\n模型虽然在 models.dev 上有数据,但需要重新运行生成脚本才能更新到 `models.generated.ts`。让我运行生成脚本:"},{"type":"toolCall","id":"call_d32e","name":"bash","arguments":{"command":"cd /shumengya/project/agent/sproutclaw && node packages/ai/scripts/generate-models.ts 2>&1","timeout":60}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":2314,"output":145,"cacheRead":36512,"cacheWrite":0,"totalTokens":38971,"cost":{"input":0.0032396,"output":0.000638,"cacheRead":0.00949312,"cacheWrite":0,"total":0.013370720000000001}},"stopReason":"toolUse","timestamp":1782037465288,"responseId":"chatcmpl-RjJkYmdEXjM8mYiIEgEmMzja","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"18d3f1ed","parentId":"a7061039","timestamp":"2026-06-21T10:24:32.771Z","message":{"role":"toolResult","toolCallId":"call_d32e","toolName":"bash","content":[{"type":"text","text":"Fetching models from models.dev API...\nLoaded 473 tool-capable models from models.dev\nFetching models from OpenRouter API...\nFetched 258 tool-capable models from OpenRouter\nFetching models from Vercel AI Gateway API...\nFetched 168 tool-capable models from Vercel AI Gateway\nGenerated src/models.generated.ts\n\nModel Statistics:\n Total tool-capable models: 957\n Reasoning-capable models: 698\n amazon-bedrock: 97 models\n anthropic: 25 models\n google: 16 models\n openai: 42 models\n groq: 7 models\n cerebras: 2 models\n cloudflare-workers-ai: 13 models\n cloudflare-ai-gateway: 37 models\n xai: 7 models\n zai: 6 models\n mistral: 30 models\n huggingface: 22 models\n fireworks: 15 models\n together: 19 models\n opencode: 44 models\n opencode-go: 13 models\n github-copilot: 22 models\n minimax: 2 models\n minimax-cn: 2 models\n kimi-coding: 3 models\n moonshotai: 9 models\n moonshotai-cn: 9 models\n xiaomi: 6 models\n xiaomi-token-plan-cn: 6 models\n xiaomi-token-plan-ams: 6 models\n xiaomi-token-plan-sgp: 6 models\n openrouter: 259 models\n vercel-ai-gateway: 168 models\n deepseek: 2 models\n openai-codex: 6 models\n google-vertex: 13 models\n azure-openai-responses: 42 models\n"}],"isError":false,"timestamp":1782037472770}}
{"type":"message","id":"a66401de","parentId":"18d3f1ed","timestamp":"2026-06-21T10:24:35.150Z","message":{"role":"assistant","content":[{"type":"text","text":"脚本成功运行了!让我验证 glm-5.2 是否已出现在生成的模型文件中:"},{"type":"toolCall","id":"call_a691","name":"ffgrep","arguments":{"pattern":"glm-5\\.2","path":"/shumengya/project/agent/sproutclaw/packages/ai/src/models.generated.ts","context":15}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":421,"output":65,"cacheRead":38944,"cacheWrite":0,"totalTokens":39430,"cost":{"input":0.0005894,"output":0.000286,"cacheRead":0.01012544,"cacheWrite":0,"total":0.01100084}},"stopReason":"toolUse","timestamp":1782037472772,"responseId":"chatcmpl-RIpwlKRzPGZj3w1mw2y8ExNU","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"aae2c0ab","parentId":"a66401de","timestamp":"2026-06-21T10:24:35.160Z","message":{"role":"toolResult","toolCallId":"call_a691","toolName":"ffgrep","content":[{"type":"text","text":"packages/ai/src/models.generated.ts [modified in git]\n 3721- api: \"openai-completions\",\n 3722- provider: \"cloudflare-workers-ai\",\n 3723- baseUrl: \"https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1\",\n 3724- compat: {\"sendSessionAffinityHeaders\":true},\n 3725- reasoning: true,\n 3726- input: [\"text\"],\n 3727- cost: {\n 3728- input: 0.0605,\n 3729- output: 0.4,\n 3730- cacheRead: 0,\n 3731- cacheWrite: 0,\n 3732- },\n 3733- contextWindow: 131072,\n 3734- maxTokens: 131072,\n 3735- } satisfies Model<\"openai-completions\">,\n 3736: \"@cf/zai-org/glm-5.2\": {\n 3737- id: \"@cf/zai-org/glm-5.2\",\n 3738- name: \"Glm 5.2\",\n 3739- api: \"openai-completions\",\n 3740- provider: \"cloudflare-workers-ai\",\n 3741- baseUrl: \"https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1\",\n 3742- compat: {\"sendSessionAffinityHeaders\":true},\n 3743- reasoning: true,\n 3744- input: [\"text\"],\n 3745- cost: {\n 3746- input: 1.4,\n 3747- output: 4.4,\n 3748- cacheRead: 0.26,\n 3749- cacheWrite: 0,\n 3750- },\n 3751- contextWindow: 262144,\n 3722- provider: \"cloudflare-workers-ai\",\n 3723- baseUrl: \"https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1\",\n 3724- compat: {\"sendSessionAffinityHeaders\":true},\n 3725- reasoning: true,\n 3726- input: [\"text\"],\n 3727- cost: {\n 3728- input: 0.0605,\n 3729- output: 0.4,\n 3730- cacheRead: 0,\n 3731- cacheWrite: 0,\n 3732- },\n 3733- contextWindow: 131072,\n 3734- maxTokens: 131072,\n 3735- } satisfies Model<\"openai-completions\">,\n 3736- \"@cf/zai-org/glm-5.2\": {\n 3737: id: \"@cf/zai-org/glm-5.2\",\n 3738- name: \"Glm 5.2\",\n 3739- api: \"openai-completions\",\n 3740- provider: \"cloudflare-workers-ai\",\n 3741- baseUrl: \"https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1\",\n 3742- compat: {\"sendSessionAffinityHeaders\":true},\n 3743- reasoning: true,\n 3744- input: [\"text\"],\n 3745- cost: {\n 3746- input: 1.4,\n 3747- output: 4.4,\n 3748- cacheRead: 0.26,\n 3749- cacheWrite: 0,\n 3750- },\n 3751- contextWindow: 262144,\n 3752- maxTokens: 262144,\n 8150- name: \"GLM-5.1\",\n 8151- api: \"openai-completions\",\n 8152- provider: \"opencode-go\",\n 8153- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8154- reasoning: true,\n 8155- input: [\"text\"],\n 8156- cost: {\n 8157- input: 1.4,\n 8158- output: 4.4,\n 8159- cacheRead: 0.26,\n 8160- cacheWrite: 0,\n 8161- },\n 8162- contextWindow: 202752,\n 8163- maxTokens: 32768,\n 8164- } satisfies Model<\"openai-completions\">,\n 8165: \"glm-5.2\": {\n 8166- id: \"glm-5.2\",\n 8167- name: \"GLM-5.2\",\n 8168- api: \"openai-completions\",\n 8169- provider: \"opencode-go\",\n 8170- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8171- reasoning: true,\n 8172- input: [\"text\"],\n 8173- cost: {\n 8174- input: 1.4,\n 8175- output: 4.4,\n 8176- cacheRead: 0.26,\n 8177- cacheWrite: 0,\n 8178- },\n 8179- contextWindow: 1000000,\n 8180- maxTokens: 131072,\n 8151- api: \"openai-completions\",\n 8152- provider: \"opencode-go\",\n 8153- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8154- reasoning: true,\n 8155- input: [\"text\"],\n 8156- cost: {\n 8157- input: 1.4,\n 8158- output: 4.4,\n 8159- cacheRead: 0.26,\n 8160- cacheWrite: 0,\n 8161- },\n 8162- contextWindow: 202752,\n 8163- maxTokens: 32768,\n 8164- } satisfies Model<\"openai-completions\">,\n 8165- \"glm-5.2\": {\n 8166: id: \"glm-5.2\",\n 8167- name: \"GLM-5.2\",\n 8168- api: \"openai-completions\",\n 8169- provider: \"opencode-go\",\n 8170- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8171- reasoning: true,\n 8172- input: [\"text\"],\n 8173- cost: {\n 8174- input: 1.4,\n 8175- output: 4.4,\n 8176- cacheRead: 0.26,\n 8177- cacheWrite: 0,\n 8178- },\n 8179- contextWindow: 1000000,\n 8180- maxTokens: 131072,\n 8181- } satisfies Model<\"openai-completions\">,\n 8152- provider: \"opencode-go\",\n 8153- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8154- reasoning: true,\n 8155- input: [\"text\"],\n 8156- cost: {\n 8157- input: 1.4,\n 8158- output: 4.4,\n 8159- cacheRead: 0.26,\n 8160- cacheWrite: 0,\n 8161- },\n 8162- contextWindow: 202752,\n 8163- maxTokens: 32768,\n 8164- } satisfies Model<\"openai-completions\">,\n 8165- \"glm-5.2\": {\n 8166- id: \"glm-5.2\",\n 8167: name: \"GLM-5.2\",\n 8168- api: \"openai-completions\",\n 8169- provider: \"opencode-go\",\n 8170- baseUrl: \"https://opencode.ai/zen/go/v1\",\n 8171- reasoning: true,\n 8172- input: [\"text\"],\n 8173- cost: {\n 8174- input: 1.4,\n 8175- output: 4.4,\n 8176- cacheRead: 0.26,\n 8177- cacheWrite: 0,\n 8178- },\n 8179- contextWindow: 1000000,\n 8180- maxTokens: 131072,\n 8181- } satisfies Model<\"openai-completions\">,\n 8182- \"kimi-k2.6\": {\n 12577- name: \"Z.ai: GLM 5.1\",\n 12578- api: \"openai-completions\",\n 12579- provider: \"openrouter\",\n 12580- baseUrl: \"https://openrouter.ai/api/v1\",\n 12581- reasoning: true,\n 12582- input: [\"text\"],\n 12583- cost: {\n 12584- input: 0.98,\n 12585- output: 3.08,\n 12586- cacheRead: 0.49,\n 12587- cacheWrite: 0,\n 12588- },\n 12589- contextWindow: 202752,\n 12590- maxTokens: 65535,\n 12591- } satisfies Model<\"openai-completions\">,\n 12592: \"z-ai/glm-5.2\": {\n 12593- id: \"z-ai/glm-5.2\",\n 12594- name: \"Z.ai: GLM 5.2\",\n 12595- api: \"openai-completions\",\n 12596- provider: \"openrouter\",\n 12597- baseUrl: \"https://openrouter.ai/api/v1\",\n 12598- reasoning: true,\n 12599- input: [\"text\"],\n 12600- cost: {\n 12601- input: 1.2,\n 12602- output: 4.1,\n 12603- cacheRead: 0.19999999999999998,\n 12604- cacheWrite: 0,\n 12605- },\n 12606- contextWindow: 1048576,\n 12607- maxTokens: 131072,\n 12578- api: \"openai-completions\",\n 12579- provider: \"openrouter\",\n 12580- baseUrl: \"https://openrouter.ai/api/v1\",\n 12581- reasoning: true,\n 12582- input: [\"text\"],\n 12583- cost: {\n 12584- input: 0.98,\n 12585- output: 3.08,\n 12586- cacheRead: 0.49,\n 12587- cacheWrite: 0,\n 12588- },\n 12589- contextWindow: 202752,\n 12590- maxTokens: 65535,\n 12591- } satisfies Model<\"openai-completions\">,\n 12592- \"z-ai/glm-5.2\": {\n 12593: id: \"z-ai/glm-5.2\",\n 12594- name: \"Z.ai: GLM 5.2\",\n 12595- api: \"openai-completions\",\n 12596- provider: \"openrouter\",\n 12597- baseUrl: \"https://openrouter.ai/api/v1\",\n 12598- reasoning: true,\n 12599- input: [\"text\"],\n 12600- cost: {\n 12601- input: 1.2,\n 12602- output: 4.1,\n 12603- cacheRead: 0.19999999999999998,\n 12604- cacheWrite: 0,\n 12605- },\n 12606- contextWindow: 1048576,\n 12607- maxTokens: 131072,\n 12608- } satisfies Model<\"openai-completions\">,\n 15943- name: \"GLM 5.1\",\n 15944- api: \"anthropic-messages\",\n 15945- provider: \"vercel-ai-gateway\",\n 15946- baseUrl: \"https://ai-gateway.vercel.sh\",\n 15947- reasoning: true,\n 15948- input: [\"text\", \"image\"],\n 15949- cost: {\n 15950- input: 1.4,\n 15951- output: 4.4,\n 15952- cacheRead: 0.26,\n 15953- cacheWrite: 0,\n 15954- },\n 15955- contextWindow: 202800,\n 15956- maxTokens: 64000,\n 15957- } satisfies Model<\"anthropic-messages\">,\n 15958: \"zai/glm-5.2\": {\n 15959- id: \"zai/glm-5.2\",\n 15960- name: \"GLM 5.2\",\n 15961- api: \"anthropic-messages\",\n 15962- provider: \"vercel-ai-gateway\",\n 15963- baseUrl: \"https://ai-gateway.vercel.sh\",\n 15964- reasoning: true,\n 15965- input: [\"text\"],\n 15966- cost: {\n 15967- input: 1.5,\n 15968- output: 4.5,\n 15969- cacheRead: 0.3,\n 15970- cacheWrite: 0,\n 15971- },\n 15972- contextWindow: 1000000,\n 15973- maxTokens: 128000,\n 15944- api: \"anthropic-messages\",\n 15945- provider: \"vercel-ai-gateway\",\n 15946- baseUrl: \"https://ai-gateway.vercel.sh\",\n 15947- reasoning: true,\n 15948- input: [\"text\", \"image\"],\n 15949- cost: {\n 15950- input: 1.4,\n 15951- output: 4.4,\n 15952- cacheRead: 0.26,\n 15953- cacheWrite: 0,\n 15954- },\n 15955- contextWindow: 202800,\n 15956- maxTokens: 64000,\n 15957- } satisfies Model<\"anthropic-messages\">,\n 15958- \"zai/glm-5.2\": {\n 15959: id: \"zai/glm-5.2\",\n 15960- name: \"GLM 5.2\",\n 15961- api: \"anthropic-messages\",\n 15962- provider: \"vercel-ai-gateway\",\n 15963- baseUrl: \"https://ai-gateway.vercel.sh\",\n 15964- reasoning: true,\n 15965- input: [\"text\"],\n 15966- cost: {\n 15967- input: 1.5,\n 15968- output: 4.5,\n 15969- cacheRead: 0.3,\n 15970- cacheWrite: 0,\n 15971- },\n 15972- contextWindow: 1000000,\n 15973- maxTokens: 128000,\n 15974- } satisfies Model<\"anthropic-messages\">,\n 16612- api: \"openai-completions\",\n 16613- provider: \"zai\",\n 16614- baseUrl: \"https://api.z.ai/api/coding/paas/v4\",\n 16615- compat: {\"supportsDeveloperRole\":false,\"thinkingFormat\":\"zai\",\"zaiToolStream\":true},\n 16616- reasoning: true,\n 16617- input: [\"text\"],\n 16618- cost: {\n 16619- input: 0,\n 16620- output: 0,\n 16621- cacheRead: 0,\n 16622- cacheWrite: 0,\n 16623- },\n 16624- contextWindow: 200000,\n 16625- maxTokens: 131072,\n 16626- } satisfies Model<\"openai-completions\">,\n 16627: \"glm-5.2\": {\n 16628- id: \"glm-5.2\",\n 16629- name: \"GLM-5.2\",\n 16630- api: \"openai-completions\",\n 16631- provider: \"zai\",\n 16632- baseUrl: \"https://api.z.ai/api/coding/paas/v4\",\n 16633- compat: {\"supportsDeveloperRole\":false,\"thinkingFormat\":\"zai\",\"zaiToolStream\":true},\n 16634- reasoning: true,\n 16635- input: [\"text\"],\n 16636- cost: {\n 16637- input: 0,\n 16638- output: 0,\n 16639- cacheRead: 0,\n 16640- cacheWrite: 0,\n 16641- },\n 16642- contextWindow: 1000000,\n 16613- provider: \"zai\",\n 16614- baseUrl: \"https://api.z.ai/api/coding/paas/v4\",\n 16615- compat: {\"supportsDeveloperRole\":false,\"thinkingFormat\":\"zai\",\"zaiToolStream\":true},\n 16616- reasoning: true,\n 16617- input: [\"text\"],\n 16618- cost: {\n 16619- input: 0,\n 16620- output: 0,\n 16621- cacheRead: 0,\n 16622- cacheWrite: 0,\n 16623- },\n 16624- contextWindow: 200000,\n 16625- maxTokens: 131072,\n 16626- } satisfies Model<\"openai-completions\">,\n 16627- \"glm-5.2\": {\n 16628: id: \"glm-5.2\",\n 16629- name: \"GLM-5.2\",\n 16630- api: \"openai-completions\",\n 16631- provider: \"zai\",\n 16632- baseUrl: \"https://api.z.ai/api/coding/paas/v4\",\n 16633- compat: {\"supportsDeveloperRole\":false,\"thinkingFormat\":\"zai\",\"zaiToolStream\":true},\n 16634- reasoning: true,\n 16635- input: [\"text\"],\n 16636- cost: {\n 16637- input: 0,\n 16638- output: 0,\n 16639- cacheRead: 0,\n 16640- cacheWrite: 0,\n 16641- },\n 16642- contextWindow: 1000000,\n 16643- maxTokens: 131072,\n 16614- baseUrl: \"https://api.z.ai/api/coding/paas/v4\",\n 16615- compat: {\"supportsDeveloperRole\":false,\"thinkingFormat\":\"zai\",\"zaiToolStream\":true},\n 16616- reasoning: true,\n 16617- input: [\"text\"],\n 16618- cost: {\n 16619- input: 0,\n 16620- output: 0,\n 16621- cacheRead: 0,\n 16622- cacheWrite: 0,\n 16623- },\n 16624- contextWindow: 200000,\n 16625- maxTokens: 131072,\n 16626- } satisfies Model<\"openai-completions\">,\n 16627- \"glm-5.2\": {\n 16628- id: \"glm-5.2\",\n 16629: name: \"GLM-5.2\",\n 16630- api: \"openai-completions\",\n 16631- provider: \"zai\",\n 16632- baseUrl: \"https://api.z.ai/api/coding/paas/v4\",\n 16633- compat: {\"supportsDeveloperRole\":false,\"thinkingFormat\":\"zai\",\"zaiToolStream\":true},\n 16634- reasoning: true,\n 16635- input: [\"text\"],\n 16636- cost: {\n 16637- input: 0,\n 16638- output: 0,\n 16639- cacheRead: 0,\n 16640- cacheWrite: 0,\n 16641- },\n 16642- contextWindow: 1000000,\n 16643- maxTokens: 131072,\n 16644- } satisfies Model<\"openai-completions\">,"}],"details":{"totalMatched":12,"totalFiles":794},"isError":false,"timestamp":1782037475160}}
{"type":"message","id":"d0e14b45","parentId":"aae2c0ab","timestamp":"2026-06-21T10:24:37.801Z","message":{"role":"assistant","content":[{"type":"text","text":"`glm-5.2` 已经成功出现在了 opencode-go provider 下。现在让我运行 `npm run check` 来验证:"},{"type":"toolCall","id":"call_af6b","name":"bash","arguments":{"command":"cd /shumengya/project/agent/sproutclaw && npm run check 2>&1 | tail -30","timeout":120}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":4493,"output":69,"cacheRead":39424,"cacheWrite":0,"totalTokens":43986,"cost":{"input":0.0062902,"output":0.0003036,"cacheRead":0.01025024,"cacheWrite":0,"total":0.01684404}},"stopReason":"toolUse","timestamp":1782037475161,"responseId":"chatcmpl-R1F7X9VoaQS7sbuSQRfO18Bt","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"1e43979b","parentId":"d0e14b45","timestamp":"2026-06-21T10:24:39.454Z","message":{"role":"toolResult","toolCallId":"call_af6b","toolName":"bash","content":[{"type":"text","text":"\n> pi-monorepo@0.0.3 check\n> biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:ts-imports && npm run check:shrinkwrap && tsgo --noEmit && npm run check:browser-smoke\n\nChecked 590 files in 1249ms. No fixes applied.\n\n> pi-monorepo@0.0.3 check:pinned-deps\n> node scripts/check-pinned-deps.mjs\n\nDirect external dependencies must use exact versions:\n .pi/agent/npm/package.json: dependencies.@ff-labs/pi-fff must be pinned, found ^0.9.4\n .pi/agent/npm/package.json: dependencies.pi-mcp-adapter must be pinned, found ^2.8.0\n .pi/agent/npm/package.json: dependencies.pi-subagents must be pinned, found ^0.25.0\n"}],"isError":false,"timestamp":1782037479454}}
{"type":"message","id":"1c25e45f","parentId":"1e43979b","timestamp":"2026-06-21T10:24:43.764Z","message":{"role":"assistant","content":[{"type":"text","text":"检查有一些关于 `.pi/agent/npm/package.json` 的依赖版本问题,但这些不是我们改动引起的。模型生成本身没有问题。\n\nGLM-5.2 模型已经成功添加到 opencode-go provider 中了 (´▽`ʃ♡ƪ) 让我总结一下:\n\n**已完成的操作:**\n\n重新运行了 `packages/ai/scripts/generate-models.ts`,从 models.dev API 拉取了最新模型数据。\n\n**opencode-go 下新增的 GLM-5.2 模型信息:**\n\n| 字段 | 值 |\n|------|-----|\n| id | `glm-5.2` |\n| name | GLM-5.2 |\n| api | openai-completions |\n| provider | opencode-go |\n| baseUrl | https://opencode.ai/zen/go/v1 |\n| reasoning | true |\n| input | [\"text\"] |\n| contextWindow | 1,000,000 |\n| maxTokens | 131,072 |\n| cost.input | $1.4/M |\n| cost.output | $4.4/M |\n| cost.cacheRead | $0.26/M |\n\n另外 glm-5已标记 deprecated也从 opencode-go 中移除了,符合 models.dev 的状态。\n\n同时 glm-5.2 也自动出现在了其他 provider 下:**openrouter**`z-ai/glm-5.2`)、**zai**、**cloudflare-workers-ai**`@cf/zai-org/glm-5.2`)和 **vercel-ai-gateway**`zai/glm-5.2`)。"}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":221,"output":344,"cacheRead":43968,"cacheWrite":0,"totalTokens":44533,"cost":{"input":0.0003094,"output":0.0015136000000000001,"cacheRead":0.01143168,"cacheWrite":0,"total":0.01325468}},"stopReason":"stop","timestamp":1782037479455,"responseId":"chatcmpl-Rm6j8R5YjRk9cQnJzw1Mq2pT","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"1fba9faa","parentId":"1c25e45f","timestamp":"2026-06-21T10:26:04.699Z","message":{"role":"user","content":[{"type":"text","text":"我是让你添加SproutClaw的模型配置models.json中 你添加了吗"}],"timestamp":1782037564696}}
{"type":"message","id":"b5c6e2f7","parentId":"1fba9faa","timestamp":"2026-06-21T10:26:09.829Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"用户说的不是 `packages/ai/scripts/generate-models.ts` 里的生成脚本,而是 SproutClaw 的 `models.json` 配置文件,这是用户自定义模型配置文件,位于 `~/.pi/agent/models.json`。根据文档,这个文件用于添加自定义 providers 和 models。\n\n让我找找这个文件在哪里。","thinkingSignature":"reasoning_content"},{"type":"text","text":"啊,你说的是 `models.json` 配置文件,不是生成脚本!让我找一下:"},{"type":"toolCall","id":"call_1541","name":"bash","arguments":{"command":"find /shumengya -name \"models.json\" -not -path \"*/node_modules/*\" 2>/dev/null"}},{"type":"toolCall","id":"call_d12d","name":"bash","arguments":{"command":"ls -la ~/.pi/agent/models.json 2>/dev/null; cat ~/.pi/agent/models.json 2>/dev/null || echo \"文件不存在\""}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":33910,"output":167,"cacheRead":10336,"cacheWrite":0,"totalTokens":44413,"cost":{"input":0.047473999999999995,"output":0.0007348000000000001,"cacheRead":0.00268736,"cacheWrite":0,"total":0.050896159999999996}},"stopReason":"toolUse","timestamp":1782037564776,"responseId":"chatcmpl-RmfdASq4WHgVYqL84fDTEqXz","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"3fe21575","parentId":"b5c6e2f7","timestamp":"2026-06-21T10:26:11.883Z","message":{"role":"toolResult","toolCallId":"call_1541","toolName":"bash","content":[{"type":"text","text":"/shumengya/project/agent/sproutclaw/.pi/agent/models.json\n"}],"isError":false,"timestamp":1782037571883}}
{"type":"message","id":"7a82a174","parentId":"3fe21575","timestamp":"2026-06-21T10:26:11.883Z","message":{"role":"toolResult","toolCallId":"call_d12d","toolName":"bash","content":[{"type":"text","text":"文件不存在\n"}],"isError":false,"timestamp":1782037571883}}
{"type":"message","id":"250ae9c8","parentId":"7a82a174","timestamp":"2026-06-21T10:26:14.112Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_f827","name":"read","arguments":{"path":"/shumengya/project/agent/sproutclaw/.pi/agent/models.json"}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":58,"output":27,"cacheRead":44384,"cacheWrite":0,"totalTokens":44469,"cost":{"input":0.0000812,"output":0.0001188,"cacheRead":0.011539839999999999,"cacheWrite":0,"total":0.01173984}},"stopReason":"toolUse","timestamp":1782037571884,"responseId":"chatcmpl-RSypltWxYolX1HOATJnTB3Lu","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"e97e8ea5","parentId":"250ae9c8","timestamp":"2026-06-21T10:26:14.116Z","message":{"role":"toolResult","toolCallId":"call_f827","toolName":"read","content":[{"type":"text","text":"{\n \"providers\": {\n \"cpa\": {\n \"baseUrl\": \"https://cpa.shumengya.top/v1\",\n \"apiKey\": \"sk-shumengya666\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"models\": [\n {\n \"id\": \"gpt-5.4-mini(high)\",\n \"name\": \"gpt-5.4-mini(high)\",\n \"reasoning\": true,\n \"input\": [\n \"text\",\n \"image\"\n ]\n }\n ]\n },\n \"grok2api\": {\n \"baseUrl\": \"https://grok2api.shumengya.top/v1\",\n \"apiKey\": \"sk-shumengya666\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"models\": [\n {\n \"id\": \"grok-4.20-0309-non-reasoning\",\n \"name\": \"grok-4.20\",\n \"reasoning\": false,\n \"input\": [\n \"text\"\n ]\n }\n ]\n },\n \"deepseek\": {\n \"baseUrl\": \"https://api.deepseek.com\",\n \"apiKey\": \"sk-96928190eea94fd984e4689b5aba1548\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"modelOverrides\": {\n \"deepseek-v4-flash\": {\n \"contextWindow\": 500000\n }\n }\n },\n \"opencode-go-openai\": {\n \"baseUrl\": \"https://opencode.ai/zen/go/v1\",\n \"apiKey\": \"sk-jPLcSAWMwDBeIIQ2w7dkonzSFbJFxvqWxEQnl8twHXX21G01QKqf6g0TVkvJuDjx\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"models\": [\n {\n \"id\": \"glm-5.1\",\n \"name\": \"GLM-5.1\",\n \"reasoning\": false,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 200000,\n \"maxTokens\": 16384,\n \"cost\": {\n \"input\": 1.4,\n \"output\": 4.4,\n \"cacheRead\": 0.26,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"glm-5\",\n \"name\": \"GLM-5\",\n \"reasoning\": false,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 200000,\n \"maxTokens\": 16384,\n \"cost\": {\n \"input\": 1,\n \"output\": 3.2,\n \"cacheRead\": 0.2,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"kimi-k2.5\",\n \"name\": \"Kimi K2.5\",\n \"reasoning\": false,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 256000,\n \"maxTokens\": 16384,\n \"cost\": {\n \"input\": 0.6,\n \"output\": 3,\n \"cacheRead\": 0.1,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"kimi-k2.6\",\n \"name\": \"Kimi K2.6\",\n \"reasoning\": false,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 256000,\n \"maxTokens\": 16384,\n \"cost\": {\n \"input\": 0.95,\n \"output\": 4,\n \"cacheRead\": 0.16,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"kimi-k2.7-code\",\n \"name\": \"Kimi K2.7 Code\",\n \"reasoning\": false,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 256000,\n \"maxTokens\": 16384,\n \"cost\": {\n \"input\": 0.95,\n \"output\": 4,\n \"cacheRead\": 0.16,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"deepseek-v4-pro\",\n \"name\": \"DeepSeek V4 Pro\",\n \"reasoning\": true,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 256000,\n \"maxTokens\": 16384,\n \"cost\": {\n \"input\": 1.74,\n \"output\": 3.48,\n \"cacheRead\": 0.0145,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"deepseek-v4-flash\",\n \"name\": \"DeepSeek V4 Flash\",\n \"reasoning\": false,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 256000,\n \"maxTokens\": 16384,\n \"cost\": {\n \"input\": 0.14,\n \"output\": 0.28,\n \"cacheRead\": 0.0028,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"mimo-v2.5\",\n \"name\": \"MiMo-V2.5\",\n \"reasoning\": true,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 1048576,\n \"maxTokens\": 131072,\n \"cost\": {\n \"input\": 0.14,\n \"output\": 0.28,\n \"cacheRead\": 0.0028,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"mimo-v2.5-pro\",\n \"name\": \"MiMo-V2.5-Pro\",\n \"reasoning\": true,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 1048576,\n \"maxTokens\": 131072,\n \"cost\": {\n \"input\": 1.74,\n \"output\": 3.48,\n \"cacheRead\": 0.0145,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"minimax-m3\",\n \"name\": \"MiniMax-M3\",\n \"reasoning\": true,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 512000,\n \"maxTokens\": 131072,\n \"cost\": {\n \"input\": 0.3,\n \"output\": 1.2,\n \"cacheRead\": 0.06,\n \"cacheWrite\": 0\n }\n }\n ]\n },\n \"opencode-go-anthropic\": {\n \"baseUrl\": \"https://opencode.ai/zen/go\",\n \"apiKey\": \"sk-jPLcSAWMwDBeIIQ2w7dkonzSFbJFxvqWxEQnl8twHXX21G01QKqf6g0TVkvJuDjx\",\n \"api\": \"anthropic-messages\",\n \"models\": [\n {\n \"id\": \"qwen3.7-max\",\n \"name\": \"Qwen3.7-Max\",\n \"reasoning\": true,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 1000000,\n \"maxTokens\": 65536,\n \"cost\": {\n \"input\": 2.5,\n \"output\": 7.5,\n \"cacheRead\": 0.5,\n \"cacheWrite\": 3.125\n }\n },\n {\n \"id\": \"qwen3.7-plus\",\n \"name\": \"Qwen3.7-Plus\",\n \"reasoning\": true,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 1000000,\n \"maxTokens\": 65536,\n \"cost\": {\n \"input\": 0.4,\n \"output\": 1.6,\n \"cacheRead\": 0.04,\n \"cacheWrite\": 0.5\n }\n }\n ]\n },\n \"xiaomimimo-openai\": {\n \"baseUrl\": \"https://token-plan-cn.xiaomimimo.com/v1\",\n \"api\": \"openai-completions\",\n \"apiKey\": \"tp-cpnmi98sv9h5fqkkeueb7mei4r3gatd0ozl7nmc7jlyqviwg\",\n \"compat\": {\n \"requiresReasoningContentOnAssistantMessages\": true,\n \"thinkingFormat\": \"deepseek\"\n },\n \"models\": [\n {\n \"id\": \"mimo-v2.5\",\n \"name\": \"MiMo-V2.5\",\n \"reasoning\": true,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 1048576,\n \"maxTokens\": 131072,\n \"cost\": {\n \"input\": 0.4,\n \"output\": 2,\n \"cacheRead\": 0.08,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"mimo-v2.5-pro\",\n \"name\": \"MiMo-V2.5-Pro\",\n \"reasoning\": true,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 1048576,\n \"maxTokens\": 131072,\n \"cost\": {\n \"input\": 1,\n \"output\": 3,\n \"cacheRead\": 0.2,\n \"cacheWrite\": 0\n }\n }\n ]\n },\n \"swpumc-openai\": {\n \"baseUrl\": \"https://codeapi.swpumc.cn/v1\",\n \"apiKey\": \"sk-10325b198a8b9a701d3ace99ad25e0c0e9a02fe4c9a28d8ad31df192fbfeb1e0\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"models\": [\n {\n \"id\": \"gpt-5.5\",\n \"name\": \"GPT 5.5\",\n \"reasoning\": true,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 200000,\n \"maxTokens\": 16384\n },\n {\n \"id\": \"gpt-5.4\",\n \"name\": \"GPT 5.4\",\n \"reasoning\": true,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 200000,\n \"maxTokens\": 16384\n },\n {\n \"id\": \"gpt-5.4-mini\",\n \"name\": \"GPT 5.4 Mini\",\n \"reasoning\": false,\n \"input\": [\n \"text\",\n \"image\"\n ],\n \"contextWindow\": 200000,\n \"maxTokens\": 16384\n }\n ]\n }\n }\n}\n"}],"isError":false,"timestamp":1782037574116}}
{"type":"message","id":"16869db9","parentId":"e97e8ea5","timestamp":"2026-06-21T10:26:17.898Z","message":{"role":"assistant","content":[{"type":"text","text":"找到了!现在我需要在 `opencode-go-openai` provider 下添加 `glm-5.2` 模型。根据 models.dev 的数据:\n\n- id: `glm-5.2`\n- name: `GLM-5.2`\n- reasoning: true\n- input: [\"text\"]\n- context: 1,000,000\n- output: 131,072\n- cost: input $1.4/M, output $4.4/M, cacheRead $0.26/M"},{"type":"toolCall","id":"call_fa62","name":"edit","arguments":{"path":"/shumengya/project/agent/sproutclaw/.pi/agent/models.json","edits":[{"oldText":" {\n \"id\": \"glm-5.1\",\n \"name\": \"GLM-5.1\",","newText":" {\n \"id\": \"glm-5.2\",\n \"name\": \"GLM-5.2\",\n \"reasoning\": true,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 1000000,\n \"maxTokens\": 131072,\n \"cost\": {\n \"input\": 1.4,\n \"output\": 4.4,\n \"cacheRead\": 0.26,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"glm-5.1\",\n \"name\": \"GLM-5.1\","}]}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":2523,"output":332,"cacheRead":44448,"cacheWrite":0,"totalTokens":47303,"cost":{"input":0.0035321999999999997,"output":0.0014608000000000002,"cacheRead":0.01155648,"cacheWrite":0,"total":0.01654948}},"stopReason":"toolUse","timestamp":1782037574117,"responseId":"chatcmpl-RBi5mwQ202CrIvIE6fUjvPXq","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"a4a99696","parentId":"16869db9","timestamp":"2026-06-21T10:26:17.907Z","message":{"role":"toolResult","toolCallId":"call_fa62","toolName":"edit","content":[{"type":"text","text":"Successfully replaced 1 block(s) in /shumengya/project/agent/sproutclaw/.pi/agent/models.json."}],"details":{"diff":" ...\n 50 \"api\": \"openai-completions\",\n 51 \"authHeader\": true,\n 52 \"models\": [\n 53 {\n+ 54 \"id\": \"glm-5.2\",\n+ 55 \"name\": \"GLM-5.2\",\n+ 56 \"reasoning\": true,\n+ 57 \"input\": [\n+ 58 \"text\"\n+ 59 ],\n+ 60 \"contextWindow\": 1000000,\n+ 61 \"maxTokens\": 131072,\n+ 62 \"cost\": {\n+ 63 \"input\": 1.4,\n+ 64 \"output\": 4.4,\n+ 65 \"cacheRead\": 0.26,\n+ 66 \"cacheWrite\": 0\n+ 67 }\n+ 68 },\n+ 69 {\n 54 \"id\": \"glm-5.1\",\n 55 \"name\": \"GLM-5.1\",\n 56 \"reasoning\": false,\n 57 \"input\": [\n ...","patch":"--- /shumengya/project/agent/sproutclaw/.pi/agent/models.json\n+++ /shumengya/project/agent/sproutclaw/.pi/agent/models.json\n@@ -50,8 +50,24 @@\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"models\": [\n {\n+ \"id\": \"glm-5.2\",\n+ \"name\": \"GLM-5.2\",\n+ \"reasoning\": true,\n+ \"input\": [\n+ \"text\"\n+ ],\n+ \"contextWindow\": 1000000,\n+ \"maxTokens\": 131072,\n+ \"cost\": {\n+ \"input\": 1.4,\n+ \"output\": 4.4,\n+ \"cacheRead\": 0.26,\n+ \"cacheWrite\": 0\n+ }\n+ },\n+ {\n \"id\": \"glm-5.1\",\n \"name\": \"GLM-5.1\",\n \"reasoning\": false,\n \"input\": [\n","firstChangedLine":54},"isError":false,"timestamp":1782037577907}}
{"type":"message","id":"6b66de0e","parentId":"a4a99696","timestamp":"2026-06-21T10:26:20.079Z","message":{"role":"assistant","content":[{"type":"text","text":"验证一下结果:"},{"type":"toolCall","id":"call_720e","name":"ffgrep","arguments":{"path":"/shumengya/project/agent/sproutclaw/.pi/agent/models.json","pattern":"glm-5","context":3}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":40,"output":46,"cacheRead":47296,"cacheWrite":0,"totalTokens":47382,"cost":{"input":0.000056,"output":0.00020240000000000001,"cacheRead":0.01229696,"cacheWrite":0,"total":0.01255536}},"stopReason":"toolUse","timestamp":1782037577907,"responseId":"chatcmpl-R9reHlfNUFbNz0CcxgLcycVc","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"b252f2a3","parentId":"6b66de0e","timestamp":"2026-06-21T10:26:20.094Z","message":{"role":"toolResult","toolCallId":"call_720e","toolName":"ffgrep","content":[{"type":"text","text":"packages/ai/src/models.generated.ts [modified in git]\n 1649- contextWindow: 200000,\n 1650- maxTokens: 131072,\n 1651- } satisfies Model<\"bedrock-converse-stream\">,\n 1652: \"zai.glm-5\": {\n 1653- id: \"zai.glm-5\",\n 1654- name: \"GLM-5\",\n 1655- api: \"bedrock-converse-stream\",\n 1650- maxTokens: 131072,\n 1651- } satisfies Model<\"bedrock-converse-stream\">,\n 1652- \"zai.glm-5\": {\n 1653: id: \"zai.glm-5\",\n 1654- name: \"GLM-5\",\n 1655- api: \"bedrock-converse-stream\",\n 1656- provider: \"amazon-bedrock\",\n 1651- } satisfies Model<\"bedrock-converse-stream\">,\n 1652- \"zai.glm-5\": {\n 1653- id: \"zai.glm-5\",\n 1654: name: \"GLM-5\",\n 1655- api: \"bedrock-converse-stream\",\n 1656- provider: \"amazon-bedrock\",\n 1657- baseUrl: \"https://bedrock-runtime.us-east-1.amazonaws.com\",\n 3733- contextWindow: 131072,\n 3734- maxTokens: 131072,\n 3735- } satisfies Model<\"openai-completions\">,\n 3736: \"@cf/zai-org/glm-5.2\": {\n 3737- id: \"@cf/zai-org/glm-5.2\",\n 3738- name: \"Glm 5.2\",\n 3739- api: \"openai-completions\",\n 3734- maxTokens: 131072,\n 3735- } satisfies Model<\"openai-completions\">,\n 3736- \"@cf/zai-org/glm-5.2\": {\n 3737: id: \"@cf/zai-org/glm-5.2\",\n 3738- name: \"Glm 5.2\",\n 3739- api: \"openai-completions\",\n 3740- provider: \"cloudflare-workers-ai\",\n 3829- contextWindow: 1000000,\n 3830- maxTokens: 384000,\n 3831- } satisfies Model<\"anthropic-messages\">,\n 3832: \"accounts/fireworks/models/glm-5p1\": {\n 3833- id: \"accounts/fireworks/models/glm-5p1\",\n 3834- name: \"GLM 5.1\",\n 3835- api: \"anthropic-messages\",\n 3830- maxTokens: 384000,\n 3831- } satisfies Model<\"anthropic-messages\">,\n 3832- \"accounts/fireworks/models/glm-5p1\": {\n 3833: id: \"accounts/fireworks/models/glm-5p1\",\n 3834- name: \"GLM 5.1\",\n 3835- api: \"anthropic-messages\",\n 3836- provider: \"fireworks\",\n 3847- contextWindow: 202800,\n 3848- maxTokens: 131072,\n 3849- } satisfies Model<\"anthropic-messages\">,\n 3850: \"accounts/fireworks/models/glm-5p2\": {\n 3851- id: \"accounts/fireworks/models/glm-5p2\",\n 3852- name: \"GLM 5.2\",\n 3853- api: \"anthropic-messages\",\n 3848- maxTokens: 131072,\n 3849- } satisfies Model<\"anthropic-messages\">,\n 3850- \"accounts/fireworks/models/glm-5p2\": {\n 3851: id: \"accounts/fireworks/models/glm-5p2\",\n 3852- name: \"GLM 5.2\",\n 3853- api: \"anthropic-messages\",\n 3854- provider: \"fireworks\",\n 3991- contextWindow: 262144,\n 3992- maxTokens: 65536,\n 3993- } satisfies Model<\"anthropic-messages\">,\n 3994: \"accounts/fireworks/routers/glm-5p1-fast\": {\n 3995- id: \"accounts/fireworks/routers/glm-5p1-fast\",\n 3996- name: \"GLM 5.1 Fast\",\n 3997- api: \"anthropic-messages\",\n 3992- maxTokens: 65536,\n 3993- } satisfies Model<\"anthropic-messages\">,\n 3994- \"accounts/fireworks/routers/glm-5p1-fast\": {\n 3995: id: \"accounts/fireworks/routers/glm-5p1-fast\",\n 3996- name: \"GLM 5.1 Fast\",\n 3997- api: \"anthropic-messages\",\n 3998- provider: \"fireworks\",\n 5474- contextWindow: 200000,\n 5475- maxTokens: 128000,\n 5476- } satisfies Model<\"openai-completions\">,\n 5477: \"zai-org/GLM-5\": {\n 5478- id: \"zai-org/GLM-5\",\n 5479- name: \"GLM-5\",\n 5480- api: \"openai-completions\",\n 5475- maxTokens: 128000,\n 5476- } satisfies Model<\"openai-completions\">,\n 5477- \"zai-org/GLM-5\": {\n 5478: id: \"zai-org/GLM-5\",\n 5479- name: \"GLM-5\",\n 5480- api: \"openai-completions\",\n 5481- provider: \"huggingface\",\n 5476- } satisfies Model<\"openai-completions\">,\n 5477- \"zai-org/GLM-5\": {\n 5478- id: \"zai-org/GLM-5\",\n 5479: name: \"GLM-5\",\n 5480- api: \"openai-completions\",\n 5481- provider: \"huggingface\",\n 5482- baseUrl: \"https://router.huggingface.co/v1\",\n 5492- contextWindow: 202752,\n 5493- maxTokens: 131072,\n 5494- } satisfies Model<\"openai-completions\">,\n 5495: \"zai-org/GLM-5.1\": {\n 5496- id: \"zai-org/GLM-5.1\",\n 5497- name: \"GLM-5.1\",\n 5498- api: \"openai-completions\",\n 5493- maxTokens: 131072,\n 5494- } satisfies Model<\"openai-completions\">,\n 5495- \"zai-org/GLM-5.1\": {\n 5496: id: \"zai-org/GLM-5.1\",\n 5497- name: \"GLM-5.1\",\n 5498- api: \"openai-completions\",\n 5499- provider: \"huggingface\",\n 5494- } satisfies Model<\"openai-completions\">,\n 5495- \"zai-org/GLM-5.1\": {\n 5496- id: \"zai-org/GLM-5.1\",\n 5497: name: \"GLM-5.1\",\n 5498- api: \"openai-completions\",\n 5499- provider: \"huggingface\",\n 5500- baseUrl: \"https://router.huggingface.co/v1\",\n 7613- contextWindow: 1048576,\n 7614- maxTokens: 65536,\n 7615- } satisfies Model<\"google-generative-ai\">,\n 7616: \"glm-5\": {\n 7617- id: \"glm-5\",\n 7618- name: \"GLM-5\",\n 7619- api: \"openai-completions\",\n 7614- maxTokens: 65536,\n 7615- } satisfies Model<\"google-generative-ai\">,\n 7616- \"glm-5\": {\n 7617: id: \"glm-5\",\n 7618- name: \"GLM-5\",\n 7619- api: \"openai-completions\",\n 7620- provider: \"opencode\",\n 7615- } satisfies Model<\"google-generative-ai\">,\n 7616- \"glm-5\": {\n 7617- id: \"glm-5\",\n 7618: name: \"GLM-5\",\n 7619- api: \"openai-completions\",\n 7620- provider: \"opencode\",\n 7621- baseUrl: \"https://opencode.ai/zen/v1\",\n\npackages/ai/scripts/generate-models.ts\n 1178- candidate.cost.cacheRead = 0.07;\n 1179- candidate.maxTokens = 4096;\n 1180- }\n 1181: if (candidate.provider === \"openrouter\" && candidate.id === \"z-ai/glm-5\") {\n 1182- candidate.cost.input = 0.6;\n 1183- candidate.cost.output = 1.9;\n 1184- candidate.cost.cacheRead = 0.119;\n\npackages/ai/test/openai-completions-tool-choice.test.ts\n 213- });\n 214- \n 215- it(\"enables tool_stream for supported z.ai models with tools\", async () => {\n 216: const model = getModel(\"zai\", \"glm-5.1\")!;\n 217- const tools: Tool[] = [\n 218- {\n 219- name: \"ping\",\n 250- });\n 251- \n 252- it(\"stores z.ai tool_stream support in model compat metadata\", () => {\n 253: expect(getModel(\"zai\", \"glm-5.1\")?.compat?.zaiToolStream).toBe(true);\n 254- expect(getModel(\"zai\", \"glm-4.7\")?.compat?.zaiToolStream).toBe(true);\n 255- expect(getModel(\"zai\", \"glm-4.7\")?.compat?.zaiToolStream).toBe(true);\n 256- expect(getModel(\"zai\", \"glm-5-turbo\")?.compat?.zaiToolStream).toBe(true);\n 253- expect(getModel(\"zai\", \"glm-5.1\")?.compat?.zaiToolStream).toBe(true);\n 254- expect(getModel(\"zai\", \"glm-4.7\")?.compat?.zaiToolStream).toBe(true);\n 255- expect(getModel(\"zai\", \"glm-4.7\")?.compat?.zaiToolStream).toBe(true);\n 256: expect(getModel(\"zai\", \"glm-5-turbo\")?.compat?.zaiToolStream).toBe(true);\n 257- expect(getModel(\"zai\", \"glm-4.5-air\")?.compat?.zaiToolStream).toBeUndefined();\n 258- });\n 259- \n 339- });\n 340- \n 341- it(\"omits tool_stream when no tools are provided\", async () => {\n 342: const model = getModel(\"zai\", \"glm-5.1\")!;\n 343- let payload: unknown;\n 344- \n 345- await streamSimple(\n 381- },\n 382- ];\n 383- \n 384: const model = getModel(\"zai\", \"glm-5.1\")!;\n 385- const response = await streamSimple(\n 386- model,\n 387- {\n\npackages/ai/test/stream.test.ts\n 896- },\n 897- );\n 898- \n 899: describe.skipIf(!process.env.ZAI_API_KEY)(\"zAI Provider (glm-5.1 via OpenAI Completions)\", () => {\n 900- const llm = getModel(\"zai\", \"glm-5.1\");\n 901- \n 902- it(\"should complete basic text generation\", { retry: 3 }, async () => {\n 897- );\n 898- \n 899- describe.skipIf(!process.env.ZAI_API_KEY)(\"zAI Provider (glm-5.1 via OpenAI Completions)\", () => {\n 900: const llm = getModel(\"zai\", \"glm-5.1\");\n 901- \n 902- it(\"should complete basic text generation\", { retry: 3 }, async () => {\n 903- await basicTextGeneration(llm);\n\npackages/coding-agent/CHANGELOG.md\n 1574- - Safer package source handling with stricter git source parsing and improved local path normalization. See [docs/packages.md](docs/packages.md).\n 1575- - New built-in model definition `gpt-5.3-codex-spark` for OpenAI and OpenAI Codex providers.\n 1576- - Improved OpenAI stream robustness for malformed trailing tool-call JSON in partial chunks.\n 1577: - Added built-in GLM-5 model support via z.ai and OpenRouter provider catalogs.\n 1578- \n 1579- ### Breaking Changes\n 1580- \n\npackages/coding-agent/src/core/model-resolver.ts\n 22- \"google-vertex\": \"gemini-3.1-pro-preview\",\n 23- \"github-copilot\": \"gpt-5.4\",\n 24- openrouter: \"moonshotai/kimi-k2.6\",\n 25: \"vercel-ai-gateway\": \"zai/glm-5.1\",\n 26- xai: \"grok-4.20-0309-reasoning\",\n 27- groq: \"openai/gpt-oss-120b\",\n 28- cerebras: \"zai-glm-4.7\",\n 26- xai: \"grok-4.20-0309-reasoning\",\n 27- groq: \"openai/gpt-oss-120b\",\n 28- cerebras: \"zai-glm-4.7\",\n 29: zai: \"glm-5.1\",\n 30- mistral: \"devstral-medium-latest\",\n 31- minimax: \"MiniMax-M2.7\",\n 32- \"minimax-cn\": \"MiniMax-M2.7\",\n 374- // If no explicit --provider, try to interpret \"provider/model\" format first.\n 375- // When the prefix before the first slash matches a known provider, prefer that\n 376- // interpretation over matching models whose IDs literally contain slashes\n 377: // (e.g. \"zai/glm-5\" should resolve to provider=zai, model=glm-5, not to a\n 378- // vercel-ai-gateway model with id \"zai/glm-5\").\n 379- let pattern = cliModel;\n 380- let inferredProvider = false;\n 375- // When the prefix before the first slash matches a known provider, prefer that\n 376- // interpretation over matching models whose IDs literally contain slashes\n 377- // (e.g. \"zai/glm-5\" should resolve to provider=zai, model=glm-5, not to a\n 378: // vercel-ai-gateway model with id \"zai/glm-5\").\n 379- let pattern = cliModel;\n 380- let inferredProvider = false;\n 381- \n\npackages/coding-agent/src/core/tools/edit.ts\n 97- \n 98- const args = input as Record<string, unknown>;\n 99- \n 100: // Some models (Opus 4.6, GLM-5.1) send edits as a JSON string instead of an array\n 101- if (typeof args.edits === \"string\") {\n 102- try {\n 103- const parsed = JSON.parse(args.edits);\n\npackages/coding-agent/test/model-registry.test.ts\n 508- maxTokens: 131072,\n 509- },\n 510- {\n 511: id: \"glm-5\",\n 512- api: \"openai-completions\",\n 513- reasoning: true,\n 514- input: [\"text\"],\n 522- \n 523- const registry = ModelRegistry.create(authStorage, modelsJsonPath);\n 524- const m25 = registry.find(\"opencode-go\", \"minimax-m2.5\");\n 525: const glm5 = registry.find(\"opencode-go\", \"glm-5\");\n 526- \n 527- expect(m25?.baseUrl).toBe(\"https://opencode.ai/zen/go\");\n 528- expect(glm5?.baseUrl).toBe(\"https://opencode.ai/zen/go/v1\");\n\npackages/coding-agent/test/model-resolver.test.ts\n 316- });\n 317- \n 318- test(\"prefers provider/model split over gateway model with matching id\", () => {\n 319: // When a user writes \"zai/glm-5\", and both a zai provider model (id: \"glm-5\")\n 320- // and a gateway model (id: \"zai/glm-5\") exist, prefer the zai provider model.\n 321- const zaiModel: Model<\"anthropic-messages\"> = {\n 322- id: \"glm-5\",\n 317- \n 318- test(\"prefers provider/model split over gateway model with matching id\", () => {\n 319- // When a user writes \"zai/glm-5\", and both a zai provider model (id: \"glm-5\")\n 320: // and a gateway model (id: \"zai/glm-5\") exist, prefer the zai provider model.\n 321- const zaiModel: Model<\"anthropic-messages\"> = {\n 322- id: \"glm-5\",\n 323- name: \"GLM-5\",\n 319- // When a user writes \"zai/glm-5\", and both a zai provider model (id: \"glm-5\")\n 320- // and a gateway model (id: \"zai/glm-5\") exist, prefer the zai provider model.\n 321- const zaiModel: Model<\"anthropic-messages\"> = {\n 322: id: \"glm-5\",\n 323- name: \"GLM-5\",\n 324- api: \"anthropic-messages\",\n 325- provider: \"zai\",\n 320- // and a gateway model (id: \"zai/glm-5\") exist, prefer the zai provider model.\n 321- const zaiModel: Model<\"anthropic-messages\"> = {\n 322- id: \"glm-5\",\n 323: name: \"GLM-5\",\n 324- api: \"anthropic-messages\",\n 325- provider: \"zai\",\n 326- baseUrl: \"https://open.bigmodel.cn/api/paas/v4\",\n 331- maxTokens: 8192,\n 332- };\n 333- const gatewayModel: Model<\"anthropic-messages\"> = {\n 334: id: \"zai/glm-5\",\n 335- name: \"GLM-5\",\n 336- api: \"anthropic-messages\",\n 337- provider: \"vercel-ai-gateway\",\n 332- };\n 333- const gatewayModel: Model<\"anthropic-messages\"> = {\n 334- id: \"zai/glm-5\",\n 335: name: \"GLM-5\",\n 336- api: \"anthropic-messages\",\n 337- provider: \"vercel-ai-gateway\",\n 338- baseUrl: \"https://ai-gateway.vercel.sh\",\n 347- } as unknown as Parameters<typeof resolveCliModel>[0][\"modelRegistry\"];\n 348- \n 349- const result = resolveCliModel({\n 350: cliModel: \"zai/glm-5\",\n 351- modelRegistry: registry,\n 352- });\n 353- \n 353- \n 354- expect(result.error).toBeUndefined();\n 355- expect(result.model?.provider).toBe(\"zai\");\n 356: expect(result.model?.id).toBe(\"glm-5\");\n 357- });\n 358- \n 359- test(\"resolves provider-prefixed fuzzy patterns (openrouter/qwen -> openrouter model)\", () => {\n 379- });\n 380- \n 381- test(\"zai, minimax, and cerebras defaults track current models\", () => {\n 382: expect(defaultModelPerProvider.zai).toBe(\"glm-5.1\");\n 383- expect(defaultModelPerProvider.minimax).toBe(\"MiniMax-M2.7\");\n 384- expect(defaultModelPerProvider[\"minimax-cn\"]).toBe(\"MiniMax-M2.7\");\n 385- expect(defaultModelPerProvider.cerebras).toBe(\"zai-glm-4.7\");\n 386- });\n 387- \n 388- test(\"ai-gateway default tracks current model\", () => {\n 389: expect(defaultModelPerProvider[\"vercel-ai-gateway\"]).toBe(\"zai/glm-5.1\");\n 390- });\n 391- \n 392- test(\"findInitialModel accepts explicit provider custom model ids\", async () => {"}],"details":{"totalMatched":46,"totalFiles":794},"isError":false,"timestamp":1782037580094}}
{"type":"message","id":"8bf4dbfb","parentId":"b252f2a3","timestamp":"2026-06-21T10:26:22.641Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_56c0","name":"read","arguments":{"path":"/shumengya/project/agent/sproutclaw/.pi/agent/models.json","offset":1,"limit":80}}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":4807,"output":39,"cacheRead":47360,"cacheWrite":0,"totalTokens":52206,"cost":{"input":0.006729799999999999,"output":0.0001716,"cacheRead":0.0123136,"cacheWrite":0,"total":0.019215}},"stopReason":"toolUse","timestamp":1782037580095,"responseId":"chatcmpl-RF6a9jF1Yzmv6dxN4m6buumR","responseModel":"frank/GLM-5.1"}}
{"type":"message","id":"7c637f38","parentId":"8bf4dbfb","timestamp":"2026-06-21T10:26:22.644Z","message":{"role":"toolResult","toolCallId":"call_56c0","toolName":"read","content":[{"type":"text","text":"{\n \"providers\": {\n \"cpa\": {\n \"baseUrl\": \"https://cpa.shumengya.top/v1\",\n \"apiKey\": \"sk-shumengya666\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"models\": [\n {\n \"id\": \"gpt-5.4-mini(high)\",\n \"name\": \"gpt-5.4-mini(high)\",\n \"reasoning\": true,\n \"input\": [\n \"text\",\n \"image\"\n ]\n }\n ]\n },\n \"grok2api\": {\n \"baseUrl\": \"https://grok2api.shumengya.top/v1\",\n \"apiKey\": \"sk-shumengya666\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"models\": [\n {\n \"id\": \"grok-4.20-0309-non-reasoning\",\n \"name\": \"grok-4.20\",\n \"reasoning\": false,\n \"input\": [\n \"text\"\n ]\n }\n ]\n },\n \"deepseek\": {\n \"baseUrl\": \"https://api.deepseek.com\",\n \"apiKey\": \"sk-96928190eea94fd984e4689b5aba1548\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"modelOverrides\": {\n \"deepseek-v4-flash\": {\n \"contextWindow\": 500000\n }\n }\n },\n \"opencode-go-openai\": {\n \"baseUrl\": \"https://opencode.ai/zen/go/v1\",\n \"apiKey\": \"sk-jPLcSAWMwDBeIIQ2w7dkonzSFbJFxvqWxEQnl8twHXX21G01QKqf6g0TVkvJuDjx\",\n \"api\": \"openai-completions\",\n \"authHeader\": true,\n \"models\": [\n {\n \"id\": \"glm-5.2\",\n \"name\": \"GLM-5.2\",\n \"reasoning\": true,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 1000000,\n \"maxTokens\": 131072,\n \"cost\": {\n \"input\": 1.4,\n \"output\": 4.4,\n \"cacheRead\": 0.26,\n \"cacheWrite\": 0\n }\n },\n {\n \"id\": \"glm-5.1\",\n \"name\": \"GLM-5.1\",\n \"reasoning\": false,\n \"input\": [\n \"text\"\n ],\n \"contextWindow\": 200000,\n \"maxTokens\": 16384,\n \"cost\": {\n \"input\": 1.4,\n \"output\": 4.4,\n\n[283 more lines in file. Use offset=81 to continue.]"}],"isError":false,"timestamp":1782037582644}}
{"type":"message","id":"0f34237f","parentId":"7c637f38","timestamp":"2026-06-21T10:26:27.560Z","message":{"role":"assistant","content":[{"type":"text","text":"已经成功添加了 `glm-5.2` 到 `models.json` 的 `opencode-go-openai` provider 中(第一个位置)O(≧口≦)\n\n配置详情\n\n| 字段 | 值 |\n|------|-----|\n| id | `glm-5.2` |\n| name | `GLM-5.2` |\n| reasoning | `true`(支持推理/思维链) |\n| input | `[\"text\"]` |\n| contextWindow | `1000000`100万 tokens |\n| maxTokens | `131072`128K 输出) |\n| cost.input | $1.4/M tokens |\n| cost.output | $4.4/M tokens |\n| cost.cacheRead | $0.26/M tokens |\n| cost.cacheWrite | $0 |\n\n相比 glm-5.1 的进展:\n- 上下文窗口从 202K → **1M tokens**\n- 最大输出从 32K → **131K tokens**\n- 正式支持推理模式reasoning: true\n\n文件重新加载即刻生效无需重启 (`・ω・´)"}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.1","usage":{"input":663,"output":242,"cacheRead":52192,"cacheWrite":0,"totalTokens":53097,"cost":{"input":0.0009282,"output":0.0010648,"cacheRead":0.01356992,"cacheWrite":0,"total":0.01556292}},"stopReason":"stop","timestamp":1782037582646,"responseId":"chatcmpl-RSUIatWV2g7aLZhFOP7zNwcw","responseModel":"frank/GLM-5.1"}}
{"type":"model_change","id":"0f6a39c8","parentId":"0f34237f","timestamp":"2026-06-21T10:26:43.915Z","provider":"opencode-go-openai","modelId":"glm-5.2"}
{"type":"thinking_level_change","id":"c696047a","parentId":"0f6a39c8","timestamp":"2026-06-21T10:26:43.915Z","thinkingLevel":"high"}
{"type":"message","id":"e3a8b139","parentId":"c696047a","timestamp":"2026-06-21T10:26:48.885Z","message":{"role":"user","content":[{"type":"text","text":"你是什么模型"}],"timestamp":1782037608882}}
{"type":"message","id":"3f937dae","parentId":"e3a8b139","timestamp":"2026-06-21T10:26:54.368Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"用户问我是什么模型。根据 AGENTS.md 我叫萌小芽/sproutclaw。但我没有明确被告知我是什么底层模型。我应该诚实回答。\n\n让我看看是否有相关信息。项目上下文里没有明确说明底层模型。我应该坦诚回答。","thinkingSignature":"reasoning_content"},{"type":"text","text":"我是**萌小芽SproutClaw**,树萌芽的智能 AI 助手 ヘ(=^・ω・^=)ノ\n\n不过说实话我无法确定自己底座具体是哪个模型——我的运行环境pi coding agent harness没有把这个信息暴露给我。我只能看到自己的系统提示和工具配置看不到后台接入的是哪个 LLM provider/模型。\n\n如果你想确认可以\n- 查看 pi 的启动配置 / 当前会话的 `--model` 参数\n- 在 TUI 里通过 `/model` 查看当前选中的模型\n- 或问树萌芽本人 (。・ω・。)"}],"api":"openai-completions","provider":"opencode-go-openai","model":"glm-5.2","usage":{"input":53409,"output":194,"cacheRead":8,"cacheWrite":0,"totalTokens":53611,"cost":{"input":0.0747726,"output":0.0008536,"cacheRead":0.00000208,"cacheWrite":0,"total":0.07562827999999999}},"stopReason":"stop","timestamp":1782037608940,"responseId":"chatcmpl-15c726920bf5465cbfa686d64c032dc7","responseModel":"accounts/fireworks/models/glm-5p2"}}