refactor(openai): simplify GPT-5.6 canonicalization (#122467)

Behavior is unchanged. Remove duplicate policy and test scaffolding while preserving canonical Sol identity and direct bare-alias compatibility.
This commit is contained in:
Peter Steinberger
2026-08-11 23:04:01 -07:00
committed by GitHub
parent 127facd39f
commit baefefa815
7 changed files with 39 additions and 136 deletions
+4 -8
View File
@@ -24,11 +24,7 @@ export function applyOpenAIProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
(next, modelRef) => ensureModelAllowlistEntry({ cfg: next, modelRef }),
cfg,
);
const next = ensureModelAllowlistEntry({
cfg: withConfiguredRefs,
modelRef: OPENAI_DEFAULT_MODEL,
});
const models = { ...next.agents?.defaults?.models };
const models = { ...withConfiguredRefs.agents?.defaults?.models };
const gptAliasClaimed = Object.entries(models).some(
([modelRef, model]) =>
modelRef !== OPENAI_DEFAULT_MODEL && model?.alias?.trim().toLowerCase() === "gpt",
@@ -41,11 +37,11 @@ export function applyOpenAIProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
};
return {
...next,
...withConfiguredRefs,
agents: {
...next.agents,
...withConfiguredRefs.agents,
defaults: {
...next.agents?.defaults,
...withConfiguredRefs.agents?.defaults,
models,
},
},
+1 -3
View File
@@ -9,7 +9,7 @@ import {
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { OPENAI_API_BASE_URL, OPENAI_CODEX_RESPONSES_BASE_URL } from "./base-url.js";
import { OPENAI_CODEX_DEFAULT_MODEL, OPENAI_DEFAULT_MODEL } from "./default-models.js";
import { OPENAI_DEFAULT_MODEL } from "./default-models.js";
import { buildOpenAIProvider } from "./openai-provider.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
import { resolveModelRoutes } from "./provider-policy-api.js";
@@ -459,8 +459,6 @@ describe("buildOpenAIProvider", () => {
cost: { input: 0.2, output: 1.25, cacheRead: 0.02, cacheWrite: 0 },
},
]);
expect(OPENAI_DEFAULT_MODEL).toBe("openai/gpt-5.6-sol");
expect(OPENAI_CODEX_DEFAULT_MODEL).toBe("openai/gpt-5.6-sol");
});
it("scopes the OpenAI API-key catalog to the OpenAI provider id", async () => {
@@ -3667,74 +3667,37 @@ describe("legacy model compat migrate", () => {
});
it("canonicalizes persisted OpenAI GPT-5.6 aliases without affecting GitHub Copilot", () => {
const legacy = "openai/gpt-5.6";
const canonical = "openai/gpt-5.6-sol";
const copilot = "github-copilot/gpt-5.6";
const res = migrateLegacyConfigForTest({
agents: {
defaults: {
model: {
primary: `${legacy}@openai:work`,
fallbacks: [legacy, "github-copilot/gpt-5.6"],
},
modelPolicy: { allow: [legacy, "github-copilot/gpt-5.6"] },
model: { primary: "openai/gpt-5.6@openai:work" },
modelPolicy: { allow: ["openai/gpt-5.6", copilot] },
models: {
[legacy]: {
alias: "GPT",
agentRuntime: { id: "openclaw" },
params: { temperature: 0.2, nested: { fromAlias: true } },
},
[canonical]: {
params: { serviceTier: "priority", nested: { fromCanonical: true } },
},
"github-copilot/gpt-5.6": { alias: "Copilot GPT" },
"openai/gpt-5.6": { alias: "GPT" },
"openai/gpt-5.6-sol": { agentRuntime: { id: "openclaw" } },
[copilot]: { alias: "Copilot GPT" },
},
},
},
models: {
providers: {
openai: {
models: [
{ id: "gpt-5.6", name: "GPT alias", maxTokens: 64_000 },
{ id: "gpt-5.6-sol", name: "GPT-5.6 Sol", contextWindow: 1_050_000 },
],
},
openai: { models: [{ id: "gpt-5.6", name: "GPT alias" }] },
"github-copilot": { models: [{ id: "gpt-5.6", name: "Copilot GPT" }] },
},
},
});
expect(res.config?.agents?.defaults).toMatchObject({
model: {
primary: `${canonical}@openai:work`,
fallbacks: [canonical, "github-copilot/gpt-5.6"],
},
modelPolicy: { allow: [canonical, "github-copilot/gpt-5.6"] },
models: {
[canonical]: {
alias: "GPT",
agentRuntime: { id: "openclaw" },
params: {
serviceTier: "priority",
temperature: 0.2,
nested: { fromAlias: true, fromCanonical: true },
},
},
"github-copilot/gpt-5.6": { alias: "Copilot GPT" },
},
const defaults = res.config?.agents?.defaults;
expect(defaults).toMatchObject({
model: { primary: "openai/gpt-5.6-sol@openai:work" },
modelPolicy: { allow: ["openai/gpt-5.6-sol", copilot] },
});
expect(res.config?.agents?.defaults?.models).not.toHaveProperty(legacy);
expect(res.config?.models?.providers?.openai?.models).toEqual([
{
id: "gpt-5.6-sol",
name: "GPT-5.6 Sol",
contextWindow: 1_050_000,
maxTokens: 64_000,
},
]);
expect(res.config?.models?.providers?.["github-copilot"]?.models).toEqual([
{ id: "gpt-5.6", name: "Copilot GPT" },
]);
expect(migrateLegacyConfigForTest(res.config)).toEqual({ config: null, changes: [] });
expect(defaults?.models).toEqual({
"openai/gpt-5.6-sol": { alias: "GPT", agentRuntime: { id: "openclaw" } },
[copilot]: { alias: "Copilot GPT" },
});
expect(res.config?.models?.providers?.openai?.models?.[0]?.id).toBe("gpt-5.6-sol");
expect(res.config?.models?.providers?.["github-copilot"]?.models?.[0]?.id).toBe("gpt-5.6");
});
it("merges provider catalog rows that normalize to an explicitly canonical id", () => {
@@ -74,15 +74,14 @@ const RETIRED_CODEX_MODEL_OVERRIDES = modelTable({
});
function applyRetiredModelTable(
model: string,
normalizedModel: string,
table: Readonly<Record<string, string>>,
overrides?: Readonly<Record<string, string>>,
): string | null {
const normalized = normalizeString(model);
if (overrides && Object.hasOwn(overrides, normalized)) {
return overrides[normalized] ?? null;
if (overrides && Object.hasOwn(overrides, normalizedModel)) {
return overrides[normalizedModel] ?? null;
}
return Object.hasOwn(table, normalized) ? (table[normalized] ?? null) : null;
return Object.hasOwn(table, normalizedModel) ? (table[normalizedModel] ?? null) : null;
}
function hasRetiredVersionPrefix(normalized: string, prefix: string): boolean {
@@ -232,14 +231,14 @@ function canonicalizeKnownModelRef(value: string): string | null {
}
const retiredOwnerModel =
normalizedProvider === "groq"
? applyRetiredModelTable(model, RETIRED_GROQ_MODELS)
? applyRetiredModelTable(normalizedModel, RETIRED_GROQ_MODELS)
: normalizedProvider === "xai"
? applyRetiredModelTable(model, RETIRED_XAI_MODELS)
? applyRetiredModelTable(normalizedModel, RETIRED_XAI_MODELS)
: normalizedProvider === "openai" ||
normalizedProvider === "openai-codex" ||
normalizedProvider === "github-copilot"
? applyRetiredModelTable(
model,
normalizedModel,
RETIRED_OPENAI_MODELS,
normalizedProvider === "openai-codex" ? RETIRED_CODEX_MODEL_OVERRIDES : undefined,
)
+2 -9
View File
@@ -4,8 +4,6 @@ import type { LocalCommandProbe } from "../system-agent/probes.js";
import {
ANTHROPIC_API_DEFAULT_MODEL_REF,
CLAUDE_CLI_DEFAULT_MODEL_REF,
CODEX_APP_SERVER_DEFAULT_MODEL_REF,
OPENAI_API_DEFAULT_MODEL_REF,
detectInferenceBackends,
} from "./onboard-inference.js";
@@ -17,11 +15,6 @@ function probeDeps(found: Record<string, boolean>) {
}
describe("detectInferenceBackends", () => {
it("uses canonical GPT-5.6 Sol defaults for direct API and Codex", () => {
expect(OPENAI_API_DEFAULT_MODEL_REF).toBe("openai/gpt-5.6-sol");
expect(CODEX_APP_SERVER_DEFAULT_MODEL_REF).toBe("openai/gpt-5.6-sol");
});
it("returns nothing when no backend exists", async () => {
const candidates = await detectInferenceBackends({
env: {},
@@ -84,8 +77,8 @@ describe("detectInferenceBackends", () => {
expect(candidates[0]?.modelRef).toBe("zai/glm-5.2");
expect(candidates[0]?.detail).toBe("zai/glm-5.2 — already configured");
expect(candidates[1]?.modelRef).toBe(CLAUDE_CLI_DEFAULT_MODEL_REF);
expect(candidates[2]?.modelRef).toBe(CODEX_APP_SERVER_DEFAULT_MODEL_REF);
expect(candidates[3]?.modelRef).toBe(OPENAI_API_DEFAULT_MODEL_REF);
expect(candidates[2]?.modelRef).toBe("openai/gpt-5.6-sol");
expect(candidates[3]?.modelRef).toBe("openai/gpt-5.6-sol");
expect(candidates[4]?.modelRef).toBe(ANTHROPIC_API_DEFAULT_MODEL_REF);
});
@@ -1,42 +0,0 @@
import { describe, expect, it } from "vitest";
import { migrateLegacyConfig } from "../../commands/doctor/shared/legacy-config-migrate.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { withEnvAsync } from "../../test-utils/env.js";
import {
catalogEntry,
listModels,
WITHOUT_OPENAI_ENV_AUTH,
} from "./models-list-result.openai-routes.test-support.js";
describe("models.list OpenAI picker", () => {
it("does not expose a configured GPT-5.6 alias beside named variants after doctor normalization", async () => {
const staleConfig = {
agents: {
defaults: {
model: { primary: "openai/gpt-5.6" },
models: {
"openai/gpt-5.6": { alias: "GPT" },
"openai/gpt-5.6-sol": {},
"openai/gpt-5.6-terra": {},
"openai/gpt-5.6-luna": {},
},
},
},
} as OpenClawConfig;
const cfg = migrateLegacyConfig(staleConfig).config ?? staleConfig;
const catalog = [
{ ...catalogEntry("gpt-5.6-sol", "openai-responses"), providerOrder: 0 },
{ ...catalogEntry("gpt-5.6-terra", "openai-responses"), providerOrder: 1 },
{ ...catalogEntry("gpt-5.6-luna", "openai-responses"), providerOrder: 2 },
];
await withEnvAsync({ ...WITHOUT_OPENAI_ENV_AUTH, OPENAI_API_KEY: "test-key" }, async () => {
const result = await listModels({ catalog, cfg, view: "configured" });
expect(result.models.map((entry) => entry.id)).toEqual([
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
]);
});
});
});
@@ -1,4 +1,3 @@
import { vi } from "vitest";
import type { ModelCatalogEntry } from "../../agents/model-catalog.types.js";
import type { createOpenAIModelRoutesResolver } from "../../agents/openai-model-routes.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
@@ -32,17 +31,14 @@ export async function listModels(params: {
const config = params.cfg ?? ({} as OpenClawConfig);
const context = {
getRuntimeConfig: () => config,
loadGatewayModelCatalog: vi.fn(() => Promise.resolve(params.catalog)),
loadGatewayModelCatalogSnapshot: vi.fn(() =>
Promise.resolve({
agentId: "main",
agentDir: "/tmp/models-list-openai-agent",
config,
entries: params.catalog,
routeVariants: params.catalog,
}),
),
logGateway: { debug: vi.fn() },
loadGatewayModelCatalogSnapshot: async () => ({
agentId: "main",
agentDir: "/tmp/models-list-openai-agent",
config,
entries: params.catalog,
routeVariants: params.catalog,
}),
logGateway: { debug: () => {} },
} as unknown as GatewayRequestContext;
return await buildModelsListResult({
context,