mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-21 01:51:39 -06:00
fix(models): preserve Codex selection for catalog-seeded models (#126531)
* fix(models): keep catalog compat out of route overrides * fix(models): preserve authored compat provenance --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
@@ -4,6 +4,10 @@ import type { Model } from "openclaw/plugin-sdk/llm";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTempDirTracker } from "../../../test/helpers/temp-dir.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
clearRuntimeConfigSnapshot,
|
||||
setRuntimeConfigSnapshot,
|
||||
} from "../../config/runtime-snapshot.js";
|
||||
import { replaceSessionEntry } from "../../config/sessions/session-accessor.js";
|
||||
import type { TranscriptEntryAnchor } from "../../config/sessions/transcript-entry-anchor.js";
|
||||
import { OPENCLAW_EMBEDDED_CONTEXT_ENGINE_HOST } from "../../context-engine/host-compat.js";
|
||||
@@ -226,6 +230,7 @@ beforeEach(async () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
closeOpenClawAgentDatabasesForTest();
|
||||
closeOpenClawStateDatabaseForTest();
|
||||
trajectoryTempDirs.cleanup();
|
||||
@@ -2036,6 +2041,45 @@ describe("selectAgentHarness", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores catalog-seeded compatibility when selecting an official OpenAI route", () => {
|
||||
const createConfig = (compat?: { supportsStore: boolean }) =>
|
||||
({
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
models: [
|
||||
{
|
||||
id: "gpt-5.5",
|
||||
name: "GPT-5.5",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
maxTokens: 8192,
|
||||
...(compat ? { compat } : {}),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}) satisfies OpenClawConfig;
|
||||
const sourceConfig = createConfig();
|
||||
const runtimeConfig = createConfig({ supportsStore: false });
|
||||
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
|
||||
|
||||
expect(
|
||||
buildAgentHarnessSupportContext({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
requestedRuntime: "codex",
|
||||
config: runtimeConfig,
|
||||
}).modelProvider,
|
||||
).toMatchObject({
|
||||
requestTransportOverrides: "none",
|
||||
runtimePolicy: { compatibleIds: ["openclaw", "codex"] },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: "default",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
resolveMergedModelProviderModels,
|
||||
resolveModelProviderRouteOverridePresence,
|
||||
} from "../../config/model-provider-config.js";
|
||||
import { projectConfigOntoRuntimeSourceSnapshot } from "../../config/runtime-source-projection.js";
|
||||
import type { ModelApi } from "../../config/types.models.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type {
|
||||
@@ -86,6 +87,9 @@ export function buildAgentHarnessSupportContext(params: {
|
||||
providerOwnership?: HarnessProviderOwnership;
|
||||
}): AgentHarnessSupportContext {
|
||||
const providerConfig = resolveMergedModelProviderConfig(params.config, params.provider);
|
||||
const authoredConfig = params.config
|
||||
? projectConfigOntoRuntimeSourceSnapshot(params.config)
|
||||
: undefined;
|
||||
const modelId = params.modelId ? normalizeModelId(params.provider, params.modelId) : undefined;
|
||||
const modelConfig = modelId
|
||||
? resolveMergedModelProviderModels({
|
||||
@@ -119,7 +123,7 @@ export function buildAgentHarnessSupportContext(params: {
|
||||
requestTransportOverrides: resolveModelProviderRouteOverridePresence({
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
config: params.config,
|
||||
authoredConfig,
|
||||
canonicalizeModelId: (configuredModelId) =>
|
||||
canonicalizeProviderModelId(params.provider, configuredModelId),
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Verifies default config values and environment-sensitive overrides.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ProviderResolveModelRoutesContext } from "../plugin-sdk/provider-model-types.js";
|
||||
import { resolveProviderModelRoutes } from "../plugins/provider-model-routes.js";
|
||||
import {
|
||||
DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES,
|
||||
DEFAULT_SUBAGENT_MAX_CONCURRENT,
|
||||
@@ -11,6 +13,8 @@ import {
|
||||
applyContextPruningDefaults,
|
||||
applyMessageDefaults,
|
||||
} from "./defaults.js";
|
||||
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "./runtime-snapshot.js";
|
||||
import type { OpenClawConfig } from "./types.openclaw.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
applyProviderConfigDefaultsForConfig: vi.fn(),
|
||||
@@ -32,6 +36,7 @@ describe("config defaults", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
@@ -158,6 +163,7 @@ describe("applyModelDefaults catalog seeding", () => {
|
||||
maxTokens: 128_000,
|
||||
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||
thinkingLevelMap: { off: "none" },
|
||||
compat: { supportsStore: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -243,6 +249,46 @@ describe("applyModelDefaults catalog seeding", () => {
|
||||
expect(model.maxTokens).toBe(4_096);
|
||||
});
|
||||
|
||||
it("keeps catalog-seeded compatibility out of authored route overrides", async () => {
|
||||
const { applyModelDefaults } = await import("./defaults.js");
|
||||
const sourceConfig: OpenClawConfig = {
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
models: [
|
||||
{
|
||||
id: "gpt-5.6-sol",
|
||||
name: "GPT-5.6 Sol",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
maxTokens: 8192,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const runtimeConfig = applyModelDefaults(sourceConfig, { manifestRegistry: catalogRegistry });
|
||||
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
|
||||
const resolveModelRoutes = vi.fn((_context: ProviderResolveModelRoutesContext) => ({
|
||||
kind: "indeterminate" as const,
|
||||
}));
|
||||
|
||||
resolveProviderModelRoutes({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.6-sol",
|
||||
config: runtimeConfig,
|
||||
env: {},
|
||||
surface: { resolveModelRoutes },
|
||||
});
|
||||
|
||||
expect(resolveModelRoutes.mock.calls[0]?.[0]).toMatchObject({
|
||||
requestTransportOverrides: "none",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves catalog tiered pricing when flat cost fields are authored", async () => {
|
||||
const { applyModelDefaults } = await import("./defaults.js");
|
||||
const tieredRegistry = {
|
||||
|
||||
@@ -68,14 +68,14 @@ describe("resolveModelProviderRouteOverridePresence", () => {
|
||||
resolveModelProviderRouteOverridePresence({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
config,
|
||||
authoredConfig: config,
|
||||
}),
|
||||
).toBe("present");
|
||||
expect(
|
||||
resolveModelProviderRouteOverridePresence({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5-empty",
|
||||
config,
|
||||
authoredConfig: config,
|
||||
}),
|
||||
).toBe("none");
|
||||
});
|
||||
@@ -85,7 +85,7 @@ describe("resolveModelProviderRouteOverridePresence", () => {
|
||||
resolveModelProviderRouteOverridePresence({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
config: {
|
||||
authoredConfig: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: { baseUrl: "", timeoutSeconds: 90, models: [model("gpt-5.5")] },
|
||||
|
||||
@@ -46,10 +46,10 @@ function hasNonEmptyRecord(value: unknown): boolean {
|
||||
export function resolveModelProviderRouteOverridePresence(params: {
|
||||
provider: string;
|
||||
modelId?: string;
|
||||
config?: OpenClawConfig;
|
||||
authoredConfig?: OpenClawConfig;
|
||||
canonicalizeModelId?: (modelId: string) => string;
|
||||
}): ProviderRouteOverridePresence {
|
||||
const providerConfig = resolveMergedModelProviderConfig(params.config, params.provider);
|
||||
const providerConfig = resolveMergedModelProviderConfig(params.authoredConfig, params.provider);
|
||||
if (!providerConfig) {
|
||||
return "none";
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
resolveMergedModelProviderModels,
|
||||
resolveModelProviderRouteOverridePresence,
|
||||
} from "../config/model-provider-config.js";
|
||||
import { projectConfigOntoRuntimeSourceSnapshot } from "../config/runtime-source-projection.js";
|
||||
import type { ModelApi, ModelDefinitionConfig } from "../config/types.models.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type {
|
||||
@@ -84,6 +85,11 @@ export function createProviderModelRoutesResolver(params: {
|
||||
: params.surface;
|
||||
const resolveModelRoutes = surface?.resolveModelRoutes;
|
||||
const providerConfig = resolveMergedModelProviderConfig(params.config, provider);
|
||||
// Runtime defaults copy catalog capabilities into configured model rows. Route
|
||||
// eligibility must read the authored view or metadata looks like request behavior.
|
||||
const authoredConfig = params.config
|
||||
? projectConfigOntoRuntimeSourceSnapshot(params.config)
|
||||
: undefined;
|
||||
const configuredProvider = providerConfig
|
||||
? { api: providerConfig.api, baseUrl: providerConfig.baseUrl }
|
||||
: undefined;
|
||||
@@ -105,7 +111,7 @@ export function createProviderModelRoutesResolver(params: {
|
||||
? "present"
|
||||
: resolveModelProviderRouteOverridePresence({
|
||||
provider,
|
||||
config: params.config,
|
||||
authoredConfig,
|
||||
});
|
||||
const routeOverridePresenceByModel = new Map(
|
||||
[...configuredModels.keys()].map(
|
||||
@@ -117,7 +123,7 @@ export function createProviderModelRoutesResolver(params: {
|
||||
: resolveModelProviderRouteOverridePresence({
|
||||
provider,
|
||||
modelId,
|
||||
config: params.config,
|
||||
authoredConfig,
|
||||
canonicalizeModelId,
|
||||
}),
|
||||
] as const,
|
||||
|
||||
Reference in New Issue
Block a user