Skip to content

Commit 59a3dcb

Browse files
authored
Merge pull request #370 from coderbuzz/fix/reasoning-effort-top-level
fix(core): send reasoning_effort at the top level of the request
2 parents 857b58c + 5954625 commit 59a3dcb

4 files changed

Lines changed: 61 additions & 12 deletions

File tree

‎packages/core/src/common/openai-thinking.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ type ThinkingConfig = {
66

77
type ThinkingRequestOptions = {
88
thinking?: ThinkingConfig;
9-
extra_body?: {
10-
reasoning_effort?: ReasoningEffort;
11-
};
9+
// Top level, not `extra_body`: `extra_body` is a Python-SDK convention. The Node OpenAI SDK sends it
10+
// as a literal JSON key, which the DeepSeek API ignores.
11+
reasoning_effort?: ReasoningEffort;
1212
};
1313

1414
export function buildThinkingRequestOptions(
@@ -20,6 +20,6 @@ export function buildThinkingRequestOptions(
2020

2121
return {
2222
thinking,
23-
...(thinkingEnabled ? { extra_body: { reasoning_effort: reasoningEffort } } : {}),
23+
...(thinkingEnabled ? { reasoning_effort: reasoningEffort } : {}),
2424
};
2525
}

‎packages/core/src/tests/openai-thinking.test.ts‎

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { test } from "node:test";
22
import assert from "node:assert/strict";
3+
import OpenAI from "openai";
34
import { buildThinkingRequestOptions } from "../common/openai-thinking";
45

56
test("buildThinkingRequestOptions explicitly disables thinking", () => {
@@ -17,27 +18,60 @@ test("buildThinkingRequestOptions uses the same disabled payload for volces endp
1718
test("buildThinkingRequestOptions enables thinking with default reasoning effort", () => {
1819
assert.deepEqual(buildThinkingRequestOptions(true, "https://api.deepseek.com"), {
1920
thinking: { type: "enabled" },
20-
extra_body: { reasoning_effort: "max" },
21+
reasoning_effort: "max",
2122
});
2223
});
2324

2425
test("buildThinkingRequestOptions uses the same enabled payload for volces endpoints", () => {
2526
assert.deepEqual(buildThinkingRequestOptions(true, "https://ark.cn-beijing.volces.com/api/v3"), {
2627
thinking: { type: "enabled" },
27-
extra_body: { reasoning_effort: "max" },
28+
reasoning_effort: "max",
2829
});
2930
});
3031

3132
test("buildThinkingRequestOptions accepts high reasoning effort", () => {
3233
assert.deepEqual(buildThinkingRequestOptions(true, "https://api.deepseek.com", "high"), {
3334
thinking: { type: "enabled" },
34-
extra_body: { reasoning_effort: "high" },
35+
reasoning_effort: "high",
3536
});
3637
});
3738

3839
test("buildThinkingRequestOptions accepts low reasoning effort", () => {
3940
assert.deepEqual(buildThinkingRequestOptions(true, "https://api.deepseek.com", "low"), {
4041
thinking: { type: "enabled" },
41-
extra_body: { reasoning_effort: "low" },
42+
reasoning_effort: "low",
4243
});
4344
});
45+
46+
test("OpenAI SDK sends thinking options at the top level", async () => {
47+
let body: Record<string, unknown> = {};
48+
const client = new OpenAI({
49+
apiKey: "test-key",
50+
maxRetries: 0,
51+
fetch: async (_url, init) => {
52+
body = JSON.parse(String(init?.body));
53+
return new Response('{"choices":[]}', { headers: { "content-type": "application/json" } });
54+
},
55+
});
56+
57+
for (const [enabled, effort] of [
58+
[true, "low"],
59+
[true, "high"],
60+
[true, "max"],
61+
[true, undefined],
62+
[false, "max"],
63+
] as const) {
64+
await client.chat.completions.create({
65+
model: "deepseek-flash",
66+
messages: [{ role: "user", content: "hello" }],
67+
...buildThinkingRequestOptions(enabled, undefined, effort),
68+
} as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming);
69+
70+
assert.deepEqual(body, {
71+
model: "deepseek-flash",
72+
messages: [{ role: "user", content: "hello" }],
73+
thinking: { type: enabled ? "enabled" : "disabled" },
74+
...(enabled ? { reasoning_effort: effort ?? "max" } : {}),
75+
});
76+
}
77+
});

‎packages/core/src/tests/tool-handlers.test.ts‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ test("Edit appends an LLM diagnosis when old_string is not found", async () => {
631631
assert.ok(snippet);
632632

633633
let llmCalls = 0;
634+
let llmRequest: Record<string, unknown> | undefined;
634635
let prompt = "";
635636
const editResult = await handleEditTool(
636637
{
@@ -645,6 +646,7 @@ test("Edit appends an LLM diagnosis when old_string is not found", async () => {
645646
completions: {
646647
create: async (request: { messages?: Array<{ content?: string }> }) => {
647648
llmCalls += 1;
649+
llmRequest = request;
648650
prompt = String(request.messages?.[1]?.content ?? "");
649651
return {
650652
choices: [
@@ -661,13 +663,17 @@ test("Edit appends an LLM diagnosis when old_string is not found", async () => {
661663
},
662664
} as any,
663665
model: "test-model",
664-
thinkingEnabled: false,
666+
thinkingEnabled: true,
667+
reasoningEffort: "max",
665668
}),
666669
})
667670
);
668671

669672
assert.equal(editResult.ok, false);
670673
assert.equal(llmCalls, 1);
674+
assert.deepEqual(llmRequest?.thinking, { type: "enabled" });
675+
assert.equal(llmRequest?.reasoning_effort, "max");
676+
assert.equal("extra_body" in llmRequest!, false);
671677
assert.equal(
672678
editResult.error,
673679
"old_string not found in file. The requested function name is computeTotal, but the snippet contains computeSubtotal."
@@ -972,6 +978,7 @@ test("Edit uses LLM correction when straight and curly quotation marks differ",
972978
const sessionId = "quotation-mark-correction";
973979
const snippet = await readSnippet(filePath, sessionId, workspace);
974980
let llmCalls = 0;
981+
let llmRequest: Record<string, unknown> | undefined;
975982
const editResult = await handleEditTool(
976983
{
977984
snippet_id: snippet.id,
@@ -989,6 +996,7 @@ test("Edit uses LLM correction when straight and curly quotation marks differ",
989996
completions: {
990997
create: async (request: { messages?: Array<{ content?: string }> }) => {
991998
llmCalls += 1;
999+
llmRequest = request;
9921000
assert.match(String(request.messages?.[0]?.content ?? ""), /the only problem is quotation mark/);
9931001
return {
9941002
choices: [
@@ -1013,13 +1021,17 @@ test("Edit uses LLM correction when straight and curly quotation marks differ",
10131021
},
10141022
} as any,
10151023
model: "test-model",
1016-
thinkingEnabled: false,
1024+
thinkingEnabled: true,
1025+
reasoningEffort: "max",
10171026
}),
10181027
})
10191028
);
10201029

10211030
assert.equal(editResult.ok, true);
10221031
assert.equal(llmCalls, 1);
1032+
assert.deepEqual(llmRequest?.thinking, { type: "enabled" });
1033+
assert.equal(llmRequest?.reasoning_effort, "max");
1034+
assert.equal("extra_body" in llmRequest!, false);
10231035
assert.equal(editResult.metadata?.matched_via, "llm_escape_correction");
10241036
assert.equal(
10251037
fs.readFileSync(filePath, "utf8"),

‎packages/core/src/tools/edit-handler.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as fs from "fs";
22
import { z } from "zod";
3+
import type { ChatCompletionCreateParamsNonStreaming } from "openai/resources/chat/completions";
34
import { buildThinkingRequestOptions } from "../common/openai-thinking";
45
import type { ToolExecutionContext, ToolExecutionResult } from "./executor";
56
import {
@@ -662,6 +663,7 @@ async function inferOldStringNotFoundReasonWithLLM(
662663

663664
try {
664665
context.signal?.throwIfAborted();
666+
// DeepSeek supports "max", which is absent from the OpenAI SDK types.
665667
const response = await client.chat.completions.create(
666668
{
667669
model,
@@ -692,7 +694,7 @@ async function inferOldStringNotFoundReasonWithLLM(
692694
},
693695
],
694696
...buildThinkingRequestOptions(thinkingEnabled, baseURL, reasoningEffort),
695-
},
697+
} as ChatCompletionCreateParamsNonStreaming,
696698
{ signal: context.signal }
697699
);
698700
context.signal?.throwIfAborted();
@@ -748,6 +750,7 @@ async function correctEscapedStringsWithLLM(
748750
try {
749751
const problemDescription = describeCorrectionProblems(oldString, matchedText);
750752
context.signal?.throwIfAborted();
753+
// DeepSeek supports "max", which is absent from the OpenAI SDK types.
751754
const response = await client.chat.completions.create(
752755
{
753756
model,
@@ -777,7 +780,7 @@ async function correctEscapedStringsWithLLM(
777780
},
778781
],
779782
...buildThinkingRequestOptions(thinkingEnabled, baseURL, reasoningEffort),
780-
},
783+
} as ChatCompletionCreateParamsNonStreaming,
781784
{ signal: context.signal }
782785
);
783786
context.signal?.throwIfAborted();

0 commit comments

Comments
 (0)