@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.
- package/dist/google-gemini-cli-core-0.4.0-preview.tgz +0 -0
- package/dist/src/config/config.d.ts +0 -4
- package/dist/src/config/config.js +11 -20
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +48 -1
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +14 -1
- package/dist/src/core/geminiChat.js +25 -24
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +135 -27
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/subagent.js +5 -6
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagent.test.js +20 -9
- package/dist/src/core/subagent.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +6 -2
- package/dist/src/core/turn.js +21 -9
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +218 -187
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +1 -1
- package/dist/src/ide/ide-client.js +13 -11
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +12 -12
- package/dist/src/mcp/oauth-provider.js +32 -31
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +74 -35
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.d.ts +8 -8
- package/dist/src/mcp/oauth-token-storage.js +8 -8
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.test.js +23 -21
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
- package/dist/src/services/loopDetectionService.d.ts +1 -0
- package/dist/src/services/loopDetectionService.js +19 -1
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.js +5 -5
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +1 -1
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +2 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +1 -0
- package/dist/src/telemetry/constants.js +1 -0
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/loggers.js +26 -1
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +39 -3
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/types.js +2 -2
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.js +4 -4
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +4 -4
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/tools/diffOptions.js +2 -2
- package/dist/src/tools/diffOptions.js.map +1 -1
- package/dist/src/tools/diffOptions.test.js +18 -18
- package/dist/src/tools/diffOptions.test.js.map +1 -1
- package/dist/src/tools/edit.js +11 -19
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +28 -8
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/mcp-client.js +27 -13
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +4 -4
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/shell.js +5 -2
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +1 -1
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.js +2 -2
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +11 -3
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +2 -2
- package/dist/src/tools/write-file.js +2 -2
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +5 -9
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/utils/fileUtils.js +5 -4
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +21 -20
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- 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
|
-
|
|
60
|
+
type: StreamEventType.CHUNK,
|
|
61
|
+
value: {
|
|
62
|
+
candidates: [{ content: { parts: [{ text: 'Hello' }] } }],
|
|
63
|
+
},
|
|
59
64
|
};
|
|
60
65
|
yield {
|
|
61
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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(
|
|
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
|
-
|
|
140
|
+
type: StreamEventType.CHUNK,
|
|
141
|
+
value: {
|
|
142
|
+
candidates: [{ content: { parts: [{ text: 'First part' }] } }],
|
|
143
|
+
},
|
|
140
144
|
};
|
|
141
145
|
abortController.abort();
|
|
142
146
|
yield {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
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(
|
|
213
|
+
expect(events.length).toBe(3);
|
|
214
|
+
// Assertions for each specific tool call event
|
|
210
215
|
const event1 = events[0];
|
|
211
|
-
expect(event1.
|
|
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
|
-
|
|
217
|
-
}));
|
|
218
|
-
expect(turn.pendingToolCalls[0]).toEqual(event1.value);
|
|
220
|
+
});
|
|
219
221
|
const event2 = events[1];
|
|
220
|
-
expect(event2.
|
|
221
|
-
expect(event2.value).toEqual(expect.objectContaining({
|
|
222
|
+
expect(event2.value).toMatchObject({
|
|
222
223
|
callId: 'fc2',
|
|
223
224
|
name: 'tool2',
|
|
224
225
|
args: {},
|
|
225
|
-
|
|
226
|
-
}));
|
|
227
|
-
expect(turn.pendingToolCalls[1]).toEqual(event2.value);
|
|
226
|
+
});
|
|
228
227
|
const event3 = events[2];
|
|
229
|
-
expect(event3.
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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' }];
|