mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
feat: optimize the validation logic for model accounts (#12251)
This commit is contained in:
@@ -105,7 +105,7 @@ type AgentAccountCreateReq struct {
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Models []AgentAccountModel `json:"models"`
|
||||
APIType string `json:"apiType"`
|
||||
APIType string `json:"apiType" validate:"required"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ type AgentAccountUpdateReq struct {
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
APIType string `json:"apiType"`
|
||||
APIType string `json:"apiType" validate:"required"`
|
||||
Remark string `json:"remark"`
|
||||
SyncAgents bool `json:"syncAgents"`
|
||||
}
|
||||
|
||||
+195
-76
@@ -13,12 +13,20 @@ type Model struct {
|
||||
Input []string
|
||||
}
|
||||
|
||||
type RuntimeDefault struct {
|
||||
APIType string
|
||||
ContextWindow int
|
||||
MaxTokens int
|
||||
Input []string
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
Key string
|
||||
DisplayName string
|
||||
Sort uint
|
||||
DefaultBaseURL string
|
||||
EnvKey string
|
||||
Default RuntimeDefault
|
||||
Models []Model
|
||||
}
|
||||
|
||||
@@ -29,12 +37,24 @@ var catalog = map[string]Meta{
|
||||
Sort: 10,
|
||||
DefaultBaseURL: "",
|
||||
EnvKey: "CUSTOM_API_KEY",
|
||||
Models: []Model{},
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{},
|
||||
},
|
||||
"ollama": {
|
||||
Key: "ollama",
|
||||
DisplayName: "Ollama",
|
||||
Sort: 15,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-responses",
|
||||
ContextWindow: 160000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
},
|
||||
"vllm": {
|
||||
Key: "vllm",
|
||||
@@ -42,7 +62,13 @@ var catalog = map[string]Meta{
|
||||
Sort: 20,
|
||||
DefaultBaseURL: "",
|
||||
EnvKey: "VLLM_API_KEY",
|
||||
Models: []Model{},
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{},
|
||||
},
|
||||
"deepseek": {
|
||||
Key: "deepseek",
|
||||
@@ -50,10 +76,16 @@ var catalog = map[string]Meta{
|
||||
Sort: 25,
|
||||
DefaultBaseURL: "https://api.deepseek.com/v1",
|
||||
EnvKey: "DEEPSEEK_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "deepseek/deepseek-chat", Name: "DeepSeek Chat"},
|
||||
{ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner"},
|
||||
{ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B"},
|
||||
{ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner", Reasoning: true},
|
||||
{ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"bailian-coding-plan": {
|
||||
@@ -62,15 +94,21 @@ var catalog = map[string]Meta{
|
||||
Sort: 30,
|
||||
DefaultBaseURL: "https://coding.dashscope.aliyuncs.com/v1",
|
||||
EnvKey: "QWEN_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "bailian-coding-plan/qwen3.5-plus", Name: "Qwen3.5-Plus"},
|
||||
{ID: "bailian-coding-plan/qwen3-max", Name: "Qwen3-Max"},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-next", Name: "Qwen3-Coder-Next"},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-plus", Name: "Qwen3-Coder-Plus"},
|
||||
{ID: "bailian-coding-plan/minimax-m2.5", Name: "MiniMax M2.5"},
|
||||
{ID: "bailian-coding-plan/glm-5", Name: "GLM-5"},
|
||||
{ID: "bailian-coding-plan/kimi-k2.5", Name: "Kimi-k2.5"},
|
||||
{ID: "bailian-coding-plan/glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "bailian-coding-plan/qwen3.5-plus", Name: "Qwen3.5-Plus", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-max", Name: "Qwen3-Max", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-next", Name: "Qwen3-Coder-Next", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-plus", Name: "Qwen3-Coder-Plus", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/minimax-m2.5", Name: "MiniMax M2.5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/glm-5", Name: "GLM-5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/kimi-k2.5", Name: "Kimi-k2.5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"ark-coding-plan": {
|
||||
@@ -79,13 +117,19 @@ var catalog = map[string]Meta{
|
||||
Sort: 35,
|
||||
DefaultBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
||||
EnvKey: "ARK_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
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"},
|
||||
{ID: "ark-coding-plan/glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "ark-coding-plan/deepseek-v3.2", Name: "DeepSeek-V3.2"},
|
||||
{ID: "ark-coding-plan/kimi-k2-thinking", Name: "Kimi-K2-thinking"},
|
||||
{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},
|
||||
},
|
||||
},
|
||||
"zai": {
|
||||
@@ -94,11 +138,17 @@ var catalog = map[string]Meta{
|
||||
Sort: 40,
|
||||
DefaultBaseURL: "https://open.bigmodel.cn/api/paas/v4",
|
||||
EnvKey: "ZAI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 204800,
|
||||
MaxTokens: 131072,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "zai/glm-5", Name: "GLM-5"},
|
||||
{ID: "zai/glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "zai/glm-4.7-flash", Name: "GLM-4.7-Flash"},
|
||||
{ID: "zai/glm-4.7-flashx", Name: "GLM-4.7-FlashX"},
|
||||
{ID: "zai/glm-5", Name: "GLM-5", Reasoning: true},
|
||||
{ID: "zai/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
||||
{ID: "zai/glm-4.7-flash", Name: "GLM-4.7-Flash", Reasoning: true},
|
||||
{ID: "zai/glm-4.7-flashx", Name: "GLM-4.7-FlashX", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"minimax": {
|
||||
@@ -107,10 +157,16 @@ var catalog = map[string]Meta{
|
||||
Sort: 45,
|
||||
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
|
||||
EnvKey: "MINIMAX_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "anthropic-messages",
|
||||
ContextWindow: 200000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "minimax/MiniMax-M2.7", Name: "MiniMax M2.7"},
|
||||
{ID: "minimax/MiniMax-M2.7-highspeed", Name: "MiniMax M2.7 highspeed"},
|
||||
{ID: "minimax/MiniMax-M2.5", Name: "MiniMax M2.5"},
|
||||
{ID: "minimax/MiniMax-M2.5", Name: "MiniMax M2.5", Reasoning: true},
|
||||
{ID: "minimax/MiniMax-M2.5-highspeed", Name: "MiniMax M2.5 highspeed"},
|
||||
},
|
||||
},
|
||||
@@ -120,11 +176,17 @@ var catalog = map[string]Meta{
|
||||
Sort: 46,
|
||||
DefaultBaseURL: "https://api.xiaomimimo.com/anthropic",
|
||||
EnvKey: "XIAOMI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "anthropic-messages",
|
||||
ContextWindow: 262144,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "xiaomi/mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro", ContextWindow: 262144, MaxTokens: 8192, Reasoning: false, Input: []string{"text"}},
|
||||
{ID: "xiaomi/mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni", ContextWindow: 262144, MaxTokens: 8192, Reasoning: false, Input: []string{"text"}},
|
||||
{ID: "xiaomi/mimo-v2-tts", Name: "Xiaomi MiMo V2 TTS", ContextWindow: 262144, MaxTokens: 8192, Reasoning: false, Input: []string{"text"}},
|
||||
{ID: "xiaomi/mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash", ContextWindow: 262144, MaxTokens: 8192, Reasoning: false, Input: []string{"text"}},
|
||||
{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"},
|
||||
},
|
||||
},
|
||||
"kimi": {
|
||||
@@ -133,10 +195,16 @@ var catalog = map[string]Meta{
|
||||
Sort: 50,
|
||||
DefaultBaseURL: "https://api.moonshot.cn/v1",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5"},
|
||||
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
||||
{ID: "kimi/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
||||
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"kimi-coding": {
|
||||
@@ -145,8 +213,14 @@ var catalog = map[string]Meta{
|
||||
Sort: 51,
|
||||
DefaultBaseURL: "https://api.kimi.com/coding/",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 262144,
|
||||
MaxTokens: 32768,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5"},
|
||||
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"openai": {
|
||||
@@ -155,13 +229,18 @@ var catalog = map[string]Meta{
|
||||
Sort: 55,
|
||||
DefaultBaseURL: "https://api.openai.com/v1",
|
||||
EnvKey: "OPENAI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "openai/codex-mini-latest", Name: "Codex Mini"},
|
||||
{ID: "openai/gpt-4.1", Name: "GPT-4.1"},
|
||||
{ID: "openai/gpt-4o", Name: "GPT-4o"},
|
||||
{ID: "openai/gpt-4o-mini", Name: "GPT-4o Mini"},
|
||||
{ID: "openai/gpt-5", Name: "GPT-5"},
|
||||
{ID: "openai/gpt-5-mini", Name: "GPT-5 Mini"},
|
||||
{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},
|
||||
},
|
||||
},
|
||||
"openrouter": {
|
||||
@@ -170,6 +249,12 @@ var catalog = map[string]Meta{
|
||||
Sort: 56,
|
||||
DefaultBaseURL: "https://openrouter.ai/api/v1",
|
||||
EnvKey: "OPENROUTER_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "openrouter/free", Name: "openrouter/free"},
|
||||
{ID: "openrouter/auto", Name: "openrouter/auto"},
|
||||
@@ -181,12 +266,18 @@ var catalog = map[string]Meta{
|
||||
Sort: 60,
|
||||
DefaultBaseURL: "https://api.anthropic.com",
|
||||
EnvKey: "ANTHROPIC_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
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"},
|
||||
{ID: "anthropic/claude-opus-4-1", Name: "Claude Opus 4.1"},
|
||||
{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},
|
||||
},
|
||||
},
|
||||
"gemini": {
|
||||
@@ -195,10 +286,16 @@ var catalog = map[string]Meta{
|
||||
Sort: 65,
|
||||
DefaultBaseURL: "https://generativelanguage.googleapis.com",
|
||||
EnvKey: "GEMINI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview"},
|
||||
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview", Reasoning: true},
|
||||
{ID: "google/gemini-flash-latest", Name: "Gemini Flash Latest"},
|
||||
{ID: "google/gemini-3-pro-preview", Name: "Gemini 3 Pro Preview"},
|
||||
{ID: "google/gemini-3-pro-preview", Name: "Gemini 3 Pro Preview", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"moonshot": {
|
||||
@@ -207,10 +304,16 @@ var catalog = map[string]Meta{
|
||||
Sort: 70,
|
||||
DefaultBaseURL: "https://api.moonshot.ai/v1",
|
||||
EnvKey: "MOONSHOT_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5"},
|
||||
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
||||
{ID: "moonshot/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
||||
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -258,64 +361,80 @@ func DisplayName(key string) string {
|
||||
return meta.DisplayName
|
||||
}
|
||||
|
||||
func FindModel(key, modelID string) (Model, bool) {
|
||||
meta, ok := Get(key)
|
||||
if !ok {
|
||||
return Model{}, false
|
||||
}
|
||||
for _, item := range meta.Models {
|
||||
if item.ID == modelID {
|
||||
return item, true
|
||||
}
|
||||
}
|
||||
return Model{}, false
|
||||
}
|
||||
|
||||
func cloneMeta(meta Meta) Meta {
|
||||
clone := meta
|
||||
if len(meta.Default.Input) > 0 {
|
||||
clone.Default.Input = make([]string, len(meta.Default.Input))
|
||||
copy(clone.Default.Input, meta.Default.Input)
|
||||
}
|
||||
if len(meta.Models) > 0 {
|
||||
clone.Models = make([]Model, len(meta.Models))
|
||||
for i, item := range meta.Models {
|
||||
clone.Models[i] = normalizeModel(meta.Key, item)
|
||||
clone.Models[i] = normalizeModel(meta, item)
|
||||
}
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func normalizeModel(provider string, model Model) Model {
|
||||
func normalizeModel(meta Meta, model Model) Model {
|
||||
clone := model
|
||||
clone.ID = strings.TrimSpace(clone.ID)
|
||||
clone.Name = strings.TrimSpace(clone.Name)
|
||||
if clone.Name == "" {
|
||||
clone.Name = clone.ID
|
||||
}
|
||||
if clone.MaxTokens <= 0 || clone.ContextWindow <= 0 {
|
||||
resolvedMaxTokens, resolvedContextWindow := catalogRuntimeDefaults(strings.ToLower(strings.TrimSpace(provider)))
|
||||
if clone.MaxTokens <= 0 {
|
||||
clone.MaxTokens = resolvedMaxTokens
|
||||
}
|
||||
if clone.ContextWindow <= 0 {
|
||||
clone.ContextWindow = resolvedContextWindow
|
||||
}
|
||||
if clone.MaxTokens <= 0 {
|
||||
clone.MaxTokens = meta.Default.MaxTokens
|
||||
}
|
||||
if len(clone.Input) == 0 {
|
||||
clone.Input = defaultModelInputs(provider)
|
||||
if clone.ContextWindow <= 0 {
|
||||
clone.ContextWindow = meta.Default.ContextWindow
|
||||
}
|
||||
if !clone.Reasoning {
|
||||
clone.Reasoning = isReasoningModel(clone.ID)
|
||||
if len(clone.Input) == 0 && len(meta.Default.Input) > 0 {
|
||||
clone.Input = make([]string, len(meta.Default.Input))
|
||||
copy(clone.Input, meta.Default.Input)
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func defaultModelInputs(provider string) []string {
|
||||
switch strings.ToLower(strings.TrimSpace(provider)) {
|
||||
case "kimi-coding":
|
||||
return []string{"text", "image"}
|
||||
default:
|
||||
return []string{"text"}
|
||||
func ResolveRuntimeParams(provider, apiType string, maxTokens, contextWindow int) (string, int, int) {
|
||||
defaultAPIType := "openai-completions"
|
||||
defaultMaxTokens := 8192
|
||||
defaultContextWindow := 256000
|
||||
if meta, ok := Get(provider); ok {
|
||||
if meta.Default.APIType != "" {
|
||||
defaultAPIType = meta.Default.APIType
|
||||
}
|
||||
if meta.Default.MaxTokens > 0 {
|
||||
defaultMaxTokens = meta.Default.MaxTokens
|
||||
}
|
||||
if meta.Default.ContextWindow > 0 {
|
||||
defaultContextWindow = meta.Default.ContextWindow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func catalogRuntimeDefaults(provider string) (int, int) {
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
return 8192, 128000
|
||||
case "zai":
|
||||
return 131072, 204800
|
||||
case "openrouter":
|
||||
return 8192, 128000
|
||||
case "minimax", "kimi-coding":
|
||||
return 8192, 200000
|
||||
case "custom", "vllm":
|
||||
return 8192, 128000
|
||||
default:
|
||||
return 8192, 256000
|
||||
resolvedAPI := apiType
|
||||
if strings.TrimSpace(apiType) == "" {
|
||||
resolvedAPI = defaultAPIType
|
||||
}
|
||||
resolvedMaxTokens := defaultMaxTokens
|
||||
resolvedContextWindow := defaultContextWindow
|
||||
if maxTokens > 0 {
|
||||
resolvedMaxTokens = maxTokens
|
||||
}
|
||||
if contextWindow > 0 {
|
||||
resolvedContextWindow = contextWindow
|
||||
}
|
||||
return resolvedAPI, resolvedMaxTokens, resolvedContextWindow
|
||||
}
|
||||
|
||||
+239
-265
@@ -10,288 +10,322 @@ type OpenClawPatch struct {
|
||||
Models map[string]interface{}
|
||||
}
|
||||
|
||||
func BuildOpenClawPatch(provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey string) (*OpenClawPatch, error) {
|
||||
type openClawModelSpec struct {
|
||||
ID string
|
||||
Name string
|
||||
Reasoning bool
|
||||
Input []string
|
||||
ContextWindow int
|
||||
MaxTokens int
|
||||
}
|
||||
|
||||
type openClawPatchSpec struct {
|
||||
PrimaryModel string
|
||||
Provider 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))
|
||||
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
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
return buildDeepseekPatch(modelName, baseURL, apiKey), nil
|
||||
spec = buildDeepseekPatchSpec(modelName, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "gemini":
|
||||
return buildGenericPatch(provider, modelName, modelID, apiType, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildGenericPatchSpec(provider, modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "moonshot", "kimi":
|
||||
return buildMoonshotPatch(provider, modelName, modelID, baseURL, apiKey), nil
|
||||
spec = buildMoonshotPatchSpec(provider, modelName, modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "bailian-coding-plan":
|
||||
return buildBailianPatch(modelID, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildBailianPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "ark-coding-plan":
|
||||
return buildArkPatch(modelID, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildArkPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "minimax":
|
||||
return buildMiniMaxPatch(modelID, baseURL, apiKey), nil
|
||||
spec = buildMiniMaxPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "xiaomi":
|
||||
return buildXiaomiPatch(modelID, baseURL, apiKey), nil
|
||||
spec = buildXiaomiPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "custom", "vllm":
|
||||
return buildCustomPatch(provider, modelName, apiType, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildCustomPatchSpec(provider, modelName, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "ollama":
|
||||
return buildOllamaPatch(modelName, modelID, apiType, baseURL), nil
|
||||
spec = buildOllamaPatchSpec(modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL)
|
||||
case "kimi-coding":
|
||||
return buildKimiCodingPatch(modelName, modelID, baseURL, apiKey), nil
|
||||
spec = buildKimiCodingPatchSpec(modelName, modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "zai":
|
||||
return buildZaiPatch(modelID, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildZaiPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
default:
|
||||
return buildGenericPatch(provider, modelName, modelID, apiType, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildGenericPatchSpec(provider, modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
}
|
||||
return buildOpenClawPatch(spec), nil
|
||||
}
|
||||
|
||||
func buildDeepseekPatch(modelName, baseURL, apiKey string) *OpenClawPatch {
|
||||
func buildOpenClawPatch(spec openClawPatchSpec) *OpenClawPatch {
|
||||
return &OpenClawPatch{
|
||||
PrimaryModel: modelName,
|
||||
Models: providerModels("deepseek", strings.TrimSpace(apiKey), firstNonEmpty(strings.TrimSpace(baseURL), "https://api.deepseek.com/v1"), "openai-completions", map[string]interface{}{
|
||||
"id": "deepseek-chat",
|
||||
"name": "DeepSeek Chat",
|
||||
"reasoning": false,
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 128000,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
PrimaryModel: spec.PrimaryModel,
|
||||
Models: providerModels(
|
||||
spec.Provider,
|
||||
spec.APIKey,
|
||||
spec.BaseURL,
|
||||
spec.APIType,
|
||||
spec.AuthHeader,
|
||||
buildOpenClawModel(spec.Model),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func buildMoonshotPatch(provider, modelName, modelID, baseURL, apiKey string) *OpenClawPatch {
|
||||
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
|
||||
primaryModel := modelName
|
||||
if provider == "kimi" {
|
||||
configProvider = "moonshot"
|
||||
primaryModel = "moonshot/" + modelID
|
||||
}
|
||||
return &OpenClawPatch{
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: primaryModel,
|
||||
Models: providerModels(configProvider, strings.TrimSpace(apiKey), withCatalogDefault(provider, baseURL), "openai-completions", map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": modelID,
|
||||
"reasoning": strings.Contains(strings.ToLower(modelID), "thinking"),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 256000,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func buildBailianPatch(modelID string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
normalizedID := normalizeBailianCodingPlanModelID(modelID)
|
||||
return &OpenClawPatch{
|
||||
PrimaryModel: "bailian-coding-plan/" + bailianPrimaryModelID(normalizedID),
|
||||
Models: providerModels("bailian-coding-plan", strings.TrimSpace(apiKey), withCatalogDefault("bailian-coding-plan", baseURL), "openai-completions", map[string]interface{}{
|
||||
"id": normalizedID,
|
||||
"name": normalizedID,
|
||||
"reasoning": isReasoningModel(normalizedID),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 256000),
|
||||
"maxTokens": fallbackInt(maxTokens, 8192),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func buildArkPatch(modelID string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
normalizedID := normalizeArkCodingPlanModelID(modelID)
|
||||
return &OpenClawPatch{
|
||||
PrimaryModel: "ark-coding-plan/" + normalizedID,
|
||||
Models: providerModels("ark-coding-plan", strings.TrimSpace(apiKey), withCatalogDefault("ark-coding-plan", baseURL), "openai-completions", map[string]interface{}{
|
||||
"id": normalizedID,
|
||||
"name": normalizedID,
|
||||
"reasoning": isReasoningModel(normalizedID),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 256000),
|
||||
"maxTokens": fallbackInt(maxTokens, 8192),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func buildMiniMaxPatch(modelID, baseURL, apiKey string) *OpenClawPatch {
|
||||
normalizedID := normalizeMiniMaxModelID(modelID)
|
||||
return &OpenClawPatch{
|
||||
PrimaryModel: "minimax/" + normalizedID,
|
||||
Models: map[string]interface{}{
|
||||
"mode": "merge",
|
||||
"providers": map[string]interface{}{
|
||||
"minimax": map[string]interface{}{
|
||||
"apiKey": strings.TrimSpace(apiKey),
|
||||
"baseUrl": firstNonEmpty(strings.TrimSpace(baseURL), "https://api.minimaxi.com/anthropic"),
|
||||
"api": "anthropic-messages",
|
||||
"authHeader": true,
|
||||
"models": []map[string]interface{}{{
|
||||
"id": normalizedID,
|
||||
"name": strings.ReplaceAll(normalizedID, "-", " "),
|
||||
"reasoning": false,
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 200000,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}},
|
||||
},
|
||||
},
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildXiaomiPatch(modelID, baseURL, apiKey string) *OpenClawPatch {
|
||||
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),
|
||||
BaseURL: baseURL,
|
||||
APIType: "openai-completions",
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildXiaomiPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
normalizedID := strings.TrimSpace(modelID)
|
||||
return &OpenClawPatch{
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: "xiaomi/" + normalizedID,
|
||||
Models: providerModels("xiaomi", strings.TrimSpace(apiKey), firstNonEmpty(strings.TrimSpace(baseURL), "https://api.xiaomimimo.com/anthropic"), "anthropic-messages", map[string]interface{}{
|
||||
"id": normalizedID,
|
||||
"name": normalizedID,
|
||||
"reasoning": false,
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildCustomPatch(provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
func buildCustomPatchSpec(provider, modelName, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
customModelID := normalizeCustomModel(modelName)
|
||||
return &OpenClawPatch{
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: provider + "/" + customModelID,
|
||||
Models: providerModels(provider, strings.TrimSpace(apiKey), strings.TrimSpace(baseURL), apiType, map[string]interface{}{
|
||||
"id": customModelID,
|
||||
"name": customModelID,
|
||||
"reasoning": isReasoningModel(customModelID),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 128000),
|
||||
"maxTokens": fallbackInt(maxTokens, 8192),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildOllamaPatch(modelName, modelID, apiType, baseURL string) *OpenClawPatch {
|
||||
api := normalizeAPIType(apiType)
|
||||
if api != "openai-completions" && api != "openai-responses" {
|
||||
api = "openai-responses"
|
||||
}
|
||||
return &OpenClawPatch{
|
||||
func buildOllamaPatchSpec(modelName, modelID, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: modelName,
|
||||
Models: providerModels("ollama", "ollama", strings.TrimSpace(baseURL), api, map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": modelID,
|
||||
"reasoning": api != "openai-completions",
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 160000,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
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 buildKimiCodingPatch(modelName, modelID, baseURL, apiKey string) *OpenClawPatch {
|
||||
return &OpenClawPatch{
|
||||
func buildKimiCodingPatchSpec(modelName, modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: modelName,
|
||||
Models: providerModels("kimi-coding", strings.TrimSpace(apiKey), withCatalogDefault("kimi-coding", baseURL), "anthropic-messages", map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": "Kimi for Coding",
|
||||
"reasoning": true,
|
||||
"input": []string{"text", "image"},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 32768,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildZaiPatch(modelID string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
return &OpenClawPatch{
|
||||
func buildZaiPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: "zai/" + modelID,
|
||||
Models: providerModels("zai", strings.TrimSpace(apiKey), withCatalogDefault("zai", baseURL), "openai-completions", map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": zaiModelDisplayName(modelID),
|
||||
"reasoning": modelID == "glm-5",
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 204800),
|
||||
"maxTokens": fallbackInt(maxTokens, 131072),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
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 buildGenericPatch(provider, modelName, modelID, apiType string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
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
|
||||
}
|
||||
return &OpenClawPatch{
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: primaryModel,
|
||||
Models: providerModels(providerName, strings.TrimSpace(apiKey), withCatalogDefault(provider, baseURL), normalizeAPIType(apiType), map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": modelID,
|
||||
"reasoning": isReasoningModel(modelID),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 256000),
|
||||
"maxTokens": fallbackInt(maxTokens, 8192),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func providerModels(provider, apiKey, baseURL, api string, model map[string]interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"mode": "merge",
|
||||
"providers": map[string]interface{}{
|
||||
provider: map[string]interface{}{
|
||||
"apiKey": apiKey,
|
||||
"baseUrl": baseURL,
|
||||
"api": api,
|
||||
"models": []map[string]interface{}{model},
|
||||
},
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func withCatalogDefault(provider, baseURL string) string {
|
||||
if strings.TrimSpace(baseURL) != "" {
|
||||
return strings.TrimSpace(baseURL)
|
||||
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 defaultURL, ok := DefaultBaseURL(provider); ok {
|
||||
return defaultURL
|
||||
if authHeader {
|
||||
providerConfig["authHeader"] = true
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"mode": "merge",
|
||||
"providers": map[string]interface{}{
|
||||
provider: providerConfig,
|
||||
},
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func fallbackInt(value, fallback int) int {
|
||||
if value > 0 {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func normalizeAPIType(apiType string) string {
|
||||
trim := strings.ToLower(strings.TrimSpace(apiType))
|
||||
if trim == "" {
|
||||
return "openai-completions"
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func normalizeCustomModel(modelName string) string {
|
||||
@@ -302,63 +336,3 @@ func normalizeCustomModel(modelName string) string {
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func normalizeBailianCodingPlanModelID(modelID string) string {
|
||||
trim := strings.TrimSpace(modelID)
|
||||
switch strings.ToLower(trim) {
|
||||
case "minimax-m2.5", "minimax m2.5", "minimax/minimax-m2.5", "minimax/minimax m2.5":
|
||||
return "MiniMax/MiniMax-M2.5"
|
||||
default:
|
||||
return trim
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeArkCodingPlanModelID(modelID string) string {
|
||||
return strings.ToLower(strings.TrimSpace(modelID))
|
||||
}
|
||||
|
||||
func normalizeMiniMaxModelID(modelID string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(modelID)) {
|
||||
case "minimax-m2.1", "minimax m2.1", "minimax-m2.1-preview", "minimax-m2.1-latest":
|
||||
return "MiniMax-M2.1"
|
||||
case "minimax-m2.1-lightning", "minimax m2.1 lightning":
|
||||
return "MiniMax-M2.1-lightning"
|
||||
default:
|
||||
return modelID
|
||||
}
|
||||
}
|
||||
|
||||
func zaiModelDisplayName(modelID string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(modelID)) {
|
||||
case "glm-5":
|
||||
return "GLM-5"
|
||||
case "glm-4.7":
|
||||
return "GLM-4.7"
|
||||
case "glm-4.7-flash":
|
||||
return "GLM-4.7-Flash"
|
||||
case "glm-4.7-flashx":
|
||||
return "GLM-4.7-FlashX"
|
||||
default:
|
||||
return strings.TrimSpace(modelID)
|
||||
}
|
||||
}
|
||||
|
||||
func bailianPrimaryModelID(modelID string) string {
|
||||
trim := strings.TrimSpace(modelID)
|
||||
if trim == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(trim, "/")
|
||||
for i := len(parts) - 1; i >= 0; i-- {
|
||||
part := strings.TrimSpace(parts[i])
|
||||
if part != "" {
|
||||
return part
|
||||
}
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func isReasoningModel(modelID string) bool {
|
||||
trim := strings.ToLower(strings.TrimSpace(modelID))
|
||||
return strings.Contains(trim, "reason") || strings.Contains(trim, "thinking")
|
||||
}
|
||||
|
||||
@@ -163,6 +163,13 @@ func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
|
||||
}},
|
||||
}},
|
||||
})
|
||||
case "openrouter":
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
if strings.Contains(base, "/v1") {
|
||||
request.URL = base + "/key"
|
||||
} else {
|
||||
request.URL = base + "/v1/key"
|
||||
}
|
||||
default:
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
if strings.Contains(base, "/v1") {
|
||||
|
||||
+45
-234
@@ -7,17 +7,15 @@ import (
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -29,6 +27,11 @@ type IAgentService interface {
|
||||
ResetToken(req dto.AgentTokenResetReq) error
|
||||
UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
GetProviders() ([]dto.ProviderInfo, error)
|
||||
GetSecurityConfig(req dto.AgentSecurityConfigReq) (*dto.AgentSecurityConfig, error)
|
||||
UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq) error
|
||||
GetOtherConfig(req dto.AgentOtherConfigReq) (*dto.AgentOtherConfig, error)
|
||||
UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
|
||||
|
||||
CreateAccount(req dto.AgentAccountCreateReq) error
|
||||
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
||||
SyncAgentsByAccount(account *model.AgentAccount) error
|
||||
@@ -39,6 +42,7 @@ type IAgentService interface {
|
||||
DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error
|
||||
VerifyAccount(req dto.AgentAccountVerifyReq) error
|
||||
DeleteAccount(req dto.AgentAccountDeleteReq) error
|
||||
|
||||
GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error)
|
||||
UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error
|
||||
GetTelegramConfig(req dto.AgentTelegramConfigReq) (*dto.AgentTelegramConfig, error)
|
||||
@@ -53,10 +57,6 @@ type IAgentService interface {
|
||||
UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error
|
||||
InstallPlugin(req dto.AgentPluginInstallReq) error
|
||||
CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error)
|
||||
GetSecurityConfig(req dto.AgentSecurityConfigReq) (*dto.AgentSecurityConfig, error)
|
||||
UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq) error
|
||||
GetOtherConfig(req dto.AgentOtherConfigReq) (*dto.AgentOtherConfig, error)
|
||||
UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
|
||||
ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error
|
||||
}
|
||||
|
||||
@@ -340,15 +340,26 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
||||
}
|
||||
|
||||
func (a AgentService) GetProviders() ([]dto.ProviderInfo, error) {
|
||||
definitions := providerDefinitions()
|
||||
definitions := providercatalog.All()
|
||||
providers := make([]dto.ProviderInfo, 0, len(definitions))
|
||||
for key, def := range definitions {
|
||||
models := make([]dto.ProviderModelInfo, 0, len(def.Models))
|
||||
for _, item := range def.Models {
|
||||
models = append(models, dto.ProviderModelInfo{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
ContextWindow: item.ContextWindow,
|
||||
MaxTokens: item.MaxTokens,
|
||||
Reasoning: item.Reasoning,
|
||||
Input: append([]string(nil), item.Input...),
|
||||
})
|
||||
}
|
||||
providers = append(providers, dto.ProviderInfo{
|
||||
Sort: def.Sort,
|
||||
Provider: key,
|
||||
DisplayName: def.DisplayName,
|
||||
BaseURL: def.BaseURL,
|
||||
Models: def.Models,
|
||||
BaseURL: def.DefaultBaseURL,
|
||||
Models: models,
|
||||
})
|
||||
}
|
||||
sort.Slice(providers, func(i, j int) bool {
|
||||
@@ -362,7 +373,7 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
||||
if exist, _ := agentAccountRepo.GetFirst(repo.WithByProvider(provider), repo.WithByName(req.Name)); exist != nil && exist.ID > 0 {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
resolvedInput, err := resolveAgentAccountInput(provider, req.APIKey, req.BaseURL, req.APIType, "")
|
||||
resolvedInput, err := resolveAgentAccountInput(provider, req.APIKey, req.BaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -372,7 +383,7 @@ func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
||||
APIKey: resolvedInput.APIKey,
|
||||
RememberAPIKey: req.RememberAPIKey,
|
||||
BaseURL: resolvedInput.BaseURL,
|
||||
APIType: resolvedInput.APIType,
|
||||
APIType: req.APIType,
|
||||
Verified: true,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
@@ -401,7 +412,7 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
||||
return err
|
||||
}
|
||||
provider := account.Provider
|
||||
resolvedInput, err := resolveAgentAccountInput(provider, req.APIKey, req.BaseURL, req.APIType, account.APIType)
|
||||
resolvedInput, err := resolveAgentAccountInput(provider, req.APIKey, req.BaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -409,7 +420,7 @@ func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
||||
account.APIKey = resolvedInput.APIKey
|
||||
account.RememberAPIKey = req.RememberAPIKey
|
||||
account.BaseURL = resolvedInput.BaseURL
|
||||
account.APIType = resolvedInput.APIType
|
||||
account.APIType = req.APIType
|
||||
account.Remark = req.Remark
|
||||
account.Verified = true
|
||||
|
||||
@@ -445,7 +456,7 @@ func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.Age
|
||||
items = append(items, dto.AgentAccountInfo{
|
||||
ID: item.ID,
|
||||
Provider: item.Provider,
|
||||
ProviderName: providerDisplayName(item.Provider),
|
||||
ProviderName: providercatalog.DisplayName(item.Provider),
|
||||
Name: item.Name,
|
||||
APIKey: apiKey,
|
||||
RememberAPIKey: item.RememberAPIKey,
|
||||
@@ -484,25 +495,22 @@ func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalized, err := normalizeAgentAccountModel(account, req.Model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := findAgentAccountModelForProvider(account.Provider, models, normalized.ID); ok {
|
||||
nextModel := cloneAgentAccountModel(req.Model)
|
||||
if _, ok := findAgentAccountModelForProvider(account.Provider, models, nextModel.ID); ok {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
inputPayload, err := json.Marshal(sanitizeAgentAccountModelInputs(normalized.Input))
|
||||
inputPayload, err := json.Marshal(nextModel.Input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sortOrder := len(models) + 1
|
||||
record := &model.AgentAccountModel{
|
||||
AccountID: account.ID,
|
||||
Model: normalized.ID,
|
||||
Name: normalized.Name,
|
||||
ContextWindow: normalized.ContextWindow,
|
||||
MaxTokens: normalized.MaxTokens,
|
||||
Reasoning: normalized.Reasoning,
|
||||
Model: nextModel.ID,
|
||||
Name: nextModel.Name,
|
||||
ContextWindow: nextModel.ContextWindow,
|
||||
MaxTokens: nextModel.MaxTokens,
|
||||
Reasoning: nextModel.Reasoning,
|
||||
Input: string(inputPayload),
|
||||
SortOrder: sortOrder,
|
||||
}
|
||||
@@ -525,22 +533,19 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalized, err := normalizeAgentAccountModel(account, req.Model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nextModel := cloneAgentAccountModel(req.Model)
|
||||
for _, item := range models {
|
||||
if item.RecordID == req.Model.RecordID {
|
||||
continue
|
||||
}
|
||||
if sameProviderModelID(account.Provider, item.ID, normalized.ID) {
|
||||
if sameProviderModelID(account.Provider, item.ID, nextModel.ID) {
|
||||
return buserr.New("ErrRecordExist")
|
||||
}
|
||||
}
|
||||
nextModels := make([]dto.AgentAccountModel, 0, len(models))
|
||||
for _, item := range models {
|
||||
if item.RecordID == req.Model.RecordID {
|
||||
nextModels = append(nextModels, normalized)
|
||||
nextModels = append(nextModels, nextModel)
|
||||
continue
|
||||
}
|
||||
nextModels = append(nextModels, item)
|
||||
@@ -548,15 +553,15 @@ func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) err
|
||||
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
|
||||
return err
|
||||
}
|
||||
inputPayload, err := json.Marshal(sanitizeAgentAccountModelInputs(normalized.Input))
|
||||
inputPayload, err := json.Marshal(nextModel.Input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.Model = normalized.ID
|
||||
record.Name = normalized.Name
|
||||
record.ContextWindow = normalized.ContextWindow
|
||||
record.MaxTokens = normalized.MaxTokens
|
||||
record.Reasoning = normalized.Reasoning
|
||||
record.Model = nextModel.ID
|
||||
record.Name = nextModel.Name
|
||||
record.ContextWindow = nextModel.ContextWindow
|
||||
record.MaxTokens = nextModel.MaxTokens
|
||||
record.Reasoning = nextModel.Reasoning
|
||||
record.Input = string(inputPayload)
|
||||
if err := agentAccountModelRepo.Save(record); err != nil {
|
||||
return err
|
||||
@@ -603,11 +608,8 @@ func (a AgentService) SyncAgentsByAccount(account *model.AgentAccount) error {
|
||||
}
|
||||
|
||||
func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
|
||||
resolvedVerification, err := resolveAgentAccountVerification(req.Provider, req.APIKey, req.BaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return verifyResolvedAgentAccount(resolvedVerification)
|
||||
_, err := resolveAgentAccountInput(req.Provider, req.APIKey, req.BaseURL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
|
||||
@@ -620,180 +622,6 @@ func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
|
||||
return agentAccountRepo.DeleteByID(req.ID)
|
||||
}
|
||||
|
||||
func (a AgentService) GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractFeishuConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setFeishuConfig(conf, dto.AgentFeishuConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotName: req.BotName,
|
||||
AppID: req.AppID,
|
||||
AppSecret: req.AppSecret,
|
||||
})
|
||||
setFeishuPluginEnabled(conf, req.Enabled)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetTelegramConfig(req dto.AgentTelegramConfigReq) (*dto.AgentTelegramConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractTelegramConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateTelegramConfig(req dto.AgentTelegramConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setTelegramConfig(conf, dto.AgentTelegramConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotToken: req.BotToken,
|
||||
Proxy: req.Proxy,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetDiscordConfig(req dto.AgentDiscordConfigReq) (*dto.AgentDiscordConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractDiscordConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateDiscordConfig(req dto.AgentDiscordConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setDiscordConfig(conf, dto.AgentDiscordConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
GroupPolicy: req.GroupPolicy,
|
||||
Token: req.Token,
|
||||
Proxy: req.Proxy,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetQQBotConfig(req dto.AgentQQBotConfigReq) (*dto.AgentQQBotConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractQQBotConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "qqbot")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setQQBotConfig(conf, dto.AgentQQBotConfig{
|
||||
Enabled: req.Enabled,
|
||||
AppID: req.AppID,
|
||||
ClientSecret: req.ClientSecret,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetWecomConfig(req dto.AgentWecomConfigReq) (*dto.AgentWecomConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractWecomConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "wecom")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateWecomConfig(req dto.AgentWecomConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setWecomConfig(conf, dto.AgentWecomConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotID: req.BotID,
|
||||
Secret: req.Secret,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetDingTalkConfig(req dto.AgentDingTalkConfigReq) (*dto.AgentDingTalkConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractDingTalkConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "dingtalk")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateDingTalkConfig(req dto.AgentDingTalkConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setDingTalkConfig(conf, dto.AgentDingTalkConfig{
|
||||
Enabled: req.Enabled,
|
||||
ClientID: req.ClientID,
|
||||
ClientSecret: req.ClientSecret,
|
||||
DmPolicy: req.DmPolicy,
|
||||
AllowFrom: req.AllowFrom,
|
||||
GroupPolicy: req.GroupPolicy,
|
||||
GroupAllowFrom: req.GroupAllowFrom,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spec, _, err := resolvePluginMeta(req.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask, err := task.NewTaskWithOps(req.Type, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask.AddSubTask("Install OpenClaw plugin", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
return mgr.RunBashCf("docker exec %s openclaw plugins install %s", install.ContainerName, spec)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := installTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("install openclaw plugin failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error) {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installed, err := checkPluginInstalled(install.ContainerName, req.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.AgentPluginStatus{Installed: installed}, nil
|
||||
}
|
||||
|
||||
func (a AgentService) GetSecurityConfig(req dto.AgentSecurityConfigReq) (*dto.AgentSecurityConfig, error) {
|
||||
agent, _, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
@@ -871,23 +699,6 @@ func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
channelType := req.Type
|
||||
if err := cmd.RunDefaultBashCf(
|
||||
"docker exec %s openclaw pairing approve %s %q",
|
||||
install.ContainerName,
|
||||
channelType,
|
||||
strings.TrimSpace(req.PairingCode),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) loadAgentAndInstall(agentID uint) (*model.Agent, *model.AppInstall, error) {
|
||||
agent, err := agentRepo.GetFirst(repo.WithByID(agentID))
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,544 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
func (a AgentService) GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractFeishuConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setFeishuConfig(conf, dto.AgentFeishuConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotName: req.BotName,
|
||||
AppID: req.AppID,
|
||||
AppSecret: req.AppSecret,
|
||||
})
|
||||
setFeishuPluginEnabled(conf, req.Enabled)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetTelegramConfig(req dto.AgentTelegramConfigReq) (*dto.AgentTelegramConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractTelegramConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateTelegramConfig(req dto.AgentTelegramConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setTelegramConfig(conf, dto.AgentTelegramConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotToken: req.BotToken,
|
||||
Proxy: req.Proxy,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetDiscordConfig(req dto.AgentDiscordConfigReq) (*dto.AgentDiscordConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractDiscordConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateDiscordConfig(req dto.AgentDiscordConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setDiscordConfig(conf, dto.AgentDiscordConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
GroupPolicy: req.GroupPolicy,
|
||||
Token: req.Token,
|
||||
Proxy: req.Proxy,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetQQBotConfig(req dto.AgentQQBotConfigReq) (*dto.AgentQQBotConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractQQBotConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "qqbot")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setQQBotConfig(conf, dto.AgentQQBotConfig{
|
||||
Enabled: req.Enabled,
|
||||
AppID: req.AppID,
|
||||
ClientSecret: req.ClientSecret,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetWecomConfig(req dto.AgentWecomConfigReq) (*dto.AgentWecomConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractWecomConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "wecom")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateWecomConfig(req dto.AgentWecomConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setWecomConfig(conf, dto.AgentWecomConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotID: req.BotID,
|
||||
Secret: req.Secret,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetDingTalkConfig(req dto.AgentDingTalkConfigReq) (*dto.AgentDingTalkConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractDingTalkConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "dingtalk")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateDingTalkConfig(req dto.AgentDingTalkConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setDingTalkConfig(conf, dto.AgentDingTalkConfig{
|
||||
Enabled: req.Enabled,
|
||||
ClientID: req.ClientID,
|
||||
ClientSecret: req.ClientSecret,
|
||||
DmPolicy: req.DmPolicy,
|
||||
AllowFrom: req.AllowFrom,
|
||||
GroupPolicy: req.GroupPolicy,
|
||||
GroupAllowFrom: req.GroupAllowFrom,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spec, _, err := resolvePluginMeta(req.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask, err := task.NewTaskWithOps(req.Type, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask.AddSubTask("Install OpenClaw plugin", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
return mgr.RunBashCf("docker exec %s openclaw plugins install %s", install.ContainerName, spec)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := installTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("install openclaw plugin failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error) {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installed, err := checkPluginInstalled(install.ContainerName, req.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.AgentPluginStatus{Installed: installed}, nil
|
||||
}
|
||||
|
||||
func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.RunDefaultBashCf(
|
||||
"docker exec %s openclaw pairing approve %s %q",
|
||||
install.ContainerName,
|
||||
req.Type,
|
||||
strings.TrimSpace(req.PairingCode),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig {
|
||||
result := dto.AgentFeishuConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
feishu, ok := channels["feishu"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := feishu["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := feishu["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
accounts, ok := feishu["accounts"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
main, ok := accounts["main"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if appID, ok := main["appId"].(string); ok {
|
||||
result.AppID = appID
|
||||
}
|
||||
if appSecret, ok := main["appSecret"].(string); ok {
|
||||
result.AppSecret = appSecret
|
||||
}
|
||||
if botName, ok := main["botName"].(string); ok {
|
||||
result.BotName = botName
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setFeishuConfig(conf map[string]interface{}, config dto.AgentFeishuConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
feishu := ensureChildMap(channels, "feishu")
|
||||
feishu["enabled"] = config.Enabled
|
||||
feishu["dmPolicy"] = config.DmPolicy
|
||||
|
||||
accounts := ensureChildMap(feishu, "accounts")
|
||||
main := ensureChildMap(accounts, "main")
|
||||
main["appId"] = config.AppID
|
||||
main["appSecret"] = config.AppSecret
|
||||
main["botName"] = config.BotName
|
||||
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
feishu["allowFrom"] = []string{"*"}
|
||||
}
|
||||
}
|
||||
|
||||
func setFeishuPluginEnabled(conf map[string]interface{}, enabled bool) {
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
feishu := ensureChildMap(entries, "feishu")
|
||||
feishu["enabled"] = enabled
|
||||
}
|
||||
|
||||
func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig {
|
||||
result := dto.AgentTelegramConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
telegram, ok := channels["telegram"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := telegram["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := telegram["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
if botToken, ok := telegram["botToken"].(string); ok {
|
||||
result.BotToken = botToken
|
||||
}
|
||||
if proxy, ok := telegram["proxy"].(string); ok {
|
||||
result.Proxy = proxy
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
telegram := map[string]interface{}{
|
||||
"enabled": config.Enabled,
|
||||
"dmPolicy": config.DmPolicy,
|
||||
"botToken": config.BotToken,
|
||||
}
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
telegram["allowFrom"] = []string{"*"}
|
||||
}
|
||||
if strings.TrimSpace(config.Proxy) != "" {
|
||||
telegram["proxy"] = strings.TrimSpace(config.Proxy)
|
||||
}
|
||||
channels["telegram"] = telegram
|
||||
}
|
||||
|
||||
func extractDiscordConfig(conf map[string]interface{}) dto.AgentDiscordConfig {
|
||||
result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", GroupPolicy: "open"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
discord, ok := channels["discord"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := discord["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if token, ok := discord["token"].(string); ok {
|
||||
result.Token = token
|
||||
}
|
||||
if groupPolicy, ok := discord["groupPolicy"].(string); ok && strings.TrimSpace(groupPolicy) != "" {
|
||||
result.GroupPolicy = groupPolicy
|
||||
}
|
||||
if proxy, ok := discord["proxy"].(string); ok {
|
||||
result.Proxy = proxy
|
||||
}
|
||||
if policy, ok := discord["dmPolicy"].(string); ok && strings.TrimSpace(policy) != "" {
|
||||
result.DmPolicy = policy
|
||||
return result
|
||||
}
|
||||
dm, ok := discord["dm"].(map[string]interface{})
|
||||
if ok {
|
||||
if policy, ok := dm["policy"].(string); ok && strings.TrimSpace(policy) != "" {
|
||||
result.DmPolicy = policy
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setDiscordConfig(conf map[string]interface{}, config dto.AgentDiscordConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
discord := ensureChildMap(channels, "discord")
|
||||
discord["enabled"] = config.Enabled
|
||||
discord["token"] = config.Token
|
||||
discord["dmPolicy"] = config.DmPolicy
|
||||
discord["groupPolicy"] = config.GroupPolicy
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
discord["allowFrom"] = []string{"*"}
|
||||
} else {
|
||||
delete(discord, "allowFrom")
|
||||
}
|
||||
if strings.TrimSpace(config.Proxy) != "" {
|
||||
discord["proxy"] = strings.TrimSpace(config.Proxy)
|
||||
} else {
|
||||
delete(discord, "proxy")
|
||||
}
|
||||
delete(discord, "dm")
|
||||
}
|
||||
|
||||
func extractQQBotConfig(conf map[string]interface{}) dto.AgentQQBotConfig {
|
||||
result := dto.AgentQQBotConfig{Enabled: true}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
qqbot, ok := channels["qqbot"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := qqbot["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if appID, ok := qqbot["appId"].(string); ok {
|
||||
result.AppID = appID
|
||||
}
|
||||
if clientSecret, ok := qqbot["clientSecret"].(string); ok {
|
||||
result.ClientSecret = clientSecret
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func extractWecomConfig(conf map[string]interface{}) dto.AgentWecomConfig {
|
||||
result := dto.AgentWecomConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
wecom, ok := channels["wecom"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := wecom["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := wecom["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = strings.TrimSpace(dmPolicy)
|
||||
}
|
||||
if botID, ok := wecom["botId"].(string); ok {
|
||||
result.BotID = botID
|
||||
}
|
||||
if secret, ok := wecom["secret"].(string); ok {
|
||||
result.Secret = secret
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func extractDingTalkConfig(conf map[string]interface{}) dto.AgentDingTalkConfig {
|
||||
result := dto.AgentDingTalkConfig{
|
||||
Enabled: true,
|
||||
DmPolicy: "pairing",
|
||||
GroupPolicy: "disabled",
|
||||
AllowFrom: []string{},
|
||||
GroupAllowFrom: []string{},
|
||||
}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
dingtalk, ok := channels["dingtalk-connector"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := dingtalk["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if clientID, ok := dingtalk["clientId"].(string); ok {
|
||||
result.ClientID = clientID
|
||||
}
|
||||
if clientSecret, ok := dingtalk["clientSecret"].(string); ok {
|
||||
result.ClientSecret = clientSecret
|
||||
}
|
||||
if dmPolicy, ok := dingtalk["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
if groupPolicy, ok := dingtalk["groupPolicy"].(string); ok && strings.TrimSpace(groupPolicy) != "" {
|
||||
result.GroupPolicy = groupPolicy
|
||||
}
|
||||
result.AllowFrom = extractStringList(dingtalk["allowFrom"])
|
||||
result.GroupAllowFrom = extractStringList(dingtalk["groupAllowFrom"])
|
||||
return result
|
||||
}
|
||||
|
||||
func setWecomConfig(conf map[string]interface{}, config dto.AgentWecomConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
wecom := ensureChildMap(channels, "wecom")
|
||||
wecom["enabled"] = config.Enabled
|
||||
wecom["botId"] = strings.TrimSpace(config.BotID)
|
||||
wecom["secret"] = strings.TrimSpace(config.Secret)
|
||||
wecom["dmPolicy"] = strings.TrimSpace(config.DmPolicy)
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
wecom["allowFrom"] = []string{"*"}
|
||||
} else {
|
||||
wecom["allowFrom"] = []string{}
|
||||
}
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
wecomEntry := ensureChildMap(entries, "wecom-openclaw-plugin")
|
||||
wecomEntry["enabled"] = config.Enabled
|
||||
}
|
||||
|
||||
func setDingTalkConfig(conf map[string]interface{}, config dto.AgentDingTalkConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
dingtalk := ensureChildMap(channels, "dingtalk-connector")
|
||||
dingtalk["enabled"] = config.Enabled
|
||||
dingtalk["clientId"] = strings.TrimSpace(config.ClientID)
|
||||
dingtalk["clientSecret"] = strings.TrimSpace(config.ClientSecret)
|
||||
dingtalk["dmPolicy"] = config.DmPolicy
|
||||
dingtalk["groupPolicy"] = config.GroupPolicy
|
||||
dingtalk["gatewayToken"] = extractGatewayToken(conf)
|
||||
switch config.DmPolicy {
|
||||
case "open":
|
||||
dingtalk["allowFrom"] = []string{"*"}
|
||||
case "allowlist":
|
||||
dingtalk["allowFrom"] = append([]string(nil), config.AllowFrom...)
|
||||
default:
|
||||
delete(dingtalk, "allowFrom")
|
||||
}
|
||||
switch config.GroupPolicy {
|
||||
case "open":
|
||||
dingtalk["groupAllowFrom"] = []string{"*"}
|
||||
case "allowlist":
|
||||
dingtalk["groupAllowFrom"] = append([]string(nil), config.GroupAllowFrom...)
|
||||
default:
|
||||
delete(dingtalk, "groupAllowFrom")
|
||||
}
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
dingtalkEntry := ensureChildMap(entries, "dingtalk-connector")
|
||||
dingtalkEntry["enabled"] = config.Enabled
|
||||
|
||||
gateway := ensureChildMap(conf, "gateway")
|
||||
httpMap := ensureChildMap(gateway, "http")
|
||||
endpoints := ensureChildMap(httpMap, "endpoints")
|
||||
chatCompletions := ensureChildMap(endpoints, "chatCompletions")
|
||||
chatCompletions["enabled"] = true
|
||||
}
|
||||
|
||||
func setQQBotConfig(conf map[string]interface{}, config dto.AgentQQBotConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
qqbot := ensureChildMap(channels, "qqbot")
|
||||
qqbot["enabled"] = config.Enabled
|
||||
qqbot["allowFrom"] = []string{"*"}
|
||||
qqbot["appId"] = strings.TrimSpace(config.AppID)
|
||||
qqbot["clientSecret"] = strings.TrimSpace(config.ClientSecret)
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
qqbotEntry := ensureChildMap(entries, "qqbot")
|
||||
qqbotEntry["enabled"] = config.Enabled
|
||||
}
|
||||
|
||||
func resolvePluginMeta(pluginType string) (string, string, error) {
|
||||
switch pluginType {
|
||||
case "qqbot":
|
||||
return "@sliverp/qqbot@latest", "qqbot", nil
|
||||
case "wecom":
|
||||
return "@wecom/wecom-openclaw-plugin", "wecom-openclaw-plugin", nil
|
||||
case "dingtalk":
|
||||
return "@dingtalk-real-ai/dingtalk-connector", "dingtalk-connector", nil
|
||||
default:
|
||||
return "", "", fmt.Errorf("unsupported plugin type")
|
||||
}
|
||||
}
|
||||
|
||||
func checkPluginInstalled(containerName, pluginType string) (bool, error) {
|
||||
_, pluginDir, err := resolvePluginMeta(pluginType)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if strings.TrimSpace(containerName) == "" {
|
||||
return false, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
pluginPath := path.Join(openclawPluginBaseDir, pluginDir)
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
if err := mgr.RunBashCf("docker exec %s test -d %s", containerName, pluginPath); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
openclawutil "github.com/1Panel-dev/1Panel/agent/utils/openclaw"
|
||||
@@ -33,100 +32,32 @@ func NewIAgentService() IAgentService {
|
||||
return &AgentService{}
|
||||
}
|
||||
|
||||
type resolvedAgentAccountVerification struct {
|
||||
Provider string
|
||||
APIKey string
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
type resolvedAgentAccountInput struct {
|
||||
Provider string
|
||||
APIKey string
|
||||
BaseURL string
|
||||
APIType string
|
||||
}
|
||||
|
||||
func resolveAgentAccountVerification(provider, apiKey, baseURL string) (resolvedAgentAccountVerification, error) {
|
||||
func resolveAgentAccountInput(provider, apiKey, baseURL string) (resolvedAgentAccountInput, error) {
|
||||
resolvedAPIKey := strings.TrimSpace(apiKey)
|
||||
if resolvedAPIKey == "" {
|
||||
return resolvedAgentAccountVerification{}, buserr.New("ErrAgentApiKeyRequired")
|
||||
}
|
||||
resolvedBaseURL := strings.TrimSpace(baseURL)
|
||||
if fixedURL, ok := fixedProviderBaseURL(provider); ok {
|
||||
resolvedBaseURL = fixedURL
|
||||
}
|
||||
if (provider == "custom" || provider == "vllm") && resolvedBaseURL == "" {
|
||||
return resolvedAgentAccountVerification{}, buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
if provider != "custom" && provider != "vllm" && resolvedBaseURL == "" {
|
||||
if defaultURL, ok := providerDefaultBaseURL(provider); ok {
|
||||
if resolvedBaseURL == "" {
|
||||
if requiresInitialAgentAccountModels(provider) {
|
||||
return resolvedAgentAccountInput{}, buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
if defaultURL, ok := providercatalog.DefaultBaseURL(provider); ok {
|
||||
resolvedBaseURL = defaultURL
|
||||
}
|
||||
}
|
||||
if provider == "ollama" && resolvedBaseURL == "" {
|
||||
return resolvedAgentAccountVerification{}, buserr.New("ErrAgentBaseURLRequired")
|
||||
}
|
||||
return resolvedAgentAccountVerification{
|
||||
Provider: provider,
|
||||
APIKey: resolvedAPIKey,
|
||||
BaseURL: resolvedBaseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func verifyResolvedAgentAccount(input resolvedAgentAccountVerification) error {
|
||||
if providercatalog.SkipVerification(input.Provider) {
|
||||
return nil
|
||||
}
|
||||
return providercatalog.VerifyAccount(input.Provider, input.BaseURL, input.APIKey)
|
||||
}
|
||||
|
||||
func resolveAgentAccountAPIType(provider, apiType, fallbackAPIType string) (string, error) {
|
||||
if provider == "minimax" || provider == "xiaomi" {
|
||||
return "anthropic-messages", nil
|
||||
}
|
||||
resolvedAPIType := normalizeAPIType(apiType)
|
||||
if provider == "custom" || provider == "vllm" {
|
||||
if !isSupportedAPIType(resolvedAPIType) {
|
||||
return "", fmt.Errorf("apiType is invalid")
|
||||
if !providercatalog.SkipVerification(provider) {
|
||||
if err := providercatalog.VerifyAccount(provider, resolvedBaseURL, resolvedAPIKey); err != nil {
|
||||
return resolvedAgentAccountInput{}, err
|
||||
}
|
||||
return resolvedAPIType, nil
|
||||
}
|
||||
if provider == "ollama" {
|
||||
if apiType == "" && fallbackAPIType != "" {
|
||||
resolvedAPIType = normalizeAPIType(fallbackAPIType)
|
||||
if !isSupportedOllamaAPIType(resolvedAPIType) {
|
||||
resolvedAPIType = "openai-responses"
|
||||
}
|
||||
return resolvedAPIType, nil
|
||||
}
|
||||
if !isSupportedOllamaAPIType(resolvedAPIType) {
|
||||
return "", fmt.Errorf("apiType is invalid")
|
||||
}
|
||||
return resolvedAPIType, nil
|
||||
}
|
||||
if fallbackAPIType != "" {
|
||||
return normalizeAPIType(fallbackAPIType), nil
|
||||
}
|
||||
return resolvedAPIType, nil
|
||||
}
|
||||
|
||||
func resolveAgentAccountInput(provider, apiKey, baseURL, apiType, fallbackAPIType string) (resolvedAgentAccountInput, error) {
|
||||
resolvedVerification, err := resolveAgentAccountVerification(provider, apiKey, baseURL)
|
||||
if err != nil {
|
||||
return resolvedAgentAccountInput{}, err
|
||||
}
|
||||
if err := verifyResolvedAgentAccount(resolvedVerification); err != nil {
|
||||
return resolvedAgentAccountInput{}, err
|
||||
}
|
||||
resolvedAPIType, err := resolveAgentAccountAPIType(provider, apiType, fallbackAPIType)
|
||||
if err != nil {
|
||||
return resolvedAgentAccountInput{}, err
|
||||
}
|
||||
return resolvedAgentAccountInput{
|
||||
Provider: provider,
|
||||
APIKey: resolvedVerification.APIKey,
|
||||
BaseURL: resolvedVerification.BaseURL,
|
||||
APIType: resolvedAPIType,
|
||||
APIKey: resolvedAPIKey,
|
||||
BaseURL: resolvedBaseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -300,163 +231,6 @@ func setTrustedProxies(gateway map[string]interface{}) {
|
||||
gateway["trustedProxies"] = proxies
|
||||
}
|
||||
|
||||
func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig {
|
||||
result := dto.AgentFeishuConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
feishu, ok := channels["feishu"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := feishu["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := feishu["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
accounts, ok := feishu["accounts"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
main, ok := accounts["main"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if appID, ok := main["appId"].(string); ok {
|
||||
result.AppID = appID
|
||||
}
|
||||
if appSecret, ok := main["appSecret"].(string); ok {
|
||||
result.AppSecret = appSecret
|
||||
}
|
||||
if botName, ok := main["botName"].(string); ok {
|
||||
result.BotName = botName
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setFeishuConfig(conf map[string]interface{}, config dto.AgentFeishuConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
feishu := ensureChildMap(channels, "feishu")
|
||||
feishu["enabled"] = config.Enabled
|
||||
feishu["dmPolicy"] = config.DmPolicy
|
||||
|
||||
accounts := ensureChildMap(feishu, "accounts")
|
||||
main := ensureChildMap(accounts, "main")
|
||||
main["appId"] = config.AppID
|
||||
main["appSecret"] = config.AppSecret
|
||||
main["botName"] = config.BotName
|
||||
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
feishu["allowFrom"] = []string{"*"}
|
||||
}
|
||||
}
|
||||
|
||||
func setFeishuPluginEnabled(conf map[string]interface{}, enabled bool) {
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
feishu := ensureChildMap(entries, "feishu")
|
||||
feishu["enabled"] = enabled
|
||||
}
|
||||
|
||||
func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig {
|
||||
result := dto.AgentTelegramConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
telegram, ok := channels["telegram"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := telegram["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := telegram["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
if botToken, ok := telegram["botToken"].(string); ok {
|
||||
result.BotToken = botToken
|
||||
}
|
||||
if proxy, ok := telegram["proxy"].(string); ok {
|
||||
result.Proxy = proxy
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
telegram := map[string]interface{}{
|
||||
"enabled": config.Enabled,
|
||||
"dmPolicy": config.DmPolicy,
|
||||
"botToken": config.BotToken,
|
||||
}
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
telegram["allowFrom"] = []string{"*"}
|
||||
}
|
||||
if strings.TrimSpace(config.Proxy) != "" {
|
||||
telegram["proxy"] = strings.TrimSpace(config.Proxy)
|
||||
}
|
||||
channels["telegram"] = telegram
|
||||
}
|
||||
|
||||
func extractDiscordConfig(conf map[string]interface{}) dto.AgentDiscordConfig {
|
||||
result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", GroupPolicy: "open"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
discord, ok := channels["discord"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := discord["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if token, ok := discord["token"].(string); ok {
|
||||
result.Token = token
|
||||
}
|
||||
if groupPolicy, ok := discord["groupPolicy"].(string); ok && strings.TrimSpace(groupPolicy) != "" {
|
||||
result.GroupPolicy = groupPolicy
|
||||
}
|
||||
if proxy, ok := discord["proxy"].(string); ok {
|
||||
result.Proxy = proxy
|
||||
}
|
||||
if policy, ok := discord["dmPolicy"].(string); ok && strings.TrimSpace(policy) != "" {
|
||||
result.DmPolicy = policy
|
||||
return result
|
||||
}
|
||||
// backward compatibility: old nested style
|
||||
dm, ok := discord["dm"].(map[string]interface{})
|
||||
if ok {
|
||||
if policy, ok := dm["policy"].(string); ok && strings.TrimSpace(policy) != "" {
|
||||
result.DmPolicy = policy
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setDiscordConfig(conf map[string]interface{}, config dto.AgentDiscordConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
discord := ensureChildMap(channels, "discord")
|
||||
discord["enabled"] = config.Enabled
|
||||
discord["token"] = config.Token
|
||||
discord["dmPolicy"] = config.DmPolicy
|
||||
discord["groupPolicy"] = config.GroupPolicy
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
discord["allowFrom"] = []string{"*"}
|
||||
} else {
|
||||
delete(discord, "allowFrom")
|
||||
}
|
||||
if strings.TrimSpace(config.Proxy) != "" {
|
||||
discord["proxy"] = strings.TrimSpace(config.Proxy)
|
||||
} else {
|
||||
delete(discord, "proxy")
|
||||
}
|
||||
delete(discord, "dm")
|
||||
}
|
||||
|
||||
func extractBrowserConfig(conf map[string]interface{}) browserConfig {
|
||||
result := browserConfig{
|
||||
Enabled: true,
|
||||
@@ -500,189 +274,6 @@ func setBrowserConfig(conf map[string]interface{}, config browserConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
func extractQQBotConfig(conf map[string]interface{}) dto.AgentQQBotConfig {
|
||||
result := dto.AgentQQBotConfig{Enabled: true}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
qqbot, ok := channels["qqbot"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := qqbot["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if appID, ok := qqbot["appId"].(string); ok {
|
||||
result.AppID = appID
|
||||
}
|
||||
if clientSecret, ok := qqbot["clientSecret"].(string); ok {
|
||||
result.ClientSecret = clientSecret
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func extractWecomConfig(conf map[string]interface{}) dto.AgentWecomConfig {
|
||||
result := dto.AgentWecomConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
wecom, ok := channels["wecom"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := wecom["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := wecom["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = strings.TrimSpace(dmPolicy)
|
||||
}
|
||||
if botID, ok := wecom["botId"].(string); ok {
|
||||
result.BotID = botID
|
||||
}
|
||||
if secret, ok := wecom["secret"].(string); ok {
|
||||
result.Secret = secret
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func extractDingTalkConfig(conf map[string]interface{}) dto.AgentDingTalkConfig {
|
||||
result := dto.AgentDingTalkConfig{
|
||||
Enabled: true,
|
||||
DmPolicy: "pairing",
|
||||
GroupPolicy: "disabled",
|
||||
AllowFrom: []string{},
|
||||
GroupAllowFrom: []string{},
|
||||
}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
dingtalk, ok := channels["dingtalk-connector"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := dingtalk["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if clientID, ok := dingtalk["clientId"].(string); ok {
|
||||
result.ClientID = clientID
|
||||
}
|
||||
if clientSecret, ok := dingtalk["clientSecret"].(string); ok {
|
||||
result.ClientSecret = clientSecret
|
||||
}
|
||||
if dmPolicy, ok := dingtalk["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
if groupPolicy, ok := dingtalk["groupPolicy"].(string); ok && strings.TrimSpace(groupPolicy) != "" {
|
||||
result.GroupPolicy = groupPolicy
|
||||
}
|
||||
result.AllowFrom = extractStringList(dingtalk["allowFrom"])
|
||||
result.GroupAllowFrom = extractStringList(dingtalk["groupAllowFrom"])
|
||||
return result
|
||||
}
|
||||
|
||||
func setWecomConfig(conf map[string]interface{}, config dto.AgentWecomConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
wecom := ensureChildMap(channels, "wecom")
|
||||
wecom["enabled"] = config.Enabled
|
||||
wecom["botId"] = strings.TrimSpace(config.BotID)
|
||||
wecom["secret"] = strings.TrimSpace(config.Secret)
|
||||
wecom["dmPolicy"] = strings.TrimSpace(config.DmPolicy)
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
wecom["allowFrom"] = []string{"*"}
|
||||
} else {
|
||||
wecom["allowFrom"] = []string{}
|
||||
}
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
wecomEntry := ensureChildMap(entries, "wecom-openclaw-plugin")
|
||||
wecomEntry["enabled"] = config.Enabled
|
||||
}
|
||||
|
||||
func setDingTalkConfig(conf map[string]interface{}, config dto.AgentDingTalkConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
dingtalk := ensureChildMap(channels, "dingtalk-connector")
|
||||
dingtalk["enabled"] = config.Enabled
|
||||
dingtalk["clientId"] = strings.TrimSpace(config.ClientID)
|
||||
dingtalk["clientSecret"] = strings.TrimSpace(config.ClientSecret)
|
||||
dingtalk["dmPolicy"] = config.DmPolicy
|
||||
dingtalk["groupPolicy"] = config.GroupPolicy
|
||||
dingtalk["gatewayToken"] = extractGatewayToken(conf)
|
||||
switch config.DmPolicy {
|
||||
case "open":
|
||||
dingtalk["allowFrom"] = []string{"*"}
|
||||
case "allowlist":
|
||||
dingtalk["allowFrom"] = append([]string(nil), config.AllowFrom...)
|
||||
default:
|
||||
delete(dingtalk, "allowFrom")
|
||||
}
|
||||
switch config.GroupPolicy {
|
||||
case "open":
|
||||
dingtalk["groupAllowFrom"] = []string{"*"}
|
||||
case "allowlist":
|
||||
dingtalk["groupAllowFrom"] = append([]string(nil), config.GroupAllowFrom...)
|
||||
default:
|
||||
delete(dingtalk, "groupAllowFrom")
|
||||
}
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
dingtalkEntry := ensureChildMap(entries, "dingtalk-connector")
|
||||
dingtalkEntry["enabled"] = config.Enabled
|
||||
|
||||
gateway := ensureChildMap(conf, "gateway")
|
||||
httpMap := ensureChildMap(gateway, "http")
|
||||
endpoints := ensureChildMap(httpMap, "endpoints")
|
||||
chatCompletions := ensureChildMap(endpoints, "chatCompletions")
|
||||
chatCompletions["enabled"] = true
|
||||
}
|
||||
|
||||
func setQQBotConfig(conf map[string]interface{}, config dto.AgentQQBotConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
qqbot := ensureChildMap(channels, "qqbot")
|
||||
qqbot["enabled"] = config.Enabled
|
||||
qqbot["allowFrom"] = []string{"*"}
|
||||
qqbot["appId"] = strings.TrimSpace(config.AppID)
|
||||
qqbot["clientSecret"] = strings.TrimSpace(config.ClientSecret)
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
qqbotEntry := ensureChildMap(entries, "qqbot")
|
||||
qqbotEntry["enabled"] = config.Enabled
|
||||
}
|
||||
|
||||
func resolvePluginMeta(pluginType string) (string, string, error) {
|
||||
switch pluginType {
|
||||
case "qqbot":
|
||||
return "@sliverp/qqbot@latest", "qqbot", nil
|
||||
case "wecom":
|
||||
return "@wecom/wecom-openclaw-plugin", "wecom-openclaw-plugin", nil
|
||||
case "dingtalk":
|
||||
return "@dingtalk-real-ai/dingtalk-connector", "dingtalk-connector", nil
|
||||
default:
|
||||
return "", "", fmt.Errorf("unsupported plugin type")
|
||||
}
|
||||
}
|
||||
|
||||
func checkPluginInstalled(containerName, pluginType string) (bool, error) {
|
||||
_, pluginDir, err := resolvePluginMeta(pluginType)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if strings.TrimSpace(containerName) == "" {
|
||||
return false, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
pluginPath := path.Join(openclawPluginBaseDir, pluginDir)
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
if err := mgr.RunBashCf("docker exec %s test -d %s", containerName, pluginPath); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func extractOtherConfig(conf map[string]interface{}) dto.AgentOtherConfig {
|
||||
result := dto.AgentOtherConfig{UserTimezone: resolveServerTimezone(), BrowserEnabled: true}
|
||||
agents, ok := conf["agents"].(map[string]interface{})
|
||||
@@ -732,7 +323,7 @@ func buildAgentItem(agent *model.Agent, appInstall *model.AppInstall, envMap map
|
||||
Name: agent.Name,
|
||||
AgentType: agentType,
|
||||
Provider: agent.Provider,
|
||||
ProviderName: providerDisplayName(agent.Provider),
|
||||
ProviderName: providercatalog.DisplayName(agent.Provider),
|
||||
Model: agent.Model,
|
||||
APIType: agent.APIType,
|
||||
MaxTokens: agent.MaxTokens,
|
||||
@@ -1174,7 +765,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 := providerEnvKey(account.Provider); envKey != "" && strings.TrimSpace(account.APIKey) != "" {
|
||||
if envKey := providercatalog.EnvKey(account.Provider); envKey != "" && strings.TrimSpace(account.APIKey) != "" {
|
||||
lines = append(lines, fmt.Sprintf("%s=%s", envKey, account.APIKey))
|
||||
}
|
||||
content := strings.Join(lines, "\n") + "\n"
|
||||
@@ -1250,7 +841,7 @@ func buildOpenclawModelsFromAccount(account *model.AgentAccount, selectedModel s
|
||||
}
|
||||
|
||||
func buildOpenclawCatalogModel(account *model.AgentAccount, model dto.AgentAccountModel) (string, modelEntry, string, modelProvider, error) {
|
||||
primaryModel, inferredEntry, providerKey, providerCfg, err := inferOpenclawCatalogModel(account, model.ID, model.MaxTokens, model.ContextWindow)
|
||||
primaryModel, inferredEntry, providerKey, providerCfg, err := inferOpenclawCatalogModel(account, model.ID, model.Reasoning, model.MaxTokens, model.ContextWindow)
|
||||
if err != nil {
|
||||
return "", modelEntry{}, "", modelProvider{}, err
|
||||
}
|
||||
@@ -1279,7 +870,7 @@ type openclawAccountModelRuntime struct {
|
||||
}
|
||||
|
||||
func buildOpenclawAccountModelRuntime(account *model.AgentAccount, model dto.AgentAccountModel) (openclawAccountModelRuntime, error) {
|
||||
apiType, maxTokens, contextWindow := resolveRuntimeParams(
|
||||
apiType, maxTokens, contextWindow := providercatalog.ResolveRuntimeParams(
|
||||
account.Provider,
|
||||
account.APIType,
|
||||
model.MaxTokens,
|
||||
@@ -1310,10 +901,10 @@ func resolveOpenclawAccountModelRuntimeByID(account *model.AgentAccount, modelID
|
||||
return buildOpenclawAccountModelRuntime(account, selectedAccountModel)
|
||||
}
|
||||
|
||||
func inferOpenclawCatalogModel(account *model.AgentAccount, modelID string, maxTokens, contextWindow int) (string, modelEntry, string, modelProvider, error) {
|
||||
func inferOpenclawCatalogModel(account *model.AgentAccount, modelID string, reasoning bool, maxTokens, contextWindow int) (string, modelEntry, string, modelProvider, error) {
|
||||
baseURL := resolveAccountBaseURL(account)
|
||||
resolvedAPIType, resolvedMaxTokens, resolvedContextWindow := resolveRuntimeParams(account.Provider, account.APIType, maxTokens, contextWindow)
|
||||
patch, err := providercatalog.BuildOpenClawPatch(account.Provider, modelID, resolvedAPIType, resolvedMaxTokens, resolvedContextWindow, baseURL, account.APIKey)
|
||||
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
|
||||
}
|
||||
@@ -1341,7 +932,7 @@ func inferOpenclawCatalogModel(account *model.AgentAccount, modelID string, maxT
|
||||
func resolveAccountBaseURL(account *model.AgentAccount) string {
|
||||
baseURL := strings.TrimSpace(account.BaseURL)
|
||||
if baseURL == "" {
|
||||
if defaultURL, ok := providerDefaultBaseURL(account.Provider); ok {
|
||||
if defaultURL, ok := providercatalog.DefaultBaseURL(account.Provider); ok {
|
||||
baseURL = defaultURL
|
||||
}
|
||||
}
|
||||
@@ -1356,9 +947,9 @@ func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto
|
||||
return nil, buserr.New("ErrAgentAccountSingleInitialModel")
|
||||
}
|
||||
if len(requested) > 0 {
|
||||
models, _, err := normalizeAgentAccountModels(account, requested, "", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
models := make([]dto.AgentAccountModel, 0, len(requested))
|
||||
for _, item := range requested {
|
||||
models = append(models, cloneAgentAccountModel(item))
|
||||
}
|
||||
return models, nil
|
||||
}
|
||||
@@ -1380,11 +971,7 @@ func buildInitialAgentAccountModels(account *model.AgentAccount, requested []dto
|
||||
Input: append([]string(nil), item.Input...),
|
||||
})
|
||||
}
|
||||
models, _, err := normalizeAgentAccountModels(account, requested, "", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return models, nil
|
||||
return requested, nil
|
||||
}
|
||||
|
||||
func compactPersistedAgentAccountModelSortOrder(accountID uint) error {
|
||||
@@ -1412,6 +999,18 @@ func loadAgentAccountModels(account *model.AgentAccount) ([]dto.AgentAccountMode
|
||||
return listPersistedAgentAccountModels(account.ID)
|
||||
}
|
||||
|
||||
func cloneAgentAccountModel(model dto.AgentAccountModel) dto.AgentAccountModel {
|
||||
return dto.AgentAccountModel{
|
||||
RecordID: model.RecordID,
|
||||
ID: model.ID,
|
||||
Name: model.Name,
|
||||
ContextWindow: model.ContextWindow,
|
||||
MaxTokens: model.MaxTokens,
|
||||
Reasoning: model.Reasoning,
|
||||
Input: append([]string(nil), model.Input...),
|
||||
}
|
||||
}
|
||||
|
||||
func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, existing []dto.AgentAccountModel) ([]dto.AgentAccountModel, error) {
|
||||
if account == nil {
|
||||
return nil, fmt.Errorf("account is required")
|
||||
@@ -1423,13 +1022,15 @@ func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, exi
|
||||
requested := append([]dto.AgentAccountModel(nil), existing...)
|
||||
seen := make(map[string]struct{}, len(existing))
|
||||
for _, item := range existing {
|
||||
if strings.TrimSpace(item.ID) == "" {
|
||||
target := strings.TrimSpace(item.ID)
|
||||
if target == "" {
|
||||
continue
|
||||
}
|
||||
seen[strings.TrimSpace(item.ID)] = struct{}{}
|
||||
seen[target] = struct{}{}
|
||||
}
|
||||
for _, item := range meta.Models {
|
||||
if _, ok := seen[strings.TrimSpace(item.ID)]; ok {
|
||||
target := strings.TrimSpace(item.ID)
|
||||
if _, ok := seen[target]; ok {
|
||||
continue
|
||||
}
|
||||
requested = append(requested, dto.AgentAccountModel{
|
||||
@@ -1444,11 +1045,7 @@ func MergeCatalogAgentAccountModelsForMigration(account *model.AgentAccount, exi
|
||||
if len(requested) == len(existing) {
|
||||
return append([]dto.AgentAccountModel(nil), existing...), nil
|
||||
}
|
||||
normalized, _, err := normalizeAgentAccountModels(account, requested, "", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return normalized, nil
|
||||
return normalizeAgentAccountModels(account, requested)
|
||||
}
|
||||
|
||||
func listPersistedAgentAccountModels(accountID uint) ([]dto.AgentAccountModel, error) {
|
||||
@@ -1510,21 +1107,17 @@ func replacePersistedAgentAccountModels(accountID uint, models []dto.AgentAccoun
|
||||
})
|
||||
}
|
||||
|
||||
func normalizeAgentAccountModels(account *model.AgentAccount, models []dto.AgentAccountModel, defaultModel string, allowFallbackDefault bool) ([]dto.AgentAccountModel, string, error) {
|
||||
func normalizeAgentAccountModels(account *model.AgentAccount, models []dto.AgentAccountModel) ([]dto.AgentAccountModel, error) {
|
||||
requested := append([]dto.AgentAccountModel(nil), models...)
|
||||
if len(requested) == 0 {
|
||||
if strings.TrimSpace(defaultModel) != "" {
|
||||
requested = []dto.AgentAccountModel{{ID: defaultModel}}
|
||||
} else {
|
||||
requested = buildLegacyAgentAccountModels(account)
|
||||
}
|
||||
return nil, fmt.Errorf("model is required")
|
||||
}
|
||||
normalized := make([]dto.AgentAccountModel, 0, len(requested))
|
||||
seen := make(map[string]struct{}, len(requested))
|
||||
for _, item := range requested {
|
||||
normalizedItem, err := normalizeAgentAccountModel(account, item)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
if strings.TrimSpace(normalizedItem.ID) == "" {
|
||||
continue
|
||||
@@ -1536,27 +1129,9 @@ func normalizeAgentAccountModels(account *model.AgentAccount, models []dto.Agent
|
||||
normalized = append(normalized, normalizedItem)
|
||||
}
|
||||
if len(normalized) == 0 {
|
||||
return nil, "", fmt.Errorf("model is required")
|
||||
return nil, fmt.Errorf("model is required")
|
||||
}
|
||||
|
||||
resolvedDefault := strings.TrimSpace(defaultModel)
|
||||
if resolvedDefault != "" {
|
||||
defaultItem, err := normalizeAgentAccountModel(account, dto.AgentAccountModel{ID: resolvedDefault})
|
||||
if err == nil {
|
||||
resolvedDefault = defaultItem.ID
|
||||
}
|
||||
}
|
||||
if resolvedDefault == "" && allowFallbackDefault {
|
||||
resolvedDefault = normalized[0].ID
|
||||
}
|
||||
if _, ok := findAgentAccountModelForProvider(account.Provider, normalized, resolvedDefault); !ok {
|
||||
if allowFallbackDefault {
|
||||
resolvedDefault = normalized[0].ID
|
||||
} else {
|
||||
return nil, "", buserr.New("ErrAgentModelNotInAccount")
|
||||
}
|
||||
}
|
||||
return normalized, resolvedDefault, nil
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAccountModel) (dto.AgentAccountModel, error) {
|
||||
@@ -1564,7 +1139,13 @@ func normalizeAgentAccountModel(account *model.AgentAccount, model dto.AgentAcco
|
||||
if modelID == "" {
|
||||
return dto.AgentAccountModel{}, fmt.Errorf("model is required")
|
||||
}
|
||||
primaryModel, inferredEntry, _, _, err := inferOpenclawCatalogModel(account, modelID, model.MaxTokens, model.ContextWindow)
|
||||
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
|
||||
}
|
||||
@@ -1657,39 +1238,6 @@ func normalizeAgentAccountModelID(provider, primaryModel, requestedID string) st
|
||||
}
|
||||
}
|
||||
|
||||
func buildLegacyAgentAccountModels(account *model.AgentAccount) []dto.AgentAccountModel {
|
||||
modelIDs := make([]string, 0, 4)
|
||||
seen := make(map[string]struct{}, 4)
|
||||
appendModel := func(value string) {
|
||||
target := strings.TrimSpace(value)
|
||||
if target == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[target]; ok {
|
||||
return
|
||||
}
|
||||
seen[target] = struct{}{}
|
||||
modelIDs = append(modelIDs, target)
|
||||
}
|
||||
if account.ID > 0 {
|
||||
if agents, err := agentRepo.List(repo.WithByAccountID(account.ID)); err == nil {
|
||||
for _, agent := range agents {
|
||||
appendModel(agent.Model)
|
||||
}
|
||||
}
|
||||
}
|
||||
if definitions, ok := providerDefinitions()[account.Provider]; ok && len(definitions.Models) > 0 {
|
||||
for _, item := range definitions.Models {
|
||||
appendModel(item.ID)
|
||||
}
|
||||
}
|
||||
models := make([]dto.AgentAccountModel, 0, len(modelIDs))
|
||||
for _, modelID := range modelIDs {
|
||||
models = append(models, dto.AgentAccountModel{ID: modelID})
|
||||
}
|
||||
return models
|
||||
}
|
||||
|
||||
func sanitizeAgentAccountModelInputs(values []string) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
@@ -1872,64 +1420,6 @@ func mapToModelsConfig(value map[string]interface{}) (*modelsConfig, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func providerEnvKey(provider string) string {
|
||||
return providercatalog.EnvKey(provider)
|
||||
}
|
||||
|
||||
type providerDefinition struct {
|
||||
Sort uint
|
||||
DisplayName string
|
||||
BaseURL string
|
||||
Models []dto.ProviderModelInfo
|
||||
}
|
||||
|
||||
func providerDefinitions() map[string]providerDefinition {
|
||||
definitions := map[string]providerDefinition{}
|
||||
for key, meta := range providercatalog.All() {
|
||||
models := make([]dto.ProviderModelInfo, 0, len(meta.Models))
|
||||
for _, m := range meta.Models {
|
||||
models = append(models, dto.ProviderModelInfo{
|
||||
ID: m.ID,
|
||||
Name: m.Name,
|
||||
ContextWindow: m.ContextWindow,
|
||||
MaxTokens: m.MaxTokens,
|
||||
Reasoning: m.Reasoning,
|
||||
Input: append([]string(nil), m.Input...),
|
||||
})
|
||||
}
|
||||
definitions[key] = providerDefinition{
|
||||
Sort: meta.Sort,
|
||||
DisplayName: meta.DisplayName,
|
||||
BaseURL: meta.DefaultBaseURL,
|
||||
Models: models,
|
||||
}
|
||||
}
|
||||
return definitions
|
||||
}
|
||||
|
||||
func providerDefaultBaseURL(provider string) (string, bool) {
|
||||
return providercatalog.DefaultBaseURL(provider)
|
||||
}
|
||||
|
||||
func fixedProviderBaseURL(provider string) (string, bool) {
|
||||
switch provider {
|
||||
case "bailian-coding-plan":
|
||||
return providerDefaultBaseURL(provider)
|
||||
case "ark-coding-plan":
|
||||
return providerDefaultBaseURL(provider)
|
||||
case "minimax":
|
||||
return providerDefaultBaseURL(provider)
|
||||
case "xiaomi":
|
||||
return providerDefaultBaseURL(provider)
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func providerDisplayName(provider string) string {
|
||||
return providercatalog.DisplayName(provider)
|
||||
}
|
||||
|
||||
func readInstallEnv(envStr string) map[string]interface{} {
|
||||
if strings.TrimSpace(envStr) == "" {
|
||||
return nil
|
||||
@@ -2009,14 +1499,14 @@ func runtimeProviderModelPrefix(provider string) string {
|
||||
}
|
||||
|
||||
func poolModelPrefix(provider string) string {
|
||||
target := provider
|
||||
if definitions, ok := providerDefinitions()[target]; ok && len(definitions.Models) > 0 {
|
||||
parts := strings.SplitN(strings.TrimSpace(definitions.Models[0].ID), "/", 2)
|
||||
meta, ok := providercatalog.Get(provider)
|
||||
if ok && len(meta.Models) > 0 {
|
||||
parts := strings.SplitN(strings.TrimSpace(meta.Models[0].ID), "/", 2)
|
||||
if len(parts) == 2 && strings.TrimSpace(parts[0]) != "" {
|
||||
return parts[0]
|
||||
}
|
||||
}
|
||||
return target
|
||||
return provider
|
||||
}
|
||||
|
||||
func supportedProviderModelPrefixes(provider string) []string {
|
||||
@@ -2037,72 +1527,6 @@ func supportedProviderModelPrefixes(provider string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeAPIType(apiType string) string {
|
||||
trim := apiType
|
||||
if trim == "" {
|
||||
return "openai-completions"
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func isSupportedAPIType(apiType string) bool {
|
||||
switch normalizeAPIType(apiType) {
|
||||
case "openai-completions", "openai-responses", "anthropic-messages":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isSupportedOllamaAPIType(apiType string) bool {
|
||||
switch normalizeAPIType(apiType) {
|
||||
case "openai-completions", "openai-responses":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func resolveRuntimeParams(provider, apiType string, maxTokens, contextWindow int) (string, int, int) {
|
||||
resolvedAPI := normalizeAPIType(apiType)
|
||||
if provider == "ollama" && !isSupportedOllamaAPIType(resolvedAPI) {
|
||||
resolvedAPI = "openai-responses"
|
||||
}
|
||||
resolvedMaxTokens := maxTokens
|
||||
resolvedContextWindow := contextWindow
|
||||
if resolvedMaxTokens <= 0 {
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
resolvedMaxTokens = 8192
|
||||
case "zai":
|
||||
resolvedMaxTokens = 131072
|
||||
case "openrouter":
|
||||
resolvedMaxTokens = 8192
|
||||
case "minimax", "kimi-coding", "custom":
|
||||
resolvedMaxTokens = 8192
|
||||
default:
|
||||
resolvedMaxTokens = 8192
|
||||
}
|
||||
}
|
||||
if resolvedContextWindow <= 0 {
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
resolvedContextWindow = 128000
|
||||
case "zai":
|
||||
resolvedContextWindow = 204800
|
||||
case "openrouter":
|
||||
resolvedContextWindow = 128000
|
||||
case "minimax", "kimi-coding":
|
||||
resolvedContextWindow = 200000
|
||||
case "custom", "vllm":
|
||||
resolvedContextWindow = 128000
|
||||
default:
|
||||
resolvedContextWindow = 256000
|
||||
}
|
||||
}
|
||||
return resolvedAPI, resolvedMaxTokens, resolvedContextWindow
|
||||
}
|
||||
|
||||
func generateToken() string {
|
||||
bytes := make([]byte, 24)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
|
||||
@@ -65,7 +65,6 @@ func InitAgentDB() {
|
||||
migrations.AddCronjobArgs,
|
||||
migrations.AddWebsiteAcmeAccountColumn,
|
||||
migrations.AddAgentTables,
|
||||
migrations.MigrateOpenclawAgents,
|
||||
migrations.AddAgentCustomModelFields,
|
||||
migrations.AddAppInstallSortOrder,
|
||||
migrations.AddAgentAccountRememberAPIKey,
|
||||
|
||||
@@ -897,114 +897,6 @@ var AddAgentTables = &gormigrate.Migration{
|
||||
},
|
||||
}
|
||||
|
||||
var MigrateOpenclawAgents = &gormigrate.Migration{
|
||||
ID: "20260207-migrate-openclaw-agents",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var installs []model.AppInstall
|
||||
if err := tx.Preload("App").Find(&installs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, install := range installs {
|
||||
appKey := install.App.Key
|
||||
if appKey == "" || install.App.Resource == "" {
|
||||
var app model.App
|
||||
if err := tx.First(&app, install.AppId).Error; err == nil {
|
||||
install.App = app
|
||||
appKey = app.Key
|
||||
}
|
||||
}
|
||||
if appKey != constant.AppOpenclaw {
|
||||
continue
|
||||
}
|
||||
var count int64
|
||||
if err := tx.Model(&model.Agent{}).Where("app_install_id = ?", install.ID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
envMap := map[string]interface{}{}
|
||||
if strings.TrimSpace(install.Env) != "" {
|
||||
_ = json.Unmarshal([]byte(install.Env), &envMap)
|
||||
}
|
||||
configPath := path.Join(install.GetPath(), "data", "conf", "openclaw.json")
|
||||
cfgMeta := migrationutils.OpenclawMeta{}
|
||||
if fileData, err := os.ReadFile(configPath); err == nil {
|
||||
cfgMeta = migrationutils.ParseOpenclawMeta(fileData)
|
||||
}
|
||||
provider := strings.ToLower(migrationutils.GetEnvStr(envMap, "PROVIDER"))
|
||||
if provider == "" {
|
||||
provider = strings.ToLower(cfgMeta.Provider)
|
||||
}
|
||||
if provider == "" {
|
||||
continue
|
||||
}
|
||||
provider = migrationutils.NormalizeOpenclawProvider(provider, cfgMeta.BaseURL)
|
||||
modelName := migrationutils.GetEnvStr(envMap, "MODEL")
|
||||
if modelName == "" {
|
||||
modelName = cfgMeta.Model
|
||||
}
|
||||
baseURL := migrationutils.GetEnvStr(envMap, "BASE_URL")
|
||||
if baseURL == "" {
|
||||
baseURL = cfgMeta.BaseURL
|
||||
}
|
||||
apiKey := migrationutils.GetEnvStr(envMap, "API_KEY")
|
||||
if apiKey == "" {
|
||||
apiKey = cfgMeta.APIKey
|
||||
}
|
||||
token := migrationutils.GetEnvStr(envMap, "OPENCLAW_GATEWAY_TOKEN")
|
||||
if token == "" {
|
||||
token = cfgMeta.Token
|
||||
}
|
||||
if provider != "ollama" {
|
||||
if baseURL == "" {
|
||||
if defaultURL, ok := migrationutils.DefaultBaseURL(provider); ok {
|
||||
baseURL = defaultURL
|
||||
}
|
||||
}
|
||||
}
|
||||
if provider == "ollama" && baseURL == "" {
|
||||
continue
|
||||
}
|
||||
var account model.AgentAccount
|
||||
err := tx.Where("provider = ? AND api_key = ? AND base_url = ?", provider, apiKey, baseURL).First(&account).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
account = model.AgentAccount{
|
||||
Provider: provider,
|
||||
Name: install.Name,
|
||||
APIKey: apiKey,
|
||||
BaseURL: baseURL,
|
||||
Verified: apiKey != "" || provider == "ollama",
|
||||
}
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
agent := model.Agent{
|
||||
Name: install.Name,
|
||||
Provider: provider,
|
||||
Model: modelName,
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
Token: token,
|
||||
Status: install.Status,
|
||||
Message: install.Message,
|
||||
AppInstallID: install.ID,
|
||||
AccountID: account.ID,
|
||||
ConfigPath: configPath,
|
||||
}
|
||||
if err := tx.Create(&agent).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAgentCustomModelFields = &gormigrate.Migration{
|
||||
ID: "20260224-add-agent-custom-model-fields",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
||||
)
|
||||
|
||||
type OpenclawMeta struct {
|
||||
Provider string
|
||||
Model string
|
||||
BaseURL string
|
||||
APIKey string
|
||||
Token string
|
||||
}
|
||||
|
||||
func GetEnvStr(envMap map[string]interface{}, key string) string {
|
||||
if envMap == nil {
|
||||
return ""
|
||||
}
|
||||
if value, ok := envMap[key]; ok {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
default:
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ParseOpenclawMeta(fileData []byte) OpenclawMeta {
|
||||
meta := OpenclawMeta{}
|
||||
content := map[string]interface{}{}
|
||||
if err := json.Unmarshal(fileData, &content); err != nil {
|
||||
return meta
|
||||
}
|
||||
|
||||
meta.Token = getNestedString(content, "gateway", "auth", "token")
|
||||
meta.Model = getNestedString(content, "agents", "defaults", "model", "primary")
|
||||
|
||||
providerKey := ""
|
||||
if parts := strings.SplitN(meta.Model, "/", 2); len(parts) == 2 {
|
||||
providerKey = strings.TrimSpace(parts[0])
|
||||
}
|
||||
|
||||
providers := getNestedMap(content, "models", "providers")
|
||||
providerConfig := map[string]interface{}{}
|
||||
if providerKey != "" {
|
||||
if cfg, ok := providers[providerKey].(map[string]interface{}); ok {
|
||||
providerConfig = cfg
|
||||
}
|
||||
}
|
||||
if len(providerConfig) == 0 && len(providers) == 1 {
|
||||
for key, value := range providers {
|
||||
if cfg, ok := value.(map[string]interface{}); ok {
|
||||
providerKey = key
|
||||
providerConfig = cfg
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
meta.Provider = providerKey
|
||||
meta.BaseURL = getString(providerConfig, "baseUrl")
|
||||
meta.APIKey = getString(providerConfig, "apiKey")
|
||||
|
||||
if meta.Model == "" && providerKey != "" {
|
||||
if modelID := getProviderFirstModelID(providerConfig); modelID != "" {
|
||||
meta.Model = providerKey + "/" + modelID
|
||||
}
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
func NormalizeOpenclawProvider(provider, baseURL string) string {
|
||||
p := strings.ToLower(strings.TrimSpace(provider))
|
||||
base := strings.ToLower(strings.TrimSpace(baseURL))
|
||||
switch p {
|
||||
case "moonshot":
|
||||
if strings.Contains(base, "moonshot.cn") {
|
||||
return "kimi"
|
||||
}
|
||||
return "moonshot"
|
||||
default:
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultBaseURL(provider string) (string, bool) {
|
||||
return providercatalog.DefaultBaseURL(provider)
|
||||
}
|
||||
|
||||
func getNestedMap(data map[string]interface{}, keys ...string) map[string]interface{} {
|
||||
current := data
|
||||
for _, key := range keys {
|
||||
next, ok := current[key].(map[string]interface{})
|
||||
if !ok {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
current = next
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func getNestedString(data map[string]interface{}, keys ...string) string {
|
||||
current := data
|
||||
for i, key := range keys {
|
||||
value, ok := current[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if i == len(keys)-1 {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
default:
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
next, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
current = next
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getString(data map[string]interface{}, key string) string {
|
||||
if data == nil {
|
||||
return ""
|
||||
}
|
||||
value, ok := data[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
default:
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
func getProviderFirstModelID(data map[string]interface{}) string {
|
||||
if data == nil {
|
||||
return ""
|
||||
}
|
||||
rawModels, ok := data["models"].([]interface{})
|
||||
if !ok || len(rawModels) == 0 {
|
||||
return ""
|
||||
}
|
||||
first, ok := rawModels[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return getString(first, "id")
|
||||
}
|
||||
@@ -33,19 +33,23 @@
|
||||
</el-form-item>
|
||||
<template v-if="showInitialModel">
|
||||
<el-divider content-position="left">{{ $t('aiTools.agents.accountModels') }}</el-divider>
|
||||
<el-form-item :label="$t('aiTools.model.model')" prop="initialModel.id" :rules="[Rules.requiredInput]">
|
||||
<el-form-item :label="$t('aiTools.model.model')" prop="initialModel.id" :rules="[Rules.noSpace]">
|
||||
<el-input v-model="form.initialModel.id" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="initialModel.name">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="initialModel.name" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model="form.initialModel.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Context Window" prop="initialModel.contextWindow">
|
||||
<el-form-item label="Context Window" prop="initialModel.contextWindow" :rules="[Rules.integerNumber]">
|
||||
<el-input-number v-model="form.initialModel.contextWindow" :min="1" :max="2000000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Max Tokens" prop="initialModel.maxTokens">
|
||||
<el-form-item label="Max Tokens" prop="initialModel.maxTokens" :rules="[Rules.integerNumber]">
|
||||
<el-input-number v-model="form.initialModel.maxTokens" :min="1" :max="2000000" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('aiTools.agents.modelInputTypes')" prop="initialModel.input">
|
||||
<el-form-item
|
||||
:label="$t('aiTools.agents.modelInputTypes')"
|
||||
prop="initialModel.input"
|
||||
:rules="[Rules.requiredSelect]"
|
||||
>
|
||||
<el-checkbox-group v-model="form.initialModel.input">
|
||||
<el-checkbox label="text">Text</el-checkbox>
|
||||
<el-checkbox label="image">Image</el-checkbox>
|
||||
@@ -155,15 +159,21 @@ const normalizeInitialModel = () => {
|
||||
const item = {
|
||||
recordId: 0,
|
||||
id: String(form.initialModel.id || '').trim(),
|
||||
name: String(form.initialModel.name || form.initialModel.id || '').trim(),
|
||||
contextWindow: Number(form.initialModel.contextWindow || 0) || defaultContextWindowForProvider(form.provider),
|
||||
maxTokens: Number(form.initialModel.maxTokens || 0) || 8192,
|
||||
name: String(form.initialModel.name || '').trim(),
|
||||
contextWindow: Number(form.initialModel.contextWindow || 0),
|
||||
maxTokens: Number(form.initialModel.maxTokens || 0),
|
||||
reasoning: Boolean(form.initialModel.reasoning),
|
||||
input: Array.from(
|
||||
new Set((form.initialModel.input || []).filter((value) => value === 'text' || value === 'image')),
|
||||
),
|
||||
};
|
||||
if (item.id === '') {
|
||||
if (
|
||||
item.id === '' ||
|
||||
item.name === '' ||
|
||||
item.contextWindow <= 0 ||
|
||||
item.maxTokens <= 0 ||
|
||||
item.input.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return item;
|
||||
|
||||
@@ -121,8 +121,11 @@ const editorTitle = computed(() =>
|
||||
);
|
||||
|
||||
const rules = reactive({
|
||||
id: [Rules.requiredInput],
|
||||
id: [Rules.noSpace],
|
||||
name: [Rules.requiredInput],
|
||||
contextWindow: [Rules.integerNumber],
|
||||
maxTokens: [Rules.integerNumber],
|
||||
input: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const buttons = [
|
||||
@@ -140,8 +143,8 @@ const buildModelItem = (model?: Partial<AI.AgentAccountModel>): AI.AgentAccountM
|
||||
recordId: Number(model?.recordId || 0),
|
||||
id: String(model?.id || '').trim(),
|
||||
name: String(model?.name || '').trim(),
|
||||
contextWindow: Number(model?.contextWindow || 0) || 128000,
|
||||
maxTokens: Number(model?.maxTokens || 0) || 8192,
|
||||
contextWindow: Number(model?.contextWindow || 0),
|
||||
maxTokens: Number(model?.maxTokens || 0),
|
||||
reasoning: Boolean(model?.reasoning),
|
||||
input: Array.from(new Set((model?.input || []).filter((value) => value === 'text' || value === 'image'))),
|
||||
});
|
||||
@@ -190,9 +193,6 @@ const submit = async () => {
|
||||
MsgError(i18n.global.t('aiTools.agents.accountModelsDuplicate'));
|
||||
return;
|
||||
}
|
||||
if (!form.input?.length) {
|
||||
form.input = ['text'];
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
if (form.recordId) {
|
||||
|
||||
Reference in New Issue
Block a user