@@ -304,51 +304,6 @@ describe('executeAnthropicProviderRequest forced tool use', () => {
304304 )
305305} )
306306
307- /**
308- * Claude Sonnet 5.5 rejects `thinking.type: "disabled"` with a 400 and names `between_tools`
309- * (which takes no effort or display field) as its lowest setting. Every other model keeps
310- * `none` as "send no thinking config".
311- */
312- describe ( 'executeAnthropicProviderRequest none thinking level' , ( ) => {
313- async function sendWithThinkingNone ( model : string ) {
314- const create = vi . fn ( ) . mockResolvedValue ( {
315- id : 'msg-none' ,
316- type : 'message' ,
317- role : 'assistant' ,
318- model,
319- content : [ { type : 'text' , text : 'Done' } ] ,
320- stop_reason : 'end_turn' ,
321- stop_sequence : null ,
322- usage : { input_tokens : 2 , output_tokens : 2 } ,
323- } )
324- await executeAnthropicProviderRequest (
325- {
326- model,
327- apiKey : 'test-key' ,
328- maxTokens : 1024 ,
329- thinkingLevel : 'none' ,
330- agentEvents : true ,
331- messages : [ { role : 'user' , content : 'Hello' } ] ,
332- } ,
333- {
334- providerId : 'anthropic' ,
335- providerLabel : 'Anthropic' ,
336- createClient : ( ) => ( { messages : { create } } ) as never ,
337- logger : { info : vi . fn ( ) , warn : vi . fn ( ) , error : vi . fn ( ) , debug : vi . fn ( ) } ,
338- }
339- )
340- return create . mock . calls [ 0 ] [ 0 ] as Anthropic . Messages . MessageCreateParams
341- }
342-
343- it . each ( [ 'claude-sonnet-5' , 'claude-opus-5-5' ] ) (
344- 'sends no thinking config on %s' ,
345- async ( model ) => {
346- const payload = await sendWithThinkingNone ( model )
347- expect ( payload . thinking ) . toBeUndefined ( )
348- }
349- )
350- } )
351-
352307describe ( 'executeAnthropicProviderRequest native structured outputs' , ( ) => {
353308 /** The subset of the wire schema these assertions read back. */
354309 interface WireSchemaNode {
@@ -793,8 +748,11 @@ describe('streaming', () => {
793748
794749 /**
795750 * Both tool loops rebuild the request for every turn after a tool call, so
796- * Sonnet 5.5's `none` mapping must survive past the first request: a later
797- * turn without it would silently run adaptive thinking at the API default.
751+ * the `none` mapping must hold past the first request. Claude Sonnet 5.5
752+ * rejects `thinking.type: "disabled"` and names `between_tools` (which takes
753+ * no effort or display field) as its lowest setting; a later turn without it
754+ * would silently run adaptive thinking. Every other model keeps `none` as
755+ * "send no thinking config".
798756 */
799757 describe ( 'executeAnthropicProviderRequest none thinking level across tool turns' , ( ) => {
800758 const lookupTool = {
@@ -804,28 +762,24 @@ describe('streaming', () => {
804762 params : { } ,
805763 parameters : { type : 'object' , properties : { } , required : [ ] } ,
806764 }
807- const toolTurn = message (
808- [ { type : 'tool_use' , id : 'tool-1' , name : 'lookup' , input : { } } ] ,
809- 'tool_use'
810- )
811- const answerTurn = message ( [ { type : 'text' , text : 'done' } ] , 'end_turn' )
765+ const turns = [
766+ message ( [ { type : 'tool_use' , id : 'tool-1' , name : 'lookup' , input : { } } ] , 'tool_use' ) ,
767+ message ( [ { type : 'text' , text : 'done' } ] , 'end_turn' ) ,
768+ ]
812769
813- function expectBareBetweenTools ( payloads : Anthropic . Messages . MessageCreateParams [ ] ) {
814- expect ( payloads ) . toHaveLength ( 2 )
815- for ( const payload of payloads ) {
816- expect ( payload . thinking ) . toEqual ( { type : 'between_tools' } )
817- expect ( payload . output_config ) . toBeUndefined ( )
818- }
819- }
820-
821- it ( 'sends bare between_tools on every non-streaming turn' , async ( ) => {
770+ /** Runs a tool exchange and returns every request body sent over the SDK boundary. */
771+ async function runToolExchange ( model : string , streaming : boolean ) {
822772 mockExecuteTool . mockResolvedValue ( { success : true , output : { value : 'tool result' } } )
823- const create = vi . fn ( ) . mockResolvedValueOnce ( toolTurn ) . mockResolvedValueOnce ( answerTurn )
824-
825- await executeAnthropicProviderRequest (
773+ const sent : Anthropic . Messages . MessageCreateParams [ ] = [ ]
774+ const nextTurn = ( payload : Anthropic . Messages . MessageCreateParams ) => {
775+ sent . push ( payload )
776+ return turns [ sent . length - 1 ]
777+ }
778+ const result = await executeAnthropicProviderRequest (
826779 {
827- model : 'claude-sonnet-5-5' ,
780+ model,
828781 apiKey : 'test-key' ,
782+ stream : streaming ,
829783 maxTokens : 1024 ,
830784 thinkingLevel : 'none' ,
831785 agentEvents : true ,
@@ -835,43 +789,42 @@ describe('streaming', () => {
835789 {
836790 providerId : 'anthropic' ,
837791 providerLabel : 'Anthropic' ,
838- createClient : ( ) => ( { messages : { create } } ) as never ,
792+ createClient : ( ) =>
793+ ( {
794+ messages : streaming
795+ ? {
796+ stream : ( payload : never ) =>
797+ stream ( [ { type : 'message_stop' } ] , nextTurn ( payload ) ) ,
798+ }
799+ : { create : async ( payload : never ) => nextTurn ( payload ) } ,
800+ } ) as never ,
839801 logger : { info : vi . fn ( ) , warn : vi . fn ( ) , error : vi . fn ( ) , debug : vi . fn ( ) } ,
840802 }
841803 )
804+ if ( streaming ) await collectEvents ( result as StreamingExecution )
805+ return sent
806+ }
842807
843- expectBareBetweenTools ( create . mock . calls . map ( ( call ) => call [ 0 ] ) )
844- } )
845-
846- it ( 'sends bare between_tools on every streaming turn' , async ( ) => {
847- mockExecuteTool . mockResolvedValue ( { success : true , output : { value : 'tool result' } } )
848- const createStream = vi
849- . fn ( )
850- . mockReturnValueOnce ( stream ( [ { type : 'message_stop' } ] , toolTurn ) )
851- . mockReturnValueOnce ( stream ( [ { type : 'message_stop' } ] , answerTurn ) )
852-
853- const result = ( await executeAnthropicProviderRequest (
854- {
855- model : 'claude-sonnet-5-5' ,
856- apiKey : 'test-key' ,
857- stream : true ,
858- maxTokens : 1024 ,
859- thinkingLevel : 'none' ,
860- agentEvents : true ,
861- messages : [ { role : 'user' , content : 'Look this up' } ] ,
862- tools : [ lookupTool ] ,
863- } ,
864- {
865- providerId : 'anthropic' ,
866- providerLabel : 'Anthropic' ,
867- createClient : ( ) => ( { messages : { stream : createStream } } ) as never ,
868- logger : { info : vi . fn ( ) , warn : vi . fn ( ) , error : vi . fn ( ) , debug : vi . fn ( ) } ,
808+ it . each ( [ false , true ] ) (
809+ 'sends bare between_tools on every turn (streaming: %s)' ,
810+ async ( streaming ) => {
811+ const sent = await runToolExchange ( 'claude-sonnet-5-5' , streaming )
812+ expect ( sent ) . toHaveLength ( 2 )
813+ for ( const payload of sent ) {
814+ expect ( payload . thinking ) . toEqual ( { type : 'between_tools' } )
815+ expect ( payload . output_config ) . toBeUndefined ( )
869816 }
870- ) ) as StreamingExecution
871- await collectEvents ( result )
817+ }
818+ )
872819
873- expectBareBetweenTools ( createStream . mock . calls . map ( ( call ) => call [ 0 ] ) )
874- } )
820+ it . each ( [ 'claude-sonnet-5' , 'claude-opus-5-5' ] ) (
821+ 'sends no thinking config on %s' ,
822+ async ( model ) => {
823+ const sent = await runToolExchange ( model , false )
824+ expect ( sent ) . toHaveLength ( 2 )
825+ for ( const payload of sent ) expect ( payload . thinking ) . toBeUndefined ( )
826+ }
827+ )
875828 } )
876829} )
877830
0 commit comments