11import { defined } from "../../testkit/defined.js" ;
22import { describe , test , expect } from "bun:test" ;
33import { ProtocolMismatchError } from "@intx/inference" ;
4+ import { createOpenAIAdapter } from "@intx/inference/providers" ;
45import type { ConversationTurn , InferenceOptions } from "@intx/types/runtime" ;
56import { createOpenAICompatibleAdapter } from "./openai-compatible-adapter.js" ;
67
@@ -178,33 +179,123 @@ describe("openai-compatible adapter V4 effort wiring", () => {
178179 return JSON . parse ( built . body ) as Record < string , unknown > ;
179180 }
180181
181- test ( "none drops reasoning_effort and sets thinking off" , ( ) => {
182- const body = bodyForModel ( "deepseek-v4-pro" , {
183- providerOptions : { reasoning_effort : "none" } ,
184- } as InferenceOptions ) ;
185- expect ( "reasoning_effort" in body ) . toBe ( false ) ;
186- expect ( body [ "chat_template_kwargs" ] ) . toEqual ( { thinking : false } ) ;
187- } ) ;
188-
189182 test . each ( [ "xhigh" , "max" ] as const ) (
190- "maps ladder %s through verbatim " ,
183+ "wires V4 effort %s through raw and turns thinking on " ,
191184 ( effort ) => {
192185 const body = bodyForModel ( "deepseek-v4-pro" , {
193186 providerOptions : { reasoning_effort : effort } ,
194187 } as InferenceOptions ) ;
195188 expect ( body [ "reasoning_effort" ] ) . toBe ( effort ) ;
189+ expect ( body [ "chat_template_kwargs" ] ) . toEqual ( { thinking : true } ) ;
196190 } ,
197191 ) ;
198192
199- test . each ( [ "low" , "medium" , "high" ] as const ) (
200- "leaves off-ladder %s untouched" ,
201- ( effort ) => {
193+ describe ( "V4 request settings (PR4)" , ( ) => {
194+ test ( "none → chat_template_kwargs.thinking false and NO reasoning_effort" , ( ) => {
202195 const body = bodyForModel ( "deepseek-v4-pro" , {
203- providerOptions : { reasoning_effort : effort } ,
196+ providerOptions : { reasoning_effort : "none" } ,
204197 } as InferenceOptions ) ;
205- expect ( body [ "reasoning_effort" ] ) . toBe ( effort ) ;
206- } ,
207- ) ;
198+ expect ( body [ "chat_template_kwargs" ] ) . toEqual ( { thinking : false } ) ;
199+ expect ( "reasoning_effort" in body ) . toBe ( false ) ;
200+ } ) ;
201+
202+ test ( "stream_options include_usage is true" , ( ) => {
203+ const body = bodyForModel ( "deepseek-v4-pro" , { } as InferenceOptions ) ;
204+ expect ( body [ "stream_options" ] ) . toEqual ( { include_usage : true } ) ;
205+ // An unset effort must not force the encoder's thinking pass on nor
206+ // inject an empty chat_template_kwargs object.
207+ expect ( body [ "chat_template_kwargs" ] ) . toBeUndefined ( ) ;
208+ } ) ;
209+
210+ test ( "top_p 0.95 and temperature 1.0 defaulted when absent" , ( ) => {
211+ const body = bodyForModel ( "deepseek-v4-pro" , { } as InferenceOptions ) ;
212+ expect ( body [ "top_p" ] ) . toBe ( 0.95 ) ;
213+ expect ( body [ "temperature" ] ) . toBe ( 1.0 ) ;
214+ // An unset effort must not force the encoder's thinking pass on nor
215+ // inject an empty chat_template_kwargs object.
216+ expect ( body [ "chat_template_kwargs" ] ) . toBeUndefined ( ) ;
217+ } ) ;
218+
219+ test ( "top_p and temperature preserved when providerOptions set them" , ( ) => {
220+ const body = bodyForModel ( "deepseek-v4-pro" , {
221+ providerOptions : { temperature : 0.7 , top_p : 0.9 } ,
222+ } as InferenceOptions ) ;
223+ expect ( body [ "top_p" ] ) . toBe ( 0.9 ) ;
224+ expect ( body [ "temperature" ] ) . toBe ( 0.7 ) ;
225+ } ) ;
226+
227+ test ( "chat_template_kwargs merge preserves existing keys" , ( ) => {
228+ const body = bodyForModel ( "deepseek-v4-pro" , {
229+ providerOptions : {
230+ reasoning_effort : "xhigh" ,
231+ chat_template_kwargs : { some : "existing" } ,
232+ } ,
233+ } as InferenceOptions ) ;
234+ expect ( body [ "chat_template_kwargs" ] ) . toEqual ( {
235+ thinking : true ,
236+ some : "existing" ,
237+ } ) ;
238+ } ) ;
239+
240+ test ( "off-ladder effort is left untouched (no high→xhigh coercion)" , ( ) => {
241+ const body = bodyForModel ( "deepseek-v4-pro" , {
242+ providerOptions : { reasoning_effort : "high" } ,
243+ } as InferenceOptions ) ;
244+ expect ( body [ "reasoning_effort" ] ) . toBe ( "high" ) ;
245+ expect ( "chat_template_kwargs" in body ) . toBe ( false ) ;
246+ } ) ;
247+
248+ test ( "none merges thinking off into, never replaces, existing kwargs" , ( ) => {
249+ const body = bodyForModel ( "deepseek-v4-pro" , {
250+ providerOptions : {
251+ reasoning_effort : "none" ,
252+ chat_template_kwargs : { some : "existing" } ,
253+ } ,
254+ } as InferenceOptions ) ;
255+ expect ( body [ "chat_template_kwargs" ] ) . toEqual ( {
256+ thinking : false ,
257+ some : "existing" ,
258+ } ) ;
259+ } ) ;
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+ } ) ;
298+ } ) ;
208299
209300 describe ( "v4-effort is no-op for non-V4" , ( ) => {
210301 const models = [
@@ -219,28 +310,50 @@ describe("openai-compatible adapter V4 effort wiring", () => {
219310 "deepseek-v3" ,
220311 ] ;
221312
222- test . each ( models ) (
223- "merged body for %s is byte-identical to a manual base+merge (V4 step no-op)" ,
224- ( model ) => {
313+ test . each (
314+ models . flatMap ( ( model ) =>
315+ ( [ "high" , "none" , "max" ] as const ) . map ( ( effort ) => [ model , effort ] ) ,
316+ ) ,
317+ ) (
318+ "merged body for %s with effort %s is byte-identical to a manual base+merge (V4 step no-op)" ,
319+ ( model , effort ) => {
225320 const base = bodyForModel ( model , { } as InferenceOptions ) ;
226321 const withEffort = bodyForModel ( model , {
227- providerOptions : { reasoning_effort : "high" } ,
322+ providerOptions : { reasoning_effort : effort } ,
228323 } as InferenceOptions ) ;
324+ // For a non-V4 model the V4 step must not run: the output body equals
325+ // the base body shallow-merged with providerOptions verbatim (no
326+ // effort mapping, no chat_template_kwargs injection) — including V4
327+ // ladder values like none (delete + thinking:false) and max (verbatim),
328+ // so a regression routing any V4 rung through the V4 branch is caught.
229329 const expected = {
230330 ...base ,
231- ...( { reasoning_effort : "high" } as const ) ,
331+ ...( { reasoning_effort : effort } as const ) ,
232332 } ;
233333 expect ( withEffort ) . toEqual ( expected ) ;
234334 } ,
235335 ) ;
236336
237337 test . each ( models ) (
238- "merged body for %s carries reasoning_effort verbatim " ,
338+ "V4 settings (top_p/temperature/stream_options/chat_template_kwargs) not injected for %s " ,
239339 ( model ) => {
240- const body = bodyForModel ( model , {
241- providerOptions : { reasoning_effort : "high" } ,
242- } as InferenceOptions ) ;
243- expect ( body [ "reasoning_effort" ] ) . toBe ( "high" ) ;
340+ const body = bodyForModel ( model , { } as InferenceOptions ) ;
341+ // Full-body equality against the raw base OpenAI adapter output: for a
342+ // non-V4 model the wrapper must short-circuit the V4 step entirely, so
343+ // the body matches the base build byte-for-byte (a regression that
344+ // injects a 5th field or mutates an existing one would fail here).
345+ const baseAdapter = createOpenAIAdapter ( {
346+ ...source ,
347+ model,
348+ } as typeof source ) ;
349+ const baseBuilt = baseAdapter . buildRequest (
350+ messages ,
351+ model ,
352+ { } as InferenceOptions ,
353+ ) ;
354+ expect ( body ) . toEqual (
355+ JSON . parse ( baseBuilt . body ) as Record < string , unknown > ,
356+ ) ;
244357 } ,
245358 ) ;
246359 } ) ;
0 commit comments