Skip to content

Commit ea39153

Browse files
committed
fix(agents): scope gpt-5.4-mini chat reasoning fallback
1 parent c1deafa commit ea39153

5 files changed

Lines changed: 42 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Docs: https://docs.openclaw.ai
2525
### Fixes
2626

2727
- Discord/status: honor explicit `messages.statusReactions.enabled: true` in tool-only guild channels so queued ack reactions can progress through thinking/done lifecycle reactions instead of stopping at the initial emoji. Thanks @Marvinthebored.
28-
- Agents/OpenAI: omit Chat Completions `reasoning_effort` for `gpt-5.4-mini` while preserving Responses reasoning support, preventing Telegram-routed fallback runs from hanging after OpenAI rejects tool payloads. Fixes #76176. Thanks @ThisIsAdilah and @chinar-amrutkar.
28+
- Agents/OpenAI: omit Chat Completions `reasoning_effort` for `gpt-5.4-mini` only when function tools are present while preserving tool-free Chat and Responses reasoning support, preventing Telegram-routed fallback runs from hanging after OpenAI rejects tool payloads. Fixes #76176. Thanks @ThisIsAdilah and @chinar-amrutkar.
2929
- Agents/models: forward model `maxTokens` as the default output-token limit for OpenAI-compatible Responses and Completions transports when no runtime override is provided, preventing provider defaults from silently truncating larger outputs. (#76645) Thanks @joeyfrasier.
3030
- Control UI/Skills: fix skill detail modal silently failing to open in all browsers by deferring `showModal()` until the dialog element is connected to the DOM; the Lit `ref` callback fired before connection causing a `DOMException: HTMLDialogElement.showModal: Dialog element is not connected` on every skill click. Thanks @nickmopen.
3131
- Gateway/update: run `doctor --non-interactive --fix` after Control UI global package updates before reporting success, so legacy config is migrated before the gateway restart. Thanks @stevenchouai.

src/agents/openai-reasoning-effort.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ describe("OpenAI reasoning effort support", () => {
1313
expect(resolveOpenAIReasoningEffortForModel({ model, effort: "xhigh" })).toBe("xhigh");
1414
});
1515

16-
it("omits reasoning_effort for gpt-5.4-mini in Chat Completions", () => {
16+
it("preserves reasoning_effort metadata for gpt-5.4-mini in Chat Completions", () => {
1717
const model = { provider: "openai", id: "gpt-5.4-mini", api: "openai-completions" };
18-
expect(resolveOpenAISupportedReasoningEfforts(model)).toHaveLength(0);
19-
expect(resolveOpenAIReasoningEffortForModel({ model, effort: "medium" })).toBeUndefined();
18+
expect(resolveOpenAISupportedReasoningEfforts(model)).toContain("medium");
19+
expect(resolveOpenAIReasoningEffortForModel({ model, effort: "medium" })).toBe("medium");
2020
});
2121

2222
it("preserves reasoning_effort for gpt-5.4-mini in Responses", () => {

src/agents/openai-reasoning-effort.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ function normalizeModelId(id: string | null | undefined): string {
2626
return normalizeLowercaseStringOrEmpty(id ?? "").replace(/-\d{4}-\d{2}-\d{2}$/u, "");
2727
}
2828

29+
export function isOpenAIGpt54MiniModel(model: OpenAIReasoningModel): boolean {
30+
const id = normalizeModelId(typeof model.id === "string" ? model.id : undefined);
31+
return /^gpt-5\.4-mini(?:-|$)/u.test(id);
32+
}
33+
2934
export function normalizeOpenAIReasoningEffort(effort: string): string {
3035
return effort === "minimal" ? "minimal" : effort;
3136
}
@@ -80,14 +85,6 @@ export function resolveOpenAISupportedReasoningEfforts(
8085
if (/^gpt-5\.[2-9](?:\.\d+)?-pro(?:-|$)/u.test(id)) {
8186
return GPT_PRO_REASONING_EFFORTS;
8287
}
83-
const api = normalizeLowercaseStringOrEmpty(typeof model.api === "string" ? model.api : "");
84-
if (api === "openai-responses" || api === "openai-codex-responses") {
85-
if (/^gpt-5\.4-mini(?:-|$)/u.test(id)) {
86-
return GPT_52_REASONING_EFFORTS;
87-
}
88-
} else if (/^gpt-5\.4-mini(?:-|$)/u.test(id)) {
89-
return [];
90-
}
9188
if (/^gpt-5\.[2-9](?:\.\d+)?(?:-|$)/u.test(id)) {
9289
return GPT_52_REASONING_EFFORTS;
9390
}

src/agents/openai-transport-stream.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,6 +2084,34 @@ describe("openai transport stream", () => {
20842084
expect(params).not.toHaveProperty("reasoning_effort");
20852085
});
20862086

2087+
it("keeps reasoning_effort for gpt-5.4-mini Chat Completions payloads without tools", () => {
2088+
const params = buildOpenAICompletionsParams(
2089+
{
2090+
id: "gpt-5.4-mini",
2091+
name: "GPT-5.4 mini",
2092+
api: "openai-completions",
2093+
provider: "openai",
2094+
baseUrl: "https://api.openai.com/v1",
2095+
reasoning: true,
2096+
input: ["text"],
2097+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2098+
contextWindow: 400000,
2099+
maxTokens: 128000,
2100+
} satisfies Model<"openai-completions">,
2101+
{
2102+
systemPrompt: "system",
2103+
messages: [],
2104+
tools: [],
2105+
} as never,
2106+
{
2107+
reasoning: "medium",
2108+
} as never,
2109+
) as { reasoning_effort?: unknown; tools?: unknown };
2110+
2111+
expect(params.tools).toEqual([]);
2112+
expect(params.reasoning_effort).toBe("medium");
2113+
});
2114+
20872115
it("uses provider-native reasoning effort values declared by model compat", () => {
20882116
const baseModel = {
20892117
id: "qwen/qwen3-32b",

src/agents/openai-transport-stream.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { detectOpenAICompletionsCompat } from "./openai-completions-compat.js";
2828
import { flattenCompletionMessagesToStringContent } from "./openai-completions-string-content.js";
2929
import { resolveOpenAIReasoningEffortMap } from "./openai-reasoning-compat.js";
3030
import {
31+
isOpenAIGpt54MiniModel,
3132
normalizeOpenAIReasoningEffort,
3233
resolveOpenAIReasoningEffortForModel,
3334
type OpenAIApiReasoningEffort,
@@ -1899,6 +1900,8 @@ export function buildOpenAICompletionsParams(
18991900
fallbackMap: compat.reasoningEffortMap,
19001901
})
19011902
: undefined;
1903+
const omitGpt54MiniToolReasoningEffort =
1904+
isOpenAIGpt54MiniModel(model) && Array.isArray(params.tools) && params.tools.length > 0;
19021905
if (
19031906
compat.thinkingFormat === "openrouter" &&
19041907
model.reasoning &&
@@ -1910,7 +1913,8 @@ export function buildOpenAICompletionsParams(
19101913
} else if (
19111914
resolvedCompletionsReasoningEffort &&
19121915
model.reasoning &&
1913-
compat.supportsReasoningEffort
1916+
compat.supportsReasoningEffort &&
1917+
!omitGpt54MiniToolReasoningEffort
19141918
) {
19151919
params.reasoning_effort = resolvedCompletionsReasoningEffort;
19161920
}

0 commit comments

Comments
 (0)