Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions agent/app/provider/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,9 @@ var catalog = map[string]Meta{
EnvKey: "GEMINI_API_KEY",
Enabled: true,
Models: []Model{
{ID: "google/gemini-1.5-flash", Name: "Gemini 1.5 Flash"},
{ID: "google/gemini-1.5-pro", Name: "Gemini 1.5 Pro"},
{ID: "google/gemini-2.0-flash", Name: "Gemini 2.0 Flash"},
{ID: "google/gemini-2.5-flash", Name: "Gemini 2.5 Flash"},
{ID: "google/gemini-2.5-pro", Name: "Gemini 2.5 Pro"},
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview"},
{ID: "google/gemini-flash-latest", Name: "Gemini Flash Latest"},
{ID: "google/gemini-3-pro-preview", Name: "Gemini 3 Pro Preview"},
},
},
"moonshot": {
Expand Down
6 changes: 6 additions & 0 deletions agent/app/provider/openclaw.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func BuildOpenClawPatch(provider, modelName, apiType string, maxTokens, contextW
switch provider {
case "deepseek":
return buildDeepseekPatch(modelName, baseURL, apiKey), nil
case "gemini":
return buildGeminiPatch(modelName), nil
case "moonshot", "kimi":
return buildMoonshotPatch(provider, modelName, modelID, baseURL, apiKey), nil
case "bailian-coding-plan":
Expand All @@ -45,6 +47,10 @@ func BuildOpenClawPatch(provider, modelName, apiType string, maxTokens, contextW
}
}

func buildGeminiPatch(modelName string) *OpenClawPatch {
return &OpenClawPatch{PrimaryModel: modelName}
Comment on lines +50 to +51

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep Gemini provider config synced when patching OpenClaw

This Gemini patch no longer includes Models, and writeOpenclawConfig only rewrites conf["models"] when patch.Models is non-nil. For upgraded agents that already have models.providers.google from the previous behavior, changing Gemini accounts now leaves stale API/baseURL values in openclaw.json, so runtime credentials can remain out of sync with the selected account.

Useful? React with 👍 / 👎.

}

func buildDeepseekPatch(modelName, baseURL, apiKey string) *OpenClawPatch {
return &OpenClawPatch{
PrimaryModel: modelName,
Expand Down
14 changes: 12 additions & 2 deletions agent/app/provider/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,21 @@
request.URL = base + "/v1/models"
}
case "gemini":
request.Method = http.MethodPost
if strings.Contains(base, "/v1beta") {
request.URL = fmt.Sprintf("%s/models?key=%s", base, apiKey)
request.URL = base + "/models/gemini-3-flash-preview:generateContent"
} else {
request.URL = fmt.Sprintf("%s/v1beta/models?key=%s", base, apiKey)
request.URL = base + "/v1beta/models/gemini-3-flash-preview:generateContent"
Comment on lines +74 to +76

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Verify Gemini keys with a generally available model

This verification flow hard-codes gemini-3-flash-preview as the probe model, so keys that are valid but lack access to that preview SKU (while still being usable with supported catalog models like google/gemini-flash-latest) will fail verification with 403/404 and be rejected during account create/update.

Useful? React with 👍 / 👎.

}
headers["x-goog-api-key"] = apiKey
headers["Content-Type"] = "application/json"

Check failure on line 79 in agent/app/provider/verify.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "application/json" 4 times.

See more on https://sonarcloud.io/project/issues?id=1Panel-dev_1Panel&issues=AZzgx1C2Bs171Xl6ZGud&open=AZzgx1C2Bs171Xl6ZGud&pullRequest=12155

Check failure on line 79 in agent/app/provider/verify.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Content-Type" 4 times.

See more on https://sonarcloud.io/project/issues?id=1Panel-dev_1Panel&issues=AZzgx1C2Bs171Xl6ZGuc&open=AZzgx1C2Bs171Xl6ZGuc&pullRequest=12155
request.Body = mustJSON(map[string]interface{}{
"contents": []map[string]interface{}{{
"parts": []map[string]string{{
"text": "Explain how AI works in a few words",
}},
}},
})
case "zai":
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
request.URL = base + "/models"
Expand Down
16 changes: 15 additions & 1 deletion agent/app/service/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@
return agentRepo.Save(agent)
}

func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error {

Check failure on line 364 in agent/app/service/agents.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 22 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=1Panel-dev_1Panel&issues=AZzgx1BvBs171Xl6ZGub&open=AZzgx1BvBs171Xl6ZGub&pullRequest=12155
agent, err := agentRepo.GetFirst(repo.WithByID(req.AgentID))
if err != nil {
return err
Expand All @@ -384,7 +384,7 @@
if modelName == "" {
return buserr.New("ErrAgentProviderMismatch")
}
if provider != "custom" && provider != "vllm" && !strings.HasPrefix(modelName, provider+"/") {
if provider != "custom" && provider != "vllm" && !modelMatchesProvider(provider, modelName) {
return buserr.New("ErrAgentProviderMismatch")
}
baseURL := strings.TrimSpace(account.BaseURL)
Expand Down Expand Up @@ -1950,6 +1950,20 @@
return trim
}

func modelMatchesProvider(provider, modelName string) bool {
prefix := providerModelPrefix(provider)
return prefix != "" && strings.HasPrefix(strings.TrimSpace(modelName), prefix+"/")
}

func providerModelPrefix(provider string) string {
switch strings.ToLower(strings.TrimSpace(provider)) {
case "gemini":
return "google"
default:
return strings.ToLower(strings.TrimSpace(provider))
}
}

func isSupportedAgentType(agentType string) bool {
switch normalizeAgentType(agentType) {
case constant.AppOpenclaw, constant.AppCopaw:
Expand Down
18 changes: 15 additions & 3 deletions frontend/src/views/ai/agents/agent/config/tabs/model.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
const modelOptions = ref<AI.ProviderModelInfo[]>([]);
const provdier = ref('');

const getModelPrefix = (provider: string) => {
switch (provider) {

Check warning on line 56 in frontend/src/views/ai/agents/agent/config/tabs/model.vue

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this "switch" statement by "if" statements to increase readability.

See more on https://sonarcloud.io/project/issues?id=1Panel-dev_1Panel&issues=AZzgx09PBs171Xl6ZGua&open=AZzgx09PBs171Xl6ZGua&pullRequest=12155
case 'gemini':
return 'google';
default:
return provider;
}
};

const form = reactive({
accountId: undefined as unknown as number,
manualModel: false,
Expand Down Expand Up @@ -128,8 +137,9 @@
setModelsByProvider(selected.provider);
return;
}
const modelPrefix = getModelPrefix(selected.provider);
setModelsByProvider(selected.provider);
if (!form.manualModel && (!form.model || !form.model.startsWith(`${selected.provider}/`))) {
if (!form.manualModel && (!form.model || !form.model.startsWith(`${modelPrefix}/`))) {
form.model = modelOptions.value.length > 0 ? modelOptions.value[0].id : '';
}
};
Expand All @@ -150,7 +160,8 @@
form.model = '';
return;
}
if (!form.model || !form.model.startsWith(`${selected.provider}/`)) {
const modelPrefix = getModelPrefix(selected.provider);
if (!form.model || !form.model.startsWith(`${modelPrefix}/`)) {
form.model = modelOptions.value.length > 0 ? modelOptions.value[0].id : '';
}
};
Expand All @@ -172,13 +183,14 @@
form.accountId = currentAccount.id;
provdier.value = currentAccount.provider;
setModelsByProvider(currentAccount.provider);
const modelPrefix = getModelPrefix(currentAccount.provider);
const inProviderModels = modelOptions.value.some((item) => item.id === agent.model);
form.manualModel =
currentAccount.provider === 'custom' ||
currentAccount.provider === 'vllm' ||
currentAccount.provider === 'ollama' ||
!inProviderModels;
if (agent.model && (form.manualModel || agent.model.startsWith(`${currentAccount.provider}/`))) {
if (agent.model && (form.manualModel || agent.model.startsWith(`${modelPrefix}/`))) {
form.model = agent.model;
} else {
form.model = modelOptions.value.length > 0 ? modelOptions.value[0].id : '';
Expand Down
Loading