@@ -154,6 +154,13 @@ describe('AgenticChatController', () => {
154
154
rm : sinon . stub ( ) . resolves ( ) ,
155
155
}
156
156
157
+ // Add agent with runTool method to testFeatures
158
+ testFeatures . agent = {
159
+ runTool : sinon . stub ( ) . resolves ( { } ) ,
160
+ getTools : sinon . stub ( ) . returns ( [ ] ) ,
161
+ addTool : sinon . stub ( ) . resolves ( ) ,
162
+ }
163
+
157
164
// @ts -ignore
158
165
const cachedInitializeParams : InitializeParams = {
159
166
initializationOptions : {
@@ -305,6 +312,326 @@ describe('AgenticChatController', () => {
305
312
assert . deepStrictEqual ( chatResult , expectedCompleteChatResult )
306
313
} )
307
314
315
+ it ( 'handles tool use responses and makes multiple requests' , async ( ) => {
316
+ // First response includes a tool use request
317
+ const mockToolUseId = 'mock-tool-use-id'
318
+ const mockToolName = 'mock-tool-name'
319
+ const mockToolInput = JSON . stringify ( { param1 : 'value1' } )
320
+ const mockToolResult = { result : 'tool execution result' }
321
+
322
+ const mockToolUseResponseList : ChatResponseStream [ ] = [
323
+ {
324
+ messageMetadataEvent : {
325
+ conversationId : mockConversationId ,
326
+ } ,
327
+ } ,
328
+ {
329
+ assistantResponseEvent : {
330
+ content : 'I need to use a tool. ' ,
331
+ } ,
332
+ } ,
333
+ {
334
+ toolUseEvent : {
335
+ toolUseId : mockToolUseId ,
336
+ name : mockToolName ,
337
+ input : mockToolInput ,
338
+ stop : true ,
339
+ } ,
340
+ } ,
341
+ ]
342
+
343
+ // Second response after tool execution
344
+ const mockFinalResponseList : ChatResponseStream [ ] = [
345
+ {
346
+ messageMetadataEvent : {
347
+ conversationId : mockConversationId ,
348
+ } ,
349
+ } ,
350
+ {
351
+ assistantResponseEvent : {
352
+ content : 'Hello ' ,
353
+ } ,
354
+ } ,
355
+ {
356
+ assistantResponseEvent : {
357
+ content : 'World' ,
358
+ } ,
359
+ } ,
360
+ {
361
+ assistantResponseEvent : {
362
+ content : '!' ,
363
+ } ,
364
+ } ,
365
+ ]
366
+
367
+ // Reset the stub and set up to return different responses on consecutive calls
368
+ generateAssistantResponseStub . restore ( )
369
+ generateAssistantResponseStub = sinon . stub ( CodeWhispererStreaming . prototype , 'generateAssistantResponse' )
370
+
371
+ generateAssistantResponseStub . onFirstCall ( ) . returns (
372
+ Promise . resolve ( {
373
+ $metadata : {
374
+ requestId : mockMessageId ,
375
+ } ,
376
+ generateAssistantResponseResponse : createIterableResponse ( mockToolUseResponseList ) ,
377
+ } )
378
+ )
379
+
380
+ generateAssistantResponseStub . onSecondCall ( ) . returns (
381
+ Promise . resolve ( {
382
+ $metadata : {
383
+ requestId : mockMessageId ,
384
+ } ,
385
+ generateAssistantResponseResponse : createIterableResponse ( mockFinalResponseList ) ,
386
+ } )
387
+ )
388
+
389
+ // Reset the runTool stub
390
+ const runToolStub = testFeatures . agent . runTool as sinon . SinonStub
391
+ runToolStub . reset ( )
392
+ runToolStub . resolves ( mockToolResult )
393
+
394
+ // Make the request
395
+ const chatResultPromise = chatController . onChatPrompt (
396
+ { tabId : mockTabId , prompt : { prompt : 'Hello with tool' } } ,
397
+ mockCancellationToken
398
+ )
399
+
400
+ const chatResult = await chatResultPromise
401
+
402
+ // Verify that generateAssistantResponse was called twice
403
+ sinon . assert . calledTwice ( generateAssistantResponseStub )
404
+
405
+ // Verify that the tool was executed
406
+ sinon . assert . calledOnce ( runToolStub )
407
+ sinon . assert . calledWith ( runToolStub , mockToolName , JSON . parse ( mockToolInput ) )
408
+
409
+ // Verify that the second request included the tool results in the userInputMessageContext
410
+ const secondCallArgs = generateAssistantResponseStub . secondCall . args [ 0 ]
411
+ assert . ok (
412
+ secondCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext ?. toolResults
413
+ )
414
+ assert . strictEqual (
415
+ secondCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext ?. toolResults
416
+ . length ,
417
+ 1
418
+ )
419
+ assert . strictEqual (
420
+ secondCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext
421
+ ?. toolResults [ 0 ] . toolUseId ,
422
+ mockToolUseId
423
+ )
424
+ assert . strictEqual (
425
+ secondCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext
426
+ ?. toolResults [ 0 ] . status ,
427
+ 'success'
428
+ )
429
+ assert . deepStrictEqual (
430
+ secondCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext
431
+ ?. toolResults [ 0 ] . content [ 0 ] . json ,
432
+ { result : mockToolResult }
433
+ )
434
+
435
+ // Verify that the history was updated correctly
436
+ assert . ok ( secondCallArgs . conversationState ?. history )
437
+ assert . strictEqual ( secondCallArgs . conversationState ?. history . length , 2 )
438
+ assert . ok ( secondCallArgs . conversationState ?. history [ 0 ] . userInputMessage )
439
+ assert . ok ( secondCallArgs . conversationState ?. history [ 1 ] . assistantResponseMessage )
440
+
441
+ // Verify the final result
442
+ assert . deepStrictEqual ( chatResult , expectedCompleteChatResult )
443
+ } )
444
+
445
+ it ( 'handles multiple iterations of tool uses with proper history updates' , async ( ) => {
446
+ // First response includes a tool use request
447
+ const mockToolUseId1 = 'mock-tool-use-id-1'
448
+ const mockToolName1 = 'mock-tool-name-1'
449
+ const mockToolInput1 = JSON . stringify ( { param1 : 'value1' } )
450
+ const mockToolResult1 = { result : 'tool execution result 1' }
451
+
452
+ // Second tool use in a subsequent response
453
+ const mockToolUseId2 = 'mock-tool-use-id-2'
454
+ const mockToolName2 = 'mock-tool-name-2'
455
+ const mockToolInput2 = JSON . stringify ( { param2 : 'value2' } )
456
+ const mockToolResult2 = { result : 'tool execution result 2' }
457
+
458
+ // First response with first tool use
459
+ const mockFirstToolUseResponseList : ChatResponseStream [ ] = [
460
+ {
461
+ messageMetadataEvent : {
462
+ conversationId : mockConversationId ,
463
+ } ,
464
+ } ,
465
+ {
466
+ assistantResponseEvent : {
467
+ content : 'I need to use tool 1. ' ,
468
+ } ,
469
+ } ,
470
+ {
471
+ toolUseEvent : {
472
+ toolUseId : mockToolUseId1 ,
473
+ name : mockToolName1 ,
474
+ input : mockToolInput1 ,
475
+ stop : true ,
476
+ } ,
477
+ } ,
478
+ ]
479
+
480
+ // Second response with second tool use
481
+ const mockSecondToolUseResponseList : ChatResponseStream [ ] = [
482
+ {
483
+ messageMetadataEvent : {
484
+ conversationId : mockConversationId ,
485
+ } ,
486
+ } ,
487
+ {
488
+ assistantResponseEvent : {
489
+ content : 'Now I need to use tool 2. ' ,
490
+ } ,
491
+ } ,
492
+ {
493
+ toolUseEvent : {
494
+ toolUseId : mockToolUseId2 ,
495
+ name : mockToolName2 ,
496
+ input : mockToolInput2 ,
497
+ stop : true ,
498
+ } ,
499
+ } ,
500
+ ]
501
+
502
+ // Final response with complete answer
503
+ const mockFinalResponseList : ChatResponseStream [ ] = [
504
+ {
505
+ messageMetadataEvent : {
506
+ conversationId : mockConversationId ,
507
+ } ,
508
+ } ,
509
+ {
510
+ assistantResponseEvent : {
511
+ content : 'Hello ' ,
512
+ } ,
513
+ } ,
514
+ {
515
+ assistantResponseEvent : {
516
+ content : 'World' ,
517
+ } ,
518
+ } ,
519
+ {
520
+ assistantResponseEvent : {
521
+ content : '!' ,
522
+ } ,
523
+ } ,
524
+ ]
525
+
526
+ // Reset the stub and set up to return different responses on consecutive calls
527
+ generateAssistantResponseStub . restore ( )
528
+ generateAssistantResponseStub = sinon . stub ( CodeWhispererStreaming . prototype , 'generateAssistantResponse' )
529
+
530
+ generateAssistantResponseStub . onFirstCall ( ) . returns (
531
+ Promise . resolve ( {
532
+ $metadata : {
533
+ requestId : mockMessageId ,
534
+ } ,
535
+ generateAssistantResponseResponse : createIterableResponse ( mockFirstToolUseResponseList ) ,
536
+ } )
537
+ )
538
+
539
+ generateAssistantResponseStub . onSecondCall ( ) . returns (
540
+ Promise . resolve ( {
541
+ $metadata : {
542
+ requestId : mockMessageId ,
543
+ } ,
544
+ generateAssistantResponseResponse : createIterableResponse ( mockSecondToolUseResponseList ) ,
545
+ } )
546
+ )
547
+
548
+ generateAssistantResponseStub . onThirdCall ( ) . returns (
549
+ Promise . resolve ( {
550
+ $metadata : {
551
+ requestId : mockMessageId ,
552
+ } ,
553
+ generateAssistantResponseResponse : createIterableResponse ( mockFinalResponseList ) ,
554
+ } )
555
+ )
556
+
557
+ // Reset the runTool stub
558
+ const runToolStub = testFeatures . agent . runTool as sinon . SinonStub
559
+ runToolStub . reset ( )
560
+ runToolStub . withArgs ( mockToolName1 , JSON . parse ( mockToolInput1 ) ) . resolves ( mockToolResult1 )
561
+ runToolStub . withArgs ( mockToolName2 , JSON . parse ( mockToolInput2 ) ) . resolves ( mockToolResult2 )
562
+
563
+ // Make the request
564
+ const chatResultPromise = chatController . onChatPrompt (
565
+ { tabId : mockTabId , prompt : { prompt : 'Hello with multiple tools' } } ,
566
+ mockCancellationToken
567
+ )
568
+
569
+ const chatResult = await chatResultPromise
570
+
571
+ // Verify that generateAssistantResponse was called three times
572
+ sinon . assert . calledThrice ( generateAssistantResponseStub )
573
+
574
+ // Verify that the tools were executed
575
+ sinon . assert . calledTwice ( runToolStub )
576
+ sinon . assert . calledWith ( runToolStub , mockToolName1 , JSON . parse ( mockToolInput1 ) )
577
+ sinon . assert . calledWith ( runToolStub , mockToolName2 , JSON . parse ( mockToolInput2 ) )
578
+
579
+ // Verify that the second request included the first tool results
580
+ const secondCallArgs = generateAssistantResponseStub . secondCall . args [ 0 ]
581
+ assert . ok (
582
+ secondCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext ?. toolResults
583
+ )
584
+ assert . strictEqual (
585
+ secondCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext ?. toolResults
586
+ . length ,
587
+ 1
588
+ )
589
+ assert . strictEqual (
590
+ secondCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext
591
+ ?. toolResults [ 0 ] . toolUseId ,
592
+ mockToolUseId1
593
+ )
594
+
595
+ // Verify that the history was updated correctly after first tool use
596
+ assert . ok ( secondCallArgs . conversationState ?. history )
597
+ assert . strictEqual ( secondCallArgs . conversationState ?. history . length , 2 )
598
+ assert . ok ( secondCallArgs . conversationState ?. history [ 0 ] . userInputMessage )
599
+ assert . ok ( secondCallArgs . conversationState ?. history [ 1 ] . assistantResponseMessage )
600
+ assert . strictEqual (
601
+ secondCallArgs . conversationState ?. history [ 1 ] . assistantResponseMessage ?. content ,
602
+ 'I need to use tool 1. '
603
+ )
604
+
605
+ // Verify that the third request included the second tool results
606
+ const thirdCallArgs = generateAssistantResponseStub . thirdCall . args [ 0 ]
607
+ assert . ok (
608
+ thirdCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext ?. toolResults
609
+ )
610
+ assert . strictEqual (
611
+ thirdCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext ?. toolResults
612
+ . length ,
613
+ 1
614
+ )
615
+ assert . strictEqual (
616
+ thirdCallArgs . conversationState ?. currentMessage ?. userInputMessage ?. userInputMessageContext
617
+ ?. toolResults [ 0 ] . toolUseId ,
618
+ mockToolUseId2
619
+ )
620
+
621
+ // Verify that the history was updated correctly after second tool use
622
+ assert . ok ( thirdCallArgs . conversationState ?. history )
623
+ assert . strictEqual ( thirdCallArgs . conversationState ?. history . length , 4 )
624
+ assert . ok ( thirdCallArgs . conversationState ?. history [ 2 ] . userInputMessage )
625
+ assert . ok ( thirdCallArgs . conversationState ?. history [ 3 ] . assistantResponseMessage )
626
+ assert . strictEqual (
627
+ thirdCallArgs . conversationState ?. history [ 3 ] . assistantResponseMessage ?. content ,
628
+ 'Now I need to use tool 2. '
629
+ )
630
+
631
+ // Verify the final result
632
+ assert . deepStrictEqual ( chatResult , expectedCompleteChatResult )
633
+ } )
634
+
308
635
it ( 'returns help message if it is a help follow up action' , async ( ) => {
309
636
const chatResultPromise = chatController . onChatPrompt (
310
637
{ tabId : mockTabId , prompt : { prompt : DEFAULT_HELP_FOLLOW_UP_PROMPT } } ,
0 commit comments