diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ecce93a166..477b59398283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,6 +131,7 @@ Docs: https://docs.openclaw.ai - Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside emitted Google provider model config, so regenerated models.json rows test `google/gemini-3.1-pro-preview`. - Google/Gemini: normalize retired Gemini 3 Pro Preview ids for explicit OpenAI-compatible Google and Gemini CLI provider configs, so emitted config targets `google/gemini-3.1-pro-preview`. - Google/Gemini: normalize retired Gemini 3 Pro Preview ids preserved from existing merged models.json providers so config emission keeps targeting `google/gemini-3.1-pro-preview`. +- Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside provider auth config patches so setup-emitted provider catalogs test `google/gemini-3.1-pro-preview`. - GitHub Copilot: mint short-lived Copilot API tokens with the same `vscode-chat` integration identity used by runtime requests, and refresh legacy cached tokens missing that identity so image-capable Copilot models no longer inherit the `copilot-language-server` scope. Fixes #79946, #80074. Thanks @TurboTheTurtle. - Plugins/doctor: drop stale managed npm install records when `openclaw doctor --fix` removes npm packages that shadow bundled plugins, so the rebuilt registry no longer resurrects the removed package metadata. - Discord/voice: reuse or suppress late realtime consult tool calls without stealing newer speaker context or speaking forced fallback answers twice. diff --git a/src/plugins/provider-auth-choice-helpers.test.ts b/src/plugins/provider-auth-choice-helpers.test.ts index 77fb3d1c5aeb..9efe8523d14b 100644 --- a/src/plugins/provider-auth-choice-helpers.test.ts +++ b/src/plugins/provider-auth-choice-helpers.test.ts @@ -154,6 +154,36 @@ describe("applyProviderAuthConfigPatch", () => { "google/gemini-3.1-pro-preview": {}, }); }); + + it("normalizes retired Google Gemini provider catalog rows from provider config patches", () => { + const patch = { + models: { + providers: { + google: { + baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai", + api: "openai-completions", + apiKey: "GOOGLE_API_KEY", + models: [ + { + id: "google/gemini-3-pro-preview", + name: "Gemini 3 Pro Preview", + 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?.google?.models?.[0]?.id).toBe("google/gemini-3.1-pro-preview"); + expect(next.models?.providers?.google?.api).toBe("openai-completions"); + }); }); describe("applyDefaultModel", () => { diff --git a/src/plugins/provider-auth-choice-helpers.ts b/src/plugins/provider-auth-choice-helpers.ts index 4bca4ab2134a..df6e682b63e4 100644 --- a/src/plugins/provider-auth-choice-helpers.ts +++ b/src/plugins/provider-auth-choice-helpers.ts @@ -3,6 +3,8 @@ import { normalizeAgentModelMapForConfig, normalizeAgentModelRefForConfig, } from "../config/model-input.js"; +import { normalizeProviderConfigForConfigDefaults } from "../config/provider-policy.js"; +import type { ModelProviderConfig } from "../config/types.models.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { normalizeLowercaseStringOrEmpty, @@ -119,12 +121,96 @@ function normalizeAgentModelMapForWrite(value: unknown): unknown { return normalizeAgentModelMapForConfig(value); } -function normalizeConfigModelRefsForWrite(cfg: OpenClawConfig): OpenClawConfig { - const defaults = cfg.agents?.defaults; - if (!defaults) { +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; +} + +function normalizeProviderCatalogModelIdsForWrite( + provider: string, + providerConfig: ModelProviderConfig, +): ModelProviderConfig { + const models = providerConfig.models; + if (!Array.isArray(models) || models.length === 0) { + return providerConfig; + } + + let mutated = false; + const nextModels = models.map((model) => { + const nextId = normalizeProviderCatalogModelIdForWrite(provider, model.id); + if (nextId === model.id) { + return model; + } + mutated = true; + return Object.assign({}, model, { id: nextId }); + }); + + return mutated ? { ...providerConfig, models: nextModels } : providerConfig; +} + +function normalizeModelProviderConfigsForWrite(cfg: OpenClawConfig): OpenClawConfig { + const providers = cfg.models?.providers; + if (!providers) { return cfg; } + let mutated = false; + const nextProviders = { ...providers }; + for (const [provider, providerConfig] of Object.entries(providers)) { + const normalizedProviderConfig = normalizeProviderCatalogModelIdsForWrite( + provider, + normalizeProviderConfigForConfigDefaults({ + provider, + providerConfig, + }), + ); + if (normalizedProviderConfig === providerConfig) { + continue; + } + nextProviders[provider] = normalizedProviderConfig; + mutated = true; + } + + if (!mutated) { + return cfg; + } + + return { + ...cfg, + models: { + ...cfg.models, + providers: nextProviders, + }, + }; +} + +function normalizeConfigModelRefsForWrite(cfg: OpenClawConfig): OpenClawConfig { + const providerNormalized = normalizeModelProviderConfigsForWrite(cfg); + const defaults = providerNormalized.agents?.defaults; + if (!defaults) { + return providerNormalized; + } + const nextDefaults: NonNullable["defaults"]> = { ...defaults, }; @@ -136,9 +222,9 @@ function normalizeConfigModelRefsForWrite(cfg: OpenClawConfig): OpenClawConfig { } return { - ...cfg, + ...providerNormalized, agents: { - ...cfg.agents, + ...providerNormalized.agents, defaults: nextDefaults, }, };