From c896d42cc410c150e5ef295b09c02ce7dcb8b87f Mon Sep 17 00:00:00 2001 From: Shakker Date: Mon, 27 Apr 2026 17:01:06 +0100 Subject: [PATCH] fix: keep manifest suppression on static model lists --- src/agents/model-suppression.ts | 27 ++++++++++++-- src/commands/models/list.rows.test.ts | 53 +++++++++++++++++++++++++++ src/commands/models/list.rows.ts | 11 +++++- 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/agents/model-suppression.ts b/src/agents/model-suppression.ts index 81f5883fe95..a511753a38c 100644 --- a/src/agents/model-suppression.ts +++ b/src/agents/model-suppression.ts @@ -4,10 +4,9 @@ import { resolveProviderBuiltInModelSuppression } from "../plugins/provider-runt import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { normalizeProviderId } from "./provider-id.js"; -function resolveBuiltInModelSuppression(params: { +function resolveBuiltInModelSuppressionFromManifest(params: { provider?: string | null; id?: string | null; - baseUrl?: string | null; config?: OpenClawConfig; }) { const provider = normalizeProviderId(params.provider ?? ""); @@ -15,15 +14,29 @@ function resolveBuiltInModelSuppression(params: { if (!provider || !modelId) { return undefined; } - const manifestResult = resolveManifestBuiltInModelSuppression({ + return resolveManifestBuiltInModelSuppression({ provider, id: modelId, ...(params.config ? { config: params.config } : {}), env: process.env, }); +} + +function resolveBuiltInModelSuppression(params: { + provider?: string | null; + id?: string | null; + baseUrl?: string | null; + config?: OpenClawConfig; +}) { + const manifestResult = resolveBuiltInModelSuppressionFromManifest(params); if (manifestResult?.suppress) { return manifestResult; } + const provider = normalizeProviderId(params.provider ?? ""); + const modelId = normalizeLowercaseStringOrEmpty(params.id); + if (!provider || !modelId) { + return undefined; + } return resolveProviderBuiltInModelSuppression({ ...(params.config ? { config: params.config } : {}), env: process.env, @@ -37,6 +50,14 @@ function resolveBuiltInModelSuppression(params: { }); } +export function shouldSuppressBuiltInModelFromManifest(params: { + provider?: string | null; + id?: string | null; + config?: OpenClawConfig; +}) { + return resolveBuiltInModelSuppressionFromManifest(params)?.suppress ?? false; +} + export function shouldSuppressBuiltInModel(params: { provider?: string | null; id?: string | null; diff --git a/src/commands/models/list.rows.test.ts b/src/commands/models/list.rows.test.ts index 663596dc9f6..afc2bc43a0a 100644 --- a/src/commands/models/list.rows.test.ts +++ b/src/commands/models/list.rows.test.ts @@ -6,6 +6,7 @@ const mocks = vi.hoisted(() => ({ shouldSuppressBuiltInModel: vi.fn(() => { throw new Error("runtime model suppression should be skipped"); }), + shouldSuppressBuiltInModelFromManifest: vi.fn(() => false), loadProviderCatalogModelsForList: vi.fn().mockResolvedValue([ { id: "gpt-5.5", @@ -21,6 +22,7 @@ const mocks = vi.hoisted(() => ({ vi.mock("../../agents/model-suppression.js", () => ({ shouldSuppressBuiltInModel: mocks.shouldSuppressBuiltInModel, + shouldSuppressBuiltInModelFromManifest: mocks.shouldSuppressBuiltInModelFromManifest, })); vi.mock("./list.provider-catalog.js", () => ({ @@ -76,6 +78,14 @@ describe("appendProviderCatalogRows", () => { }); expect(mocks.shouldSuppressBuiltInModel).not.toHaveBeenCalled(); + expect(mocks.shouldSuppressBuiltInModelFromManifest).toHaveBeenCalledWith({ + provider: "codex", + id: "gpt-5.5", + config: { + agents: { defaults: { model: { primary: "codex/gpt-5.5" } } }, + models: { providers: {} }, + }, + }); expect(rows).toMatchObject([ { key: "codex/gpt-5.5", @@ -84,4 +94,47 @@ describe("appendProviderCatalogRows", () => { }, ]); }); + + it("applies manifest suppression when runtime model-suppression hooks are skipped", async () => { + mocks.loadProviderCatalogModelsForList.mockResolvedValueOnce([ + { + id: "gpt-5.3-codex-spark", + name: "GPT-5.3 Codex Spark", + provider: "openai", + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", + input: ["text", "image"], + }, + ]); + mocks.shouldSuppressBuiltInModelFromManifest.mockReturnValueOnce(true); + const rows: ModelRow[] = []; + + await appendProviderCatalogRows({ + rows, + seenKeys: new Set(), + context: { + cfg: { + agents: { defaults: { model: { primary: "openai/gpt-5.5" } } }, + models: { providers: {} }, + }, + agentDir: "/tmp/openclaw-agent", + authStore: { version: 1, profiles: {}, order: {} }, + configuredByKey: new Map(), + discoveredKeys: new Set(), + filter: { provider: "openai", local: false }, + skipRuntimeModelSuppression: true, + }, + }); + + expect(mocks.shouldSuppressBuiltInModel).not.toHaveBeenCalled(); + expect(mocks.shouldSuppressBuiltInModelFromManifest).toHaveBeenCalledWith({ + provider: "openai", + id: "gpt-5.3-codex-spark", + config: { + agents: { defaults: { model: { primary: "openai/gpt-5.5" } } }, + models: { providers: {} }, + }, + }); + expect(rows).toEqual([]); + }); }); diff --git a/src/commands/models/list.rows.ts b/src/commands/models/list.rows.ts index df6b9bd5dc5..f8ea35ab2e4 100644 --- a/src/commands/models/list.rows.ts +++ b/src/commands/models/list.rows.ts @@ -7,7 +7,10 @@ import { resolveAwsSdkEnvVarName, resolveEnvApiKey, } from "../../agents/model-auth.js"; -import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js"; +import { + shouldSuppressBuiltInModel, + shouldSuppressBuiltInModelFromManifest, +} from "../../agents/model-suppression.js"; import { normalizeProviderId } from "../../agents/provider-id.js"; import type { ModelDefinitionConfig, ModelProviderConfig } from "../../config/types.models.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -137,7 +140,11 @@ function shouldSuppressListModel(params: { context: RowBuilderContext; }): boolean { if (params.context.skipRuntimeModelSuppression) { - return false; + return shouldSuppressBuiltInModelFromManifest({ + provider: params.model.provider, + id: params.model.id, + config: params.context.cfg, + }); } return shouldSuppressBuiltInModel({ provider: params.model.provider,