@google/gemini-cli-core 0.4.0-nightly.20250904.e133acd2 → 0.4.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 (90) hide show
  1. package/dist/google-gemini-cli-core-0.4.0-preview.tgz +0 -0
  2. package/dist/src/config/config.d.ts +0 -4
  3. package/dist/src/config/config.js +11 -20
  4. package/dist/src/config/config.js.map +1 -1
  5. package/dist/src/config/config.test.js +48 -1
  6. package/dist/src/config/config.test.js.map +1 -1
  7. package/dist/src/core/geminiChat.d.ts +14 -1
  8. package/dist/src/core/geminiChat.js +25 -24
  9. package/dist/src/core/geminiChat.js.map +1 -1
  10. package/dist/src/core/geminiChat.test.js +135 -27
  11. package/dist/src/core/geminiChat.test.js.map +1 -1
  12. package/dist/src/core/subagent.js +5 -6
  13. package/dist/src/core/subagent.js.map +1 -1
  14. package/dist/src/core/subagent.test.js +20 -9
  15. package/dist/src/core/subagent.test.js.map +1 -1
  16. package/dist/src/core/turn.d.ts +6 -2
  17. package/dist/src/core/turn.js +21 -9
  18. package/dist/src/core/turn.js.map +1 -1
  19. package/dist/src/core/turn.test.js +218 -187
  20. package/dist/src/core/turn.test.js.map +1 -1
  21. package/dist/src/generated/git-commit.d.ts +2 -2
  22. package/dist/src/generated/git-commit.js +2 -2
  23. package/dist/src/generated/git-commit.js.map +1 -1
  24. package/dist/src/ide/ide-client.d.ts +1 -1
  25. package/dist/src/ide/ide-client.js +13 -11
  26. package/dist/src/ide/ide-client.js.map +1 -1
  27. package/dist/src/mcp/oauth-provider.d.ts +12 -12
  28. package/dist/src/mcp/oauth-provider.js +32 -31
  29. package/dist/src/mcp/oauth-provider.js.map +1 -1
  30. package/dist/src/mcp/oauth-provider.test.js +74 -35
  31. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  32. package/dist/src/mcp/oauth-token-storage.d.ts +8 -8
  33. package/dist/src/mcp/oauth-token-storage.js +8 -8
  34. package/dist/src/mcp/oauth-token-storage.js.map +1 -1
  35. package/dist/src/mcp/oauth-token-storage.test.js +23 -21
  36. package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
  37. package/dist/src/services/loopDetectionService.d.ts +1 -0
  38. package/dist/src/services/loopDetectionService.js +19 -1
  39. package/dist/src/services/loopDetectionService.js.map +1 -1
  40. package/dist/src/services/shellExecutionService.js +5 -5
  41. package/dist/src/services/shellExecutionService.js.map +1 -1
  42. package/dist/src/services/shellExecutionService.test.js +1 -1
  43. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  44. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +2 -2
  45. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  46. package/dist/src/telemetry/constants.d.ts +1 -0
  47. package/dist/src/telemetry/constants.js +1 -0
  48. package/dist/src/telemetry/constants.js.map +1 -1
  49. package/dist/src/telemetry/loggers.js +26 -1
  50. package/dist/src/telemetry/loggers.js.map +1 -1
  51. package/dist/src/telemetry/loggers.test.js +39 -3
  52. package/dist/src/telemetry/loggers.test.js.map +1 -1
  53. package/dist/src/telemetry/types.js +2 -2
  54. package/dist/src/telemetry/types.js.map +1 -1
  55. package/dist/src/telemetry/uiTelemetry.js +4 -4
  56. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  57. package/dist/src/telemetry/uiTelemetry.test.js +4 -4
  58. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  59. package/dist/src/tools/diffOptions.js +2 -2
  60. package/dist/src/tools/diffOptions.js.map +1 -1
  61. package/dist/src/tools/diffOptions.test.js +18 -18
  62. package/dist/src/tools/diffOptions.test.js.map +1 -1
  63. package/dist/src/tools/edit.js +11 -19
  64. package/dist/src/tools/edit.js.map +1 -1
  65. package/dist/src/tools/edit.test.js +28 -8
  66. package/dist/src/tools/edit.test.js.map +1 -1
  67. package/dist/src/tools/mcp-client.js +27 -13
  68. package/dist/src/tools/mcp-client.js.map +1 -1
  69. package/dist/src/tools/read-many-files.test.js +4 -4
  70. package/dist/src/tools/read-many-files.test.js.map +1 -1
  71. package/dist/src/tools/shell.js +5 -2
  72. package/dist/src/tools/shell.js.map +1 -1
  73. package/dist/src/tools/shell.test.js +1 -1
  74. package/dist/src/tools/shell.test.js.map +1 -1
  75. package/dist/src/tools/smart-edit.js +2 -2
  76. package/dist/src/tools/smart-edit.js.map +1 -1
  77. package/dist/src/tools/smart-edit.test.js +11 -3
  78. package/dist/src/tools/smart-edit.test.js.map +1 -1
  79. package/dist/src/tools/tools.d.ts +2 -2
  80. package/dist/src/tools/write-file.js +2 -2
  81. package/dist/src/tools/write-file.js.map +1 -1
  82. package/dist/src/tools/write-file.test.js +5 -9
  83. package/dist/src/tools/write-file.test.js.map +1 -1
  84. package/dist/src/utils/fileUtils.js +5 -4
  85. package/dist/src/utils/fileUtils.js.map +1 -1
  86. package/dist/src/utils/fileUtils.test.js +21 -20
  87. package/dist/src/utils/fileUtils.test.js.map +1 -1
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +2 -1
  90. package/dist/google-gemini-cli-core-0.2.2.tgz +0 -0
@@ -6,6 +6,7 @@
6
6
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
7
  import { Turn, GeminiEventType } from './turn.js';
8
8
  import { reportError } from '../utils/errorReporting.js';
9
+ import { StreamEventType } from './geminiChat.js';
9
10
  const mockSendMessageStream = vi.fn();
10
11
  const mockGetHistory = vi.fn();
11
12
  const mockMaybeIncludeSchemaDepthContext = vi.fn();
@@ -24,6 +25,7 @@ vi.mock('@google/genai', async (importOriginal) => {
24
25
  vi.mock('../utils/errorReporting', () => ({
25
26
  reportError: vi.fn(),
26
27
  }));
28
+ // Use the actual implementation from partUtils now that it's provided.
27
29
  vi.mock('../utils/generateContentResponseUtilities', () => ({
28
30
  getResponseText: (resp) => resp.candidates?.[0]?.content?.parts?.map((part) => part.text).join('') ||
29
31
  undefined,
@@ -55,10 +57,16 @@ describe('Turn', () => {
55
57
  it('should yield content events for text parts', async () => {
56
58
  const mockResponseStream = (async function* () {
57
59
  yield {
58
- candidates: [{ content: { parts: [{ text: 'Hello' }] } }],
60
+ type: StreamEventType.CHUNK,
61
+ value: {
62
+ candidates: [{ content: { parts: [{ text: 'Hello' }] } }],
63
+ },
59
64
  };
60
65
  yield {
61
- candidates: [{ content: { parts: [{ text: ' world' }] } }],
66
+ type: StreamEventType.CHUNK,
67
+ value: {
68
+ candidates: [{ content: { parts: [{ text: ' world' }] } }],
69
+ },
62
70
  };
63
71
  })();
64
72
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -73,36 +81,29 @@ describe('Turn', () => {
73
81
  }, 'prompt-id-1');
74
82
  expect(events).toEqual([
75
83
  { type: GeminiEventType.Content, value: 'Hello' },
76
- {
77
- type: GeminiEventType.Finished,
78
- value: {
79
- reason: undefined,
80
- usageMetadata: undefined,
81
- },
82
- },
83
84
  { type: GeminiEventType.Content, value: ' world' },
84
- {
85
- type: GeminiEventType.Finished,
86
- value: {
87
- reason: undefined,
88
- usageMetadata: undefined,
89
- },
90
- },
91
85
  ]);
92
86
  expect(turn.getDebugResponses().length).toBe(2);
93
87
  });
94
88
  it('should yield tool_call_request events for function calls', async () => {
95
89
  const mockResponseStream = (async function* () {
96
90
  yield {
97
- functionCalls: [
98
- {
99
- id: 'fc1',
100
- name: 'tool1',
101
- args: { arg1: 'val1' },
102
- isClientInitiated: false,
103
- },
104
- { name: 'tool2', args: { arg2: 'val2' }, isClientInitiated: false }, // No ID
105
- ],
91
+ type: StreamEventType.CHUNK,
92
+ value: {
93
+ functionCalls: [
94
+ {
95
+ id: 'fc1',
96
+ name: 'tool1',
97
+ args: { arg1: 'val1' },
98
+ isClientInitiated: false,
99
+ },
100
+ {
101
+ name: 'tool2',
102
+ args: { arg2: 'val2' },
103
+ isClientInitiated: false,
104
+ }, // No ID
105
+ ],
106
+ },
106
107
  };
107
108
  })();
108
109
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -111,7 +112,7 @@ describe('Turn', () => {
111
112
  for await (const event of turn.run(reqParts, new AbortController().signal)) {
112
113
  events.push(event);
113
114
  }
114
- expect(events.length).toBe(3);
115
+ expect(events.length).toBe(2);
115
116
  const event1 = events[0];
116
117
  expect(event1.type).toBe(GeminiEventType.ToolCallRequest);
117
118
  expect(event1.value).toEqual(expect.objectContaining({
@@ -136,17 +137,23 @@ describe('Turn', () => {
136
137
  const abortController = new AbortController();
137
138
  const mockResponseStream = (async function* () {
138
139
  yield {
139
- candidates: [{ content: { parts: [{ text: 'First part' }] } }],
140
+ type: StreamEventType.CHUNK,
141
+ value: {
142
+ candidates: [{ content: { parts: [{ text: 'First part' }] } }],
143
+ },
140
144
  };
141
145
  abortController.abort();
142
146
  yield {
143
- candidates: [
144
- {
145
- content: {
146
- parts: [{ text: 'Second part - should not be processed' }],
147
+ type: StreamEventType.CHUNK,
148
+ value: {
149
+ candidates: [
150
+ {
151
+ content: {
152
+ parts: [{ text: 'Second part - should not be processed' }],
153
+ },
147
154
  },
148
- },
149
- ],
155
+ ],
156
+ },
150
157
  };
151
158
  })();
152
159
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -157,13 +164,6 @@ describe('Turn', () => {
157
164
  }
158
165
  expect(events).toEqual([
159
166
  { type: GeminiEventType.Content, value: 'First part' },
160
- {
161
- type: GeminiEventType.Finished,
162
- value: {
163
- reason: undefined,
164
- usageMetadata: undefined,
165
- },
166
- },
167
167
  { type: GeminiEventType.UserCancelled },
168
168
  ]);
169
169
  expect(turn.getDebugResponses().length).toBe(1);
@@ -193,71 +193,68 @@ describe('Turn', () => {
193
193
  it('should handle function calls with undefined name or args', async () => {
194
194
  const mockResponseStream = (async function* () {
195
195
  yield {
196
- functionCalls: [
197
- { id: 'fc1', name: undefined, args: { arg1: 'val1' } },
198
- { id: 'fc2', name: 'tool2', args: undefined },
199
- { id: 'fc3', name: undefined, args: undefined },
200
- ],
196
+ type: StreamEventType.CHUNK,
197
+ value: {
198
+ candidates: [],
199
+ functionCalls: [
200
+ // Add `id` back to the mock to match what the code expects
201
+ { id: 'fc1', name: undefined, args: { arg1: 'val1' } },
202
+ { id: 'fc2', name: 'tool2', args: undefined },
203
+ { id: 'fc3', name: undefined, args: undefined },
204
+ ],
205
+ },
201
206
  };
202
207
  })();
203
208
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
204
209
  const events = [];
205
- const reqParts = [{ text: 'Test undefined tool parts' }];
206
- for await (const event of turn.run(reqParts, new AbortController().signal)) {
210
+ for await (const event of turn.run([{ text: 'Test undefined tool parts' }], new AbortController().signal)) {
207
211
  events.push(event);
208
212
  }
209
- expect(events.length).toBe(4);
213
+ expect(events.length).toBe(3);
214
+ // Assertions for each specific tool call event
210
215
  const event1 = events[0];
211
- expect(event1.type).toBe(GeminiEventType.ToolCallRequest);
212
- expect(event1.value).toEqual(expect.objectContaining({
216
+ expect(event1.value).toMatchObject({
213
217
  callId: 'fc1',
214
218
  name: 'undefined_tool_name',
215
219
  args: { arg1: 'val1' },
216
- isClientInitiated: false,
217
- }));
218
- expect(turn.pendingToolCalls[0]).toEqual(event1.value);
220
+ });
219
221
  const event2 = events[1];
220
- expect(event2.type).toBe(GeminiEventType.ToolCallRequest);
221
- expect(event2.value).toEqual(expect.objectContaining({
222
+ expect(event2.value).toMatchObject({
222
223
  callId: 'fc2',
223
224
  name: 'tool2',
224
225
  args: {},
225
- isClientInitiated: false,
226
- }));
227
- expect(turn.pendingToolCalls[1]).toEqual(event2.value);
226
+ });
228
227
  const event3 = events[2];
229
- expect(event3.type).toBe(GeminiEventType.ToolCallRequest);
230
- expect(event3.value).toEqual(expect.objectContaining({
228
+ expect(event3.value).toMatchObject({
231
229
  callId: 'fc3',
232
230
  name: 'undefined_tool_name',
233
231
  args: {},
234
- isClientInitiated: false,
235
- }));
236
- expect(turn.pendingToolCalls[2]).toEqual(event3.value);
237
- expect(turn.getDebugResponses().length).toBe(1);
232
+ });
238
233
  });
239
234
  it('should yield finished event when response has finish reason', async () => {
240
235
  const mockResponseStream = (async function* () {
241
236
  yield {
242
- candidates: [
243
- {
244
- content: { parts: [{ text: 'Partial response' }] },
245
- finishReason: 'STOP',
237
+ type: StreamEventType.CHUNK,
238
+ value: {
239
+ candidates: [
240
+ {
241
+ content: { parts: [{ text: 'Partial response' }] },
242
+ finishReason: 'STOP',
243
+ },
244
+ ],
245
+ usageMetadata: {
246
+ promptTokenCount: 17,
247
+ candidatesTokenCount: 50,
248
+ cachedContentTokenCount: 10,
249
+ thoughtsTokenCount: 5,
250
+ toolUsePromptTokenCount: 2,
246
251
  },
247
- ],
248
- usageMetadata: {
249
- promptTokenCount: 17,
250
- candidatesTokenCount: 50,
251
- cachedContentTokenCount: 10,
252
- thoughtsTokenCount: 5,
253
- toolUsePromptTokenCount: 2,
254
252
  },
255
253
  };
256
254
  })();
257
255
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
258
256
  const events = [];
259
- const reqParts = [{ text: 'Test finish reason' }];
260
- for await (const event of turn.run(reqParts, new AbortController().signal)) {
257
+ for await (const event of turn.run([{ text: 'Test finish reason' }], new AbortController().signal)) {
261
258
  events.push(event);
262
259
  }
263
260
  expect(events).toEqual([
@@ -280,16 +277,19 @@ describe('Turn', () => {
280
277
  it('should yield finished event for MAX_TOKENS finish reason', async () => {
281
278
  const mockResponseStream = (async function* () {
282
279
  yield {
283
- candidates: [
284
- {
285
- content: {
286
- parts: [
287
- { text: 'This is a long response that was cut off...' },
288
- ],
280
+ type: StreamEventType.CHUNK,
281
+ value: {
282
+ candidates: [
283
+ {
284
+ content: {
285
+ parts: [
286
+ { text: 'This is a long response that was cut off...' },
287
+ ],
288
+ },
289
+ finishReason: 'MAX_TOKENS',
289
290
  },
290
- finishReason: 'MAX_TOKENS',
291
- },
292
- ],
291
+ ],
292
+ },
293
293
  };
294
294
  })();
295
295
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -312,12 +312,15 @@ describe('Turn', () => {
312
312
  it('should yield finished event for SAFETY finish reason', async () => {
313
313
  const mockResponseStream = (async function* () {
314
314
  yield {
315
- candidates: [
316
- {
317
- content: { parts: [{ text: 'Content blocked' }] },
318
- finishReason: 'SAFETY',
319
- },
320
- ],
315
+ type: StreamEventType.CHUNK,
316
+ value: {
317
+ candidates: [
318
+ {
319
+ content: { parts: [{ text: 'Content blocked' }] },
320
+ finishReason: 'SAFETY',
321
+ },
322
+ ],
323
+ },
321
324
  };
322
325
  })();
323
326
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -337,12 +340,17 @@ describe('Turn', () => {
337
340
  it('should yield finished event with undefined reason when there is no finish reason', async () => {
338
341
  const mockResponseStream = (async function* () {
339
342
  yield {
340
- candidates: [
341
- {
342
- content: { parts: [{ text: 'Response without finish reason' }] },
343
- // No finishReason property
344
- },
345
- ],
343
+ type: StreamEventType.CHUNK,
344
+ value: {
345
+ candidates: [
346
+ {
347
+ content: {
348
+ parts: [{ text: 'Response without finish reason' }],
349
+ },
350
+ // No finishReason property
351
+ },
352
+ ],
353
+ },
346
354
  };
347
355
  })();
348
356
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -356,29 +364,31 @@ describe('Turn', () => {
356
364
  type: GeminiEventType.Content,
357
365
  value: 'Response without finish reason',
358
366
  },
359
- {
360
- type: GeminiEventType.Finished,
361
- value: { reason: undefined, usageMetadata: undefined },
362
- },
363
367
  ]);
364
368
  });
365
369
  it('should handle multiple responses with different finish reasons', async () => {
366
370
  const mockResponseStream = (async function* () {
367
371
  yield {
368
- candidates: [
369
- {
370
- content: { parts: [{ text: 'First part' }] },
371
- // No finish reason on first response
372
- },
373
- ],
372
+ type: StreamEventType.CHUNK,
373
+ value: {
374
+ candidates: [
375
+ {
376
+ content: { parts: [{ text: 'First part' }] },
377
+ // No finish reason on first response
378
+ },
379
+ ],
380
+ },
374
381
  };
375
382
  yield {
376
- candidates: [
377
- {
378
- content: { parts: [{ text: 'Second part' }] },
379
- finishReason: 'OTHER',
380
- },
381
- ],
383
+ value: {
384
+ type: StreamEventType.CHUNK,
385
+ candidates: [
386
+ {
387
+ content: { parts: [{ text: 'Second part' }] },
388
+ finishReason: 'OTHER',
389
+ },
390
+ ],
391
+ },
382
392
  };
383
393
  })();
384
394
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -389,13 +399,6 @@ describe('Turn', () => {
389
399
  }
390
400
  expect(events).toEqual([
391
401
  { type: GeminiEventType.Content, value: 'First part' },
392
- {
393
- type: GeminiEventType.Finished,
394
- value: {
395
- reason: undefined,
396
- usageMetadata: undefined,
397
- },
398
- },
399
402
  { type: GeminiEventType.Content, value: 'Second part' },
400
403
  {
401
404
  type: GeminiEventType.Finished,
@@ -406,20 +409,23 @@ describe('Turn', () => {
406
409
  it('should yield citation and finished events when response has citationMetadata', async () => {
407
410
  const mockResponseStream = (async function* () {
408
411
  yield {
409
- candidates: [
410
- {
411
- content: { parts: [{ text: 'Some text.' }] },
412
- citationMetadata: {
413
- citations: [
414
- {
415
- uri: 'https://example.com/source1',
416
- title: 'Source 1 Title',
417
- },
418
- ],
412
+ type: StreamEventType.CHUNK,
413
+ value: {
414
+ candidates: [
415
+ {
416
+ content: { parts: [{ text: 'Some text.' }] },
417
+ citationMetadata: {
418
+ citations: [
419
+ {
420
+ uri: 'https://example.com/source1',
421
+ title: 'Source 1 Title',
422
+ },
423
+ ],
424
+ },
425
+ finishReason: 'STOP',
419
426
  },
420
- finishReason: 'STOP',
421
- },
422
- ],
427
+ ],
428
+ },
423
429
  };
424
430
  })();
425
431
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -442,24 +448,27 @@ describe('Turn', () => {
442
448
  it('should yield a single citation event for multiple citations in one response', async () => {
443
449
  const mockResponseStream = (async function* () {
444
450
  yield {
445
- candidates: [
446
- {
447
- content: { parts: [{ text: 'Some text.' }] },
448
- citationMetadata: {
449
- citations: [
450
- {
451
- uri: 'https://example.com/source2',
452
- title: 'Title2',
453
- },
454
- {
455
- uri: 'https://example.com/source1',
456
- title: 'Title1',
457
- },
458
- ],
451
+ type: StreamEventType.CHUNK,
452
+ value: {
453
+ candidates: [
454
+ {
455
+ content: { parts: [{ text: 'Some text.' }] },
456
+ citationMetadata: {
457
+ citations: [
458
+ {
459
+ uri: 'https://example.com/source2',
460
+ title: 'Title2',
461
+ },
462
+ {
463
+ uri: 'https://example.com/source1',
464
+ title: 'Title1',
465
+ },
466
+ ],
467
+ },
468
+ finishReason: 'STOP',
459
469
  },
460
- finishReason: 'STOP',
461
- },
462
- ],
470
+ ],
471
+ },
463
472
  };
464
473
  })();
465
474
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -482,20 +491,23 @@ describe('Turn', () => {
482
491
  it('should not yield citation event if there is no finish reason', async () => {
483
492
  const mockResponseStream = (async function* () {
484
493
  yield {
485
- candidates: [
486
- {
487
- content: { parts: [{ text: 'Some text.' }] },
488
- citationMetadata: {
489
- citations: [
490
- {
491
- uri: 'https://example.com/source1',
492
- title: 'Source 1 Title',
493
- },
494
- ],
494
+ type: StreamEventType.CHUNK,
495
+ value: {
496
+ candidates: [
497
+ {
498
+ content: { parts: [{ text: 'Some text.' }] },
499
+ citationMetadata: {
500
+ citations: [
501
+ {
502
+ uri: 'https://example.com/source1',
503
+ title: 'Source 1 Title',
504
+ },
505
+ ],
506
+ },
507
+ // No finishReason
495
508
  },
496
- // No finishReason
497
- },
498
- ],
509
+ ],
510
+ },
499
511
  };
500
512
  })();
501
513
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -505,10 +517,6 @@ describe('Turn', () => {
505
517
  }
506
518
  expect(events).toEqual([
507
519
  { type: GeminiEventType.Content, value: 'Some text.' },
508
- {
509
- type: GeminiEventType.Finished,
510
- value: { reason: undefined, usageMetadata: undefined },
511
- },
512
520
  ]);
513
521
  // No Citation event (but we do get a Finished event with undefined reason)
514
522
  expect(events.some((e) => e.type === GeminiEventType.Citation)).toBe(false);
@@ -516,24 +524,27 @@ describe('Turn', () => {
516
524
  it('should ignore citations without a URI', async () => {
517
525
  const mockResponseStream = (async function* () {
518
526
  yield {
519
- candidates: [
520
- {
521
- content: { parts: [{ text: 'Some text.' }] },
522
- citationMetadata: {
523
- citations: [
524
- {
525
- uri: 'https://example.com/source1',
526
- title: 'Good Source',
527
- },
528
- {
529
- // uri is undefined
530
- title: 'Bad Source',
531
- },
532
- ],
527
+ type: StreamEventType.CHUNK,
528
+ value: {
529
+ candidates: [
530
+ {
531
+ content: { parts: [{ text: 'Some text.' }] },
532
+ citationMetadata: {
533
+ citations: [
534
+ {
535
+ uri: 'https://example.com/source1',
536
+ title: 'Good Source',
537
+ },
538
+ {
539
+ // uri is undefined
540
+ title: 'Bad Source',
541
+ },
542
+ ],
543
+ },
544
+ finishReason: 'STOP',
533
545
  },
534
- finishReason: 'STOP',
535
- },
536
- ],
546
+ ],
547
+ },
537
548
  };
538
549
  })();
539
550
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
@@ -572,6 +583,26 @@ describe('Turn', () => {
572
583
  expect(events).toEqual([{ type: GeminiEventType.UserCancelled }]);
573
584
  expect(reportError).not.toHaveBeenCalled();
574
585
  });
586
+ it('should yield a Retry event when it receives one from the chat stream', async () => {
587
+ const mockResponseStream = (async function* () {
588
+ yield { type: StreamEventType.RETRY };
589
+ yield {
590
+ type: StreamEventType.CHUNK,
591
+ value: {
592
+ candidates: [{ content: { parts: [{ text: 'Success' }] } }],
593
+ },
594
+ };
595
+ })();
596
+ mockSendMessageStream.mockResolvedValue(mockResponseStream);
597
+ const events = [];
598
+ for await (const event of turn.run([], new AbortController().signal)) {
599
+ events.push(event);
600
+ }
601
+ expect(events).toEqual([
602
+ { type: GeminiEventType.Retry },
603
+ { type: GeminiEventType.Content, value: 'Success' },
604
+ ]);
605
+ });
575
606
  });
576
607
  describe('getDebugResponses', () => {
577
608
  it('should return collected debug responses', async () => {
@@ -582,8 +613,8 @@ describe('Turn', () => {
582
613
  functionCalls: [{ name: 'debugTool' }],
583
614
  };
584
615
  const mockResponseStream = (async function* () {
585
- yield resp1;
586
- yield resp2;
616
+ yield { type: StreamEventType.CHUNK, value: resp1 };
617
+ yield { type: StreamEventType.CHUNK, value: resp2 };
587
618
  })();
588
619
  mockSendMessageStream.mockResolvedValue(mockResponseStream);
589
620
  const reqParts = [{ text: 'Hi' }];