From 44f533fe63533298aa366f57751a96c7b58c1bea Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <1paneldev@sina.com> Date: Thu, 2 Apr 2026 18:13:04 +0800 Subject: [PATCH] feat: update default model config --- agent/app/provider/catalog.go | 70 ++-- agent/app/provider/openclaw.go | 354 +++++------------- agent/app/provider/verify.go | 23 +- agent/app/service/agents.go | 5 +- agent/app/service/agents_utils.go | 191 +++------- .../src/views/ai/agents/model/add/index.vue | 16 +- 6 files changed, 210 insertions(+), 449 deletions(-) diff --git a/agent/app/provider/catalog.go b/agent/app/provider/catalog.go index c311e89b857f..06818efff274 100644 --- a/agent/app/provider/catalog.go +++ b/agent/app/provider/catalog.go @@ -78,14 +78,13 @@ var catalog = map[string]Meta{ EnvKey: "DEEPSEEK_API_KEY", Default: RuntimeDefault{ APIType: "openai-completions", - ContextWindow: 128000, + ContextWindow: 131072, MaxTokens: 8192, Input: []string{"text"}, }, Models: []Model{ {ID: "deepseek/deepseek-chat", Name: "DeepSeek Chat"}, - {ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner", Reasoning: true}, - {ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B", Reasoning: true}, + {ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner", MaxTokens: 65536, ContextWindow: 131072, Reasoning: true}, }, }, "bailian-coding-plan": { @@ -120,16 +119,16 @@ var catalog = map[string]Meta{ Default: RuntimeDefault{ APIType: "openai-completions", ContextWindow: 256000, - MaxTokens: 8192, + MaxTokens: 4096, Input: []string{"text"}, }, Models: []Model{ - {ID: "ark-coding-plan/doubao-seed-2.0-code", Name: "Doubao-Seed-2.0-Code"}, - {ID: "ark-coding-plan/doubao-seed-code", Name: "Doubao-Seed-Code"}, - {ID: "ark-coding-plan/kimi-k2.5", Name: "Kimi-K2.5", Reasoning: true}, - {ID: "ark-coding-plan/glm-4.7", Name: "GLM-4.7", Reasoning: true}, - {ID: "ark-coding-plan/deepseek-v3.2", Name: "DeepSeek-V3.2", Reasoning: true}, - {ID: "ark-coding-plan/kimi-k2-thinking", Name: "Kimi-K2-thinking", Reasoning: true}, + {ID: "ark-coding-plan/ark-code-latest", Name: "Ark Coding Plan", ContextWindow: 256000, MaxTokens: 4096}, + {ID: "ark-coding-plan/doubao-seed-code", Name: "Doubao Seed Code", ContextWindow: 256000, MaxTokens: 4096}, + {ID: "ark-coding-plan/glm-4.7", Name: "GLM 4.7 Coding", ContextWindow: 200000, MaxTokens: 4096}, + {ID: "ark-coding-plan/kimi-k2-thinking", Name: "Kimi K2 Thinking", ContextWindow: 256000, MaxTokens: 4096}, + {ID: "ark-coding-plan/kimi-k2.5", Name: "Kimi K2.5 Coding", ContextWindow: 256000, MaxTokens: 4096}, + {ID: "ark-coding-plan/doubao-seed-code-preview-251028", Name: "Doubao Seed Code Preview", ContextWindow: 256000, MaxTokens: 4096}, }, }, "zai": { @@ -174,19 +173,18 @@ var catalog = map[string]Meta{ Key: "xiaomi", DisplayName: "Xiaomi", Sort: 46, - DefaultBaseURL: "https://api.xiaomimimo.com/anthropic", + DefaultBaseURL: "https://api.xiaomimimo.com/v1", EnvKey: "XIAOMI_API_KEY", Default: RuntimeDefault{ - APIType: "anthropic-messages", + APIType: "openai-completions", ContextWindow: 262144, MaxTokens: 8192, Input: []string{"text"}, }, Models: []Model{ - {ID: "xiaomi/mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro"}, - {ID: "xiaomi/mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni"}, - {ID: "xiaomi/mimo-v2-tts", Name: "Xiaomi MiMo V2 TTS"}, - {ID: "xiaomi/mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash"}, + {ID: "xiaomi/mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash", ContextWindow: 262144, MaxTokens: 8192, Input: []string{"text"}}, + {ID: "xiaomi/mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro", ContextWindow: 1048576, MaxTokens: 32000, Reasoning: true, Input: []string{"text"}}, + {ID: "xiaomi/mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni", ContextWindow: 262144, MaxTokens: 32000, Reasoning: true, Input: []string{"text", "image"}}, }, }, "kimi": { @@ -214,13 +212,14 @@ var catalog = map[string]Meta{ DefaultBaseURL: "https://api.kimi.com/coding/", EnvKey: "KIMI_API_KEY", Default: RuntimeDefault{ - APIType: "openai-completions", + APIType: "anthropic-messages", ContextWindow: 262144, MaxTokens: 32768, Input: []string{"text", "image"}, }, Models: []Model{ - {ID: "kimi-coding/k2p5", Name: "Kimi K2.5", Reasoning: true}, + {ID: "kimi-coding/kimi-code", Name: "Kimi Code", ContextWindow: 262144, MaxTokens: 32768, Reasoning: true, Input: []string{"text", "image"}}, + {ID: "kimi-coding/k2p5", Name: "Kimi K2.5", ContextWindow: 262144, MaxTokens: 32768, Reasoning: true, Input: []string{"text", "image"}}, }, }, "openai": { @@ -230,17 +229,16 @@ var catalog = map[string]Meta{ DefaultBaseURL: "https://api.openai.com/v1", EnvKey: "OPENAI_API_KEY", Default: RuntimeDefault{ - APIType: "openai-completions", - ContextWindow: 256000, - MaxTokens: 8192, + APIType: "openai-responses", + ContextWindow: 272000, + MaxTokens: 128000, Input: []string{"text", "image"}, }, Models: []Model{ - {ID: "openai/codex-mini-latest", Name: "Codex Mini", Reasoning: true}, - {ID: "openai/gpt-5", Name: "GPT-5", Reasoning: true}, - {ID: "openai/gpt-5-mini", Name: "GPT-5 Mini", Reasoning: true}, - {ID: "openai/gpt-5.4", Name: "GPT-5.4", Reasoning: true}, - {ID: "openai/gpt-5.3-codex", Name: "GPT-5.3-Codex", Reasoning: true}, + {ID: "openai/gpt-5.4", Name: "gpt-5.4", ContextWindow: 272000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}}, + {ID: "openai/gpt-5.4-pro", Name: "gpt-5.4-pro", ContextWindow: 1050000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}}, + {ID: "openai/gpt-5.4-mini", Name: "gpt-5.4-mini", ContextWindow: 400000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}}, + {ID: "openai/gpt-5.4-nano", Name: "gpt-5.4-nano", ContextWindow: 400000, MaxTokens: 128000, Reasoning: true, Input: []string{"text", "image"}}, }, }, "openrouter": { @@ -267,17 +265,17 @@ var catalog = map[string]Meta{ DefaultBaseURL: "https://api.anthropic.com", EnvKey: "ANTHROPIC_API_KEY", Default: RuntimeDefault{ - APIType: "openai-completions", + APIType: "anthropic-messages", ContextWindow: 256000, MaxTokens: 8192, Input: []string{"text", "image"}, }, Models: []Model{ - {ID: "anthropic/claude-3-haiku-20240307", Name: "Claude 3 Haiku"}, - {ID: "anthropic/claude-3-5-haiku-latest", Name: "Claude 3.5 Haiku"}, - {ID: "anthropic/claude-3-5-sonnet-20241022", Name: "Claude 3.5 Sonnet"}, - {ID: "anthropic/claude-3-7-sonnet-20250219", Name: "Claude 3.7 Sonnet", Reasoning: true}, - {ID: "anthropic/claude-opus-4-1", Name: "Claude Opus 4.1", Reasoning: true}, + {ID: "anthropic/claude-sonnet-4-6", Name: "Claude Sonnet 4.6", Reasoning: true}, + {ID: "anthropic/claude-opus-4-6", Name: "Claude Opus 4.6", Reasoning: true}, + {ID: "anthropic/claude-opus-4-5", Name: "Claude Opus 4.5"}, + {ID: "anthropic/claude-sonnet-4-5", Name: "Claude Sonnet 4.5"}, + {ID: "anthropic/claude-haiku-4-5", Name: "Claude Haiku 4.5"}, }, }, "gemini": { @@ -319,7 +317,7 @@ var catalog = map[string]Meta{ } func Get(key string) (Meta, bool) { - meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))] + meta, ok := catalog[key] if !ok { return Meta{}, false } @@ -335,7 +333,7 @@ func All() map[string]Meta { } func DefaultBaseURL(key string) (string, bool) { - meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))] + meta, ok := catalog[key] if !ok || strings.TrimSpace(meta.DefaultBaseURL) == "" { return "", false } @@ -343,7 +341,7 @@ func DefaultBaseURL(key string) (string, bool) { } func EnvKey(key string) string { - meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))] + meta, ok := catalog[key] if !ok { return "" } @@ -351,7 +349,7 @@ func EnvKey(key string) string { } func DisplayName(key string) string { - meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))] + meta, ok := catalog[key] if !ok { return key } diff --git a/agent/app/provider/openclaw.go b/agent/app/provider/openclaw.go index 230a08232749..89c6c165ee99 100644 --- a/agent/app/provider/openclaw.go +++ b/agent/app/provider/openclaw.go @@ -10,322 +10,160 @@ type OpenClawPatch struct { Models map[string]interface{} } -type openClawModelSpec struct { - ID string - Name string - Reasoning bool - Input []string - ContextWindow int - MaxTokens int -} - -type openClawPatchSpec struct { +type OpenClawProviderPatch struct { PrimaryModel string - Provider string + ProviderKey string + ModelID string APIKey string BaseURL string APIType string AuthHeader bool - Model openClawModelSpec } func BuildOpenClawPatch(provider, modelName, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) (*OpenClawPatch, error) { - provider = strings.ToLower(strings.TrimSpace(provider)) + providerPatch, err := BuildOpenClawProviderPatch(provider, modelName, apiType, baseURL, apiKey) + if err != nil { + return nil, err + } + _, maxTokens, contextWindow = ResolveRuntimeParams(provider, apiType, maxTokens, contextWindow) + return newOpenClawPatch( + providerPatch, + resolveOpenClawModelName(provider, providerPatch.ModelID), + resolveOpenClawModelInput(provider, providerPatch.ModelID), + reasoning, + contextWindow, + maxTokens, + ), nil +} + +func BuildOpenClawProviderPatch(provider, modelName, apiType, baseURL, apiKey string) (*OpenClawProviderPatch, error) { modelName = strings.TrimSpace(modelName) if modelName == "" { return nil, fmt.Errorf("model is required") } - apiType, maxTokens, contextWindow = ResolveRuntimeParams(provider, apiType, maxTokens, contextWindow) - modelID := modelName - if parts := strings.SplitN(modelName, "/", 2); len(parts) == 2 { - modelID = parts[1] - } - - var spec openClawPatchSpec + resolvedAPIType, _, _ := ResolveRuntimeParams(provider, apiType, 0, 0) + modelID := resolveOpenClawModelID(provider, modelName) switch provider { case "deepseek": - spec = buildDeepseekPatchSpec(modelName, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch(modelName, "deepseek", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil case "gemini": - spec = buildGenericPatchSpec(provider, modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch("google/"+modelID, "google", modelID, strings.TrimSpace(apiKey), baseURL, resolvedAPIType, false), nil case "moonshot", "kimi": - spec = buildMoonshotPatchSpec(provider, modelName, modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey), nil case "bailian-coding-plan": - spec = buildBailianPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch("bailian-coding-plan/"+modelID, "bailian-coding-plan", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil case "ark-coding-plan": - spec = buildArkPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch("ark-coding-plan/"+modelID, "ark-coding-plan", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil case "minimax": - spec = buildMiniMaxPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch("minimax/"+modelID, "minimax", modelID, strings.TrimSpace(apiKey), baseURL, "anthropic-messages", true), nil case "xiaomi": - spec = buildXiaomiPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch("xiaomi/"+modelID, "xiaomi", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil case "custom", "vllm": - spec = buildCustomPatchSpec(provider, modelName, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch(provider+"/"+modelID, provider, modelID, strings.TrimSpace(apiKey), strings.TrimSpace(baseURL), resolvedAPIType, false), nil case "ollama": - spec = buildOllamaPatchSpec(modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL) + return newOpenClawProviderPatch(modelName, "ollama", modelID, "ollama", strings.TrimSpace(baseURL), resolvedAPIType, false), nil case "kimi-coding": - spec = buildKimiCodingPatchSpec(modelName, modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch(modelName, "kimi-coding", modelID, strings.TrimSpace(apiKey), baseURL, "anthropic-messages", false), nil case "zai": - spec = buildZaiPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey) + return newOpenClawProviderPatch("zai/"+modelID, "zai", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil default: - spec = buildGenericPatchSpec(provider, modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey) - } - return buildOpenClawPatch(spec), nil -} - -func buildOpenClawPatch(spec openClawPatchSpec) *OpenClawPatch { - return &OpenClawPatch{ - PrimaryModel: spec.PrimaryModel, - Models: providerModels( - spec.Provider, - spec.APIKey, - spec.BaseURL, - spec.APIType, - spec.AuthHeader, - buildOpenClawModel(spec.Model), - ), + return newOpenClawProviderPatch(modelName, provider, modelID, strings.TrimSpace(apiKey), baseURL, resolvedAPIType, false), nil } } -func buildOpenClawModel(spec openClawModelSpec) map[string]interface{} { - return map[string]interface{}{ - "id": spec.ID, - "name": spec.Name, - "reasoning": spec.Reasoning, - "input": spec.Input, - "contextWindow": spec.ContextWindow, - "maxTokens": spec.MaxTokens, - "cost": map[string]interface{}{}, - } -} - -func buildDeepseekPatchSpec(modelName string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - return openClawPatchSpec{ - PrimaryModel: modelName, - Provider: "deepseek", - APIKey: strings.TrimSpace(apiKey), - BaseURL: baseURL, - APIType: "openai-completions", - Model: openClawModelSpec{ - ID: "deepseek-chat", - Name: "DeepSeek Chat", - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, - } -} - -func buildMoonshotPatchSpec(provider, modelName, modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - configProvider := provider +func buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey string) *OpenClawProviderPatch { + providerKey := provider primaryModel := modelName if provider == "kimi" { - configProvider = "moonshot" + providerKey = "moonshot" primaryModel = "moonshot/" + modelID } - return openClawPatchSpec{ - PrimaryModel: primaryModel, - Provider: configProvider, - APIKey: strings.TrimSpace(apiKey), - BaseURL: baseURL, - APIType: "openai-completions", - Model: openClawModelSpec{ - ID: modelID, - Name: modelID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, - } + return newOpenClawProviderPatch(primaryModel, providerKey, modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false) } -func buildBailianPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - return openClawPatchSpec{ - PrimaryModel: "bailian-coding-plan/" + modelID, - Provider: "bailian-coding-plan", - APIKey: strings.TrimSpace(apiKey), +func newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, apiType string, authHeader bool) *OpenClawProviderPatch { + return &OpenClawProviderPatch{ + PrimaryModel: primaryModel, + ProviderKey: providerKey, + ModelID: modelID, + APIKey: apiKey, BaseURL: baseURL, - APIType: "openai-completions", - Model: openClawModelSpec{ - ID: modelID, - Name: modelID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, + APIType: apiType, + AuthHeader: authHeader, } } -func buildArkPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - return openClawPatchSpec{ - PrimaryModel: "ark-coding-plan/" + modelID, - Provider: "ark-coding-plan", - APIKey: strings.TrimSpace(apiKey), - BaseURL: baseURL, - APIType: "openai-completions", - Model: openClawModelSpec{ - ID: modelID, - Name: modelID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, +func newOpenClawPatch(providerPatch *OpenClawProviderPatch, modelName string, input []string, reasoning bool, contextWindow, maxTokens int) *OpenClawPatch { + model := map[string]interface{}{ + "id": providerPatch.ModelID, + "name": modelName, + "reasoning": reasoning, + "input": input, + "contextWindow": contextWindow, + "maxTokens": maxTokens, + "cost": map[string]interface{}{}, } -} - -func buildMiniMaxPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - return openClawPatchSpec{ - PrimaryModel: "minimax/" + modelID, - Provider: "minimax", - APIKey: strings.TrimSpace(apiKey), - BaseURL: baseURL, - APIType: "anthropic-messages", - AuthHeader: true, - Model: openClawModelSpec{ - ID: modelID, - Name: modelID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, + providerConfig := map[string]interface{}{ + "apiKey": providerPatch.APIKey, + "baseUrl": providerPatch.BaseURL, + "api": providerPatch.APIType, + "models": []map[string]interface{}{model}, } -} - -func buildXiaomiPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - normalizedID := strings.TrimSpace(modelID) - return openClawPatchSpec{ - PrimaryModel: "xiaomi/" + normalizedID, - Provider: "xiaomi", - APIKey: strings.TrimSpace(apiKey), - BaseURL: baseURL, - APIType: "anthropic-messages", - Model: openClawModelSpec{ - ID: normalizedID, - Name: normalizedID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, + if providerPatch.AuthHeader { + providerConfig["authHeader"] = true } -} - -func buildCustomPatchSpec(provider, modelName, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - customModelID := normalizeCustomModel(modelName) - return openClawPatchSpec{ - PrimaryModel: provider + "/" + customModelID, - Provider: provider, - APIKey: strings.TrimSpace(apiKey), - BaseURL: strings.TrimSpace(baseURL), - APIType: apiType, - Model: openClawModelSpec{ - ID: customModelID, - Name: customModelID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, + return &OpenClawPatch{ + PrimaryModel: providerPatch.PrimaryModel, + Models: map[string]interface{}{ + "mode": "merge", + "providers": map[string]interface{}{ + providerPatch.ProviderKey: providerConfig, + }, }, } } -func buildOllamaPatchSpec(modelName, modelID, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL string) openClawPatchSpec { - return openClawPatchSpec{ - PrimaryModel: modelName, - Provider: "ollama", - APIKey: "ollama", - BaseURL: strings.TrimSpace(baseURL), - APIType: apiType, - Model: openClawModelSpec{ - ID: modelID, - Name: modelID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, +func resolveOpenClawModelID(provider, modelName string) string { + if provider == "custom" || provider == "vllm" { + return normalizeCustomModel(modelName) } -} - -func buildKimiCodingPatchSpec(modelName, modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - return openClawPatchSpec{ - PrimaryModel: modelName, - Provider: "kimi-coding", - APIKey: strings.TrimSpace(apiKey), - BaseURL: baseURL, - APIType: "anthropic-messages", - Model: openClawModelSpec{ - ID: modelID, - Name: "Kimi for Coding", - Reasoning: reasoning, - Input: []string{"text", "image"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, + if parts := strings.SplitN(modelName, "/", 2); len(parts) == 2 { + return parts[1] } + return modelName } -func buildZaiPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - return openClawPatchSpec{ - PrimaryModel: "zai/" + modelID, - Provider: "zai", - APIKey: strings.TrimSpace(apiKey), - BaseURL: baseURL, - APIType: "openai-completions", - Model: openClawModelSpec{ - ID: modelID, - Name: modelID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, +func resolveOpenClawModelName(provider, modelID string) string { + if item, ok := resolveOpenClawCatalogModel(provider, modelID); ok { + return item.Name + } + if provider == "kimi-coding" { + return "Kimi for Coding" } + return modelID } -func buildGenericPatchSpec(provider, modelName, modelID, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec { - providerName := provider - primaryModel := modelName - if provider == "gemini" { - providerName = "google" - primaryModel = "google/" + modelID +func resolveOpenClawModelInput(provider, modelID string) []string { + if item, ok := resolveOpenClawCatalogModel(provider, modelID); ok && len(item.Input) > 0 { + return item.Input } - return openClawPatchSpec{ - PrimaryModel: primaryModel, - Provider: providerName, - APIKey: strings.TrimSpace(apiKey), - BaseURL: baseURL, - APIType: apiType, - Model: openClawModelSpec{ - ID: modelID, - Name: modelID, - Reasoning: reasoning, - Input: []string{"text"}, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - }, + if provider == "kimi-coding" { + return []string{"text", "image"} } + return []string{"text"} } -func providerModels(provider, apiKey, baseURL, api string, authHeader bool, model map[string]interface{}) map[string]interface{} { - providerConfig := map[string]interface{}{ - "apiKey": apiKey, - "baseUrl": baseURL, - "api": api, - "models": []map[string]interface{}{model}, - } - if authHeader { - providerConfig["authHeader"] = true +func resolveOpenClawCatalogModel(provider, modelID string) (Model, bool) { + candidates := []string{provider + "/" + modelID} + if provider == "gemini" { + candidates = append(candidates, "google/"+modelID) } - return map[string]interface{}{ - "mode": "merge", - "providers": map[string]interface{}{ - provider: providerConfig, - }, + for _, candidate := range candidates { + if item, ok := FindModel(provider, candidate); ok { + return item, true + } } + return Model{}, false } func normalizeCustomModel(modelName string) string { diff --git a/agent/app/provider/verify.go b/agent/app/provider/verify.go index 6317951a4f4d..2a8700abc468 100644 --- a/agent/app/provider/verify.go +++ b/agent/app/provider/verify.go @@ -23,7 +23,7 @@ const ( ) func SkipVerification(key string) bool { - switch strings.ToLower(strings.TrimSpace(key)) { + switch key { case "custom", "vllm", "ollama", "kimi-coding": return true default: @@ -62,7 +62,6 @@ func verifyTimeout() time.Duration { } func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest { - provider = strings.ToLower(strings.TrimSpace(provider)) base := strings.TrimRight(strings.TrimSpace(baseURL), "/") headers := map[string]string{} request := VerifyRequest{Method: http.MethodGet, Headers: headers} @@ -117,7 +116,7 @@ func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest { headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey) headers["Content-Type"] = "application/json" request.Body = mustJSON(map[string]interface{}{ - "model": "doubao-seed-2.0-code", + "model": "ark-code-latest", "messages": []map[string]string{{"role": "user", "content": "test"}}, "max_tokens": 1, }) @@ -144,24 +143,16 @@ func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest { }) case "xiaomi": request.Method = http.MethodPost - headers["x-api-key"] = apiKey - headers["anthropic-version"] = "2023-06-01" + headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey) headers["Content-Type"] = "application/json" - if strings.Contains(base, "/v1") { - request.URL = base + "/messages" - } else { - request.URL = base + "/v1/messages" + if !strings.Contains(base, "/v1") { + base = base + "/v1" } + request.URL = base + "/chat/completions" request.Body = mustJSON(map[string]interface{}{ "model": "mimo-v2-flash", "max_tokens": 1, - "messages": []map[string]interface{}{{ - "role": "user", - "content": []map[string]string{{ - "type": "text", - "text": "test", - }}, - }}, + "messages": []map[string]string{{"role": "user", "content": "test"}}, }) case "openrouter": headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey) diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go index 6a7d16fe6cfd..ab5abd110ec8 100644 --- a/agent/app/service/agents.go +++ b/agent/app/service/agents.go @@ -167,7 +167,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) { return nil, buserr.New("ErrAgentAccountNotVerified") } provider = account.Provider - baseURL = strings.TrimSpace(account.BaseURL) + baseURL = account.BaseURL resolvedRuntime, err := resolveOpenclawAccountModelRuntimeByID(account, req.Model) if err != nil { return nil, err @@ -887,7 +887,6 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error { if len(accountModels) == 0 { return nil } - baseURL := resolveAccountBaseURL(account) for _, agent := range agents { confDir := path.Dir(agent.ConfigPath) modelName := strings.TrimSpace(agent.Model) @@ -914,7 +913,7 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error { if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks); err != nil { return err } - agent.BaseURL = baseURL + agent.BaseURL = account.BaseURL agent.APIKey = account.APIKey agent.Provider = account.Provider agent.Model = modelName diff --git a/agent/app/service/agents_utils.go b/agent/app/service/agents_utils.go index b303fad425b5..4981c5b2f582 100644 --- a/agent/app/service/agents_utils.go +++ b/agent/app/service/agents_utils.go @@ -684,6 +684,23 @@ type modelEntry struct { Cost modelCost `json:"cost"` } +func requiresOpenclawProviderModels(provider string) bool { + return provider != "gemini" +} + +func applyOpenclawModelsConfig(conf map[string]interface{}, models *modelsConfig) error { + if models == nil { + delete(conf, "models") + return nil + } + modelsMap, err := structToMap(models) + if err != nil { + return err + } + conf["models"] = modelsMap + return nil +} + type modelCost struct { Input float64 `json:"input"` Output float64 `json:"output"` @@ -784,12 +801,8 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, } conf = initial } else { - if cfg.Models != nil { - modelsMap, err := structToMap(cfg.Models) - if err != nil { - return err - } - conf["models"] = modelsMap + if err := applyOpenclawModelsConfig(conf, cfg.Models); err != nil { + return err } if _, ok := conf["browser"]; !ok { browserMap, err := structToMap(cfg.Browser) @@ -847,7 +860,7 @@ func writeOpenclawConfig(confDir string, account *model.AgentAccount, modelName, } envPath := path.Join(confDir, ".env") lines := []string{fmt.Sprintf("OPENCLAW_GATEWAY_TOKEN=%s", token)} - if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && strings.TrimSpace(account.APIKey) != "" { + if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && account.APIKey != "" { lines = append(lines, fmt.Sprintf("%s=%s", envKey, account.APIKey)) } content := strings.Join(lines, "\n") + "\n" @@ -886,7 +899,7 @@ func resolveOpenclawFallbackModelsFromPool(account *model.AgentAccount, accountM } continue } - resolvedPrimary, _, _, _, err := buildOpenclawCatalogModel(account, accountModel) + resolvedPrimary, _, _, _, err := buildOpenclawAccountModelConfig(account, accountModel) if err != nil { return nil, err } @@ -961,7 +974,7 @@ func openclawModelRefMap(conf map[string]interface{}) map[string]interface{} { func buildOpenclawResolvedModelIDMap(account *model.AgentAccount, accountModels []dto.AgentAccountModel) (map[string]string, error) { result := make(map[string]string, len(accountModels)) for _, item := range accountModels { - resolvedPrimary, _, _, _, err := buildOpenclawCatalogModel(account, item) + resolvedPrimary, _, _, _, err := buildOpenclawAccountModelConfig(account, item) if err != nil { return nil, err } @@ -1009,7 +1022,7 @@ func buildOpenclawModelsFromAccount(account *model.AgentAccount, selectedModel s primaryModel := "" defaultsModels := make(map[string]map[string]interface{}, len(accountModels)) for _, item := range accountModels { - resolvedPrimary, entry, key, baseCfg, err := buildOpenclawCatalogModel(account, item) + resolvedPrimary, entry, key, baseCfg, err := buildOpenclawAccountModelConfig(account, item) if err != nil { return "", nil, nil, err } @@ -1029,6 +1042,9 @@ func buildOpenclawModelsFromAccount(account *model.AgentAccount, selectedModel s if primaryModel == "" { return "", nil, nil, buserr.New("ErrAgentModelNotInAccount") } + if !requiresOpenclawProviderModels(account.Provider) { + return primaryModel, defaultsModels, nil, nil + } providerCfg.Models = entries return primaryModel, defaultsModels, &modelsConfig{ Mode: "merge", @@ -1038,25 +1054,33 @@ func buildOpenclawModelsFromAccount(account *model.AgentAccount, selectedModel s }, nil } -func buildOpenclawCatalogModel(account *model.AgentAccount, model dto.AgentAccountModel) (string, modelEntry, string, modelProvider, error) { - primaryModel, inferredEntry, providerKey, providerCfg, err := inferOpenclawCatalogModel(account, model.ID, model.Reasoning, model.MaxTokens, model.ContextWindow) +func buildOpenclawAccountModelConfig(account *model.AgentAccount, model dto.AgentAccountModel) (string, modelEntry, string, modelProvider, error) { + providerPatch, err := providercatalog.BuildOpenClawProviderPatch(account.Provider, model.ID, account.APIType, account.BaseURL, account.APIKey) if err != nil { return "", modelEntry{}, "", modelProvider{}, err } - if strings.TrimSpace(model.Name) != "" { - inferredEntry.Name = strings.TrimSpace(model.Name) - } - if len(model.Input) > 0 { - inferredEntry.Input = sanitizeAgentAccountModelInputs(model.Input) - } - inferredEntry.Reasoning = model.Reasoning - if model.ContextWindow > 0 { - inferredEntry.ContextWindow = model.ContextWindow + return providerPatch.PrimaryModel, buildOpenclawModelEntry(providerPatch.ModelID, model), providerPatch.ProviderKey, modelProvider{ + ApiKey: providerPatch.APIKey, + BaseUrl: providerPatch.BaseURL, + Api: providerPatch.APIType, + AuthHeader: providerPatch.AuthHeader, + }, nil +} + +func buildOpenclawModelEntry(modelID string, model dto.AgentAccountModel) modelEntry { + name := strings.TrimSpace(model.Name) + if name == "" { + name = strings.TrimSpace(modelID) } - if model.MaxTokens > 0 { - inferredEntry.MaxTokens = model.MaxTokens + return modelEntry{ + ID: strings.TrimSpace(modelID), + Name: name, + Reasoning: model.Reasoning, + Input: sanitizeAgentAccountModelInputs(model.Input), + ContextWindow: model.ContextWindow, + MaxTokens: model.MaxTokens, + Cost: modelCost{}, } - return primaryModel, inferredEntry, providerKey, providerCfg, nil } type openclawAccountModelRuntime struct { @@ -1068,22 +1092,16 @@ type openclawAccountModelRuntime struct { } func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.AgentAccountModel) (openclawAccountModelRuntime, error) { - apiType, maxTokens, contextWindow := providercatalog.ResolveRuntimeParams( - account.Provider, - account.APIType, - model.MaxTokens, - model.ContextWindow, - ) - primaryModel, _, _, _, err := buildOpenclawCatalogModel(account, model) + primaryModel, _, _, _, err := buildOpenclawAccountModelConfig(account, model) if err != nil { return openclawAccountModelRuntime{}, err } return openclawAccountModelRuntime{ StoredModel: model.ID, PrimaryModel: primaryModel, - APIType: apiType, - MaxTokens: maxTokens, - ContextWindow: contextWindow, + APIType: account.APIType, + MaxTokens: model.MaxTokens, + ContextWindow: model.ContextWindow, }, nil } @@ -1099,44 +1117,6 @@ func resolveOpenclawAccountModelRuntimeByID(account *model.AgentAccount, modelID return buildOpenclawAccountModelRuntime(account, selectedAccountModel) } -func inferOpenclawCatalogModel(account *model.AgentAccount, modelID string, reasoning bool, maxTokens, contextWindow int) (string, modelEntry, string, modelProvider, error) { - baseURL := resolveAccountBaseURL(account) - resolvedAPIType, resolvedMaxTokens, resolvedContextWindow := providercatalog.ResolveRuntimeParams(account.Provider, account.APIType, maxTokens, contextWindow) - patch, err := providercatalog.BuildOpenClawPatch(account.Provider, modelID, resolvedAPIType, reasoning, resolvedMaxTokens, resolvedContextWindow, baseURL, account.APIKey) - if err != nil { - return "", modelEntry{}, "", modelProvider{}, err - } - if patch.Models == nil { - return "", modelEntry{}, "", modelProvider{}, fmt.Errorf("models patch is required") - } - modelsCfg, err := mapToModelsConfig(patch.Models) - if err != nil { - return "", modelEntry{}, "", modelProvider{}, err - } - for key, providerCfg := range modelsCfg.Providers { - if len(providerCfg.Models) == 0 { - continue - } - return patch.PrimaryModel, providerCfg.Models[0], key, modelProvider{ - ApiKey: providerCfg.ApiKey, - BaseUrl: providerCfg.BaseUrl, - Api: providerCfg.Api, - AuthHeader: providerCfg.AuthHeader, - }, nil - } - return "", modelEntry{}, "", modelProvider{}, fmt.Errorf("models patch is invalid") -} - -func resolveAccountBaseURL(account *model.AgentAccount) string { - baseURL := strings.TrimSpace(account.BaseURL) - if baseURL == "" { - if defaultURL, ok := providercatalog.DefaultBaseURL(account.Provider); ok { - baseURL = defaultURL - } - } - return baseURL -} - func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto.AgentAccountModel) ([]dto.AgentAccountModel, error) { if account == nil { return nil, fmt.Errorf("account is required") @@ -1331,76 +1311,37 @@ func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAcco if modelID == "" { return dto.AgentAccountModel{}, fmt.Errorf("model is required") } - inferredReasoning := model.Reasoning - if !model.Reasoning && model.Name == "" && model.MaxTokens == 0 && model.ContextWindow == 0 && len(model.Input) == 0 { - if catalogModel, ok := providercatalog.FindModel(account.Provider, modelID); ok { - inferredReasoning = catalogModel.Reasoning - } - } - primaryModel, inferredEntry, _, _, err := inferOpenclawCatalogModel(account, modelID, inferredReasoning, model.MaxTokens, model.ContextWindow) - if err != nil { - return dto.AgentAccountModel{}, err - } name := strings.TrimSpace(model.Name) if name == "" { - name = strings.TrimSpace(inferredEntry.Name) - } - reasoning := model.Reasoning - if !model.Reasoning && model.Name == "" && model.MaxTokens == 0 && model.ContextWindow == 0 && len(model.Input) == 0 { - reasoning = inferredEntry.Reasoning + name = modelID } inputs := sanitizeAgentAccountModelInputs(model.Input) - if len(inputs) == 0 { - inputs = sanitizeAgentAccountModelInputs(inferredEntry.Input) - } - contextWindow := model.ContextWindow - if contextWindow <= 0 { - contextWindow = inferredEntry.ContextWindow - } - maxTokens := model.MaxTokens - if maxTokens <= 0 { - maxTokens = inferredEntry.MaxTokens - } return dto.AgentAccountModel{ - ID: normalizeAgentAccountModelID(account.Provider, primaryModel, modelID), + ID: normalizeAgentAccountModelID(account.Provider, modelID), Name: name, - ContextWindow: contextWindow, - MaxTokens: maxTokens, - Reasoning: reasoning, + ContextWindow: model.ContextWindow, + MaxTokens: model.MaxTokens, + Reasoning: model.Reasoning, Input: inputs, }, nil } -func normalizeAgentAccountModelID(provider, primaryModel, requestedID string) string { +func normalizeAgentAccountModelID(provider, requestedID string) string { switch provider { case "custom", "vllm": - target := requestedID - if strings.TrimSpace(target) == "" { - target = primaryModel - } - return normalizeCustomModel(target) + return normalizeCustomModel(requestedID) case "ollama": - target := strings.TrimSpace(primaryModel) - if strings.HasPrefix(target, "ollama/") { - return target - } - target = strings.TrimSpace(requestedID) + target := strings.TrimSpace(requestedID) if strings.HasPrefix(target, "ollama/") { return target } target = strings.TrimLeft(strings.TrimSpace(target), "/") - if target == "" { - target = strings.TrimLeft(strings.TrimSpace(primaryModel), "/") - } if target == "" { return "" } return "ollama/" + target default: target := strings.TrimSpace(requestedID) - if target == "" { - target = strings.TrimSpace(primaryModel) - } if target == "" { return "" } @@ -1590,18 +1531,6 @@ func structToMap(value interface{}) (map[string]interface{}, error) { return result, nil } -func mapToModelsConfig(value map[string]interface{}) (*modelsConfig, error) { - payload, err := json.Marshal(value) - if err != nil { - return nil, err - } - result := &modelsConfig{} - if err := json.Unmarshal(payload, result); err != nil { - return nil, err - } - return result, nil -} - func readInstallEnv(envStr string) map[string]interface{} { if strings.TrimSpace(envStr) == "" { return nil diff --git a/frontend/src/views/ai/agents/model/add/index.vue b/frontend/src/views/ai/agents/model/add/index.vue index 5d5decbdc8a7..eff419720a6c 100644 --- a/frontend/src/views/ai/agents/model/add/index.vue +++ b/frontend/src/views/ai/agents/model/add/index.vue @@ -119,7 +119,7 @@ const headerTitle = computed(() => const showInitialModel = computed(() => !form.id && initialModelProviders.includes(form.provider)); const apiTypeOptions = computed(() => { - if (form.provider === 'minimax' || form.provider === 'xiaomi') { + if (form.provider === 'minimax' || form.provider === 'anthropic' || form.provider === 'kimi-coding') { return ['anthropic-messages']; } if (form.provider === 'custom' || form.provider === 'vllm') { @@ -186,12 +186,18 @@ const resetInitialModel = () => { }; const normalizeProviderAPIType = (provider: string, apiType?: string) => { - if (provider === 'minimax' || provider === 'xiaomi') { + if (provider === 'minimax' || provider === 'anthropic' || provider === 'kimi-coding') { return 'anthropic-messages'; } if (provider === 'ollama') { return 'openai-responses'; } + if (provider === 'openai') { + if (apiType === 'openai-completions' || apiType === 'openai-responses') { + return apiType; + } + return 'openai-responses'; + } if (apiType) { return apiType; } @@ -325,14 +331,14 @@ const loadProviders = async () => { const handleProviderChange = () => { if (form.provider === 'custom' || form.provider === 'vllm') { form.baseURL = ''; - form.apiType = normalizeProviderAPIType(form.provider, form.apiType); } else if (form.provider === 'ollama') { form.baseURL = ''; - form.apiType = normalizeProviderAPIType(form.provider, form.apiType); } else { form.baseURL = providerBaseURL.value[form.provider] || ''; - form.apiType = normalizeProviderAPIType(form.provider, form.apiType); } + form.apiType = form.id + ? normalizeProviderAPIType(form.provider, form.apiType) + : normalizeProviderAPIType(form.provider); if (!form.id) { resetInitialModel(); }