diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d58f5bd15..08710bc498e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Docs: https://docs.openclaw.ai - Memory: keep `memory_search` result `corpus` labels aligned with the hit source, so session transcript hits surface as `sessions` and memory-file hits stay `memory`. Fixes #72885. (#71898, #72886) Thanks @rubencu. - Codex app-server: default native plugin app tool approvals to automatic so non-destructive read tools run when destructive actions are disabled. - Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids while converting manifest catalog rows into emitted provider config, so `google/gemini-3.1-pro-preview` is used for testing instead of `google/gemini-3-pro-preview`. +- Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids in configured proxy/provider-auth model catalogs, so regenerated config keeps testing `google/gemini-3.1-pro-preview` instead of `google/gemini-3-pro-preview`. - Native apps: advertise the Gateway protocol compatibility range so chat and node sessions can connect to v3 gateways after additive v4 client updates. - Gateway/agents: keep stale `sessions_send` ACP manager and `web_fetch` runtime chunks importable after package updates, preventing live gateways from breaking before restart. Fixes #78804. Thanks @Gomesy72. - Gateway/install: preserve service environment value-source metadata in `openclaw gateway install`, so systemd reinstall paths keep env-file-backed secrets out of inline unit metadata. Refs #77406, #77427. Thanks @stainlu and @brokemac79. diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 3f9e0999c4c..dbe89fd97b7 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -1,4 +1,5 @@ import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js"; +import { normalizeConfiguredProviderCatalogModelId } from "../agents/model-ref-shared.js"; import { normalizeProviderId } from "../agents/provider-id.js"; import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js"; @@ -172,6 +173,10 @@ export function applyModelDefaults( const nextModels = models.map((model) => { const raw = model as ModelDefinitionLike; let modelMutated = false; + const id = normalizeConfiguredProviderCatalogModelId(providerId, raw.id); + if (id !== raw.id) { + modelMutated = true; + } const reasoning = typeof raw.reasoning === "boolean" ? raw.reasoning : false; if (raw.reasoning !== reasoning) { @@ -205,7 +210,7 @@ export function applyModelDefaults( const rawMaxTokens = isPositiveNumber(raw.maxTokens) ? raw.maxTokens : defaultMaxTokens; const maxTokens = resolveNormalizedProviderModelMaxTokens({ providerId, - modelId: raw.id, + modelId: id, contextWindow, rawMaxTokens, }); @@ -222,6 +227,7 @@ export function applyModelDefaults( } providerMutated = true; return Object.assign({}, raw, { + id, reasoning, input, cost, diff --git a/src/config/model-alias-defaults.test.ts b/src/config/model-alias-defaults.test.ts index f837baeee59..dc4545dd632 100644 --- a/src/config/model-alias-defaults.test.ts +++ b/src/config/model-alias-defaults.test.ts @@ -224,6 +224,17 @@ describe("applyModelDefaults", () => { expect(next.models?.providers?.google?.models?.[0]?.id).toBe("google/gemini-3.1-pro-preview"); }); + it("normalizes nested retired Gemini ids in proxy provider rows", () => { + const cfg = buildProxyProviderConfig(); + const model = cfg.models.providers.myproxy.models[0]; + model.id = "google/gemini-3-pro-preview"; + model.name = "Gemini via proxy"; + + const next = applyModelDefaults(cfg); + + expect(next.models?.providers?.myproxy?.models?.[0]?.id).toBe("google/gemini-3.1-pro-preview"); + }); + it("fills missing model provider defaults", () => { const cfg = buildProxyProviderConfig(); diff --git a/src/plugins/provider-auth-choice-helpers.test.ts b/src/plugins/provider-auth-choice-helpers.test.ts index 9efe8523d14..df40e548cff 100644 --- a/src/plugins/provider-auth-choice-helpers.test.ts +++ b/src/plugins/provider-auth-choice-helpers.test.ts @@ -184,6 +184,35 @@ describe("applyProviderAuthConfigPatch", () => { expect(next.models?.providers?.google?.models?.[0]?.id).toBe("google/gemini-3.1-pro-preview"); expect(next.models?.providers?.google?.api).toBe("openai-completions"); }); + + it("normalizes nested retired Gemini provider catalog rows from proxy config patches", () => { + const patch = { + models: { + providers: { + kilocode: { + baseUrl: "https://proxy.example/v1", + api: "openai-completions", + apiKey: "KILOCODE_API_KEY", + models: [ + { + id: "google/gemini-3-pro-preview", + name: "Gemini via Kilo", + input: ["text", "image"], + reasoning: true, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1_048_576, + maxTokens: 65_536, + }, + ], + }, + }, + }, + } satisfies OpenClawConfig; + + const next = applyProviderAuthConfigPatch({}, patch); + + expect(next.models?.providers?.kilocode?.models?.[0]?.id).toBe("google/gemini-3.1-pro-preview"); + }); }); describe("applyDefaultModel", () => { diff --git a/src/plugins/provider-auth-choice-helpers.ts b/src/plugins/provider-auth-choice-helpers.ts index df6e682b63e..1b663514b96 100644 --- a/src/plugins/provider-auth-choice-helpers.ts +++ b/src/plugins/provider-auth-choice-helpers.ts @@ -1,3 +1,4 @@ +import { normalizeConfiguredProviderCatalogModelId } from "../agents/model-ref-shared.js"; import { normalizeProviderId } from "../agents/model-selection.js"; import { normalizeAgentModelMapForConfig, @@ -121,29 +122,12 @@ function normalizeAgentModelMapForWrite(value: unknown): unknown { return normalizeAgentModelMapForConfig(value); } -const GOOGLE_CONFIG_MODEL_PROVIDERS = new Set(["google", "google-gemini-cli", "google-vertex"]); - function normalizeProviderCatalogModelIdForWrite(provider: string, modelId: string): string { const trimmed = modelId.trim(); if (!trimmed) { return trimmed; } - - const slash = trimmed.indexOf("/"); - if (slash > 0 && slash < trimmed.length - 1) { - return normalizeAgentModelRefForConfig(trimmed); - } - - const providerId = normalizeProviderId(provider); - if (!GOOGLE_CONFIG_MODEL_PROVIDERS.has(providerId)) { - return trimmed; - } - - const normalizedQualified = normalizeAgentModelRefForConfig(`${providerId}/${trimmed}`); - const prefix = `${providerId}/`; - return normalizedQualified.startsWith(prefix) - ? normalizedQualified.slice(prefix.length) - : normalizedQualified; + return normalizeConfiguredProviderCatalogModelId(normalizeProviderId(provider), trimmed); } function normalizeProviderCatalogModelIdsForWrite(