@@ -35,14 +35,14 @@ func TestAgentSimpleResponse(t *testing.T) {
3535func TestAgentToolCall (t * testing.T ) {
3636 toolCall := iteragent.ToolCall {
3737 Tool : "echo" ,
38- Args : map [string ]string {"msg" : "ping" },
38+ Args : map [string ]interface {} {"msg" : "ping" },
3939 }
4040
4141 called := false
4242 echoTool := iteragent.Tool {
4343 Name : "echo" ,
4444 Description : "Echo the msg argument" ,
45- Execute : func (ctx context.Context , args map [string ]string ) (string , error ) {
45+ Execute : func (ctx context.Context , args map [string ]interface {} ) (string , error ) {
4646 called = true
4747 return "pong" , nil
4848 },
@@ -130,6 +130,57 @@ func TestParseToolCallsNone(t *testing.T) {
130130 }
131131}
132132
133+ // TestParseToolCallsMalformedJSON verifies that truncated/malformed tool blocks
134+ // are recovered where possible and silently dropped when unrecoverable.
135+ func TestParseToolCallsMalformedJSON (t * testing.T ) {
136+ cases := []struct {
137+ name string
138+ input string
139+ wantN int
140+ wantTool string
141+ }{
142+ {
143+ name : "truncated after tool value" ,
144+ input : "```tool\n {\" tool\" :\" bash\" ,\" args\" :{\" command\" :\" ls\" \n ```" ,
145+ wantN : 1 ,
146+ wantTool : "bash" ,
147+ },
148+ {
149+ name : "missing outer closing brace" ,
150+ input : "```tool\n {\" tool\" :\" read_file\" ,\" args\" :{\" path\" :\" /tmp/x\" }\n ```" ,
151+ wantN : 1 ,
152+ wantTool : "read_file" ,
153+ },
154+ {
155+ name : "completely invalid JSON" ,
156+ input : "```tool\n not json at all\n ```" ,
157+ wantN : 0 ,
158+ },
159+ {
160+ name : "empty block" ,
161+ input : "```tool\n \n ```" ,
162+ wantN : 0 ,
163+ },
164+ {
165+ name : "trailing garbage after closing brace" ,
166+ input : "```tool\n {\" tool\" :\" bash\" ,\" args\" :{\" command\" :\" ls\" }} extra stuff\n ```" ,
167+ wantN : 1 ,
168+ wantTool : "bash" ,
169+ },
170+ }
171+ for _ , tc := range cases {
172+ t .Run (tc .name , func (t * testing.T ) {
173+ calls := iteragent .ParseToolCalls (tc .input )
174+ if len (calls ) != tc .wantN {
175+ t .Fatalf ("want %d calls, got %d" , tc .wantN , len (calls ))
176+ }
177+ if tc .wantN > 0 && calls [0 ].Tool != tc .wantTool {
178+ t .Errorf ("want tool=%q, got %q" , tc .wantTool , calls [0 ].Tool )
179+ }
180+ })
181+ }
182+ }
183+
133184// TestToolDescriptions verifies that tool descriptions are formatted.
134185func TestToolDescriptions (t * testing.T ) {
135186 tools := []iteragent.Tool {
@@ -226,13 +277,13 @@ func TestAgentHooks_BeforeAfterTurn(t *testing.T) {
226277func TestAgentHooks_OnToolStartEnd (t * testing.T ) {
227278 toolCall := iteragent.ToolCall {
228279 Tool : "greet" ,
229- Args : map [string ]string {"name" : "world" },
280+ Args : map [string ]interface {} {"name" : "world" },
230281 }
231282 greetTool := iteragent.Tool {
232283 Name : "greet" ,
233284 Description : "Greet someone" ,
234- Execute : func (ctx context.Context , args map [string ]string ) (string , error ) {
235- return "hello " + args [ "name" ] , nil
285+ Execute : func (ctx context.Context , args map [string ]interface {} ) (string , error ) {
286+ return "hello " + iteragent . ArgStr ( args , "name" ) , nil
236287 },
237288 }
238289
@@ -243,10 +294,10 @@ func TestAgentHooks_OnToolStartEnd(t *testing.T) {
243294 var endResults []string
244295 var endErrors []error
245296 a .WithHooks (iteragent.AgentHooks {
246- OnToolStart : func (toolName string , args map [string ]string ) {
297+ OnToolStart : func (toolName string , args map [string ]interface {} ) {
247298 startTools = append (startTools , toolName )
248- if args [ "name" ] != "world" {
249- t .Errorf ("OnToolStart: args[name]=%q, want %q" , args [ "name" ] , "world" )
299+ if iteragent . ArgStr ( args , "name" ) != "world" {
300+ t .Errorf ("OnToolStart: args[name]=%q, want %q" , iteragent . ArgStr ( args , "name" ) , "world" )
250301 }
251302 },
252303 OnToolEnd : func (toolName string , result string , err error ) {
@@ -357,10 +408,10 @@ func TestAgentTokenStreamer_RunReturnsFullResponse(t *testing.T) {
357408// call precedes the final response. The mock streams every turn, so we verify
358409// that EventTokenUpdate events were emitted and the final answer is correct.
359410func TestAgentTokenStreamer_WithTools (t * testing.T ) {
360- toolCall := iteragent.ToolCall {Tool : "noop" , Args : map [string ]string {}}
411+ toolCall := iteragent.ToolCall {Tool : "noop" , Args : map [string ]interface {} {}}
361412 noopTool := iteragent.Tool {
362413 Name : "noop" ,
363- Execute : func (ctx context.Context , args map [string ]string ) (string , error ) {
414+ Execute : func (ctx context.Context , args map [string ]interface {} ) (string , error ) {
364415 return "done" , nil
365416 },
366417 }
@@ -421,13 +472,13 @@ func TestAgentClose(t *testing.T) {
421472// when the parallel execution strategy is used.
422473func TestAgentHooks_Parallel (t * testing.T ) {
423474 calls := []iteragent.ToolCall {
424- {Tool : "t1" , Args : map [string ]string {}},
425- {Tool : "t2" , Args : map [string ]string {}},
475+ {Tool : "t1" , Args : map [string ]interface {} {}},
476+ {Tool : "t2" , Args : map [string ]interface {} {}},
426477 }
427478 makeTool := func (name string ) iteragent.Tool {
428479 return iteragent.Tool {
429480 Name : name ,
430- Execute : func (ctx context.Context , args map [string ]string ) (string , error ) {
481+ Execute : func (ctx context.Context , args map [string ]interface {} ) (string , error ) {
431482 return name + "-result" , nil
432483 },
433484 }
@@ -441,7 +492,7 @@ func TestAgentHooks_Parallel(t *testing.T) {
441492 var started , ended []string
442493
443494 a .WithHooks (iteragent.AgentHooks {
444- OnToolStart : func (toolName string , args map [string ]string ) {
495+ OnToolStart : func (toolName string , args map [string ]interface {} ) {
445496 mu .Lock ()
446497 started = append (started , toolName )
447498 mu .Unlock ()
@@ -507,10 +558,10 @@ func TestPromptMessages_Hooks_BeforeAfterTurn(t *testing.T) {
507558// TestPromptMessages_Hooks_OnToolStartEnd verifies OnToolStart and OnToolEnd fire
508559// around each tool execution during PromptMessages().
509560func TestPromptMessages_Hooks_OnToolStartEnd (t * testing.T ) {
510- toolCall := iteragent.ToolCall {Tool : "ping" , Args : map [string ]string {}}
561+ toolCall := iteragent.ToolCall {Tool : "ping" , Args : map [string ]interface {} {}}
511562 pingTool := iteragent.Tool {
512563 Name : "ping" ,
513- Execute : func (ctx context.Context , args map [string ]string ) (string , error ) {
564+ Execute : func (ctx context.Context , args map [string ]interface {} ) (string , error ) {
514565 return "pong" , nil
515566 },
516567 }
@@ -519,7 +570,7 @@ func TestPromptMessages_Hooks_OnToolStartEnd(t *testing.T) {
519570
520571 var startTools , endTools []string
521572 a .WithHooks (iteragent.AgentHooks {
522- OnToolStart : func (toolName string , args map [string ]string ) {
573+ OnToolStart : func (toolName string , args map [string ]interface {} ) {
523574 startTools = append (startTools , toolName )
524575 },
525576 OnToolEnd : func (toolName string , result string , err error ) {
@@ -778,7 +829,7 @@ func TestAgentWithMcpServerHttp_ToolCallable(t *testing.T) {
778829 ctx := context .Background ()
779830 // Agent will call mcp_echo on turn 1, then return "done" on turn 2.
780831 p := iteragent .NewMockWithTools ("done" , []iteragent.ToolCall {
781- {Tool : "mcp_echo" , Args : map [string ]string {}},
832+ {Tool : "mcp_echo" , Args : map [string ]interface {} {}},
782833 })
783834 a , err := iteragent .New (p , nil , testLogger ()).
784835 WithMcpServerHttp (ctx , srv .URL )
0 commit comments