@google/gemini-cli-core 0.17.0-nightly.20251117.cf8de02c6 → 0.17.0-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/google-gemini-cli-core-0.17.0-preview.0.tgz +0 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/code_assist/experiments/flagNames.d.ts +3 -0
  6. package/dist/src/code_assist/experiments/flagNames.js +3 -0
  7. package/dist/src/code_assist/experiments/flagNames.js.map +1 -1
  8. package/dist/src/config/config.d.ts +13 -0
  9. package/dist/src/config/config.js +44 -1
  10. package/dist/src/config/config.js.map +1 -1
  11. package/dist/src/config/config.test.js +77 -0
  12. package/dist/src/config/config.test.js.map +1 -1
  13. package/dist/src/config/models.d.ts +22 -1
  14. package/dist/src/config/models.js +48 -5
  15. package/dist/src/config/models.js.map +1 -1
  16. package/dist/src/config/models.test.js +71 -10
  17. package/dist/src/config/models.test.js.map +1 -1
  18. package/dist/src/core/client.d.ts +0 -1
  19. package/dist/src/core/client.js +5 -12
  20. package/dist/src/core/client.js.map +1 -1
  21. package/dist/src/core/client.test.js +5 -16
  22. package/dist/src/core/client.test.js.map +1 -1
  23. package/dist/src/core/geminiChat.d.ts +2 -0
  24. package/dist/src/core/geminiChat.js +91 -12
  25. package/dist/src/core/geminiChat.js.map +1 -1
  26. package/dist/src/core/geminiChat.test.js +253 -18
  27. package/dist/src/core/geminiChat.test.js.map +1 -1
  28. package/dist/src/fallback/handler.js +52 -5
  29. package/dist/src/fallback/handler.js.map +1 -1
  30. package/dist/src/fallback/handler.test.js +64 -4
  31. package/dist/src/fallback/handler.test.js.map +1 -1
  32. package/dist/src/fallback/types.d.ts +1 -1
  33. package/dist/src/generated/git-commit.d.ts +2 -2
  34. package/dist/src/generated/git-commit.js +2 -2
  35. package/dist/src/generated/git-commit.js.map +1 -1
  36. package/dist/src/ide/detect-ide.d.ts +4 -0
  37. package/dist/src/ide/detect-ide.js +4 -0
  38. package/dist/src/ide/detect-ide.js.map +1 -1
  39. package/dist/src/ide/detect-ide.test.js +5 -0
  40. package/dist/src/ide/detect-ide.test.js.map +1 -1
  41. package/dist/src/ide/ide-client.d.ts +3 -1
  42. package/dist/src/ide/ide-client.js +5 -4
  43. package/dist/src/ide/ide-client.js.map +1 -1
  44. package/dist/src/ide/ide-installer.js +65 -20
  45. package/dist/src/ide/ide-installer.js.map +1 -1
  46. package/dist/src/ide/ide-installer.test.js +41 -0
  47. package/dist/src/ide/ide-installer.test.js.map +1 -1
  48. package/dist/src/index.d.ts +1 -0
  49. package/dist/src/index.js +1 -0
  50. package/dist/src/index.js.map +1 -1
  51. package/dist/src/routing/modelRouterService.test.js +62 -0
  52. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  53. package/dist/src/routing/strategies/classifierStrategy.d.ts +1 -1
  54. package/dist/src/routing/strategies/classifierStrategy.js +4 -4
  55. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  56. package/dist/src/routing/strategies/classifierStrategy.test.js +1 -0
  57. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  58. package/dist/src/routing/strategies/fallbackStrategy.js +1 -1
  59. package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
  60. package/dist/src/routing/strategies/fallbackStrategy.test.js +4 -0
  61. package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
  62. package/dist/src/routing/strategies/overrideStrategy.js +2 -2
  63. package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
  64. package/dist/src/routing/strategies/overrideStrategy.test.js +3 -0
  65. package/dist/src/routing/strategies/overrideStrategy.test.js.map +1 -1
  66. package/dist/src/tools/edit.test.js +203 -200
  67. package/dist/src/tools/edit.test.js.map +1 -1
  68. package/dist/src/tools/mcp-client.d.ts +4 -2
  69. package/dist/src/tools/mcp-client.js +76 -22
  70. package/dist/src/tools/mcp-client.js.map +1 -1
  71. package/dist/src/tools/mcp-client.test.js +111 -42
  72. package/dist/src/tools/mcp-client.test.js.map +1 -1
  73. package/dist/src/tools/mcp-tool.test.js +186 -273
  74. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  75. package/dist/src/tools/ripGrep.test.js +84 -126
  76. package/dist/src/tools/ripGrep.test.js.map +1 -1
  77. package/dist/src/tools/smart-edit.test.js +92 -111
  78. package/dist/src/tools/smart-edit.test.js.map +1 -1
  79. package/dist/src/tools/tool-registry.test.js +59 -90
  80. package/dist/src/tools/tool-registry.test.js.map +1 -1
  81. package/dist/src/tools/web-fetch.test.js +121 -179
  82. package/dist/src/tools/web-fetch.test.js.map +1 -1
  83. package/dist/src/tools/write-file.test.js +105 -116
  84. package/dist/src/tools/write-file.test.js.map +1 -1
  85. package/dist/src/utils/editor.d.ts +3 -1
  86. package/dist/src/utils/editor.js +18 -1
  87. package/dist/src/utils/editor.js.map +1 -1
  88. package/dist/src/utils/editor.test.js +11 -0
  89. package/dist/src/utils/editor.test.js.map +1 -1
  90. package/dist/src/utils/flashFallback.test.js +2 -2
  91. package/dist/src/utils/flashFallback.test.js.map +1 -1
  92. package/dist/src/utils/googleQuotaErrors.d.ts +2 -1
  93. package/dist/src/utils/googleQuotaErrors.js +20 -12
  94. package/dist/src/utils/googleQuotaErrors.js.map +1 -1
  95. package/dist/src/utils/httpErrors.d.ts +18 -0
  96. package/dist/src/utils/httpErrors.js +36 -0
  97. package/dist/src/utils/httpErrors.js.map +1 -0
  98. package/dist/src/utils/retry.d.ts +0 -9
  99. package/dist/src/utils/retry.js +24 -28
  100. package/dist/src/utils/retry.js.map +1 -1
  101. package/dist/src/utils/retry.test.js +51 -0
  102. package/dist/src/utils/retry.test.js.map +1 -1
  103. package/dist/tsconfig.tsbuildinfo +1 -1
  104. package/package.json +2 -2
  105. package/dist/google-gemini-cli-core-0.17.0-nightly.20251116.e650a4ee5.tgz +0 -0
@@ -17,6 +17,14 @@ const mockCallableToolInstance = {
17
17
  callTool: mockCallTool,
18
18
  // Add other methods if DiscoveredMCPTool starts using them
19
19
  };
20
+ const createSdkResponse = (toolName, response) => [
21
+ {
22
+ functionResponse: {
23
+ name: toolName,
24
+ response,
25
+ },
26
+ },
27
+ ];
20
28
  describe('generateValidName', () => {
21
29
  it('should return a valid name for a simple function', () => {
22
30
  expect(generateValidName('myFunction')).toBe('myFunction');
@@ -30,14 +38,12 @@ describe('generateValidName', () => {
30
38
  it('should handle names with only invalid characters', () => {
31
39
  expect(generateValidName('!@#$%^&*()')).toBe('__________');
32
40
  });
33
- it('should handle names that are exactly 63 characters long', () => {
34
- expect(generateValidName('a'.repeat(63)).length).toBe(63);
35
- });
36
- it('should handle names that are exactly 64 characters long', () => {
37
- expect(generateValidName('a'.repeat(64)).length).toBe(63);
38
- });
39
- it('should handle names that are longer than 64 characters', () => {
40
- expect(generateValidName('a'.repeat(80)).length).toBe(63);
41
+ it.each([
42
+ { length: 63, expected: 63, description: 'exactly 63 characters' },
43
+ { length: 64, expected: 63, description: 'exactly 64 characters' },
44
+ { length: 80, expected: 63, description: 'longer than 64 characters' },
45
+ ])('should handle names that are $description long', ({ length, expected }) => {
46
+ expect(generateValidName('a'.repeat(length)).length).toBe(expected);
41
47
  });
42
48
  });
43
49
  describe('DiscoveredMCPTool', () => {
@@ -189,20 +195,9 @@ describe('DiscoveredMCPTool', () => {
189
195
  it('should handle a simple text response correctly', async () => {
190
196
  const params = { param: 'test' };
191
197
  const successMessage = 'This is a success message.';
192
- // Simulate the response from the GenAI SDK, which wraps the MCP
193
- // response in a functionResponse Part.
194
- const sdkResponse = [
195
- {
196
- functionResponse: {
197
- name: serverToolName,
198
- response: {
199
- // The `content` array contains MCP ContentBlocks.
200
- content: [{ type: 'text', text: successMessage }],
201
- },
202
- },
203
- },
204
- ];
205
- mockCallTool.mockResolvedValue(sdkResponse);
198
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
199
+ content: [{ type: 'text', text: successMessage }],
200
+ }));
206
201
  const invocation = tool.build(params);
207
202
  const toolResult = await invocation.execute(new AbortController().signal);
208
203
  // 1. Assert that the llmContent sent to the scheduler is a clean Part array.
@@ -216,23 +211,15 @@ describe('DiscoveredMCPTool', () => {
216
211
  });
217
212
  it('should handle an AudioBlock response', async () => {
218
213
  const params = { param: 'play' };
219
- const sdkResponse = [
220
- {
221
- functionResponse: {
222
- name: serverToolName,
223
- response: {
224
- content: [
225
- {
226
- type: 'audio',
227
- data: 'BASE64_AUDIO_DATA',
228
- mimeType: 'audio/mp3',
229
- },
230
- ],
231
- },
214
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
215
+ content: [
216
+ {
217
+ type: 'audio',
218
+ data: 'BASE64_AUDIO_DATA',
219
+ mimeType: 'audio/mp3',
232
220
  },
233
- },
234
- ];
235
- mockCallTool.mockResolvedValue(sdkResponse);
221
+ ],
222
+ }));
236
223
  const invocation = tool.build(params);
237
224
  const toolResult = await invocation.execute(new AbortController().signal);
238
225
  expect(toolResult.llmContent).toEqual([
@@ -250,24 +237,16 @@ describe('DiscoveredMCPTool', () => {
250
237
  });
251
238
  it('should handle a ResourceLinkBlock response', async () => {
252
239
  const params = { param: 'get' };
253
- const sdkResponse = [
254
- {
255
- functionResponse: {
256
- name: serverToolName,
257
- response: {
258
- content: [
259
- {
260
- type: 'resource_link',
261
- uri: 'file:///path/to/thing',
262
- name: 'resource-name',
263
- title: 'My Resource',
264
- },
265
- ],
266
- },
240
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
241
+ content: [
242
+ {
243
+ type: 'resource_link',
244
+ uri: 'file:///path/to/thing',
245
+ name: 'resource-name',
246
+ title: 'My Resource',
267
247
  },
268
- },
269
- ];
270
- mockCallTool.mockResolvedValue(sdkResponse);
248
+ ],
249
+ }));
271
250
  const invocation = tool.build(params);
272
251
  const toolResult = await invocation.execute(new AbortController().signal);
273
252
  expect(toolResult.llmContent).toEqual([
@@ -279,26 +258,18 @@ describe('DiscoveredMCPTool', () => {
279
258
  });
280
259
  it('should handle an embedded text ResourceBlock response', async () => {
281
260
  const params = { param: 'get' };
282
- const sdkResponse = [
283
- {
284
- functionResponse: {
285
- name: serverToolName,
286
- response: {
287
- content: [
288
- {
289
- type: 'resource',
290
- resource: {
291
- uri: 'file:///path/to/text.txt',
292
- text: 'This is the text content.',
293
- mimeType: 'text/plain',
294
- },
295
- },
296
- ],
261
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
262
+ content: [
263
+ {
264
+ type: 'resource',
265
+ resource: {
266
+ uri: 'file:///path/to/text.txt',
267
+ text: 'This is the text content.',
268
+ mimeType: 'text/plain',
297
269
  },
298
270
  },
299
- },
300
- ];
301
- mockCallTool.mockResolvedValue(sdkResponse);
271
+ ],
272
+ }));
302
273
  const invocation = tool.build(params);
303
274
  const toolResult = await invocation.execute(new AbortController().signal);
304
275
  expect(toolResult.llmContent).toEqual([
@@ -308,26 +279,18 @@ describe('DiscoveredMCPTool', () => {
308
279
  });
309
280
  it('should handle an embedded binary ResourceBlock response', async () => {
310
281
  const params = { param: 'get' };
311
- const sdkResponse = [
312
- {
313
- functionResponse: {
314
- name: serverToolName,
315
- response: {
316
- content: [
317
- {
318
- type: 'resource',
319
- resource: {
320
- uri: 'file:///path/to/data.bin',
321
- blob: 'BASE64_BINARY_DATA',
322
- mimeType: 'application/octet-stream',
323
- },
324
- },
325
- ],
282
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
283
+ content: [
284
+ {
285
+ type: 'resource',
286
+ resource: {
287
+ uri: 'file:///path/to/data.bin',
288
+ blob: 'BASE64_BINARY_DATA',
289
+ mimeType: 'application/octet-stream',
326
290
  },
327
291
  },
328
- },
329
- ];
330
- mockCallTool.mockResolvedValue(sdkResponse);
292
+ ],
293
+ }));
331
294
  const invocation = tool.build(params);
332
295
  const toolResult = await invocation.execute(new AbortController().signal);
333
296
  expect(toolResult.llmContent).toEqual([
@@ -345,25 +308,17 @@ describe('DiscoveredMCPTool', () => {
345
308
  });
346
309
  it('should handle a mix of content block types', async () => {
347
310
  const params = { param: 'complex' };
348
- const sdkResponse = [
349
- {
350
- functionResponse: {
351
- name: serverToolName,
352
- response: {
353
- content: [
354
- { type: 'text', text: 'First part.' },
355
- {
356
- type: 'image',
357
- data: 'BASE64_IMAGE_DATA',
358
- mimeType: 'image/jpeg',
359
- },
360
- { type: 'text', text: 'Second part.' },
361
- ],
362
- },
311
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
312
+ content: [
313
+ { type: 'text', text: 'First part.' },
314
+ {
315
+ type: 'image',
316
+ data: 'BASE64_IMAGE_DATA',
317
+ mimeType: 'image/jpeg',
363
318
  },
364
- },
365
- ];
366
- mockCallTool.mockResolvedValue(sdkResponse);
319
+ { type: 'text', text: 'Second part.' },
320
+ ],
321
+ }));
367
322
  const invocation = tool.build(params);
368
323
  const toolResult = await invocation.execute(new AbortController().signal);
369
324
  expect(toolResult.llmContent).toEqual([
@@ -383,20 +338,12 @@ describe('DiscoveredMCPTool', () => {
383
338
  });
384
339
  it('should ignore unknown content block types', async () => {
385
340
  const params = { param: 'test' };
386
- const sdkResponse = [
387
- {
388
- functionResponse: {
389
- name: serverToolName,
390
- response: {
391
- content: [
392
- { type: 'text', text: 'Valid part.' },
393
- { type: 'future_block', data: 'some-data' },
394
- ],
395
- },
396
- },
397
- },
398
- ];
399
- mockCallTool.mockResolvedValue(sdkResponse);
341
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
342
+ content: [
343
+ { type: 'text', text: 'Valid part.' },
344
+ { type: 'future_block', data: 'some-data' },
345
+ ],
346
+ }));
400
347
  const invocation = tool.build(params);
401
348
  const toolResult = await invocation.execute(new AbortController().signal);
402
349
  expect(toolResult.llmContent).toEqual([{ text: 'Valid part.' }]);
@@ -404,38 +351,30 @@ describe('DiscoveredMCPTool', () => {
404
351
  });
405
352
  it('should handle a complex mix of content block types', async () => {
406
353
  const params = { param: 'super-complex' };
407
- const sdkResponse = [
408
- {
409
- functionResponse: {
410
- name: serverToolName,
411
- response: {
412
- content: [
413
- { type: 'text', text: 'Here is a resource.' },
414
- {
415
- type: 'resource_link',
416
- uri: 'file:///path/to/resource',
417
- name: 'resource-name',
418
- title: 'My Resource',
419
- },
420
- {
421
- type: 'resource',
422
- resource: {
423
- uri: 'file:///path/to/text.txt',
424
- text: 'Embedded text content.',
425
- mimeType: 'text/plain',
426
- },
427
- },
428
- {
429
- type: 'image',
430
- data: 'BASE64_IMAGE_DATA',
431
- mimeType: 'image/jpeg',
432
- },
433
- ],
354
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
355
+ content: [
356
+ { type: 'text', text: 'Here is a resource.' },
357
+ {
358
+ type: 'resource_link',
359
+ uri: 'file:///path/to/resource',
360
+ name: 'resource-name',
361
+ title: 'My Resource',
362
+ },
363
+ {
364
+ type: 'resource',
365
+ resource: {
366
+ uri: 'file:///path/to/text.txt',
367
+ text: 'Embedded text content.',
368
+ mimeType: 'text/plain',
434
369
  },
435
370
  },
436
- },
437
- ];
438
- mockCallTool.mockResolvedValue(sdkResponse);
371
+ {
372
+ type: 'image',
373
+ data: 'BASE64_IMAGE_DATA',
374
+ mimeType: 'image/jpeg',
375
+ },
376
+ ],
377
+ }));
439
378
  const invocation = tool.build(params);
440
379
  const toolResult = await invocation.execute(new AbortController().signal);
441
380
  expect(toolResult.llmContent).toEqual([
@@ -457,6 +396,8 @@ describe('DiscoveredMCPTool', () => {
457
396
  expect(toolResult.returnDisplay).toBe('Here is a resource.\n[Link to My Resource: file:///path/to/resource]\nEmbedded text content.\n[Image: image/jpeg]');
458
397
  });
459
398
  describe('AbortSignal support', () => {
399
+ const MOCK_TOOL_DELAY = 1000;
400
+ const ABORT_DELAY = 50;
460
401
  it('should abort immediately if signal is already aborted', async () => {
461
402
  const params = { param: 'test' };
462
403
  const controller = new AbortController();
@@ -482,28 +423,20 @@ describe('DiscoveredMCPTool', () => {
482
423
  },
483
424
  },
484
425
  ]);
485
- }, 1000);
426
+ }, MOCK_TOOL_DELAY);
486
427
  }));
487
428
  const invocation = tool.build(params);
488
429
  const promise = invocation.execute(controller.signal);
489
430
  // Abort after a short delay to simulate cancellation during execution
490
- setTimeout(() => controller.abort(), 50);
431
+ setTimeout(() => controller.abort(), ABORT_DELAY);
491
432
  await expect(promise).rejects.toThrow('Tool call aborted');
492
433
  });
493
434
  it('should complete successfully if not aborted', async () => {
494
435
  const params = { param: 'test' };
495
436
  const controller = new AbortController();
496
- const successResponse = [
497
- {
498
- functionResponse: {
499
- name: serverToolName,
500
- response: {
501
- content: [{ type: 'text', text: 'Success' }],
502
- },
503
- },
504
- },
505
- ];
506
- mockCallTool.mockResolvedValue(successResponse);
437
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
438
+ content: [{ type: 'text', text: 'Success' }],
439
+ }));
507
440
  const invocation = tool.build(params);
508
441
  const result = await invocation.execute(controller.signal);
509
442
  expect(result.llmContent).toEqual([{ text: 'Success' }]);
@@ -515,15 +448,7 @@ describe('DiscoveredMCPTool', () => {
515
448
  it('should handle tool error even when abort signal is provided', async () => {
516
449
  const params = { param: 'test' };
517
450
  const controller = new AbortController();
518
- const errorResponse = [
519
- {
520
- functionResponse: {
521
- name: serverToolName,
522
- response: { error: { isError: true } },
523
- },
524
- },
525
- ];
526
- mockCallTool.mockResolvedValue(errorResponse);
451
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, { error: { isError: true } }));
527
452
  const invocation = tool.build(params);
528
453
  const result = await invocation.execute(controller.signal);
529
454
  expect(result.error?.type).toBe(ToolErrorType.MCP_TOOL_ERROR);
@@ -537,38 +462,40 @@ describe('DiscoveredMCPTool', () => {
537
462
  const invocation = tool.build(params);
538
463
  await expect(invocation.execute(controller.signal)).rejects.toThrow(expectedError);
539
464
  });
540
- it('should cleanup event listeners properly on successful completion', async () => {
541
- const params = { param: 'test' };
542
- const controller = new AbortController();
543
- const successResponse = [
544
- {
545
- functionResponse: {
546
- name: serverToolName,
547
- response: {
548
- content: [{ type: 'text', text: 'Success' }],
549
- },
550
- },
465
+ it.each([
466
+ {
467
+ name: 'successful completion',
468
+ setup: () => {
469
+ mockCallTool.mockResolvedValue(createSdkResponse(serverToolName, {
470
+ content: [{ type: 'text', text: 'Success' }],
471
+ }));
551
472
  },
552
- ];
553
- mockCallTool.mockResolvedValue(successResponse);
554
- const invocation = tool.build(params);
555
- await invocation.execute(controller.signal);
556
- controller.abort();
557
- expect(controller.signal.aborted).toBe(true);
558
- });
559
- it('should cleanup event listeners properly on error', async () => {
473
+ expectError: false,
474
+ },
475
+ {
476
+ name: 'error',
477
+ setup: () => {
478
+ mockCallTool.mockRejectedValue(new Error('Tool execution failed'));
479
+ },
480
+ expectError: true,
481
+ },
482
+ ])('should cleanup event listeners properly on $name', async ({ setup, expectError }) => {
560
483
  const params = { param: 'test' };
561
484
  const controller = new AbortController();
562
- const expectedError = new Error('Tool execution failed');
563
- mockCallTool.mockRejectedValue(expectedError);
485
+ setup();
564
486
  const invocation = tool.build(params);
565
- try {
566
- await invocation.execute(controller.signal);
487
+ if (expectError) {
488
+ try {
489
+ await invocation.execute(controller.signal);
490
+ }
491
+ catch (_error) {
492
+ // Expected error
493
+ }
567
494
  }
568
- catch (error) {
569
- expect(error).toBe(expectedError);
495
+ else {
496
+ await invocation.execute(controller.signal);
570
497
  }
571
- // Verify cleanup by aborting after error
498
+ // Verify cleanup by aborting after execution
572
499
  controller.abort();
573
500
  expect(controller.signal.aborted).toBe(true);
574
501
  });
@@ -609,22 +536,32 @@ describe('DiscoveredMCPTool', () => {
609
536
  throw new Error('Confirmation details not in expected format or was false');
610
537
  }
611
538
  });
612
- it('should add server to allowlist on ProceedAlwaysServer', async () => {
613
- const invocation = tool.build({ param: 'mock' });
614
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
615
- expect(confirmation).not.toBe(false);
616
- if (confirmation &&
617
- typeof confirmation === 'object' &&
618
- 'onConfirm' in confirmation &&
619
- typeof confirmation.onConfirm === 'function') {
620
- await confirmation.onConfirm(ToolConfirmationOutcome.ProceedAlwaysServer);
621
- expect(invocation.constructor.allowlist.has(serverName)).toBe(true);
622
- }
623
- else {
624
- throw new Error('Confirmation details or onConfirm not in expected format');
625
- }
626
- });
627
- it('should add tool to allowlist on ProceedAlwaysTool', async () => {
539
+ it.each([
540
+ {
541
+ outcome: ToolConfirmationOutcome.ProceedAlwaysServer,
542
+ description: 'add server to allowlist on ProceedAlwaysServer',
543
+ shouldAddServer: true,
544
+ shouldAddTool: false,
545
+ },
546
+ {
547
+ outcome: ToolConfirmationOutcome.ProceedAlwaysTool,
548
+ description: 'add tool to allowlist on ProceedAlwaysTool',
549
+ shouldAddServer: false,
550
+ shouldAddTool: true,
551
+ },
552
+ {
553
+ outcome: ToolConfirmationOutcome.Cancel,
554
+ description: 'handle Cancel confirmation outcome',
555
+ shouldAddServer: false,
556
+ shouldAddTool: false,
557
+ },
558
+ {
559
+ outcome: ToolConfirmationOutcome.ProceedOnce,
560
+ description: 'handle ProceedOnce confirmation outcome',
561
+ shouldAddServer: false,
562
+ shouldAddTool: false,
563
+ },
564
+ ])('should $description', async ({ outcome, shouldAddServer, shouldAddTool }) => {
628
565
  const toolAllowlistKey = `${serverName}.${serverToolName}`;
629
566
  const invocation = tool.build({ param: 'mock' });
630
567
  const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
@@ -633,42 +570,9 @@ describe('DiscoveredMCPTool', () => {
633
570
  typeof confirmation === 'object' &&
634
571
  'onConfirm' in confirmation &&
635
572
  typeof confirmation.onConfirm === 'function') {
636
- await confirmation.onConfirm(ToolConfirmationOutcome.ProceedAlwaysTool);
637
- expect(invocation.constructor.allowlist.has(toolAllowlistKey)).toBe(true);
638
- }
639
- else {
640
- throw new Error('Confirmation details or onConfirm not in expected format');
641
- }
642
- });
643
- it('should handle Cancel confirmation outcome', async () => {
644
- const invocation = tool.build({ param: 'mock' });
645
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
646
- expect(confirmation).not.toBe(false);
647
- if (confirmation &&
648
- typeof confirmation === 'object' &&
649
- 'onConfirm' in confirmation &&
650
- typeof confirmation.onConfirm === 'function') {
651
- // Cancel should not add anything to allowlist
652
- await confirmation.onConfirm(ToolConfirmationOutcome.Cancel);
653
- expect(invocation.constructor.allowlist.has(serverName)).toBe(false);
654
- expect(invocation.constructor.allowlist.has(`${serverName}.${serverToolName}`)).toBe(false);
655
- }
656
- else {
657
- throw new Error('Confirmation details or onConfirm not in expected format');
658
- }
659
- });
660
- it('should handle ProceedOnce confirmation outcome', async () => {
661
- const invocation = tool.build({ param: 'mock' });
662
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
663
- expect(confirmation).not.toBe(false);
664
- if (confirmation &&
665
- typeof confirmation === 'object' &&
666
- 'onConfirm' in confirmation &&
667
- typeof confirmation.onConfirm === 'function') {
668
- // ProceedOnce should not add anything to allowlist
669
- await confirmation.onConfirm(ToolConfirmationOutcome.ProceedOnce);
670
- expect(invocation.constructor.allowlist.has(serverName)).toBe(false);
671
- expect(invocation.constructor.allowlist.has(`${serverName}.${serverToolName}`)).toBe(false);
573
+ await confirmation.onConfirm(outcome);
574
+ expect(invocation.constructor.allowlist.has(serverName)).toBe(shouldAddServer);
575
+ expect(invocation.constructor.allowlist.has(toolAllowlistKey)).toBe(shouldAddTool);
672
576
  }
673
577
  else {
674
578
  throw new Error('Confirmation details or onConfirm not in expected format');
@@ -679,27 +583,36 @@ describe('DiscoveredMCPTool', () => {
679
583
  const mockConfig = (isTrusted) => ({
680
584
  isTrustedFolder: () => isTrusted,
681
585
  });
682
- it('should return false if trust is true and folder is trusted', async () => {
683
- const trustedTool = new DiscoveredMCPTool(mockCallableToolInstance, serverName, serverToolName, baseDescription, inputSchema, true, // trust = true
684
- undefined, mockConfig(true));
685
- const invocation = trustedTool.build({ param: 'mock' });
686
- expect(await invocation.shouldConfirmExecute(new AbortController().signal)).toBe(false);
687
- });
688
- it('should return confirmation details if trust is true but folder is not trusted', async () => {
689
- const trustedTool = new DiscoveredMCPTool(mockCallableToolInstance, serverName, serverToolName, baseDescription, inputSchema, true, // trust = true
690
- undefined, mockConfig(false));
691
- const invocation = trustedTool.build({ param: 'mock' });
692
- const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
693
- expect(confirmation).not.toBe(false);
694
- expect(confirmation).toHaveProperty('type', 'mcp');
695
- });
696
- it('should return confirmation details if trust is false, even if folder is trusted', async () => {
697
- const untrustedTool = new DiscoveredMCPTool(mockCallableToolInstance, serverName, serverToolName, baseDescription, inputSchema, false, // trust = false
698
- undefined, mockConfig(true));
699
- const invocation = untrustedTool.build({ param: 'mock' });
586
+ it.each([
587
+ {
588
+ trust: true,
589
+ isTrusted: true,
590
+ shouldConfirm: false,
591
+ description: 'return false if trust is true and folder is trusted',
592
+ },
593
+ {
594
+ trust: true,
595
+ isTrusted: false,
596
+ shouldConfirm: true,
597
+ description: 'return confirmation details if trust is true but folder is not trusted',
598
+ },
599
+ {
600
+ trust: false,
601
+ isTrusted: true,
602
+ shouldConfirm: true,
603
+ description: 'return confirmation details if trust is false, even if folder is trusted',
604
+ },
605
+ ])('should $description', async ({ trust, isTrusted, shouldConfirm }) => {
606
+ const testTool = new DiscoveredMCPTool(mockCallableToolInstance, serverName, serverToolName, baseDescription, inputSchema, trust, undefined, mockConfig(isTrusted));
607
+ const invocation = testTool.build({ param: 'mock' });
700
608
  const confirmation = await invocation.shouldConfirmExecute(new AbortController().signal);
701
- expect(confirmation).not.toBe(false);
702
- expect(confirmation).toHaveProperty('type', 'mcp');
609
+ if (shouldConfirm) {
610
+ expect(confirmation).not.toBe(false);
611
+ expect(confirmation).toHaveProperty('type', 'mcp');
612
+ }
613
+ else {
614
+ expect(confirmation).toBe(false);
615
+ }
703
616
  });
704
617
  });
705
618
  describe('DiscoveredMCPToolInvocation', () => {