Skip to content

Commit ae424d1

Browse files
committed
fix(provider): make v4 thinking and stream absent-only
1 parent da5b241 commit ae424d1

2 files changed

Lines changed: 44 additions & 6 deletions

File tree

‎src/provider/openai-compatible-adapter.test.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,44 @@ describe("openai-compatible adapter V4 effort wiring", () => {
257257
some: "existing",
258258
});
259259
});
260+
261+
test("provider-set thinking is preserved (absent-only thinking)", () => {
262+
// A provider-supplied thinking value beats the V4 effort's implied one:
263+
// `none` normally forces thinking off, but an explicit thinking:true
264+
// must be kept (never force-stamped), and vice versa.
265+
const noneThenTrue = bodyForModel("deepseek-v4-pro", {
266+
providerOptions: {
267+
reasoning_effort: "none",
268+
chat_template_kwargs: { thinking: true },
269+
},
270+
} as InferenceOptions);
271+
expect(noneThenTrue["chat_template_kwargs"]).toEqual({ thinking: true });
272+
273+
const xhighThenFalse = bodyForModel("deepseek-v4-pro", {
274+
providerOptions: {
275+
reasoning_effort: "xhigh",
276+
chat_template_kwargs: { thinking: false },
277+
},
278+
} as InferenceOptions);
279+
expect(xhighThenFalse["chat_template_kwargs"]).toEqual({
280+
thinking: false,
281+
});
282+
});
283+
284+
test("unset effort does not inject empty chat_template_kwargs", () => {
285+
const body = bodyForModel("deepseek-v4-pro", {} as InferenceOptions);
286+
expect("chat_template_kwargs" in body).toBe(false);
287+
expect(body["reasoning_effort"]).toBeUndefined();
288+
});
289+
290+
test("provider-set stream_options is preserved (absent-only)", () => {
291+
const body = bodyForModel("deepseek-v4-pro", {
292+
providerOptions: {
293+
stream_options: { include_usage: false },
294+
},
295+
} as InferenceOptions);
296+
expect(body["stream_options"]).toEqual({ include_usage: false });
297+
});
260298
});
261299

262300
describe("v4-effort is no-op for non-V4", () => {

‎src/provider/openai-compatible-adapter.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,16 @@ export function createOpenAICompatibleAdapter(
6363

6464
// Merge a thinking toggle into a clone of the existing chat_template_kwargs
6565
// (never overwrite the object wholesale, and never mutate the caller's
66-
// providerOptions ref). For a wire effort the toggle is absent-only: an
67-
// explicit provider-set thinking value wins.
66+
// providerOptions ref). The toggle is absent-only: a provider-supplied
67+
// thinking value wins over the V4 effort's implied value.
6868
const applyV4Thinking = (thinking: boolean): void => {
6969
const existing =
7070
body["chat_template_kwargs"] !== null &&
7171
typeof body["chat_template_kwargs"] === "object"
7272
? (body["chat_template_kwargs"] as Record<string, unknown>)
7373
: {};
7474
const merged = { ...existing };
75-
if (thinking) merged.thinking ??= true;
76-
else merged.thinking = false;
75+
merged.thinking ??= thinking;
7776
Object.assign(body, { chat_template_kwargs: merged });
7877
};
7978
// V4 drives its own effort ladder (none/xhigh/max) and encodes thinking via
@@ -100,8 +99,9 @@ export function createOpenAICompatibleAdapter(
10099
body["top_p"] = DEEPSEEK_V4_MODEL_CARD.topP;
101100
if (body["temperature"] === undefined)
102101
body["temperature"] = DEEPSEEK_V4_MODEL_CARD.temperature;
103-
if (body["stream_options"] === undefined)
104-
body["stream_options"] = V4_STREAM_OPTIONS.stream_options;
102+
// Absent-only, mirroring the sampling guard: provider-set stream_options
103+
// is never overwritten.
104+
body["stream_options"] ??= V4_STREAM_OPTIONS.stream_options;
105105
}
106106
if (stripReasoning && Array.isArray(body["messages"])) {
107107
for (const msg of body["messages"]) {

0 commit comments

Comments
 (0)