@@ -446,16 +446,110 @@ Deno.test("string formats are described in instructions instead of the schema",
446446 assert ( compatibility . instructions . includes ( "`input.slug` must match `^[a-z]+$`" ) ) ;
447447} ) ;
448448
449- Deno . test ( "tools are not strict unless the caller opts in" , ( ) => {
449+ Deno . test ( "tools are strict unless one opts out" , ( ) => {
450+ const options = {
451+ description : "A tool" ,
452+ parameters : z . object ( { query : z . string ( ) } ) ,
453+ execute : ( ) => "ok" ,
454+ } ;
455+ const plain = new Tool ( { ...options , name : "plain" } ) ;
456+ const optOut = new Tool ( { ...options , name : "opt_out" , anthropicStrict : false } ) ;
457+
458+ assertEquals ( normalizeAnthropicTools ( [ plain , optOut ] ) . map ( ( { anthropic } ) => anthropic . strict ) , [ true , false ] ) ;
459+ } ) ;
460+
461+ Deno . test ( "tool input is not streamed eagerly, so the API validates it" , ( ) => {
462+ const tool = new Tool ( {
463+ name : "search" ,
464+ description : "A tool" ,
465+ parameters : z . object ( { query : z . string ( ) } ) ,
466+ execute : ( ) => "ok" ,
467+ } ) ;
468+
469+ const voidTool = new Tool ( {
470+ name : "ping" ,
471+ description : "A tool" ,
472+ parameters : z . void ( ) ,
473+ execute : ( ) => "ok" ,
474+ } ) ;
475+
476+ for ( const normalized of normalizeAnthropicTools ( [ tool , voidTool ] ) ) {
477+ assertEquals ( "eager_input_streaming" in normalized . anthropic , false ) ;
478+ }
479+ } ) ;
480+
481+ Deno . test ( "a malformed tool call still replays as a legal tool_use block" , async ( ) => {
450482 const tool = new Tool ( {
451483 name : "search" ,
452484 description : "A tool" ,
453485 parameters : z . object ( { query : z . string ( ) } ) ,
454486 execute : ( ) => "ok" ,
455487 } ) ;
488+ const malformed = '{"query": what is a cat}' ;
456489
457- assertEquals ( normalizeAnthropicTools ( [ tool ] ) [ 0 ] . anthropic . strict , false ) ;
458- assertEquals ( normalizeAnthropicTools ( [ tool ] , true ) [ 0 ] . anthropic . strict , true ) ;
490+ const history = await getAnthropicHistory ( {
491+ history : [
492+ { type : "tool_use" , tool_use_id : "call_1" , kind : "search" , content : malformed } ,
493+ { type : "tool_result_text" , tool_use_id : "call_1" , content : "Error: Invalid parameters for tool" } ,
494+ ] ,
495+ normalizedTools : normalizeAnthropicTools ( [ tool ] ) ,
496+ signal : AbortSignal . abort ( ) ,
497+ } ) ;
498+
499+ assertEquals ( history [ 0 ] , {
500+ role : "assistant" ,
501+ content : [ { type : "tool_use" , id : "call_1" , name : "search" , input : { content : malformed } } ] ,
502+ } ) ;
503+ } ) ;
504+
505+ Deno . test ( "malformed tool arguments are handed on rather than ending the run" , async ( ) => {
506+ const tool = new Tool ( {
507+ name : "search" ,
508+ description : "A tool" ,
509+ parameters : z . object ( { query : z . string ( ) } ) ,
510+ execute : ( ) => "ok" ,
511+ } ) ;
512+ const malformed = '{"query": what is a cat}' ;
513+ const events = [
514+ { type : "content_block_start" , index : 0 , content_block : { type : "tool_use" , id : "call_1" , name : "search" } } ,
515+ { type : "content_block_delta" , index : 0 , delta : { type : "input_json_delta" , partial_json : malformed } } ,
516+ { type : "content_block_stop" , index : 0 } ,
517+ ] ;
518+ const adapter = anthropicModel ( {
519+ model : "claude-opus-5" ,
520+ apiKey : "unused" ,
521+ client : {
522+ beta : {
523+ messages : {
524+ stream : ( ) =>
525+ Object . assign (
526+ async function * ( ) {
527+ yield * events ;
528+ } ( ) ,
529+ {
530+ finalMessage : ( ) => Promise . resolve ( { usage : { input_tokens : 1 , output_tokens : 1 } } ) ,
531+ } ,
532+ ) ,
533+ } ,
534+ } ,
535+ // deno-lint-ignore no-explicit-any
536+ } as any ,
537+ } ) ;
538+
539+ const { items } = await collectAdapterStream ( adapter . stream ( {
540+ history : [ { type : "input_text" , content : "hi" } ] ,
541+ instructions : "" ,
542+ tools : [ tool ] ,
543+ signal : AbortSignal . timeout ( 1000 ) ,
544+ } ) ) ;
545+
546+ assertEquals ( items . filter ( ( item ) => item . type === "tool_use" ) , [ {
547+ type : "tool_use" ,
548+ index : 0 ,
549+ kind : tool . name ,
550+ tool_use_id : "call_1" ,
551+ content : malformed ,
552+ } ] ) ;
459553} ) ;
460554
461555Deno . test ( "Anthropic retry feedback is replayed as a user message" , async ( ) => {
0 commit comments