Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
40 KiB
JSON
1 line
40 KiB
JSON
{"repo": "artiffusion-inc/9gouter", "files": {"internal/adapter/provider/registry.go": {"3503d6441339e2570fb649f1c5d9365e85572c43": {"t": 1786068370.413716, "n": 36514, "h": "2ac84a009a9c3a2a", "c": "// Package provider is the provider adapter: registry, base executor wiring, and\n// provider-specific config. It mirrors open-sse/executors/index.js + default.js.\npackage provider\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/antigravity\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/azure\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/base\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/codebuddy\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/codex\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/commandcode\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/cursor\"\n\tdefexec \"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/default\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/gemini-cli\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/github\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/grok-cli\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/grok-web\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/iflow\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/kimchi\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/kiro\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/mimo-free\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/ollama-local\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/opencode\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/opencode-go\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/perplexity-web\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/qoder\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/qwen\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/vertex\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/provider/xiaomi-tokenplan\"\n\t\"github.com/Artiffusion-Inc/9gouter/internal/adapter/thinking\"\n\tdomain \"github.com/Artiffusion-Inc/9gouter/internal/domain/provider\"\n)\n\n// Anthropic Claude API headers reused across claude-format providers.\nvar claudeAPIHeaders = map[string]string{\n\t\"Anthropic-Version\": \"2023-06-01\",\n\t\"Anthropic-Beta\": \"claude-code-20250219,interleaved-thinking-2025-05-14\",\n}\n\n// claudeCLIHeaders is the static full Claude Code fingerprint used by the\n// claude provider. Values match the JS registry exactly.\nvar claudeCLIHeaders = map[string]string{\n\t\"Anthropic-Version\": \"2023-06-01\",\n\t\"Anthropic-Beta\": \"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advanced-tool-use-2025-11-20,effort-2025-11-24,structured-outputs-2025-12-15,fast-mode-2026-02-01,redact-thinking-2026-02-12,token-efficient-tools-2026-03-28\",\n\t\"Anthropic-Dangerous-Direct-Browser-Access\": \"true\",\n\t\"User-Agent\": \"claude-cli/2.1.92 (external, sdk-cli)\",\n\t\"X-App\": \"cli\",\n\t\"X-Stainless-Helper-Method\": \"stream\",\n\t\"X-Stainless-Retry-Count\": \"0\",\n\t\"X-Stainless-Runtime-Version\": \"v24.14.0\",\n\t\"X-Stainless-Package-Version\": \"0.80.0\",\n\t\"X-Stainless-Runtime\": \"node\",\n\t\"X-Stainless-Lang\": \"js\",\n\t\"X-Stainless-Arch\": \"arm64\",\n\t\"X-Stainless-Os\": \"MacOS\",\n\t\"X-Stainless-Timeout\": \"600\",\n}\n\n// ProviderConfig is the static per-provider transport configuration. It is\n// intentionally minimal: only the fields required by executors for the\n// url/header golden contract plus enough shape to keep Execute honest.\nvar configs = map[string]base.Config{\n\t\"alicode\": {\n\t\tBaseURL: \"https://coding.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\tQuirks: base.Quirks{PreserveCacheControl: true},\n\t},\n\t\"alicode-intl\": {\n\t\t// Coding Plan keys (sk-sp-...) only work on the coding-intl host.\n\t\t// Reverted from the DashScope compatible-mode endpoint by upstream\n\t\t// 55628eea: the two key types use two different hosts and are not\n\t\t// interchangeable. Standard DashScope keys (sk-...) live on alims-intl.\n\t\tBaseURL: \"https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions\",\n\t\tQuirks: base.Quirks{PreserveCacheControl: true},\n\t},\n\t\"alims-intl\": {\n\t\t// Model Studio Intl — standard DashScope API keys (sk-...), NOT Coding\n\t\t// Plan keys. Sibling of alicode-intl (Coding Plan). Added by upstream\n\t\t// 55628eea; the two key types use two different hosts.\n\t\tBaseURL: \"https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions\",\n\t\tQuirks: base.Quirks{PreserveCacheControl: true},\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"alims-intl\",\n\t\t\tAlias: \"alims-intl\",\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"qwen3.5-plus\", Name: \"Qwen3.5 Plus\"},\n\t\t\t\t{ID: \"kimi-k2.5\", Name: \"Kimi K2.5\"},\n\t\t\t\t{ID: \"glm-5\", Name: \"GLM 5\"},\n\t\t\t\t{ID: \"MiniMax-M2.5\", Name: \"MiniMax M2.5\"},\n\t\t\t\t{ID: \"qwen3-coder-next\", Name: \"Qwen3 Coder Next\"},\n\t\t\t\t{ID: \"qwen3-coder-plus\", Name: \"Qwen3 Coder Plus\"},\n\t\t\t\t{ID: \"glm-4.7\", Name: \"GLM 4.7\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"anthropic\": {\n\t\tBaseURL: \"https://api.anthropic.com/v1/messages\",\n\t\tFormat: \"claude\",\n\t\tHeaders: map[string]string{\n\t\t\t\"anthropic-version\": \"2023-06-01\",\n\t\t\t\"Anthropic-Beta\": \"claude-code-20250219,interleaved-thinking-2025-05-14\",\n\t\t},\n\t},\n\t\"antigravity\": {\n\t\tBaseURLs: []string{\"https://cloudcode-pa.googleapis.com\"},\n\t\tFormat: \"antigravity\",\n\t\tHeaders: map[string]string{\n\t\t\t// Port 71cd5b2f: align with Antigravity IDE Desktop 2.1.1 fingerprint\n\t\t\t// (captured from macOS arm64). Keep the IDE UA static regardless of the\n\t\t\t// server host — the provider profile intentionally matches the IDE\n\t\t\t// client, not 9router's host.\n\t\t\t\"User-Agent\": \"antigravity/ide/2.1.1 darwin/arm64\",\n\t\t},\n\t\tRetry: map[int]base.RetryEntry{\n\t\t\t429: {Attempts: 6, DelayMs: 2000},\n\t\t\t500: {Attempts: 3, DelayMs: 3000},\n\t\t\t503: {Attempts: 3, DelayMs: 2000},\n\t\t},\n\t},\n\t\"assemblyai\": {\n\t\tBaseURL: \"https://api.assemblyai.com/v1/audio/transcriptions\",\n\t},\n\t\"azure\": {\n\t\tBaseURL: \"\",\n\t\tHeaders: map[string]string{},\n\t},\n\t\"blackbox\": {\n\t\tBaseURL: \"https://api.blackbox.ai/v1/chat/completions\",\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"blackbox\",\n\t\t\tAlias: \"blackbox\",\n\t\t\tServiceKinds: []string{\"llm\"},\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"claude-fable-5\", Name: \"Claude Fable 5\", UpstreamModelID: \"blackboxai/anthropic/claude-fable-5\"},\n\t\t\t\t{ID: \"claude-opus-4.8\", Name: \"Claude Opus 4.8\", UpstreamModelID: \"blackboxai/anthropic/claude-opus-4.8\"},\n\t\t\t\t{ID: \"claude-sonnet-4.6\", Name: \"Claude Sonnet 4.6\", UpstreamModelID: \"blackboxai/anthropic/claude-sonnet-4.6\"},\n\t\t\t\t{ID: \"gpt-5.5\", Name: \"GPT-5.5\", UpstreamModelID: \"blackboxai/openai/gpt-5.5\"},\n\t\t\t\t{ID: \"gpt-5.4-pro\", Name: \"GPT-5.4 Pro\", UpstreamModelID: \"blackboxai/openai/gpt-5.4-pro\"},\n\t\t\t\t{ID: \"gpt-5.4\", Name: \"GPT-5.4\", UpstreamModelID: \"blackboxai/openai/gpt-5.4\"},\n\t\t\t\t{ID: \"gpt-5.3-codex\", Name: \"GPT-5.3 Codex\", UpstreamModelID: \"blackboxai/openai/gpt-5.3-codex\"},\n\t\t\t\t{ID: \"gpt-5.4-nano\", Name: \"GPT-5.4 Nano\", UpstreamModelID: \"blackboxai/openai/gpt-5.4-nano\"},\n\t\t\t\t{ID: \"deepseek-v4-flash\", Name: \"DeepSeek V4 Flash\", UpstreamModelID: \"blackboxai/deepseek/deepseek-v4-flash\"},\n\t\t\t\t{ID: \"grok-4.3\", Name: \"Grok 4.3\", UpstreamModelID: \"blackboxai/x-ai/grok-4.3\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"byteplus\": {\n\t\tBaseURL: \"https://ark.ap-southeast.bytepluses.com/api/coding/v3/chat/completions\",\n\t},\n\t\"cerebras\": {\n\t\tBaseURL: \"https://api.cerebras.ai/v1/chat/completions\",\n\t},\n\t\"chutes\": {\n\t\tBaseURL: \"https://llm.chutes.ai/v1/chat/completions\",\n\t},\n\t\"claude\": {\n\t\tBaseURL: \"https://api.anthropic.com/v1/messages\",\n\t\tFormat: \"claude\",\n\t\tURLSuffix: \"?beta=true\",\n\t\tHeaders: claudeCLIHeaders,\n\t\tAuth: base.AuthDescriptor{\n\t\t\tAPIKey: &base.AuthSpec{Header: \"x-api-key\", Scheme: \"raw\"},\n\t\t\tOAuth: &base.AuthSpec{Header: \"Authorization\", Scheme: \"bearer\"},\n\t\t\tHooks: []string{\"claudeOverlay\"},\n\t\t\tAnthropicVersion: true,\n\t\t},\n\t},\n\t\"cline\": {\n\t\tBaseURL: \"https://api.cline.bot/api/v1/chat/completions\",\n\t\tHeaders: map[string]string{\n\t\t\t\"HTTP-Referer\": \"https://cline.bot\",\n\t\t\t\"X-Title\": \"Cline\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{\n\t\t\tCombined: true,\n\t\t\tHeader: \"Authorization\",\n\t\t\tScheme: \"bearer\",\n\t\t\tHooks: []string{\"clineHeaders\"},\n\t\t},\n\t},\n\t\"clinepass\": {\n\t\tBaseURL: \"https://api.cline.bot/api/v1/chat/completions\",\n\t\tHeaders: map[string]string{\n\t\t\t\"HTTP-Referer\": \"https://cline.bot\",\n\t\t\t\"X-Title\": \"Cline\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{\n\t\t\tCombined: true,\n\t\t\tHeader: \"Authorization\",\n\t\t\tScheme: \"bearer\",\n\t\t\tHooks: []string{\"clineHeaders\"},\n\t\t},\n\t},\n\t\"cloudflare-ai\": {\n\t\tBaseURL: \"https://api.cloudflare.com/client/v4/accounts/{accountId}/ai/v1/chat/completions\",\n\t},\n\t\"codebuddy-cn\": {\n\t\tBaseURL: \"https://copilot.tencent.com/v2/chat/completions\",\n\t\tHeaders: map[string]string{\n\t\t\t\"User-Agent\": \"CLI/2.108.1 CodeBuddy/2.108.1\",\n\t\t\t\"X-Product\": \"SaaS\",\n\t\t\t\"X-IDE-Type\": \"CLI\",\n\t\t\t\"X-IDE-Name\": \"CLI\",\n\t\t\t\"x-requested-with\": \"XMLHttpRequest\",\n\t\t\t\"x-codebuddy-request\": \"1\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t\tForceStream: true,\n\t},\n\t\"codex\": {\n\t\tBaseURL: \"https://chatgpt.com/backend-api/codex/responses\",\n\t\tFormat: \"openai-responses\",\n\t\tHeaders: map[string]string{\n\t\t\t\"originator\": \"codex_cli_rs\",\n\t\t\t\"User-Agent\": \"codex_cli_rs/0.136.0\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t\tForceStream: true,\n\t},\n\t\"cohere\": {\n\t\tBaseURL: \"https://api.cohere.ai/v1/chat/completions\",\n\t},\n\t\"commandcode\": {\n\t\tBaseURL: \"https://api.commandcode.ai/alpha/generate\",\n\t\tFormat: \"commandcode\",\n\t\tHeaders: map[string]string{\n\t\t\t\"x-command-code-version\": \"0.25.7\",\n\t\t\t\"x-cli-environment\": \"cli\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t\tForceStream: true,\n\t},\n\t\"cursor\": {\n\t\tBaseURL: \"https://api2.cursor.sh\",\n\t\tURLSuffix: \"/aiserver.v1.ChatService/StreamUnifiedChatWithTools\",\n\t\tFormat: \"cursor\",\n\t\tHeaders: map[string]string{\n\t\t\t\"connect-accept-encoding\": \"gzip\",\n\t\t\t\"connect-protocol-version\": \"1\",\n\t\t\t\"Content-Type\": \"application/connect+proto\",\n\t\t\t\"User-Agent\": \"connect-es/1.6.1\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t},\n\t\"deepgram\": {\n\t\tBaseURL: \"https://api.deepgram.com/v1/listen\",\n\t},\n\t\"deepseek\": {\n\t\tBaseURL: \"https://api.deepseek.com/chat/completions\",\n\t},\n\t\"featherless\": {\n\t\tBaseURL: \"https://api.featherless.ai/v1/chat/completions\",\n\t},\n\t\"fireworks\": {\n\t\tBaseURL: \"https://api.fireworks.ai/inference/v1/chat/completions\",\n\t},\n\t\"gemini\": {\n\t\tBaseURL: \"https://generativelanguage.googleapis.com/v1beta/models\",\n\t\tFormat: \"gemini\",\n\t\tAuth: base.AuthDescriptor{\n\t\t\tAPIKey: &base.AuthSpec{Header: \"x-goog-api-key\", Scheme: \"raw\"},\n\t\t\tOAuth: &base.AuthSpec{Header: \"Authorization\", Scheme: \"bearer\"},\n\t\t},\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"gemini\",\n\t\t\tAlias: \"gemini\",\n\t\t\tServiceKinds: []string{\"llm\", \"embedding\", \"image\", \"imageToText\", \"webSearch\", \"tts\", \"stt\"},\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"gemini-3.1-pro-preview\", Name: \"Gemini 3.1 Pro Preview\"},\n\t\t\t\t{ID: \"gemini-3.1-flash-lite-preview\", Name: \"Gemini 3.1 Flash Lite Preview\"},\n\t\t\t\t{ID: \"gemini-3-flash-preview\", Name: \"Gemini 3 Flash Preview\"},\n\t\t\t\t{ID: \"gemini-2.5-pro\", Name: \"Gemini 2.5 Pro\"},\n\t\t\t\t{ID: \"gemini-2.5-flash\", Name: \"Gemini 2.5 Flash\"},\n\t\t\t\t{ID: \"gemini-2.5-flash-lite\", Name: \"Gemini 2.5 Flash Lite\"},\n\t\t\t\t{ID: \"gemma-4-31b-it\", Name: \"Gemma 4 31B IT\"},\n\t\t\t\t{ID: \"gemini-embedding-2-preview\", Name: \"Gemini Embedding 2 Preview\", Kind: \"embedding\"},\n\t\t\t\t{ID: \"gemini-embedding-001\", Name: \"Gemini Embedding 001\", Kind: \"embedding\"},\n\t\t\t\t{ID: \"text-embedding-005\", Name: \"Text Embedding 005\", Kind: \"embedding\"},\n\t\t\t\t{ID: \"text-embedding-004\", Name: \"Text Embedding 004 (Legacy)\", Kind: \"embedding\"},\n\t\t\t\t{ID: \"embedding-001\", Name: \"Embedding 001\", Kind: \"embedding\"},\n\t\t\t\t{ID: \"gemini-3.1-flash-image-preview\", Name: \"Gemini 3.1 Flash Image (Nano Banana 2)\", Kind: \"image\"},\n\t\t\t\t{ID: \"gemini-3-pro-image-preview\", Name: \"Gemini 3 Pro Image (Nano Banana Pro)\", Kind: \"image\"},\n\t\t\t\t{ID: \"gemini-2.5-flash-image\", Name: \"Gemini 2.5 Flash Image (Nano Banana)\", Kind: \"image\"},\n\t\t\t\t{ID: \"gemini-3.1-flash-tts-preview\", Name: \"Gemini 3.1 Flash TTS\", Kind: \"tts\"},\n\t\t\t\t{ID: \"gemini-2.5-flash-preview-tts\", Name: \"Gemini 2.5 Flash TTS\", Kind: \"tts\"},\n\t\t\t\t{ID: \"gemini-2.5-pro-preview-tts\", Name: \"Gemini 2.5 Pro TTS\", Kind: \"tts\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"gemini-cli\": {\n\t\tBaseURL: \"https://cloudcode-pa.googleapis.com/v1internal\",\n\t\tFormat: \"gemini-cli\",\n\t\tHeaders: map[string]string{\n\t\t\t\"X-Goog-Api-Client\": \"google-genai-sdk/1.41.0 gl-node/v22.19.0\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t},\n\t\"gitlab\": {\n\t\tBaseURL: \"https://gitlab.com/api/v4/chat/completions\",\n\t},\n\t\"github\": {\n\t\tBaseURL: \"https://api.githubcopilot.com/chat/completions\",\n\t\tBaseURLs: []string{\"https://api.githubcopilot.com/chat/completions\", \"https://api.githubcopilot.com/responses\", \"https://api.githubcopilot.com/v1/messages\"},\n\t\tFormat: \"openai\",\n\t\tHeaders: map[string]string{\n\t\t\t\"copilot-integration-id\": \"vscode-chat\",\n\t\t\t\"editor-version\": \"vscode/1.110.0\",\n\t\t\t\"editor-plugin-version\": \"copilot-chat/0.38.0\",\n\t\t\t\"user-agent\": \"GitHubCopilotChat/0.38.0\",\n\t\t\t\"openai-intent\": \"conversation-panel\",\n\t\t\t\"x-github-api-version\": \"2025-04-01\",\n\t\t\t\"x-vscode-user-agent-library-version\": \"electron-fetch\",\n\t\t\t\"X-Initiator\": \"user\",\n\t\t\t// Port decolua/9router 542a088c: required by the /v1/messages shim\n\t\t\t// (the Anthropic-native Claude route) and a harmless no-op on\n\t\t\t// /chat/completions and /responses. Lives in Config.Headers (not the\n\t\t\t// executor's BuildHeaders override) so it is applied by\n\t\t\t// BaseExecutor.BuildHeaders on the real Execute path.\n\t\t\t\"anthropic-version\": base.AnthropicAPIVersion,\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t},\n\t\"glm\": {\n\t\tBaseURL: \"https://api.z.ai/api/anthropic/v1/messages\",\n\t\tFormat: \"claude\",\n\t\tURLSuffix: \"?beta=true\",\n\t\tHeaders: claudeAPIHeaders,\n\t\tAuth: base.AuthDescriptor{\n\t\t\tCombined: true,\n\t\t\tHeader: \"x-api-key\",\n\t\t\tScheme: \"raw\",\n\t\t\tAnthropicVersion: true,\n\t\t},\n\t},\n\t\"glm-cn\": {\n\t\tBaseURL: \"https://open.bigmodel.cn/api/coding/paas/v4/chat/completions\",\n\t},\n\t\"grok-cli\": {\n\t\tBaseURL: \"https://cli-chat-proxy.grok.com/v1/responses\",\n\t\tFormat: \"openai-responses\",\n\t\tHeaders: map[string]string{\n\t\t\t\"User-Agent\": \"grok-shell/0.2.99 (linux; x86_64)\",\n\t\t\t\"x-grok-client-identifier\": \"grok-shell\",\n\t\t\t\"x-grok-client-version\": \"0.2.99\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t\tForceStream: true,\n\t\tRetry: map[int]base.RetryEntry{\n\t\t\t429: {Attempts: 2, DelayMs: 2000},\n\t\t\t502: {Attempts: 2, DelayMs: 1500},\n\t\t\t503: {Attempts: 2, DelayMs: 1500},\n\t\t},\n\t\t// Static model catalog mirroring open-sse/providers/registry/grok-cli.js\n\t\t// (upstream 59b78282): the Grok Build subscription model (grok-build) +\n\t\t// the grok-4.5 family with effort-suffix aliases. grok-cli also has a live\n\t\t// resolver, so /v1/models merges these with the live list; the static\n\t\t// set is the fallback when the live fetch is unavailable and the source\n\t\t// of the effort-suffix variants the live endpoint does not enumerate.\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"grok-cli\",\n\t\t\tAlias: \"gcli\",\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"grok-build\", Name: \"Grok Build\"},\n\t\t\t\t{ID: \"grok-4.5\", Name: \"Grok 4.5\"},\n\t\t\t\t{ID: \"grok-4.5-high\", Name: \"Grok 4.5 (High)\", UpstreamModelID: \"grok-4.5\"},\n\t\t\t\t{ID: \"grok-4.5-medium\", Name: \"Grok 4.5 (Medium)\", UpstreamModelID: \"grok-4.5\"},\n\t\t\t\t{ID: \"grok-4.5-low\", Name: \"Grok 4.5 (Low)\", UpstreamModelID: \"grok-4.5\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"grok-web\": {\n\t\tBaseURL: \"https://grok.com/rest/app-chat/conversations/new\",\n\t\tFormat: \"grok-web\",\n\t\tNoAuth: true,\n\t},\n\t\"groq\": {\n\t\tBaseURL: \"https://api.groq.com/openai/v1/chat/completions\",\n\t},\n\t\"hyperbolic\": {\n\t\tBaseURL: \"https://api.hyperbolic.xyz/v1/chat/completions\",\n\t},\n\t\"iflow\": {\n\t\tBaseURL: \"https://apis.iflow.cn/v1/chat/completions\",\n\t\tHeaders: map[string]string{\n\t\t\t\"User-Agent\": \"iFlow-Cli\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t},\n\t\"kilocode\": {\n\t\tBaseURL: \"https://api.kilo.ai/api/openrouter/chat/completions\",\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"kilocode\",\n\t\t\tAlias: \"kc\",\n\t\t\tServiceKinds: []string{\"llm\"},\n\t\t\t// The 8 hardcoded upstream OpenRouter models are a fallback; the live\n\t\t\t// catalog (the free long-context subset) is fetched from\n\t\t\t// api.kilo.ai/api/gateway/models by the kilocode live resolver\n\t\t\t// (resolver/kilocode.go, 713c5637) and replaces these for active\n\t\t\t// kilocode connections. The static list only surfaces when the live\n\t\t\t// fetch fails or no kilocode connection is active.\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"anthropic/claude-sonnet-4-20250514\", Name: \"Claude Sonnet 4\"},\n\t\t\t\t{ID: \"anthropic/claude-opus-4-20250514\", Name: \"Claude Opus 4\"},\n\t\t\t\t{ID: \"google/gemini-2.5-pro\", Name: \"Gemini 2.5 Pro\"},\n\t\t\t\t{ID: \"google/gemini-2.5-flash\", Name: \"Gemini 2.5 Flash\"},\n\t\t\t\t{ID: \"openai/gpt-4.1\", Name: \"GPT-4.1\"},\n\t\t\t\t{ID: \"openai/o3\", Name: \"o3\"},\n\t\t\t\t{ID: \"deepseek/deepseek-chat\", Name: \"DeepSeek Chat\"},\n\t\t\t\t{ID: \"deepseek/deepseek-reasoner\", Name: \"DeepSeek Reasoner\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"kimchi\": {\n\t\tBaseURL: \"https://llm.kimchi.dev/openai/v1/chat/completions\",\n\t\tHeaders: map[string]string{\n\t\t\t\"User-Agent\": \"kimchi/0.1.50\",\n\t\t},\n\t\tAuth: base.AuthDescriptor{Combined: true, Header: \"Authorization\", Scheme: \"bearer\"},\n\t},\n\t// kimi is the merged dual-auth provider (upstream v0.5.40, commit 68566f53):\n\t// OAuth = Kimi Code subscription (device-code flow, acquired out-of-band —\n\t// the dashboard device-code surface is not yet ported to Go), API key =\n\t// platform.moonshot / api.kimi.com. Transport is shared, so both auth\n\t// modes ride the same claude-format endpoint + kimiHeaders hook (the\n\t// X-Msh-* / device-id headers required by CLIProxyAPI parity). The legacy\n\t// `kimi-coding` id resolves to `kimi` via the alias map.\n\t\"kimi\": {\n\t\tBaseURL: \"https://api.kimi.com/coding/v1/messages\",\n\t\tFormat: \"claude\",\n\t\tURLSuffix: \"?beta=true\",\n\t\tHeaders: claudeAPIHeaders,\n\t\tAuth: base.AuthDescriptor{\n\t\t\tCombined: true,\n\t\t\tHeader: \"x-api-key\",\n\t\t\tScheme: \"raw\",\n\t\t\tHooks: []string{\"kimiHeaders\"},\n\t\t\tAnthropicVersion: true,\n\t\t},\n\t\t// Static model catalog mirroring open-sse/providers/registry/kimi.js\n\t\t// (upstream 68566f53). kimi has no live resolver, so /v1/models and\n\t\t// dashboard validation fall back to this list; without it the merged\n\t\t// dual-auth provider advertised no models at all.\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"kimi\",\n\t\t\tAlias: \"kimi\",\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"kimi-k3\", Name: \"Kimi K3\"},\n\t\t\t\t{ID: \"k3\", Name: \"Kimi K3 (Code)\"},\n\t\t\t\t{ID: \"kimi-for-coding\", Name: \"Kimi for Coding\"},\n\t\t\t\t{ID: \"kimi-for-coding-highspeed\", Name: \"Kimi for Coding Highspeed\"},\n\t\t\t\t{ID: \"kimi-k2.7-code\", Name: \"Kimi K2.7 Code\"},\n\t\t\t\t{ID: \"kimi-k2.7-code-highspeed\", Name: \"Kimi K2.7 Code Highspeed\"},\n\t\t\t\t{ID: \"kimi-k2.6\", Name: \"Kimi K2.6\"},\n\t\t\t\t{ID: \"kimi-k2.5\", Name: \"Kimi K2.5\"},\n\t\t\t\t{ID: \"kimi-k2.5-thinking\", Name: \"Kimi K2.5 Thinking\"},\n\t\t\t\t{ID: \"kimi-latest\", Name: \"Kimi Latest\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"kiro\": {\n\t\tBaseURLs: []string{\n\t\t\t\"https://runtime.us-east-1.kiro.dev/generateAssistantResponse\",\n\t\t\t\"https://codewhisperer.us-east-1.amazonaws.com/generateAssistantResponse\",\n\t\t\t\"https://q.us-east-1.amazonaws.com/generateAssistantResponse\",\n\t\t},\n\t\tFormat: \"kiro\",\n\t\tHeaders: map[string]string{\n\t\t\t\"Accept\": \"application/vnd.amazon.eventstream\",\n\t\t\t\"User-Agent\": \"AWS-SDK-JS/3.0.0 kiro-ide/1.0.0\",\n\t\t\t\"X-Amz-User-Agent\": \"aws-sdk-js/3.0.0 kiro-ide/1.0.0\",\n\t\t},\n\t\tRetry: map[int]base.RetryEntry{\n\t\t\t429: {Attempts: 0, DelayMs: 2000},\n\t\t},\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"kiro\", Alias: \"kr\", ServiceKinds: []string{\"llm\"},\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"claude-opus-5\", Name: \"Claude Opus 5\"},\n\t\t\t\t{ID: \"claude-opus-5-thinking\", Name: \"Claude Opus 5 (Thinking)\"},\n\t\t\t\t{ID: \"claude-opus-5-agentic\", Name: \"Claude Opus 5 (Agentic)\"},\n\t\t\t\t{ID: \"claude-opus-5-thinking-agentic\", Name: \"Claude Opus 5 (Thinking + Agentic)\"},\n\t\t\t\t{ID: \"claude-opus-4.8\", Name: \"Claude Opus 4.8\"},\n\t\t\t\t{ID: \"claude-opus-4.8-thinking\", Name: \"Claude Opus 4.8 (Thinking)\"},\n\t\t\t\t{ID: \"claude-opus-4.8-agentic\", Name: \"Claude Opus 4.8 (Agentic)\"},\n\t\t\t\t{ID: \"claude-opus-4.7\", Name: \"Claude Opus 4.7\"},\n\t\t\t\t{ID: \"claude-opus-4.7-thinking\", Name: \"Claude Opus 4.7 (Thinking)\"},\n\t\t\t\t{ID: \"claude-opus-4.7-agentic\", Name: \"Claude Opus 4.7 (Agentic)\"},\n\t\t\t\t{ID: \"claude-opus-4.5\", Name: \"Claude Opus 4.5\"},\n\t\t\t\t{ID: \"claude-opus-4.5-thinking\", Name: \"Claude Opus 4.5 (Thinking)\"},\n\t\t\t\t{ID: \"claude-opus-4.5-agentic\", Name: \"Claude Opus 4.5 (Agentic)\"},\n\t\t\t\t{ID: \"claude-sonnet-5\", Name: \"Claude Sonnet 5\"},\n\t\t\t\t{ID: \"claude-sonnet-4.5\", Name: \"Claude Sonnet 4.5\"},\n\t\t\t\t{ID: \"claude-haiku-4.5\", Name: \"Claude Haiku 4.5\"},\n\t\t\t\t{ID: \"deepseek-3.2\", Name: \"DeepSeek 3.2\"},\n\t\t\t\t{ID: \"qwen3-coder-next\", Name: \"Qwen3 Coder Next\"},\n\t\t\t\t{ID: \"glm-5\", Name: \"GLM 5\"},\n\t\t\t\t{ID: \"MiniMax-M2.5\", Name: \"MiniMax M2.5\"},\n\t\t\t\t{ID: \"gpt-5.6-sol\", Name: \"GPT-5.6 Sol\"},\n\t\t\t\t{ID: \"gpt-5.6-terra\", Name: \"GPT-5.6 Terra\"},\n\t\t\t\t{ID: \"gpt-5.6-luna\", Name: \"GPT-5.6 Luna\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"minimax\": {\n\t\tBaseURL: \"https://api.minimax.io/anthropic/v1/messages\",\n\t\tFormat: \"claude\",\n\t\tURLSuffix: \"?beta=true\",\n\t\tHeaders: claudeAPIHeaders,\n\t\tAuth: base.AuthDescriptor{\n\t\t\tCombined: true,\n\t\t\tHeader: \"x-api-key\",\n\t\t\tScheme: \"raw\",\n\t\t\tAnthropicVersion: true,\n\t\t},\n\t\tReasoningInject: &base.ReasoningInject{Scope: \"all\"},\n\t\tQuirks: base.Quirks{DropOutputConfig: true},\n\t},\n\t\"minimax-cn\": {\n\t\tBaseURL: \"https://api.minimaxi.com/anthropic/v1/messages\",\n\t\tFormat: \"claude\",\n\t\tURLSuffix: \"?beta=true\",\n\t\tHeaders: claudeAPIHeaders,\n\t\tAuth: base.AuthDescriptor{\n\t\t\tCombined: true,\n\t\t\tHeader: \"x-api-key\",\n\t\t\tScheme: \"raw\",\n\t\t\tAnthropicVersion: true,\n\t\t},\n\t},\n\t\"mistral\": {\n\t\tBaseURL: \"https://api.mistral.ai/v1/chat/completions\",\n\t},\n\t\"mimo-free\": {\n\t\tBaseURL: \"https://api.xiaomimimo.com/api/free-ai/openai/chat\",\n\t\tNoAuth: true,\n\t},\n\t\"mmf\": {\n\t\tBaseURL: \"https://api.xiaomimimo.com/api/free-ai/openai/chat\",\n\t\tNoAuth: true,\n\t},\n\t\"nanobanana\": {\n\t\tBaseURL: \"https://api.nanobananaapi.ai/v1/chat/completions\",\n\t},\n\t\"nebius\": {\n\t\tBaseURL: \"https://api.studio.nebius.ai/v1/chat/completions\",\n\t},\n\t\"nvidia\": {\n\t\tBaseURL: \"https://integrate.api.nvidia.com/v1/chat/completions\",\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"nvidia\",\n\t\t\tAlias: \"nvidia\",\n\t\t\tServiceKinds: []string{\"llm\", \"tts\", \"embedding\"},\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"minimaxai/minimax-m2.7\", Name: \"MiniMax M2.7\"},\n\t\t\t\t{ID: \"minimaxai/minimax-m3\", Name: \"MiniMax M3\"},\n\t\t\t\t{ID: \"z-ai/glm-5.2\", Name: \"GLM 5.2\"},\n\t\t\t\t{ID: \"deepseek-ai/deepseek-v4-pro\", Name: \"DeepSeek V4 Pro\"},\n\t\t\t\t{ID: \"deepseek-ai/deepseek-v4-flash\", Name: \"DeepSeek V4 Flash\"},\n\t\t\t\t{ID: \"moonshotai/kimi-k2.6\", Name: \"Kimi K2.6\"},\n\t\t\t\t{ID: \"nvidia/nemotron-3-ultra-550b-a55b\", Name: \"Nemotron 3 Ultra\"},\n\t\t\t\t{ID: \"nvidia/nv-embedqa-e5-v5\", Name: \"NV EmbedQA E5 v5\", Kind: \"embedding\"},\n\t\t\t\t{ID: \"nvidia/parakeet-ctc-1.1b-asr\", Name: \"Parakeet CTC 1.1B\", Kind: \"stt\"},\n\t\t\t\t{ID: \"fastpitch\", Name: \"FastPitch\", Kind: \"tts\"},\n\t\t\t\t{ID: \"tacotron2\", Name: \"Tacotron2\", Kind: \"tts\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"ollama\": {\n\t\tBaseURL: \"https://ollama.com/api/chat\",\n\t\tFormat: \"ollama\",\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"ollama\",\n\t\t\tAlias: \"ollama\",\n\t\t\tServiceKinds: []string{\"llm\"},\n\t\t\tModels: []domain.Model{\n\t\t\t\t{ID: \"gpt-oss:120b\", Name: \"GPT OSS 120B\"},\n\t\t\t\t{ID: \"kimi-k2.5\", Name: \"Kimi K2.5\"},\n\t\t\t\t{ID: \"glm-5\", Name: \"GLM 5\"},\n\t\t\t\t{ID: \"minimax-m2.5\", Name: \"MiniMax M2.5\"},\n\t\t\t\t{ID: \"glm-4.7-flash\", Name: \"GLM 4.7 Flash\"},\n\t\t\t\t{ID: \"qwen3.5\", Name: \"Qwen3.5\"},\n\t\t\t\t{ID: \"minimax-m3\", Name: \"MiniMax M3\"},\n\t\t\t},\n\t\t},\n\t},\n\t\"ollama-local\": {\n\t\tBaseURL: \"http://localhost:11434/api/chat\",\n\t\tFormat: \"ollama\",\n\t\tCatalog: domain.ProviderCatalog{\n\t\t\tID: \"ollama-local\",\n\t\t\tAlias: \"ollama-local\",\n\t\t\tServiceKinds: []string{\"llm\"},\n\t\t},\n\t},\n\t\"openai\": {\n\t\tBaseURL: \"https://api.openai.com/v1/chat/completions\",\n\t\tForceStream: true,\n\t},\n\t\"opencode\": {\n\t\tBaseURL: \"https://opencode.ai\",\n\t\tHeaders: map[string]string{\n\t\t\t\"x-opencode-client\": \"desktop\",\n\t\t},\n\t\tNoAuth: true,\n\t},\n\t\"opencode-go\": {\n\t\tBaseURL: \"https://opencode.ai/zen/go/v1/chat/completions\",\n\t},\n\t\"openrouter\": {\n\t\tBaseURL: \"https://openrouter.ai/api/v1/chat/completions\",\n\t\tHeaders: map[string]string{\n\t\t\t\"HTTP-Referer\": \"https://endpoint-proxy.local\",\n\t\t\t\"X-Title\": \"Endpoint Proxy\",\n\t\t},\n\t},\n\t\"perplexity\": {\n\t\tBaseURL: \"https://api.perplexity.ai/chat/completions\",\n\t},\n\t\"perplexity-agent\": {\n\t\tBaseURL: \"https://api.perplexity.ai/v1/responses\",\n\t\tFormat: \"openai-responses\",\n\t},\n\t\"perplexity-web\": {\n\t\tBaseURL: \"https://www.perplexity.ai/rest/sse/perplexity_ask\",\n\t\tFormat: \"perplexity-web\",\n\t\tNoAuth: true,\n\t},\n\t\"qoder\": {\n\t\tBaseURL: \"https://api3.qoder.sh/algo/api/v2/service/pro/sse/agent_chat_generation\",\n\t\tTimeoutMs: 120000,\n\t},\n\t\"qwen\": {\n\t\tBaseURL: \"https://portal.qwen.ai/v1/chat/completions\",\n\t},\n\t\"siliconflow\": {\n\t\tBaseURL: \"https://api.siliconflow.com/v1/chat/completions\",\n\t},\n\t\"together\": {\n\t\tBaseURL: \"https://api.together.xyz/v1/chat/completions\",\n\t},\n\t\"venice\": {\n\t\tBaseURL: \"https://api.venice.ai/api/v1/chat/completions\",\n\t},\n\t\"vercel-ai-gateway\": {\n\t\tBaseURL: \"https://ai-gateway.vercel.sh/v1/chat/completions\",\n\t},\n\t\"vertex\": {\n\t\tBaseURL: \"https://aiplatform.googleapis.com\",\n\t\tFormat: \"vertex\",\n\t},\n\t\"vertex-partner\": {\n\t\tBaseURL: \"https://aiplatform.googleapis.com\",\n\t\tFormat: \"openai\",\n\t},\n\t\"volcengine-ark\": {\n\t\tBaseURL: \"https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions\",\n\t},\n\t\"xai\": {\n\t\tBaseURL: \"https://api.x.ai/v1/chat/completions\",\n\t},\n\t\"xiaomi-mimo\": {\n\t\tBaseURL: \"https://api.xiaomimimo.com/v1/chat/completions\",\n\t},\n\t\"xiaomi-tokenplan\": {\n\t\tBaseURL: \"https://token-plan-sgp.xiaomimimo.com/v1/chat/completions\",\n\t\tBaseURLs: []string{\n\t\t\t\"https://token-plan-sgp.xiaomimimo.com/v1\",\n\t\t\t\"https://token-plan-cn.xiaomimimo.com/v1\",\n\t\t\t\"https://token-plan-ams.xiaomimimo.com/v1\",\n\t\t},\n\t},\n}\n\n// aliases maps alias -> canonical provider id, matching open-sse/executors/index.js.\nvar aliases = map[string]string{\n\t// Canonical ids are self-mapping.\n\t\"alicode\": \"alicode\",\n\t\"alicode-intl\": \"alicode-intl\",\n\t\"alims-intl\": \"alims-intl\",\n\t\"anthropic\": \"anthropic\",\n\t\"antigravity\": \"antigravity\",\n\t\"assemblyai\": \"assemblyai\",\n\t\"azure\": \"azure\",\n\t\"blackbox\": \"blackbox\",\n\t\"byteplus\": \"byteplus\",\n\t\"cartesia\": \"cartesia\",\n\t\"cerebras\": \"cerebras\",\n\t\"chutes\": \"chutes\",\n\t\"claude\": \"claude\",\n\t\"cline\": \"cline\",\n\t\"clinepass\": \"clinepass\",\n\t\"cloudflare-ai\": \"cloudflare-ai\",\n\t\"codebuddy-cn\": \"codebuddy-cn\",\n\t\"codex\": \"codex\",\n\t\"cohere\": \"cohere\",\n\t\"commandcode\": \"commandcode\",\n\t\"cursor\": \"cursor\",\n\t\"deepgram\": \"deepgram\",\n\t\"deepseek\": \"deepseek\",\n\t\"featherless\": \"featherless\",\n\t\"fireworks\": \"fireworks\",\n\t\"gemini\": \"gemini\",\n\t\"gemini-cli\": \"gemini-cli\",\n\t\"github\": \"github\",\n\t\"gitlab\": \"gitlab\",\n\t\"glm\": \"glm\",\n\t\"glm-cn\": \"glm-cn\",\n\t\"grok-cli\": \"grok-cli\",\n\t\"grok-web\": \"grok-web\",\n\t\"groq\": \"groq\",\n\t\"hyperbolic\": \"hyperbolic\",\n\t\"iflow\": \"iflow\",\n\t\"kilocode\": \"kilocode\",\n\t\"kimchi\": \"kimchi\",\n\t\"kimi\": \"kimi\",\n\t// kimi-coding was merged into kimi as a dual-auth provider (upstream\n\t// v0.5.40); the legacy id resolves to the canonical `kimi`.\n\t\"kimi-coding\": \"kimi\",\n\t\"kiro\": \"kiro\",\n\t\"minimax\": \"minimax\",\n\t\"minimax-cn\": \"minimax-cn\",\n\t\"mistral\": \"mistral\",\n\t\"mimo-free\": \"mimo-free\",\n\t\"mmf\": \"mmf\",\n\t\"nanobanana\": \"nanobanana\",\n\t\"nebius\": \"nebius\",\n\t\"nvidia\": \"nvidia\",\n\t\"ollama\": \"ollama\",\n\t\"ollama-local\": \"ollama-local\",\n\t\"openai\": \"openai\",\n\t\"opencode\": \"opencode\",\n\t\"opencode-go\": \"opencode-go\",\n\t\"openrouter\": \"openrouter\",\n\t\"perplexity\": \"perplexity\",\n\t\"perplexity-agent\": \"perplexity-agent\",\n\t\"perplexity-web\": \"perplexity-web\",\n\t\"playht\": \"playht\",\n\t\"qoder\": \"qoder\",\n\t\"qwen\": \"qwen\",\n\t\"recraft\": \"recraft\",\n\t\"siliconflow\": \"siliconflow\",\n\t\"stability-ai\": \"stability-ai\",\n\t\"together\": \"together\",\n\t\"topaz\": \"topaz\",\n\t\"tortoise\": \"tortoise\",\n\t\"venice\": \"venice\",\n\t\"vercel-ai-gateway\": \"vercel-ai-gateway\",\n\t\"vertex\": \"vertex\",\n\t\"vertex-partner\": \"vertex-partner\",\n\t\"volcengine-ark\": \"volcengine-ark\",\n\t\"xai\": \"xai\",\n\t\"xiaomi-mimo\": \"xiaomi-mimo\",\n\t\"xiaomi-tokenplan\": \"xiaomi-tokenplan\",\n\t// Short aliases from open-sse/executors/index.js.\n\t\"cc\": \"claude\",\n\t\"cl\": \"cline\",\n\t\"cf\": \"cloudflare-ai\",\n\t\"cbcn\": \"codebuddy-cn\",\n\t\"cx\": \"codex\",\n\t\"cu\": \"cursor\",\n\t\"gcli\": \"grok-cli\",\n\t\"gb\": \"grok-cli\",\n\t\"kmc\": \"kimi\",\n\t\"ark\": \"volcengine-ark\",\n\t\"mimo\": \"xiaomi-mimo\",\n\t\"pplx-agent\": \"perplexity-agent\",\n\t\"pplx-responses\": \"perplexity-agent\",\n}\n\n// providerAlias returns the canonical alias (short form) for a provider id.\n// This is a best-effort mirror of the JS alias used for display/logging.\nfunc providerAlias(id string) string {\n\tfor alias, canonical := range aliases {\n\t\tif canonical == id && alias != id {\n\t\t\treturn alias\n\t\t}\n\t}\n\treturn id\n}\n\n// p is a provider implementation that wires a static id/alias to an executor.\ntype p struct {\n\tid string\n\talias string\n\texec domain.Executor\n}\n\nfunc (p p) ID() string { return p.id }\nfunc (p p) Alias() string { return p.alias }\nfunc (p p) Executor() domain.Executor { return p.exec }\n\n// newDefault returns a DefaultExecutor for providers without custom logic.\nfunc newDefault(id string, cfg base.Config) domain.Executor {\n\tcfg.ID = id\n\treturn defexec.New(id, cfg)\n}\n\n// Lookup resolves a provider id or alias to a Provider. It returns an error\n// for unknown ids.\nfunc Lookup(providerID string) (domain.Provider, error) {\n\tid, ok := aliases[providerID]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unknown provider %q\", providerID)\n\t}\n\tcfg, ok := configs[id]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"provider %q not configured\", providerID)\n\t}\n\talias := providerAlias(id)\n\tcfg.ID = id\n\n\tvar exec domain.Executor\n\tswitch id {\n\tcase \"antigravity\":\n\t\texec = antigravityexec.New(cfg)\n\tcase \"azure\":\n\t\texec = azureexec.New(cfg)\n\tcase \"codebuddy-cn\":\n\t\texec = codebuddyexec.New(cfg)\n\tcase \"codex\":\n\t\texec = codexec.New(cfg)\n\tcase \"commandcode\":\n\t\texec = commandcodeexec.New(cfg)\n\tcase \"cursor\":\n\t\texec = cursorexec.New(cfg)\n\tcase \"gemini-cli\":\n\t\texec = geminicliexec.New(cfg)\n\tcase \"github\":\n\t\texec = githubexec.New(cfg)\n\tcase \"grok-cli\":\n\t\texec = grokcliexec.New(cfg)\n\tcase \"grok-web\":\n\t\texec = grokwebexec.New(cfg)\n\tcase \"iflow\":\n\t\texec = iflowexec.New(cfg)\n\tcase \"kimchi\":\n\t\texec = kimchiexec.New(cfg)\n\tcase \"kiro\":\n\t\texec = kiroexec.New(cfg)\n\tcase \"mimo-free\":\n\t\texec = mimofreeexec.New(cfg)\n\tcase \"ollama-local\":\n\t\texec = ollamalocalexec.New(cfg)\n\tcase \"opencode\":\n\t\texec = opencodeexec.New(cfg)\n\tcase \"opencode-go\":\n\t\texec = opencodegoexec.New(cfg)\n\tcase \"perplexity-web\":\n\t\texec = perplexitywebexec.New(cfg)\n\tcase \"qoder\":\n\t\texec = qoderexec.New(cfg)\n\tcase \"qwen\":\n\t\texec = qwenexec.New(cfg)\n\tcase \"vertex\":\n\t\texec = vertexexec.New(id, cfg)\n\tcase \"vertex-partner\":\n\t\texec = vertexexec.New(id, cfg)\n\tcase \"xiaomi-tokenplan\":\n\t\texec = xiaomitokenplanexec.New(cfg)\n\tdefault:\n\t\texec = newDefault(id, cfg)\n\t}\n\treturn p{id: id, alias: alias, exec: exec}, nil\n}\n\n// Alias returns the canonical alias for a provider id.\nfunc Alias(providerID string) string {\n\treturn providerAlias(providerID)\n}\n\n// ChatBaseURL returns the raw chat BaseURL from the static provider config, or\n// \"\" if the provider is unknown. It is the embeddings adapter's fallback path:\n// for providers without an explicit embeddings URL it derives the embeddings\n// endpoint by swapping the chat suffix (/chat/completions, /messages, ...) for\n// /embeddings under the same versioned API root.\nfunc ChatBaseURL(providerID string) string {\n\tid, ok := aliases[providerID]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\tcfg, ok := configs[id]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn cfg.BaseURL\n}\n\n// ProviderForceStream reports whether the provider's upstream only accepts\n// streaming requests (mirrors JS PROVIDERS[id].forceStream). Callers use it to\n// force stream=true for JSON clients that would otherwise send a non-streaming\n// request to a stream-only endpoint.\nfunc ProviderForceStream(providerID string) bool {\n\tid, ok := aliases[providerID]\n\tif !ok {\n\t\treturn false\n\t}\n\tcfg, ok := configs[id]\n\tif !ok {\n\t\treturn false\n\t}\n\treturn cfg.ForceStream\n}\n\n// Catalog returns the static provider catalog (alias, models, serviceKinds)\n// for a provider id, or false if the provider is unknown or has no catalog.\n// Models may be empty for providers whose catalog is populated at runtime by\n// live-model resolvers (not yet ported) or compatible-fetch — callers should\n// treat an empty Models list as \"no static catalog\".\nfunc Catalog(providerID string) (domain.ProviderCatalog, bool) {\n\tid, ok := aliases[providerID]\n\tif !ok {\n\t\treturn domain.ProviderCatalog{}, false\n\t}\n\tcfg, ok := configs[id]\n\tif !ok {\n\t\treturn domain.ProviderCatalog{}, false\n\t}\n\tcat := cfg.Catalog\n\tif cat.ID == \"\" {\n\t\tcat.ID = id\n\t}\n\tif cat.Alias == \"\" {\n\t\tcat.Alias = providerAlias(id)\n\t}\n\tif len(cat.ServiceKinds) == 0 {\n\t\tcat.ServiceKinds = []string{\"llm\"}\n\t}\n\treturn cat, true\n}\n\n// UpstreamModelID resolves the raw upstream model id for a provider+model,\n// mirroring the JS getModelUpstreamId (open-sse/config/providerModels.js).\n// It strips a trailing \"(level)\" thinking suffix before lookup (so a model\n// copied with its level still resolves), looks up the catalog entry by id,\n// and returns UpstreamModelID when the entry defines one (e.g. blackbox\n// \"claude-opus-4.8\" → \"blackboxai/anthropic/claude-opus-4.8\"), re-appending\n// the suffix. When the provider has no catalog, the model id is not in it, or\n// the entry has no UpstreamModelID, it returns the input id unchanged.\n//\n// The suffix is re-appended so downstream thinking handling (which strips it\n// again from body.model via thinking.StripThinkingSuffix) still sees the\n// suffix on the resolved upstream id.\nfunc UpstreamModelID(providerID, model string) string {\n\tif model == \"\" {\n\t\treturn model\n\t}\n\t// Strip a trailing \"(level)\" thinking suffix so lookup hits the base id.\n\tbase, suffix := thinking.SplitLevelSuffix(model)\n\tcat, ok := Catalog(providerID)\n\tif !ok {\n\t\treturn model\n\t}\n\tfor _, m := range cat.Models {\n\t\tif m.ID == base {\n\t\t\tif m.UpstreamModelID != \"\" {\n\t\t\t\treturn m.UpstreamModelID + suffix\n\t\t\t}\n\t\t\treturn base + suffix\n\t\t}\n\t}\n\treturn model\n}\n\n// AllCatalogs returns the static catalog for every configured provider.\n// Providers without an explicit Catalog still appear with their alias and a\n// default [\"llm\"] service kind, but an empty Models list. This is the data\n// source for GET /v1/models (static portion).\nfunc AllCatalogs() []domain.ProviderCatalog {\n\tout := make([]domain.ProviderCatalog, 0, len(configs))\n\tfor id, cfg := range configs {\n\t\tcat := cfg.Catalog\n\t\tif cat.ID == \"\" {\n\t\t\tcat.ID = id\n\t\t}\n\t\tif cat.Alias == \"\" {\n\t\t\tcat.Alias = providerAlias(id)\n\t\t}\n\t\tif len(cat.ServiceKinds) == 0 {\n\t\t\tcat.ServiceKinds = []string{\"llm\"}\n\t\t}\n\t\tout = append(out, cat)\n\t}\n\treturn out\n}\n"}}}} |