fix: normalize nested gemini preview config refs

This commit is contained in:
Peter Steinberger
2026-05-11 06:34:39 +01:00
parent 2d65786d8c
commit b7db08f54e
5 changed files with 50 additions and 19 deletions

View File

@@ -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.

View File

@@ -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,

View File

@@ -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();

View File

@@ -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", () => {

View File

@@ -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(