Skip to content

Commit 947aae5

Browse files
committed
refactor(models): move suppressions to manifests
1 parent c0fdf99 commit 947aae5

32 files changed

Lines changed: 642 additions & 302 deletions

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Docs: https://docs.openclaw.ai
1111
- Plugin SDK: expose shared channel route normalization, parser-driven target resolution, raw-target compact keys, parsed-target types, and route comparison helpers through `openclaw/plugin-sdk/channel-route`, switch native approval origin matching onto that route contract with optional delivery and match-only target normalization, and retire the internal channel-route shim behind dated compatibility aliases for legacy key/comparable-target helpers. Thanks @vincentkoc.
1212
- Docs/Codex: document how Codex Computer Use, direct `cua-driver mcp`, and OpenClaw.app's PeekabooBridge fit together so desktop-control setup choices are clearer. Thanks @pash-openai and @trycua.
1313
- Matrix/streaming: stream tool-progress updates into live Matrix preview edits by default when preview streaming is active, with `streaming.preview.toolProgress: false` to keep answer previews while hiding interim tool lines. Thanks @gumadeiras.
14-
- Plugins/models: wire manifest `modelCatalog.aliases` and `modelCatalog.suppressions` into model-catalog planning and built-in model suppression, with OpenAI stale Spark suppression now declared in the plugin manifest before runtime fallback. Thanks @shakkernerd.
14+
- Plugins/models: wire manifest `modelCatalog.aliases` and `modelCatalog.suppressions` into model-catalog planning and built-in model suppression, with stale Spark and Qwen Coding Plan suppressions now declared in plugin manifests instead of runtime fallback hooks. Thanks @shakkernerd.
1515
- Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (`openclaw-plugin-yuanbao`) in the official channel catalog, contract suites, and community plugin docs, with a new `docs/channels/yuanbao.md` quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay.
1616
- Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh.
1717
- Gateway/startup: reuse lookup-table plugin manifests when loading startup plugins so Gateway boot avoids rebuilding plugin discovery and manifest metadata. Thanks @shakkernerd.

docs/plugins/architecture-internals.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ The "When to use" column is the quick decision guide.
248248
| 28 | `classifyFailoverReason` | Provider-owned failover reason classification | Provider can map raw API/transport errors to rate-limit/overload/etc |
249249
| 29 | `isCacheTtlEligible` | Prompt-cache policy for proxy/backhaul providers | Provider needs proxy-specific cache TTL gating |
250250
| 30 | `buildMissingAuthMessage` | Replacement for the generic missing-auth recovery message | Provider needs a provider-specific missing-auth recovery hint |
251-
| 31 | `suppressBuiltInModel` | Stale upstream model suppression plus optional user-facing error hint | Provider needs to hide stale upstream rows or replace them with a vendor hint |
251+
| 31 | `suppressBuiltInModel` | Deprecated. Runtime hook is no longer called; use manifest `modelCatalog.suppressions` | Historical hook for hiding stale upstream rows; keep new suppression data in the plugin manifest |
252252
| 32 | `augmentModelCatalog` | Synthetic/final catalog rows appended after discovery | Provider needs synthetic forward-compat rows in `models list` and pickers |
253253
| 33 | `resolveThinkingProfile` | Model-specific `/think` level set, display labels, and default | Provider exposes a custom thinking ladder or binary label for selected models |
254254
| 34 | `isBinaryThinking` | On/off reasoning toggle compatibility hook | Provider exposes only binary thinking on/off |

docs/plugins/manifest.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -735,11 +735,10 @@ Alias targets must be top-level providers owned by the same plugin. When a
735735
provider-filtered list uses an alias, OpenClaw can read the owning manifest and
736736
apply alias API/base URL overrides without loading provider runtime.
737737

738-
`suppressions` is the preferred static replacement for provider runtime
739-
`suppressBuiltInModel` hooks. Suppression entries are honored only when the
740-
provider is owned by the plugin or declared as a `modelCatalog.aliases` key that
741-
targets an owned provider. Runtime suppression hooks still run as deprecated
742-
compatibility fallback for plugins that have not migrated.
738+
`suppressions` replaces the old provider runtime `suppressBuiltInModel` hook.
739+
Suppression entries are honored only when the provider is owned by the plugin or
740+
declared as a `modelCatalog.aliases` key that targets an owned provider. Runtime
741+
suppression hooks are no longer called during model resolution.
743742

744743
Provider fields:
745744

@@ -772,6 +771,16 @@ Model fields:
772771
| `replacedBy` | `string` | Replacement provider-local model id for deprecated rows. |
773772
| `tags` | `string[]` | Stable tags used by pickers and filters. |
774773

774+
Suppression fields:
775+
776+
| Field | Type | What it means |
777+
| -------------------------- | ---------- | --------------------------------------------------------------------------------------------------------- |
778+
| `provider` | `string` | Provider id for the upstream row to suppress. Must be owned by this plugin or declared as an owned alias. |
779+
| `model` | `string` | Provider-local model id to suppress. |
780+
| `reason` | `string` | Optional message shown when the suppressed row is requested directly. |
781+
| `when.baseUrlHosts` | `string[]` | Optional list of effective provider base URL hosts required before the suppression applies. |
782+
| `when.providerConfigApiIn` | `string[]` | Optional list of exact provider-config `api` values required before the suppression applies. |
783+
775784
Do not put runtime-only data in `modelCatalog`. If a provider needs account
776785
state, an API request, or local process discovery to know the complete model
777786
set, declare that provider as `refreshable` or `runtime` in `discovery`.

docs/plugins/sdk-provider-plugins.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ API key auth, and dynamic model resolution.
452452
| 27 | `classifyFailoverReason` | Provider-owned rate-limit/overload classification |
453453
| 28 | `isCacheTtlEligible` | Prompt cache TTL gating |
454454
| 29 | `buildMissingAuthMessage` | Custom missing-auth hint |
455-
| 30 | `suppressBuiltInModel` | Hide stale upstream rows |
455+
| 30 | `suppressBuiltInModel` | Deprecated. Runtime hook is no longer called; use manifest `modelCatalog.suppressions` |
456456
| 31 | `augmentModelCatalog` | Synthetic forward-compat rows |
457457
| 32 | `resolveThinkingProfile` | Model-specific `/think` option set |
458458
| 33 | `isBinaryThinking` | Binary thinking on/off compatibility |

docs/plugins/sdk-subpaths.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
110110
| `plugin-sdk/provider-env-vars` | Provider auth env-var lookup helpers |
111111
| `plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod`, `ensureApiKeyFromOptionEnvOrPrompt`, `upsertAuthProfile`, `upsertApiKeyProfile`, `writeOAuthCredentials` |
112112
| `plugin-sdk/provider-model-shared` | `ProviderReplayFamily`, `buildProviderReplayFamilyHooks`, `normalizeModelCompat`, shared replay-policy builders, provider-endpoint helpers, and model-id normalization helpers such as `normalizeNativeXaiModelId` |
113-
| `plugin-sdk/provider-catalog-runtime` | Provider catalog runtime hook and plugin-provider registry seams for contract tests |
113+
| `plugin-sdk/provider-catalog-runtime` | Provider catalog augmentation runtime hook and plugin-provider registry seams for contract tests |
114114
| `plugin-sdk/provider-catalog-shared` | `findCatalogTemplate`, `buildSingleProviderApiKeyCatalog`, `supportsNativeStreamingUsageCompat`, `applyProviderNativeStreamingUsageCompat` |
115115
| `plugin-sdk/provider-http` | Generic provider HTTP/endpoint capability helpers, provider HTTP errors, and audio transcription multipart form helpers |
116116
| `plugin-sdk/provider-web-fetch-contract` | Narrow web-fetch config/selection contract helpers such as `enablePluginInConfig` and `WebFetchProviderPlugin` |

extensions/feishu/src/setup-surface.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ async function getStatusWithEnvRefs(params: { appIdKey: string; appSecretKey: st
6767

6868
const feishuConfigure = createPluginSetupWizardConfigure(feishuPlugin);
6969
const feishuGetStatus = createPluginSetupWizardStatus(feishuPlugin);
70+
7071
describe("feishu setup wizard", () => {
7172
it("does not throw when config appId/appSecret are SecretRef objects", async () => {
7273
const text = vi

extensions/openai/openai-codex-provider.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -507,15 +507,6 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
507507
],
508508
}),
509509
isModernModelRef: ({ modelId }) => matchesExactOrPrefix(modelId, OPENAI_CODEX_MODERN_MODEL_IDS),
510-
suppressBuiltInModel: ({ provider, modelId }) =>
511-
normalizeProviderId(provider) === PROVIDER_ID &&
512-
normalizeLowercaseStringOrEmpty(modelId) === "gpt-5.3-codex-spark"
513-
? {
514-
suppress: true,
515-
errorMessage:
516-
"gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.",
517-
}
518-
: undefined,
519510
preferRuntimeResolvedModel: (ctx) => {
520511
if (normalizeProviderId(ctx.provider) !== PROVIDER_ID) {
521512
return false;

extensions/openai/openai-provider.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ const OPENAI_MODERN_MODEL_IDS = [
7777
OPENAI_GPT_54_NANO_MODEL_ID,
7878
"gpt-5.2",
7979
] as const;
80-
const OPENAI_DIRECT_SPARK_MODEL_ID = "gpt-5.3-codex-spark";
81-
const SUPPRESSED_SPARK_PROVIDERS = new Set(["openai", "azure-openai-responses"]);
8280
function shouldUseOpenAIResponsesTransport(params: {
8381
provider: string;
8482
api?: string | null;
@@ -260,18 +258,6 @@ export function buildOpenAIProvider(): ProviderPlugin {
260258
}
261259
return 'No API key found for provider "openai". You are authenticated with OpenAI Codex OAuth. Use openai-codex/gpt-5.5, or set OPENAI_API_KEY for direct OpenAI API access.';
262260
},
263-
suppressBuiltInModel: (ctx) => {
264-
if (
265-
!SUPPRESSED_SPARK_PROVIDERS.has(normalizeProviderId(ctx.provider)) ||
266-
normalizeLowercaseStringOrEmpty(ctx.modelId) !== OPENAI_DIRECT_SPARK_MODEL_ID
267-
) {
268-
return undefined;
269-
}
270-
return {
271-
suppress: true,
272-
errorMessage: `Unknown model: ${ctx.provider}/${OPENAI_DIRECT_SPARK_MODEL_ID}. ${OPENAI_DIRECT_SPARK_MODEL_ID} is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.`,
273-
};
274-
},
275261
augmentModelCatalog: (ctx) => {
276262
const openAiGpt55ProTemplate = findCatalogTemplate({
277263
entries: ctx.entries,

extensions/openai/test-support/provider-catalog.contract-test-support.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
import {
66
expectAugmentedCodexCatalog,
77
expectedAugmentedOpenaiCodexCatalogEntriesWithGpt55,
8-
expectCodexBuiltInSuppression,
98
expectCodexMissingAuthHint,
109
importProviderRuntimeCatalogModule,
1110
loadBundledPluginPublicSurface,
@@ -49,17 +48,6 @@ vi.mock("openclaw/plugin-sdk/provider-catalog-runtime", async () => {
4948
}
5049
return supplemental;
5150
},
52-
resolveProviderBuiltInModelSuppression: (params: {
53-
context: Parameters<NonNullable<ProviderPlugin["suppressBuiltInModel"]>>[0];
54-
}) => {
55-
for (const provider of resolveCatalogHookProviders(params)) {
56-
const result = provider.suppressBuiltInModel?.(params.context);
57-
if (result?.suppress) {
58-
return result;
59-
}
60-
}
61-
return undefined;
62-
},
6351
resolveOwningPluginIdsForProvider: (params: unknown) =>
6452
resolveOwningPluginIdsForProviderMock(params as never),
6553
resolveCatalogHookProviderPluginIds: (params: unknown) =>
@@ -86,15 +74,11 @@ export function describeOpenAIProviderCatalogContract() {
8674
})
8775
).providers;
8876
const openaiProvider = requireRegisteredProvider(openaiProviders, "openai", "provider");
89-
const {
90-
augmentModelCatalogWithProviderPlugins,
91-
resetProviderRuntimeHookCacheForTest,
92-
resolveProviderBuiltInModelSuppression,
93-
} = await importProviderRuntimeCatalogModule();
77+
const { augmentModelCatalogWithProviderPlugins, resetProviderRuntimeHookCacheForTest } =
78+
await importProviderRuntimeCatalogModule();
9479
return {
9580
augmentModelCatalogWithProviderPlugins,
9681
resetProviderRuntimeHookCacheForTest,
97-
resolveProviderBuiltInModelSuppression,
9882
openaiProviders,
9983
openaiProvider,
10084
};
@@ -141,11 +125,6 @@ export function describeOpenAIProviderCatalogContract() {
141125
);
142126
});
143127

144-
it("keeps built-in model suppression wired through the provider runtime", async () => {
145-
const { resolveProviderBuiltInModelSuppression } = await contractDepsPromise;
146-
expectCodexBuiltInSuppression(resolveProviderBuiltInModelSuppression);
147-
});
148-
149128
it("keeps bundled model augmentation wired through the provider runtime", async () => {
150129
const { augmentModelCatalogWithProviderPlugins } = await contractDepsPromise;
151130
await expectAugmentedCodexCatalog(

extensions/qwen/index.test.ts

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
21
import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime";
32
import { describe, expect, it } from "vitest";
43
import qwenPlugin from "./index.js";
@@ -8,42 +7,9 @@ async function registerQwenProvider() {
87
}
98

109
describe("qwen provider plugin", () => {
11-
it("does not suppress exact custom modelstudio providers owned by another api", async () => {
10+
it("does not expose runtime model suppression hooks", async () => {
1211
const provider = await registerQwenProvider();
13-
const config = {
14-
models: {
15-
providers: {
16-
modelstudio: {
17-
api: "openai-completions",
18-
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1",
19-
models: [{ id: "qwen3.6-plus", name: "Qwen 3.6 Plus" }],
20-
},
21-
},
22-
},
23-
} as unknown as OpenClawConfig;
2412

25-
expect(
26-
provider.suppressBuiltInModel?.({
27-
config,
28-
env: {},
29-
provider: "modelstudio",
30-
modelId: "qwen3.6-plus",
31-
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1",
32-
}),
33-
).toBeUndefined();
34-
});
35-
36-
it("still suppresses legacy modelstudio refs on Qwen Coding Plan endpoints", async () => {
37-
const provider = await registerQwenProvider();
38-
39-
expect(
40-
provider.suppressBuiltInModel?.({
41-
config: {},
42-
env: {},
43-
provider: "modelstudio",
44-
modelId: "qwen3.6-plus",
45-
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1",
46-
})?.suppress,
47-
).toBe(true);
13+
expect(provider.suppressBuiltInModel).toBeUndefined();
4814
});
4915
});

0 commit comments

Comments
 (0)