Skip to content

Commit df5d3d7

Browse files
feat: Optimize the openclaw channel bot add flow (#12507)
1 parent 3c5b3c3 commit df5d3d7

File tree

25 files changed

+342
-286
lines changed

25 files changed

+342
-286
lines changed

agent/app/dto/agents.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ type AgentTelegramConfig struct {
371371

372372
type AgentChannelPairingApproveReq struct {
373373
AgentID uint `json:"agentId" validate:"required"`
374-
Type string `json:"type" validate:"required,oneof=feishu telegram discord wecom"`
374+
Type string `json:"type" validate:"required,oneof=feishu telegram discord wecom qqbot"`
375375
PairingCode string `json:"pairingCode" validate:"required"`
376376
AccountID string `json:"accountId"`
377377
}

agent/app/provider/openclaw.go

Lines changed: 17 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ import (
55
"strings"
66
)
77

8-
type OpenClawPatch struct {
9-
PrimaryModel string
10-
Models map[string]interface{}
11-
}
12-
138
type OpenClawProviderPatch struct {
149
PrimaryModel string
1510
ProviderKey string
@@ -20,54 +15,37 @@ type OpenClawProviderPatch struct {
2015
AuthHeader bool
2116
}
2217

23-
func BuildOpenClawPatch(provider, modelName, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) (*OpenClawPatch, error) {
24-
providerPatch, err := BuildOpenClawProviderPatch(provider, modelName, apiType, baseURL, apiKey)
25-
if err != nil {
26-
return nil, err
27-
}
28-
_, maxTokens, contextWindow = ResolveRuntimeParams(provider, apiType, maxTokens, contextWindow)
29-
return newOpenClawPatch(
30-
providerPatch,
31-
resolveOpenClawModelName(provider, providerPatch.ModelID),
32-
resolveOpenClawModelInput(provider, providerPatch.ModelID),
33-
reasoning,
34-
contextWindow,
35-
maxTokens,
36-
), nil
37-
}
38-
3918
func BuildOpenClawProviderPatch(provider, modelName, apiType, baseURL, apiKey string) (*OpenClawProviderPatch, error) {
40-
modelName = strings.TrimSpace(modelName)
4119
if modelName == "" {
4220
return nil, fmt.Errorf("model is required")
4321
}
4422
resolvedAPIType, _, _ := ResolveRuntimeParams(provider, apiType, 0, 0)
4523
modelID := resolveOpenClawModelID(provider, modelName)
4624
switch provider {
4725
case "deepseek":
48-
return newOpenClawProviderPatch(modelName, "deepseek", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil
26+
return newOpenClawProviderPatch(modelName, "deepseek", modelID, apiKey, baseURL, "openai-completions", false), nil
4927
case "gemini":
50-
return newOpenClawProviderPatch("google/"+modelID, "google", modelID, strings.TrimSpace(apiKey), baseURL, resolvedAPIType, false), nil
28+
return newOpenClawProviderPatch("google/"+modelID, "google", modelID, apiKey, baseURL, resolvedAPIType, false), nil
5129
case "moonshot", "kimi":
5230
return buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey), nil
5331
case "bailian-coding-plan":
54-
return newOpenClawProviderPatch("bailian-coding-plan/"+modelID, "bailian-coding-plan", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil
32+
return newOpenClawProviderPatch("bailian-coding-plan/"+modelID, "bailian-coding-plan", modelID, apiKey, baseURL, "openai-completions", false), nil
5533
case "ark-coding-plan":
56-
return newOpenClawProviderPatch("ark-coding-plan/"+modelID, "ark-coding-plan", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil
34+
return newOpenClawProviderPatch("ark-coding-plan/"+modelID, "ark-coding-plan", modelID, apiKey, baseURL, "openai-completions", false), nil
5735
case "minimax":
58-
return newOpenClawProviderPatch("minimax/"+modelID, "minimax", modelID, strings.TrimSpace(apiKey), baseURL, "anthropic-messages", true), nil
36+
return newOpenClawProviderPatch("minimax/"+modelID, "minimax", modelID, apiKey, baseURL, "anthropic-messages", true), nil
5937
case "xiaomi":
60-
return newOpenClawProviderPatch("xiaomi/"+modelID, "xiaomi", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil
38+
return newOpenClawProviderPatch("xiaomi/"+modelID, "xiaomi", modelID, apiKey, baseURL, "openai-completions", false), nil
6139
case "custom", "vllm":
62-
return newOpenClawProviderPatch(provider+"/"+modelID, provider, modelID, strings.TrimSpace(apiKey), strings.TrimSpace(baseURL), resolvedAPIType, false), nil
40+
return newOpenClawProviderPatch(provider+"/"+modelID, provider, modelID, apiKey, baseURL, resolvedAPIType, false), nil
6341
case "ollama":
64-
return newOpenClawProviderPatch(modelName, "ollama", modelID, "ollama", strings.TrimSpace(baseURL), resolvedAPIType, false), nil
42+
return newOpenClawProviderPatch(modelName, "ollama", modelID, "ollama", baseURL, resolvedAPIType, false), nil
6543
case "kimi-coding":
66-
return newOpenClawProviderPatch(modelName, "kimi-coding", modelID, strings.TrimSpace(apiKey), baseURL, "anthropic-messages", false), nil
44+
return newOpenClawProviderPatch(modelName, "kimi-coding", modelID, apiKey, baseURL, "anthropic-messages", false), nil
6745
case "zai":
68-
return newOpenClawProviderPatch("zai/"+modelID, "zai", modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false), nil
46+
return newOpenClawProviderPatch("zai/"+modelID, "zai", modelID, apiKey, baseURL, "openai-completions", false), nil
6947
default:
70-
return newOpenClawProviderPatch(modelName, provider, modelID, strings.TrimSpace(apiKey), baseURL, resolvedAPIType, false), nil
48+
return newOpenClawProviderPatch(modelName, provider, modelID, apiKey, baseURL, resolvedAPIType, false), nil
7149
}
7250
}
7351

@@ -78,7 +56,7 @@ func buildMoonshotProviderPatch(provider, modelName, modelID, baseURL, apiKey st
7856
providerKey = "moonshot"
7957
primaryModel = "moonshot/" + modelID
8058
}
81-
return newOpenClawProviderPatch(primaryModel, providerKey, modelID, strings.TrimSpace(apiKey), baseURL, "openai-completions", false)
59+
return newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, "openai-completions", false)
8260
}
8361

8462
func newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseURL, apiType string, authHeader bool) *OpenClawProviderPatch {
@@ -93,84 +71,16 @@ func newOpenClawProviderPatch(primaryModel, providerKey, modelID, apiKey, baseUR
9371
}
9472
}
9573

96-
func newOpenClawPatch(providerPatch *OpenClawProviderPatch, modelName string, input []string, reasoning bool, contextWindow, maxTokens int) *OpenClawPatch {
97-
model := map[string]interface{}{
98-
"id": providerPatch.ModelID,
99-
"name": modelName,
100-
"reasoning": reasoning,
101-
"input": input,
102-
"contextWindow": contextWindow,
103-
"maxTokens": maxTokens,
104-
"cost": map[string]interface{}{},
105-
}
106-
providerConfig := map[string]interface{}{
107-
"apiKey": providerPatch.APIKey,
108-
"baseUrl": providerPatch.BaseURL,
109-
"api": providerPatch.APIType,
110-
"models": []map[string]interface{}{model},
111-
}
112-
if providerPatch.AuthHeader {
113-
providerConfig["authHeader"] = true
114-
}
115-
return &OpenClawPatch{
116-
PrimaryModel: providerPatch.PrimaryModel,
117-
Models: map[string]interface{}{
118-
"mode": "merge",
119-
"providers": map[string]interface{}{
120-
providerPatch.ProviderKey: providerConfig,
121-
},
122-
},
123-
}
124-
}
125-
12674
func resolveOpenClawModelID(provider, modelName string) string {
12775
if provider == "custom" || provider == "vllm" {
128-
return normalizeCustomModel(modelName)
76+
target := strings.TrimLeft(modelName, "/")
77+
if parts := strings.SplitN(target, "/", 2); len(parts) == 2 && parts[0] == "custom" {
78+
return strings.TrimLeft(parts[1], "/")
79+
}
80+
return target
12981
}
13082
if parts := strings.SplitN(modelName, "/", 2); len(parts) == 2 {
13183
return parts[1]
13284
}
13385
return modelName
13486
}
135-
136-
func resolveOpenClawModelName(provider, modelID string) string {
137-
if item, ok := resolveOpenClawCatalogModel(provider, modelID); ok {
138-
return item.Name
139-
}
140-
if provider == "kimi-coding" {
141-
return "Kimi for Coding"
142-
}
143-
return modelID
144-
}
145-
146-
func resolveOpenClawModelInput(provider, modelID string) []string {
147-
if item, ok := resolveOpenClawCatalogModel(provider, modelID); ok && len(item.Input) > 0 {
148-
return item.Input
149-
}
150-
if provider == "kimi-coding" {
151-
return []string{"text", "image"}
152-
}
153-
return []string{"text"}
154-
}
155-
156-
func resolveOpenClawCatalogModel(provider, modelID string) (Model, bool) {
157-
candidates := []string{provider + "/" + modelID}
158-
if provider == "gemini" {
159-
candidates = append(candidates, "google/"+modelID)
160-
}
161-
for _, candidate := range candidates {
162-
if item, ok := FindModel(provider, candidate); ok {
163-
return item, true
164-
}
165-
}
166-
return Model{}, false
167-
}
168-
169-
func normalizeCustomModel(modelName string) string {
170-
trim := strings.TrimSpace(modelName)
171-
trim = strings.TrimLeft(trim, "/")
172-
if parts := strings.SplitN(trim, "/", 2); len(parts) == 2 && strings.EqualFold(parts[0], "custom") {
173-
return strings.TrimLeft(strings.TrimSpace(parts[1]), "/")
174-
}
175-
return trim
176-
}

agent/app/service/agents.go

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ type IAgentService interface {
9595
}
9696

9797
const (
98-
defaultBrowserExecutablePath = "/home/node/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome"
98+
defaultBrowserExecutablePath = "/home/node/.cache/ms-playwright/openclaw-browser"
9999
defaultBrowserProfile = "openclaw"
100100
defaultUserTimezone = "Asia/Shanghai"
101101
defaultToolsProfile = "full"
@@ -114,9 +114,6 @@ const (
114114

115115
func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
116116
agentType := req.AgentType
117-
if agentType != constant.AppOpenclaw && agentType != constant.AppCopaw && agentType != constant.AppHermesAgent {
118-
return nil, fmt.Errorf("unsupported agent type: %s", agentType)
119-
}
120117
if err := checkPortExist(req.WebUIPort); err != nil {
121118
return nil, err
122119
}
@@ -516,8 +513,8 @@ func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
516513
return err
517514
}
518515
} else {
519-
if agent, err = loadOpenclawAgentByID(req.AgentID); err != nil {
520-
return err
516+
if agent.AgentType != constant.AppOpenclaw {
517+
return fmt.Errorf("%s does not support", agent.AgentType)
521518
}
522519
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks); err != nil {
523520
return err
@@ -1068,21 +1065,15 @@ func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
10681065
return nil
10691066
}
10701067
for _, agent := range agents {
1071-
modelName := strings.TrimSpace(agent.Model)
1072-
var selectedAccountModel dto.AgentAccountModel
1073-
if modelName != "" {
1074-
selectedAccountModel, err = requireAgentAccountModelForProvider(account.Provider, accountModels, modelName)
1075-
if err != nil {
1076-
return buserr.WithName("ErrAgentModelInUse", agent.Name)
1077-
}
1078-
} else {
1079-
selectedAccountModel = accountModels[0]
1068+
selectedAccountModel, err := requireAgentAccountModelForProvider(account.Provider, accountModels, agent.Model)
1069+
if err != nil {
1070+
return buserr.WithName("ErrAgentModelInUse", agent.Name)
10801071
}
10811072
resolvedRuntime, err := buildOpenclawAccountModelRuntime(account, selectedAccountModel)
10821073
if err != nil {
10831074
return err
10841075
}
1085-
modelName = resolvedRuntime.StoredModel
1076+
modelName := resolvedRuntime.StoredModel
10861077
apiType, maxTokens, contextWindow := resolvedRuntime.APIType, resolvedRuntime.MaxTokens, resolvedRuntime.ContextWindow
10871078
confDir := path.Dir(agent.ConfigPath)
10881079
switch agent.AgentType {

agent/app/service/agents_channels.go

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig {
570570
RequireMention: "true",
571571
GroupPolicy: "open",
572572
GroupAllowFrom: []string{},
573-
Bots: []dto.AgentFeishuBot{defaultFeishuBot()},
573+
Bots: []dto.AgentFeishuBot{},
574574
}
575575
feishu := getChannelConfig(conf, "feishu")
576576
if len(feishu) == 0 {
@@ -620,7 +620,10 @@ func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig {
620620
baseEnabled := defaultBot.Enabled
621621
baseDmPolicy := defaultBot.DmPolicy
622622
baseAllowFrom := append([]string(nil), defaultBot.AllowFrom...)
623-
bots := []dto.AgentFeishuBot{defaultBot}
623+
bots := make([]dto.AgentFeishuBot, 0, len(accounts)+1)
624+
if defaultBot.AppID != "" || defaultBot.AppSecret != "" {
625+
bots = append(bots, defaultBot)
626+
}
624627
for _, accountID := range sortedChildKeys(accounts) {
625628
if accountID == "default" {
626629
continue
@@ -942,31 +945,34 @@ func extractQQBotConfig(conf map[string]interface{}) dto.AgentQQBotConfig {
942945
result := dto.AgentQQBotConfig{Enabled: true}
943946
qqbot := getChannelConfig(conf, "qqbot")
944947
if len(qqbot) == 0 {
945-
result.Bots = []dto.AgentQQBotBot{defaultQQBot()}
948+
result.Bots = []dto.AgentQQBotBot{}
946949
return result
947950
}
948951
if enabled, ok := qqbot["enabled"].(bool); ok {
949952
result.Enabled = enabled
950953
}
951-
bots := []dto.AgentQQBotBot{
952-
{
953-
AgentChannelBotBase: dto.AgentChannelBotBase{
954-
AccountID: "default",
955-
Name: extractStringValue(qqbot["name"]),
956-
Enabled: extractBoolValue(qqbot["enabled"], true),
957-
IsDefault: true,
958-
},
959-
AppID: extractStringValue(qqbot["appId"]),
960-
ClientSecret: extractStringValue(qqbot["clientSecret"]),
961-
AllowFrom: extractStringList(qqbot["allowFrom"]),
962-
SystemPrompt: extractStringValue(qqbot["systemPrompt"]),
954+
bots := make([]dto.AgentQQBotBot, 0, len(childMap(qqbot, "accounts"))+1)
955+
defaultBot := dto.AgentQQBotBot{
956+
AgentChannelBotBase: dto.AgentChannelBotBase{
957+
AccountID: "default",
958+
Name: extractStringValue(qqbot["name"]),
959+
Enabled: extractBoolValue(qqbot["enabled"], true),
960+
IsDefault: true,
963961
},
962+
AppID: extractStringValue(qqbot["appId"]),
963+
ClientSecret: extractStringValue(qqbot["clientSecret"]),
964+
AllowFrom: extractStringList(qqbot["allowFrom"]),
965+
SystemPrompt: extractStringValue(qqbot["systemPrompt"]),
966+
}
967+
if defaultBot.Name == "" {
968+
defaultBot.Name = "Default"
964969
}
965-
if bots[0].Name == "" {
966-
bots[0].Name = "Default"
970+
if defaultBot.AppID != "" || defaultBot.ClientSecret != "" {
971+
bots = append(bots, defaultBot)
967972
}
968-
for _, accountID := range sortedChildKeys(childMap(qqbot, "accounts")) {
969-
account := childMap(childMap(qqbot, "accounts"), accountID)
973+
accounts := childMap(qqbot, "accounts")
974+
for _, accountID := range sortedChildKeys(accounts) {
975+
account := childMap(accounts, accountID)
970976
bots = append(bots, dto.AgentQQBotBot{
971977
AgentChannelBotBase: dto.AgentChannelBotBase{
972978
AccountID: accountID,

agent/app/service/agents_hermes.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func writeHermesConfig(confDir string, account *model.AgentAccount, modelName st
7878
cfg["model"] = map[string]interface{}{
7979
"default": resolveHermesModel(account.Provider, provider, modelName),
8080
"provider": provider,
81-
"base_url": strings.TrimSpace(account.BaseURL),
81+
"base_url": account.BaseURL,
8282
}
8383
cfg["terminal"] = map[string]interface{}{
8484
"backend": "local",
@@ -390,8 +390,8 @@ func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
390390
if account == nil {
391391
return nil
392392
}
393-
apiKey := strings.TrimSpace(account.APIKey)
394-
baseURL := strings.TrimSpace(account.BaseURL)
393+
apiKey := account.APIKey
394+
baseURL := account.BaseURL
395395
entries := make([]hermesEnvEntry, 0, 4)
396396

397397
appendEntry := func(key, value string) {

agent/app/service/agents_utils.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,14 +1006,10 @@ func buildOpenclawModelsFromAccount(account *model.AgentAccount, selectedModel s
10061006
return "", nil, nil, err
10071007
}
10081008
if len(accountModels) == 0 {
1009-
return "", nil, nil, fmt.Errorf("model is required")
1010-
}
1011-
selectedModel = strings.TrimSpace(selectedModel)
1012-
if selectedModel == "" {
1013-
selectedModel = strings.TrimSpace(accountModels[0].ID)
1009+
return "", nil, nil, buserr.New("ErrAgentModelNotInAccount")
10141010
}
10151011
if selectedModel == "" {
1016-
return "", nil, nil, fmt.Errorf("model is required")
1012+
return "", nil, nil, buserr.New("ErrAgentModelNotInAccount")
10171013
}
10181014
selectedAccountModel, err := requireAgentAccountModelForProvider(account.Provider, accountModels, selectedModel)
10191015
if err != nil {
@@ -1464,9 +1460,6 @@ func ensureAccountModelsNotBound(account *model.AgentAccount, models []dto.Agent
14641460
return err
14651461
}
14661462
for _, agent := range agents {
1467-
if strings.TrimSpace(agent.Model) == "" {
1468-
continue
1469-
}
14701463
if _, ok := findAgentAccountModelForProvider(account.Provider, models, agent.Model); !ok {
14711464
return buserr.WithName("ErrAgentModelInUse", agent.Name)
14721465
}

frontend/src/api/interface/ai.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@ export namespace AI {
704704

705705
export interface AgentQQBotConfig {
706706
enabled: boolean;
707-
dmPolicy?: 'open' | 'allowlist' | 'disabled';
707+
dmPolicy?: 'pairing' | 'open' | 'allowlist' | 'disabled';
708708
allowFrom?: string[];
709709
groupPolicy?: 'open' | 'allowlist' | 'disabled';
710710
groupAllowFrom?: string[];
@@ -715,7 +715,7 @@ export namespace AI {
715715
export interface AgentQQBotConfigUpdateReq {
716716
agentId: number;
717717
enabled: boolean;
718-
dmPolicy?: 'open' | 'allowlist' | 'disabled';
718+
dmPolicy?: 'pairing' | 'open' | 'allowlist' | 'disabled';
719719
allowFrom?: string[];
720720
groupPolicy?: 'open' | 'allowlist' | 'disabled';
721721
groupAllowFrom?: string[];

frontend/src/lang/modules/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,8 +801,10 @@ const message = {
801801
bots: 'Bots',
802802
addBot: 'Add Bot',
803803
accountId: 'Account ID',
804+
accountIdPlaceholder: 'A unique ID used to distinguish bots and for later config and routing.',
804805
setDefaultBot: 'Set as Default',
805806
botDuplicateAccountId: 'Account ID already exists',
807+
botDuplicateField: '{field} already exists',
806808
botRequired: 'Add at least one bot',
807809
botId: 'Bot ID',
808810
allowFrom: 'DM Allowlist',

0 commit comments

Comments
 (0)