{"repo": "acosmi-fushihua/crabclaw", "files": {"backend/internal/agents/models/providers.go": {"f1ed71addaa2352c15c98de18228056cd03e5773": {"t": 1786068297.9697924, "n": 7330, "h": "80935e4e00672a99", "c": "package models\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/Acosmi/CrabClawApp/pkg/types\"\n)\n\n// ---------- 供应商配置 ----------\n\n// TS 参考: src/agents/models-config.providers.ts (637 行)\n\n// ProviderDefaults 供应商默认配置。\ntype ProviderDefaults struct {\n\tBaseURL string `json:\"baseUrl,omitempty\"`\n\tAPIURL string `json:\"apiUrl,omitempty\"`\n\tDefaultModel string `json:\"defaultModel,omitempty\"`\n\tContextWindow int `json:\"contextWindow,omitempty\"`\n\tMaxTokens int `json:\"maxTokens,omitempty\"`\n\tCostInput float64 `json:\"costInput,omitempty\"`\n\tCostOutput float64 `json:\"costOutput,omitempty\"`\n\tCostCacheRead float64 `json:\"costCacheRead,omitempty\"`\n\tCostCacheWrite float64 `json:\"costCacheWrite,omitempty\"`\n}\n\n// 供应商默认值\n// TS 参考: models-config.providers.ts 常量定义\nvar providerDefaults = map[string]ProviderDefaults{\n\t\"minimax\": {\n\t\tBaseURL: \"https://api.minimax.io/v1\",\n\t\tDefaultModel: \"MiniMax-Text-01\",\n\t\tContextWindow: 1_000_000,\n\t\tMaxTokens: 8192,\n\t},\n\t\"minimax-portal\": {\n\t\tBaseURL: \"https://api.minimax.io/anthropic\",\n\t\tDefaultModel: \"MiniMax-M2.7\",\n\t\tContextWindow: 1_000_000,\n\t\tMaxTokens: 8192,\n\t},\n\t\"moonshot\": {\n\t\tBaseURL: \"https://api.moonshot.ai/v1\",\n\t\tDefaultModel: \"moonshot-v1-kimi-k2\", // K2.5 稳定别名(2026-01-27)\n\t\tContextWindow: 131_072,\n\t\tMaxTokens: 16384,\n\t},\n\t\"qwen-portal\": {\n\t\tBaseURL: \"https://portal.qwen.ai/v1\",\n\t\tDefaultModel: \"qwen3.6-plus\",\n\t\tContextWindow: 1_000_000,\n\t\tMaxTokens: 81920,\n\t},\n\t\"ollama\": {\n\t\tBaseURL: \"http://127.0.0.1:11434/v1\",\n\t\tAPIURL: \"http://127.0.0.1:11434\",\n\t\tContextWindow: 128_000,\n\t\tMaxTokens: 8192,\n\t},\n\t\"deepseek\": {\n\t\tBaseURL: \"https://api.deepseek.com/v1\",\n\t\tDefaultModel: \"deepseek-chat\",\n\t\tContextWindow: 131_072, // DeepSeek-V3.2 实际 128K\n\t\tMaxTokens: 8192,\n\t},\n\t\"google\": {\n\t\tBaseURL: \"https://generativelanguage.googleapis.com/v1beta\",\n\t\tDefaultModel: \"gemini-2.5-flash\",\n\t\tContextWindow: 1_000_000,\n\t\tMaxTokens: 8192,\n\t},\n\t\"doubao\": {\n\t\tBaseURL: \"https://ark.cn-beijing.volces.com/api/v3\",\n\t\tDefaultModel: \"doubao-seed-2-0-pro-260215\",\n\t\tContextWindow: 131_072,\n\t\tMaxTokens: 16384,\n\t},\n\t\"qwen\": {\n\t\tBaseURL: \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n\t\tDefaultModel: \"qwen3.6-plus\",\n\t\tContextWindow: 1_000_000,\n\t\tMaxTokens: 81920,\n\t},\n\t\"zai\": {\n\t\tBaseURL: \"https://open.bigmodel.cn/api/paas/v4\",\n\t\tDefaultModel: \"glm-5.1\",\n\t\tContextWindow: 204_800,\n\t\tMaxTokens: 65536,\n\t},\n\t\"openai\": {\n\t\tBaseURL: \"https://api.openai.com/v1\",\n\t\tDefaultModel: \"gpt-5.4\",\n\t\tContextWindow: 1_000_000,\n\t\tMaxTokens: 32768,\n\t},\n\t\"anthropic\": {\n\t\tBaseURL: \"https://api.anthropic.com/v1\",\n\t\tDefaultModel: \"claude-sonnet-4-20250514\",\n\t\tContextWindow: 200_000,\n\t\tMaxTokens: 8192,\n\t},\n\t\"xai\": {\n\t\tBaseURL: \"https://api.x.ai/v1\",\n\t\tDefaultModel: \"grok-3\",\n\t\tContextWindow: 131_072,\n\t\tMaxTokens: 8192,\n\t},\n}\n\n// EnvApiKeyVarNames 供应商 API Key 环境变量名映射。\n// P4-DRIFT4: 对齐 TS model-auth.ts L287-307 完整映射。\nvar EnvApiKeyVarNames = map[string]string{\n\t\"anthropic\": \"ANTHROPIC_API_KEY\",\n\t\"openai\": \"OPENAI_API_KEY\",\n\t\"google\": \"GEMINI_API_KEY\",\n\t\"deepseek\": \"DEEPSEEK_API_KEY\",\n\t\"qwen\": \"DASHSCOPE_API_KEY\",\n\t\"qwen-portal\": \"DASHSCOPE_API_KEY\",\n\t\"moonshot\": \"MOONSHOT_API_KEY\",\n\t\"minimax\": \"MINIMAX_API_KEY\",\n\t\"minimax-portal\": \"MINIMAX_API_KEY\",\n\t\"xai\": \"XAI_API_KEY\",\n\t\"zai\": \"ZAI_API_KEY\",\n\t\"doubao\": \"ARK_API_KEY\",\n\t\"crabclaw\": \"CRABCLAW_API_KEY\",\n\t\"ollama\": \"OLLAMA_API_KEY\",\n}\n\n// EnvApiKeyFallbacks OAuth/令牌回退链。\n// TS 参考: model-auth.ts resolveEnvApiKey 中的 fallback 链。\nvar EnvApiKeyFallbacks = map[string][]string{\n\t\"anthropic\": {\"ANTHROPIC_OAUTH_TOKEN\"},\n\t\"qwen-portal\": {\"QWEN_PORTAL_API_KEY\"},\n\t\"minimax-portal\": {\"MINIMAX_PORTAL_API_KEY\"},\n}\n\n// ResolveEnvApiKeyVarName 解析供应商 API Key 环境变量名。\nfunc ResolveEnvApiKeyVarName(provider string) string {\n\tnormalized := strings.ToLower(strings.TrimSpace(provider))\n\tif varName, ok := EnvApiKeyVarNames[normalized]; ok {\n\t\treturn varName\n\t}\n\treturn \"\"\n}\n\n// ResolveEnvApiKeyWithFallback P4-DRIFT4: 解析 API Key,支持 OAuth 回退链。\n// 对齐 TS model-auth.ts resolveEnvApiKey() 中的 fallback 逻辑。\nfunc ResolveEnvApiKeyWithFallback(provider string) string {\n\tnormalized := strings.ToLower(strings.TrimSpace(provider))\n\n\t// 主环境变量\n\tif varName, ok := EnvApiKeyVarNames[normalized]; ok {\n\t\tif val := os.Getenv(varName); val != \"\" {\n\t\t\treturn val\n\t\t}\n\t}\n\n\t// 回退链\n\tif fallbacks, ok := EnvApiKeyFallbacks[normalized]; ok {\n\t\tfor _, fb := range fallbacks {\n\t\t\tif val := os.Getenv(fb); val != \"\" {\n\t\t\t\treturn val\n\t\t\t}\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\n// NormalizeApiKeyConfig 规范化 API Key 配置字符串。\nfunc NormalizeApiKeyConfig(value string) string {\n\ttrimmed := strings.TrimSpace(value)\n\tif trimmed == \"\" {\n\t\treturn \"\"\n\t}\n\t// 检查是否为环境变量引用 (以 $ 开头)\n\tif strings.HasPrefix(trimmed, \"$\") {\n\t\tenvVar := strings.TrimPrefix(trimmed, \"$\")\n\t\tenvVar = strings.Trim(envVar, \"{}\")\n\t\treturn os.Getenv(envVar)\n\t}\n\treturn trimmed\n}\n\n// NormalizeGoogleModelId 规范化 Google 模型 ID。\n// TS 参考: models-config.providers.ts L191-198\nfunc NormalizeGoogleModelId(id string) string {\n\tif id == \"\" {\n\t\treturn id\n\t}\n\t// gemini-3 系列名称映射\n\tif id == \"gemini-3.1-pro\" {\n\t\treturn \"gemini-3.1-pro-preview\"\n\t}\n\tif id == \"gemini-3-pro\" {\n\t\treturn \"gemini-3-pro-preview\"\n\t}\n\tif id == \"gemini-3-flash\" {\n\t\treturn \"gemini-3-flash-preview\"\n\t}\n\t// 移除 models/ 前缀\n\tif strings.HasPrefix(id, \"models/\") {\n\t\treturn strings.TrimPrefix(id, \"models/\")\n\t}\n\treturn id\n}\n\n// GetProviderDefaults 获取供应商默认配置。\nfunc GetProviderDefaults(provider string) *ProviderDefaults {\n\tnormalized := strings.ToLower(strings.TrimSpace(provider))\n\tif defaults, ok := providerDefaults[normalized]; ok {\n\t\treturn &defaults\n\t}\n\treturn nil\n}\n\n// ResolveProviderBaseURL 解析供应商 base URL。\n// 优先使用配置文件中用户自定义的 baseUrl,回退到硬编码默认值。\nfunc ResolveProviderBaseURL(provider string, cfg *types.CrabClawConfig) string {\n\t// 1. 优先: 配置文件中用户自定义 baseUrl\n\tif cfg != nil && cfg.Models != nil && cfg.Models.Providers != nil {\n\t\tnormalized := strings.ToLower(strings.TrimSpace(provider))\n\t\tif pc := cfg.Models.Providers[normalized]; pc != nil && pc.BaseURL != \"\" {\n\t\t\treturn pc.BaseURL\n\t\t}\n\t\tif pc := cfg.Models.Providers[provider]; pc != nil && pc.BaseURL != \"\" {\n\t\t\treturn pc.BaseURL\n\t\t}\n\t}\n\t// 2. 回退: 硬编码默认值\n\tdefaults := GetProviderDefaults(provider)\n\tif defaults != nil {\n\t\treturn defaults.BaseURL\n\t}\n\treturn \"\"\n}\n\n// ---------- 工具级 API Key 环境变量(非 LLM provider) ----------\n// 对应 TS: tools/web-search.ts 和 tools/web-fetch.ts 中的 env fallback 逻辑。\n\n// ToolEnvApiKeyVarNames 工具名称到环境变量名的映射。\n// configPath 对应 schema_hints_data.go 中的 \"fallback: XXX_API_KEY env var\" 描述。\nvar ToolEnvApiKeyVarNames = map[string]string{\n\t\"brave_search\": \"BRAVE_API_KEY\",\n\t\"firecrawl\": \"FIRECRAWL_API_KEY\",\n\t\"perplexity\": \"PERPLEXITY_API_KEY\",\n\t\"elevenlabs\": \"ELEVENLABS_API_KEY\",\n\t\"elevenlabs_xi\": \"XI_API_KEY\",\n}\n\n// ResolveToolApiKey 解析工具级 API Key。\n// configValue 为配置文件中的显式值(优先);tool 为工具名(用于 env fallback)。\n// TS 参考: web-search.ts resolveSearchApiKey(), web-fetch.ts resolveFirecrawlApiKey()\nfunc ResolveToolApiKey(tool, configValue string) string {\n\tif v := NormalizeApiKeyConfig(configValue); v != \"\" {\n\t\treturn v\n\t}\n\tif envVar, ok := ToolEnvApiKeyVarNames[strings.ToLower(tool)]; ok {\n\t\treturn os.Getenv(envVar)\n\t}\n\treturn \"\"\n}\n"}}}}