fix: normalize gemini auth provider config

This commit is contained in:
Peter Steinberger
2026-05-10 23:14:42 +01:00
parent d4aaa8f0d7
commit d2ba0adab7
3 changed files with 122 additions and 5 deletions
+1
View File
@@ -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.
@@ -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", () => {
+91 -5
View File
@@ -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<NonNullable<OpenClawConfig["agents"]>["defaults"]> = {
...defaults,
};
@@ -136,9 +222,9 @@ function normalizeConfigModelRefsForWrite(cfg: OpenClawConfig): OpenClawConfig {
}
return {
...cfg,
...providerNormalized,
agents: {
...cfg.agents,
...providerNormalized.agents,
defaults: nextDefaults,
},
};