fix(doctor): migrate legacy OpenAI provider api

This commit is contained in:
Ayaan Zaidi
2026-04-27 09:04:49 +05:30
parent 831f03b814
commit 6a7980e984
3 changed files with 111 additions and 3 deletions

View File

@@ -342,6 +342,40 @@ describe("normalizeCompatibilityConfigValues", () => {
);
});
it("migrates legacy OpenAI provider api values to OpenAI completions", () => {
const res = normalizeCompatibilityConfigValues({
models: {
providers: {
openrouter: {
baseUrl: "https://openrouter.ai/api/v1",
api: "openai",
models: [
{
id: "openai/gpt-4o-mini",
name: "OpenRouter GPT-4o Mini",
api: "openai",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128_000,
maxTokens: 16_384,
},
],
},
},
},
} as unknown as OpenClawConfig);
expect(res.config.models?.providers?.openrouter?.api).toBe("openai-completions");
expect(res.config.models?.providers?.openrouter?.models?.[0]?.api).toBe("openai-completions");
expect(res.changes).toContain(
'Moved models.providers.openrouter.api "openai" → "openai-completions".',
);
expect(res.changes).toContain(
'Moved models.providers.openrouter.models[0].api "openai" → "openai-completions".',
);
});
it("marks legacy untagged /models add OpenAI Codex metadata rows for doctor repair", () => {
const res = normalizeCompatibilityConfigValues({
models: {

View File

@@ -4,6 +4,7 @@ import {
normalizeLegacyCrossContextMessageConfig,
normalizeLegacyMediaProviderOptions,
normalizeLegacyMistralModelMaxTokens,
normalizeLegacyOpenAIModelProviderApi,
normalizeLegacyRuntimeModelRefs,
normalizeLegacyNanoBananaSkill,
normalizeLegacyTalkConfig,
@@ -37,6 +38,7 @@ export function normalizeBaseCompatibilityConfigValues(
next = normalizeLegacyNanoBananaSkill(next, changes);
next = normalizeLegacyTalkConfig(next, changes);
next = normalizeLegacyOpenAIModelProviderApi(next, changes);
next = normalizeLegacyRuntimeModelRefs(next, changes);
next = normalizeLegacyCrossContextMessageConfig(next, changes);
next = normalizeLegacyMediaProviderOptions(next, changes);

View File

@@ -390,9 +390,10 @@ export function normalizeLegacyOpenAICodexModelsAddMetadata(
return cfg;
}
const rawProviders: Record<string, unknown> = rawModels.providers;
let providersChanged = false;
const nextProviders = { ...rawModels.providers };
for (const [providerId, rawProvider] of Object.entries(rawModels.providers)) {
const nextProviders: Record<string, unknown> = { ...rawProviders };
for (const [providerId, rawProvider] of Object.entries(rawProviders)) {
if (normalizeProviderId(providerId) !== "openai-codex" || !isRecord(rawProvider)) {
continue;
}
@@ -413,7 +414,7 @@ export function normalizeLegacyOpenAICodexModelsAddMetadata(
) {
providerChanged = true;
const safeProviderId = sanitizeForLog(providerId);
const safeModelId = sanitizeForLog(model.id);
const safeModelId = sanitizeForLog(normalizeOptionalString(model.id) ?? "unknown");
changes.push(
`Marked models.providers.${safeProviderId}.models.${safeModelId} as /models add metadata so official OpenAI Codex metadata can override it.`,
);
@@ -446,6 +447,77 @@ export function normalizeLegacyOpenAICodexModelsAddMetadata(
};
}
export function normalizeLegacyOpenAIModelProviderApi(
cfg: OpenClawConfig,
changes: string[],
): OpenClawConfig {
const rawModels = cfg.models;
if (!isRecord(rawModels) || !isRecord(rawModels.providers)) {
return cfg;
}
const rawProviders: Record<string, unknown> = rawModels.providers;
let providersChanged = false;
const nextProviders: Record<string, unknown> = { ...rawProviders };
for (const [providerId, rawProvider] of Object.entries(rawProviders)) {
if (!isRecord(rawProvider)) {
continue;
}
let providerChanged = false;
const nextProvider: Record<string, unknown> = { ...rawProvider };
if (nextProvider.api === "openai") {
nextProvider.api = "openai-completions";
providerChanged = true;
changes.push(
`Moved models.providers.${sanitizeForLog(providerId)}.api "openai" → "openai-completions".`,
);
}
const rawProviderModels = rawProvider.models;
if (Array.isArray(rawProviderModels)) {
let modelsChanged = false;
const nextModels: unknown[] = [];
rawProviderModels.forEach((model, index) => {
if (!isRecord(model) || model.api !== "openai") {
nextModels.push(model);
return;
}
modelsChanged = true;
changes.push(
`Moved models.providers.${sanitizeForLog(providerId)}.models[${index}].api "openai" → "openai-completions".`,
);
nextModels.push({
...model,
api: "openai-completions",
});
});
if (modelsChanged) {
nextProvider.models = nextModels;
providerChanged = true;
}
}
if (!providerChanged) {
continue;
}
nextProviders[providerId] = nextProvider;
providersChanged = true;
}
if (!providersChanged) {
return cfg;
}
return {
...cfg,
models: {
...rawModels,
providers: nextProviders as NonNullable<OpenClawConfig["models"]>["providers"],
},
};
}
export function normalizeLegacyNanoBananaSkill(
cfg: OpenClawConfig,
changes: string[],