@@ -142,7 +142,16 @@ describe("FriendliHandler", () => {
142142 } ,
143143 ] ) (
144144 "should expose newly added model $modelId" ,
145- ( { modelId, contextWindow, maxTokens, supportsMaxTokens, inputPrice, outputPrice, cacheWritesPrice, cacheReadsPrice } ) => {
145+ ( {
146+ modelId,
147+ contextWindow,
148+ maxTokens,
149+ supportsMaxTokens,
150+ inputPrice,
151+ outputPrice,
152+ cacheWritesPrice,
153+ cacheReadsPrice,
154+ } ) => {
146155 expect ( friendliModels [ modelId ] ) . toBeDefined ( )
147156 const info = friendliModels [ modelId ] as import ( "@roo-code/types" ) . ModelInfo
148157 expect ( info . maxTokens ) . toBe ( maxTokens )
@@ -394,3 +403,217 @@ describe("Friendli model max output tokens (clamping behavior)", () => {
394403 expect ( result ) . toBe ( 80_000 )
395404 } )
396405} )
406+
407+ describe ( "FriendliHandler — Friendli-specific reasoning params" , ( ) => {
408+ beforeEach ( ( ) => {
409+ vi . clearAllMocks ( )
410+ } )
411+
412+ it ( "should include reasoning_effort, chat_template_kwargs, parse_reasoning for GLM-5.2 with reasoning enabled" , async ( ) => {
413+ const handler = new FriendliHandler ( {
414+ apiModelId : "zai-org/GLM-5.2" ,
415+ friendliApiKey : "test-key" ,
416+ enableReasoningEffort : true ,
417+ reasoningEffort : "high" ,
418+ } )
419+
420+ mockCreate . mockImplementationOnce ( ( ) => ( {
421+ [ Symbol . asyncIterator ] : ( ) => ( {
422+ async next ( ) {
423+ return { done : true }
424+ } ,
425+ } ) ,
426+ } ) )
427+
428+ await handler . createMessage ( "system" , [ ] ) . next ( )
429+
430+ expect ( mockCreate ) . toHaveBeenCalledWith (
431+ expect . objectContaining ( {
432+ model : "zai-org/GLM-5.2" ,
433+ reasoning_effort : "high" ,
434+ chat_template_kwargs : { enable_thinking : true } ,
435+ parse_reasoning : true ,
436+ include_reasoning : true ,
437+ } ) ,
438+ undefined ,
439+ )
440+ } )
441+
442+ it ( "should send enable_thinking: false when enableReasoningEffort is false on controllable model" , async ( ) => {
443+ const handler = new FriendliHandler ( {
444+ apiModelId : "zai-org/GLM-5.2" ,
445+ friendliApiKey : "test-...ey" ,
446+ enableReasoningEffort : false ,
447+ } )
448+
449+ mockCreate . mockImplementationOnce ( ( ) => ( {
450+ [ Symbol . asyncIterator ] : ( ) => ( {
451+ async next ( ) {
452+ return { done : true }
453+ } ,
454+ } ) ,
455+ } ) )
456+
457+ await handler . createMessage ( "system" , [ ] ) . next ( )
458+
459+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ] as Record < string , unknown >
460+ expect ( callArgs . reasoning_effort ) . toBeUndefined ( )
461+ expect ( callArgs . chat_template_kwargs ) . toEqual ( { enable_thinking : false } )
462+ expect ( callArgs . parse_reasoning ) . toBeUndefined ( )
463+ expect ( callArgs . include_reasoning ) . toBeUndefined ( )
464+ } )
465+
466+ it ( "should send enable_thinking: false when reasoningEffort is none on controllable model" , async ( ) => {
467+ const handler = new FriendliHandler ( {
468+ apiModelId : "zai-org/GLM-5.2" ,
469+ friendliApiKey : "test-...ey" ,
470+ enableReasoningEffort : true ,
471+ reasoningEffort : "none" ,
472+ } )
473+
474+ mockCreate . mockImplementationOnce ( ( ) => ( {
475+ [ Symbol . asyncIterator ] : ( ) => ( {
476+ async next ( ) {
477+ return { done : true }
478+ } ,
479+ } ) ,
480+ } ) )
481+
482+ await handler . createMessage ( "system" , [ ] ) . next ( )
483+
484+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ] as Record < string , unknown >
485+ expect ( callArgs . reasoning_effort ) . toBeUndefined ( )
486+ expect ( callArgs . chat_template_kwargs ) . toEqual ( { enable_thinking : false } )
487+ expect ( callArgs . parse_reasoning ) . toBeUndefined ( )
488+ expect ( callArgs . include_reasoning ) . toBeUndefined ( )
489+ } )
490+
491+ it ( "should send enable_thinking: false when reasoningEffort is disable on controllable model" , async ( ) => {
492+ const handler = new FriendliHandler ( {
493+ apiModelId : "zai-org/GLM-5.2" ,
494+ friendliApiKey : "test-...ey" ,
495+ enableReasoningEffort : true ,
496+ reasoningEffort : "disable" ,
497+ } )
498+
499+ mockCreate . mockImplementationOnce ( ( ) => ( {
500+ [ Symbol . asyncIterator ] : ( ) => ( {
501+ async next ( ) {
502+ return { done : true }
503+ } ,
504+ } ) ,
505+ } ) )
506+
507+ await handler . createMessage ( "system" , [ ] ) . next ( )
508+
509+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ] as Record < string , unknown >
510+ expect ( callArgs . reasoning_effort ) . toBeUndefined ( )
511+ expect ( callArgs . chat_template_kwargs ) . toEqual ( { enable_thinking : false } )
512+ expect ( callArgs . parse_reasoning ) . toBeUndefined ( )
513+ expect ( callArgs . include_reasoning ) . toBeUndefined ( )
514+ } )
515+
516+ it ( "should use model default reasoningEffort when no explicit settings are provided" , async ( ) => {
517+ const handler = new FriendliHandler ( {
518+ apiModelId : "zai-org/GLM-5.2" ,
519+ friendliApiKey : "test-...ey" ,
520+ // No enableReasoningEffort or reasoningEffort — model default "high" kicks in
521+ } )
522+
523+ mockCreate . mockImplementationOnce ( ( ) => ( {
524+ [ Symbol . asyncIterator ] : ( ) => ( {
525+ async next ( ) {
526+ return { done : true }
527+ } ,
528+ } ) ,
529+ } ) )
530+
531+ await handler . createMessage ( "system" , [ ] ) . next ( )
532+
533+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ] as Record < string , unknown >
534+ expect ( callArgs . reasoning_effort ) . toBe ( "high" )
535+ expect ( callArgs . chat_template_kwargs ) . toEqual ( { enable_thinking : true } )
536+ expect ( callArgs . parse_reasoning ) . toBe ( true )
537+ expect ( callArgs . include_reasoning ) . toBe ( true )
538+ } )
539+
540+ it ( "should not include any reasoning params for non-reasoning DeepSeek-V3.2" , async ( ) => {
541+ const handler = new FriendliHandler ( {
542+ apiModelId : "deepseek-ai/DeepSeek-V3.2" ,
543+ friendliApiKey : "test-key" ,
544+ enableReasoningEffort : true ,
545+ reasoningEffort : "high" ,
546+ } )
547+
548+ mockCreate . mockImplementationOnce ( ( ) => ( {
549+ [ Symbol . asyncIterator ] : ( ) => ( {
550+ async next ( ) {
551+ return { done : true }
552+ } ,
553+ } ) ,
554+ } ) )
555+
556+ await handler . createMessage ( "system" , [ ] ) . next ( )
557+
558+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ] as Record < string , unknown >
559+ expect ( callArgs . reasoning_effort ) . toBeUndefined ( )
560+ expect ( callArgs . chat_template_kwargs ) . toBeUndefined ( )
561+ expect ( callArgs . parse_reasoning ) . toBeUndefined ( )
562+ } )
563+
564+ it ( "should handle delta.reasoning_content from parse_reasoning=true stream" , async ( ) => {
565+ const handler = new FriendliHandler ( {
566+ apiModelId : "zai-org/GLM-5.2" ,
567+ friendliApiKey : "test-key" ,
568+ enableReasoningEffort : true ,
569+ reasoningEffort : "high" ,
570+ } )
571+
572+ mockCreate . mockImplementationOnce ( async ( ) => ( {
573+ [ Symbol . asyncIterator ] : async function * ( ) {
574+ yield {
575+ choices : [ { delta : { reasoning_content : "Let me think..." } } ] ,
576+ usage : null ,
577+ }
578+ yield {
579+ choices : [ { delta : { content : "The answer is 42" } } ] ,
580+ usage : null ,
581+ }
582+ yield {
583+ choices : [ { delta : { } } ] ,
584+ usage : { prompt_tokens : 10 , completion_tokens : 20 , total_tokens : 30 } ,
585+ }
586+ } ,
587+ } ) )
588+
589+ const stream = handler . createMessage ( "system" , [ ] )
590+ const chunks = [ ]
591+ for await ( const chunk of stream ) {
592+ chunks . push ( chunk )
593+ }
594+
595+ expect ( chunks ) . toContainEqual ( { type : "reasoning" , text : "Let me think..." } )
596+ expect ( chunks ) . toContainEqual ( { type : "text" , text : "The answer is 42" } )
597+ } )
598+
599+ it ( "completePrompt should include reasoning params when enabled" , async ( ) => {
600+ const handler = new FriendliHandler ( {
601+ apiModelId : "zai-org/GLM-5.2" ,
602+ friendliApiKey : "test-key" ,
603+ enableReasoningEffort : true ,
604+ reasoningEffort : "medium" ,
605+ } )
606+
607+ mockCreate . mockResolvedValueOnce ( {
608+ choices : [ { message : { content : "test result" } } ] ,
609+ } )
610+
611+ await handler . completePrompt ( "test" )
612+
613+ const callArgs = mockCreate . mock . calls [ 0 ] [ 0 ] as Record < string , unknown >
614+ expect ( callArgs . reasoning_effort ) . toBe ( "medium" )
615+ expect ( callArgs . chat_template_kwargs ) . toEqual ( { enable_thinking : true } )
616+ expect ( callArgs . parse_reasoning ) . toBe ( true )
617+ expect ( callArgs . include_reasoning ) . toBe ( true )
618+ } )
619+ } )
0 commit comments