fix: normalize oauth auth-result config patches

This commit is contained in:
Peter Steinberger
2026-05-12 13:31:30 +01:00
parent a538e58075
commit 88714d6803
3 changed files with 196 additions and 9 deletions

View File

@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
- Gateway/OpenAI HTTP: honor `max_completion_tokens` and `max_tokens` on inbound `/v1/chat/completions` requests so client-provided token caps reach the upstream provider via `streamParams.maxTokens`, with `max_completion_tokens` taking precedence when both are sent. Thanks @Lellansin.
- Models/OpenAI CLI auth: make `openclaw models auth login --provider openai` start the ChatGPT/Codex account login by default, while `--method api-key` remains the explicit OpenAI API-key setup path.
- Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside explicit SDK OAuth auth-result config patches, so provider helpers emit `google/gemini-3.1-pro-preview` for Gemini 3.1 testing.
- Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside SDK OAuth auth-result default config patches, so helper-built provider auth flows emit `google/gemini-3.1-pro-preview` for Gemini 3.1 testing.
- Google/Gemini: normalize retired Gemini 3 Pro Preview ids returned by direct `openclaw models auth login --set-default` provider auth flows before writing config, so Gemini testing targets `google/gemini-3.1-pro-preview`.
- Google/Gemini: normalize retired Gemini 3 Pro Preview ids in provider catalog rows when API-key onboarding only reapplies the agent default, so emitted config keeps testing `google/gemini-3.1-pro-preview`.

View File

@@ -20,4 +20,76 @@ describe("buildOauthProviderAuthResult", () => {
},
});
});
it("normalizes retired Gemini refs inside explicit config patches", () => {
const result = buildOauthProviderAuthResult({
providerId: "google",
defaultModel: "google/gemini-3-pro-preview",
access: "access-token",
configPatch: {
agents: {
defaults: {
model: {
primary: "google/gemini-3-pro-preview",
fallbacks: ["google/gemini-3-pro-preview", "openai/gpt-5.5"],
},
models: {
"google/gemini-3-pro-preview": { alias: "gemini" },
},
},
},
models: {
providers: {
google: {
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
models: [
{
id: "google/gemini-3-pro-preview",
name: "Gemini 3 Pro",
contextWindow: 1_048_576,
maxTokens: 65_536,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
reasoning: true,
},
],
},
},
},
},
});
expect(result.defaultModel).toBe("google/gemini-3.1-pro-preview");
expect(result.configPatch).toEqual({
agents: {
defaults: {
model: {
primary: "google/gemini-3.1-pro-preview",
fallbacks: ["google/gemini-3.1-pro-preview", "openai/gpt-5.5"],
},
models: {
"google/gemini-3.1-pro-preview": { alias: "gemini" },
},
},
},
models: {
providers: {
google: {
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
models: [
{
id: "google/gemini-3.1-pro-preview",
name: "Gemini 3 Pro",
contextWindow: 1_048_576,
maxTokens: 65_536,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
reasoning: true,
},
],
},
},
},
});
});
});

View File

@@ -1,9 +1,122 @@
import { buildAuthProfileId } from "../agents/auth-profiles/identity.js";
import type { AuthProfileCredential } from "../agents/auth-profiles/types.js";
import { normalizeAgentModelRefForConfig } from "../config/model-input.js";
import { normalizeConfiguredProviderCatalogModelId } from "../agents/model-ref-shared.js";
import {
normalizeAgentModelMapForConfig,
normalizeAgentModelRefForConfig,
} from "../config/model-input.js";
import type { ModelProviderConfig } from "../config/types.models.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { ProviderAuthResult } from "../plugins/types.js";
function normalizeAgentModelConfigForAuthResult(value: unknown): unknown {
if (typeof value === "string") {
return normalizeAgentModelRefForConfig(value);
}
if (!value || typeof value !== "object" || Array.isArray(value)) {
return value;
}
let mutated = false;
const next: Record<string, unknown> = { ...(value as Record<string, unknown>) };
if (typeof next.primary === "string") {
const primary = normalizeAgentModelRefForConfig(next.primary);
if (primary !== next.primary) {
next.primary = primary;
mutated = true;
}
}
if (Array.isArray(next.fallbacks)) {
const originalFallbacks = next.fallbacks;
const fallbacks = originalFallbacks.map((fallback) =>
typeof fallback === "string" ? normalizeAgentModelRefForConfig(fallback) : fallback,
);
if (fallbacks.some((fallback, index) => fallback !== originalFallbacks[index])) {
next.fallbacks = fallbacks;
mutated = true;
}
}
return mutated ? next : value;
}
function normalizeProviderConfigModelIdsForAuthResult(
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 id = normalizeConfiguredProviderCatalogModelId(provider, model.id);
if (id === model.id) {
return model;
}
mutated = true;
return Object.assign({}, model, { id });
});
return mutated ? { ...providerConfig, models: nextModels } : providerConfig;
}
function normalizeProviderAuthConfigPatchModelRefs(
patch: Partial<OpenClawConfig>,
): Partial<OpenClawConfig> {
let next = patch;
const defaults = patch.agents?.defaults;
if (defaults) {
let nextDefaults = defaults;
if (defaults.model !== undefined) {
const model = normalizeAgentModelConfigForAuthResult(defaults.model);
if (model !== defaults.model) {
nextDefaults = { ...nextDefaults, model: model as typeof defaults.model };
}
}
if (defaults.models) {
const models = normalizeAgentModelMapForConfig(defaults.models);
if (models !== defaults.models) {
nextDefaults = { ...nextDefaults, models };
}
}
if (nextDefaults !== defaults) {
next = {
...next,
agents: {
...next.agents,
defaults: nextDefaults,
},
};
}
}
const providers = patch.models?.providers;
if (!providers) {
return next;
}
let mutated = false;
const nextProviders = { ...providers };
for (const [provider, providerConfig] of Object.entries(providers)) {
const normalized = normalizeProviderConfigModelIdsForAuthResult(provider, providerConfig);
if (normalized === providerConfig) {
continue;
}
nextProviders[provider] = normalized;
mutated = true;
}
return mutated
? {
...next,
models: {
...next.models,
providers: nextProviders,
},
}
: next;
}
/** Build the standard auth result payload for OAuth-style provider login flows. */
export function buildOauthProviderAuthResult(params: {
providerId: string;
@@ -41,17 +154,18 @@ export function buildOauthProviderAuthResult(params: {
return {
profiles: [{ profileId, credential }],
configPatch:
configPatch: normalizeProviderAuthConfigPatchModelRefs(
params.configPatch ??
({
agents: {
defaults: {
models: {
[defaultModel]: {},
({
agents: {
defaults: {
models: {
[defaultModel]: {},
},
},
},
},
} as Partial<OpenClawConfig>),
} as Partial<OpenClawConfig>),
),
defaultModel,
notes: params.notes,
};