mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:20:43 +00:00
fix(models): unconditionally suppress stale openai-codex/gpt-5.4-mini inline entries (#74451) (#74655)
* fix(models): block stale openai-codex/gpt-5.4-mini inline entries via unconditional suppression (#74451) Suppress explicitly user-configured openai-codex/gpt-5.4-mini inline entries so a stale models config written by `openclaw doctor --fix` cannot bypass the manifest capability block and cause repeated assistant-turn failures when the runtime switches to that model on ChatGPT-backed Codex accounts. Adds `unconditionalOnly` flag to `buildManifestBuiltInModelSuppressionResolver` and a `shouldUnconditionallySuppress` helper. Inside `resolveExplicitModelWithRegistry`, inline matches are now gated on unconditional suppressions (no `when` clause) before returning. Conditional suppressions such as the qwen Coding Plan endpoint guard remain bypassable by explicit user configuration, preserving the existing `resolves explicitly configured qwen3.6-plus before Coding Plan built-in suppression` behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(changelog): add missing reporter attribution for #74451 models suppression fix * docs: credit codex mini suppression contributors --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Shakker <shakkerdroid@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Models: suppress explicitly configured openai-codex/gpt-5.4-mini inline entries so a stale models config written by `openclaw doctor --fix` cannot bypass the manifest capability block and cause repeated assistant-turn failures when the runtime switches to that model on ChatGPT-backed Codex accounts. Conditional suppressions (e.g. qwen Coding Plan endpoint guards) remain bypassable by explicit user configuration. (#74451) Thanks @0xCyda, @hclsys, and @Marvae.
|
||||
- Dependencies: refresh workspace runtime, plugin, and tooling packages, including ACP, Pi, AWS SDK, TypeBox, pnpm, oxlint, oxfmt, jsdom, pdfjs, ciao, and tokenjuice, while keeping patched ACP behavior and lint gates current. Thanks @mariozechner.
|
||||
- Messages/queue: make `steer` drain all pending Pi steering messages at the next model boundary, keep legacy one-at-a-time steering as `queue`, and add a dedicated steering queue docs page. Thanks @vincentkoc.
|
||||
- Messages/queue: default active-run queueing to `steer` with a 500ms followup fallback debounce, and document the queue modes, precedence, and drop policies on the command queue page. Thanks @vincentkoc.
|
||||
|
||||
@@ -11,6 +11,7 @@ function resolveBuiltInModelSuppressionFromManifest(params: {
|
||||
id?: string | null;
|
||||
baseUrl?: string | null;
|
||||
config?: OpenClawConfig;
|
||||
unconditionalOnly?: boolean;
|
||||
}) {
|
||||
const provider = normalizeProviderId(params.provider ?? "");
|
||||
const modelId = normalizeLowercaseStringOrEmpty(params.id);
|
||||
@@ -22,6 +23,7 @@ function resolveBuiltInModelSuppressionFromManifest(params: {
|
||||
id: modelId,
|
||||
...(params.config ? { config: params.config } : {}),
|
||||
...(params.baseUrl ? { baseUrl: params.baseUrl } : {}),
|
||||
unconditionalOnly: params.unconditionalOnly,
|
||||
env: process.env,
|
||||
});
|
||||
}
|
||||
@@ -61,6 +63,20 @@ export function shouldSuppressBuiltInModel(params: {
|
||||
return resolveBuiltInModelSuppression(params)?.suppress ?? false;
|
||||
}
|
||||
|
||||
// Checks only unconditional suppressions (no `when` clause). Used for inline
|
||||
// model entries where user configuration may override conditional suppressions
|
||||
// (e.g. custom endpoint overrides) but not absolute provider capability blocks.
|
||||
export function shouldUnconditionallySuppress(params: {
|
||||
provider?: string | null;
|
||||
id?: string | null;
|
||||
config?: OpenClawConfig;
|
||||
}): boolean {
|
||||
return (
|
||||
resolveBuiltInModelSuppressionFromManifest({ ...params, unconditionalOnly: true })?.suppress ??
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
export function buildSuppressedBuiltInModelError(params: {
|
||||
provider?: string | null;
|
||||
id?: string | null;
|
||||
|
||||
@@ -69,6 +69,20 @@ vi.mock("../model-suppression.js", () => {
|
||||
isQwenCodingPlanBaseUrl(baseUrl ?? resolveConfiguredQwenBaseUrl(config))
|
||||
);
|
||||
},
|
||||
shouldUnconditionallySuppress: ({ provider, id }: { provider?: string; id?: string }) => {
|
||||
if (
|
||||
(provider === "openai" ||
|
||||
provider === "azure-openai-responses" ||
|
||||
provider === "openai-codex") &&
|
||||
id?.trim().toLowerCase() === "gpt-5.3-codex-spark"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (provider === "openai-codex" && id?.trim().toLowerCase() === "gpt-5.4-mini") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
buildSuppressedBuiltInModelError: ({
|
||||
provider,
|
||||
id,
|
||||
@@ -355,6 +369,34 @@ describe("resolveModel", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("#74451: suppresses explicitly configured openai-codex/gpt-5.4-mini despite inline entry", () => {
|
||||
const cfg = {
|
||||
models: {
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
api: "openai-codex-responses",
|
||||
models: [
|
||||
{
|
||||
id: "gpt-5.4-mini",
|
||||
name: "GPT-5.4 mini",
|
||||
api: "openai-codex-responses",
|
||||
contextWindow: 400_000,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const result = resolveModelForTest("openai-codex", "gpt-5.4-mini", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe(
|
||||
"Unknown model: openai-codex/gpt-5.4-mini. gpt-5.4-mini is not supported by the OpenAI Codex OAuth route. Use openai/gpt-5.4-mini with an OpenAI API key or openai-codex/gpt-5.5 with Codex OAuth.",
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes Google fallback baseUrls for custom providers", () => {
|
||||
const cfg = {
|
||||
models: {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { findNormalizedProviderValue, normalizeProviderId } from "../model-selec
|
||||
import {
|
||||
buildSuppressedBuiltInModelError,
|
||||
shouldSuppressBuiltInModel,
|
||||
shouldUnconditionallySuppress,
|
||||
} from "../model-suppression.js";
|
||||
import { isLegacyModelsAddCodexMetadataModel } from "../openai-codex-models-add-legacy.js";
|
||||
import { discoverAuthStorage, discoverModels } from "../pi-model-discovery.js";
|
||||
@@ -614,6 +615,14 @@ function resolveExplicitModelWithRegistry(params: {
|
||||
modelId,
|
||||
});
|
||||
if (inlineMatch?.api) {
|
||||
// Unconditional suppressions (no `when` clause) represent absolute provider
|
||||
// capability blocks that cannot be overridden by inline user configuration.
|
||||
// Conditional suppressions (e.g. baseUrlHosts-gated qwen restrictions) are
|
||||
// intentionally bypassable when the user has explicitly configured the model.
|
||||
// (#74451)
|
||||
if (shouldUnconditionallySuppress({ provider, id: modelId, config: cfg })) {
|
||||
return { kind: "suppressed" };
|
||||
}
|
||||
const resolvedParams = mergeConfiguredRuntimeModelParams({
|
||||
cfg,
|
||||
provider,
|
||||
|
||||
@@ -118,6 +118,7 @@ export function buildManifestBuiltInModelSuppressionResolver(params: {
|
||||
provider?: string | null;
|
||||
id?: string | null;
|
||||
baseUrl?: string | null;
|
||||
unconditionalOnly?: boolean;
|
||||
}) => {
|
||||
const provider = normalizeLowercaseStringOrEmpty(input.provider);
|
||||
const modelId = normalizeLowercaseStringOrEmpty(input.id);
|
||||
@@ -128,6 +129,7 @@ export function buildManifestBuiltInModelSuppressionResolver(params: {
|
||||
const suppression = suppressions.find(
|
||||
(entry) =>
|
||||
entry.mergeKey === mergeKey &&
|
||||
(!input.unconditionalOnly || !entry.when) &&
|
||||
manifestSuppressionMatchesConditions({
|
||||
suppression: entry,
|
||||
provider,
|
||||
@@ -163,6 +165,7 @@ export function resolveManifestBuiltInModelSuppression(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
baseUrl?: string | null;
|
||||
unconditionalOnly?: boolean;
|
||||
}) {
|
||||
const resolver = buildManifestBuiltInModelSuppressionResolver({
|
||||
config: params.config,
|
||||
@@ -173,5 +176,6 @@ export function resolveManifestBuiltInModelSuppression(params: {
|
||||
provider: params.provider,
|
||||
id: params.id,
|
||||
baseUrl: params.baseUrl,
|
||||
unconditionalOnly: params.unconditionalOnly,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user