@machina.ai/cell-cli-core 1.2.2-rc1 → 1.4.0-rc1
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/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/package.json +2 -1
- package/dist/src/code_assist/codeAssist.d.ts +2 -0
- package/dist/src/code_assist/codeAssist.js +12 -0
- package/dist/src/code_assist/codeAssist.js.map +1 -1
- package/dist/src/code_assist/converter.d.ts +2 -1
- package/dist/src/code_assist/converter.js +1 -1
- package/dist/src/code_assist/converter.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +58 -27
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +252 -0
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/config/config.d.ts +0 -4
- package/dist/src/config/config.js +18 -22
- 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/client.d.ts +4 -2
- package/dist/src/core/client.js +8 -8
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +34 -6
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +18 -0
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +30 -1
- package/dist/src/core/geminiChat.js +131 -25
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +202 -23
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.js +9 -9
- package/dist/src/core/loggingContentGenerator.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +2 -0
- package/dist/src/core/nonInteractiveToolExecutor.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 +12 -4
- package/dist/src/core/turn.js +18 -3
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +256 -153
- 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/ide/ide-client.d.ts +7 -2
- package/dist/src/ide/ide-client.js +33 -13
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-installer.js +1 -1
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +12 -0
- package/dist/src/ide/ideContext.js +1 -0
- package/dist/src/ide/ideContext.js.map +1 -1
- package/dist/src/ide/process-utils.d.ts +0 -1
- package/dist/src/ide/process-utils.js +36 -31
- package/dist/src/ide/process-utils.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.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/mcp/token-storage/hybrid-token-storage.d.ts +23 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.js +78 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.js.map +1 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.test.js +193 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.test.js.map +1 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.d.ts +31 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.js +190 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.js.map +1 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.test.js +254 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.test.js.map +1 -0
- package/dist/src/mcp/token-storage/types.d.ts +4 -0
- package/dist/src/mcp/token-storage/types.js +5 -1
- package/dist/src/mcp/token-storage/types.js.map +1 -1
- package/dist/src/services/chatRecordingService.d.ts +5 -12
- package/dist/src/services/chatRecordingService.js +25 -16
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/chatRecordingService.test.js +54 -17
- package/dist/src/services/chatRecordingService.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.d.ts +4 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +31 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +19 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +6 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +13 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.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.d.ts +10 -1
- package/dist/src/telemetry/types.js +18 -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 +21 -13
- package/dist/src/tools/diffOptions.js.map +1 -1
- package/dist/src/tools/diffOptions.test.js +58 -22
- 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 +36 -8
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/mcp-client.js +31 -26
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +1 -2
- package/dist/src/tools/mcp-tool.js +4 -8
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +4 -10
- package/dist/src/tools/mcp-tool.test.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 +6 -3
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +3 -3
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +1 -1
- package/dist/src/tools/smart-edit.js +3 -3
- 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 +6 -2
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.js +4 -3
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-search.d.ts +1 -1
- package/dist/src/tools/web-search.js +3 -1
- package/dist/src/tools/web-search.js.map +1 -1
- 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/src/utils/gitIgnoreParser.d.ts +1 -0
- package/dist/src/utils/gitIgnoreParser.js +101 -10
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.test.js +66 -0
- package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
- package/dist/src/utils/ide-trust.d.ts +10 -0
- package/dist/src/utils/ide-trust.js +14 -0
- package/dist/src/utils/ide-trust.js.map +1 -0
- package/dist/src/utils/nextSpeakerChecker.test.js +33 -0
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +1 -1
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
|
@@ -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);
|
|
@@ -80,15 +88,22 @@ describe('Turn', () => {
|
|
|
80
88
|
it('should yield tool_call_request events for function calls', async () => {
|
|
81
89
|
const mockResponseStream = (async function* () {
|
|
82
90
|
yield {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
},
|
|
92
107
|
};
|
|
93
108
|
})();
|
|
94
109
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -122,17 +137,23 @@ describe('Turn', () => {
|
|
|
122
137
|
const abortController = new AbortController();
|
|
123
138
|
const mockResponseStream = (async function* () {
|
|
124
139
|
yield {
|
|
125
|
-
|
|
140
|
+
type: StreamEventType.CHUNK,
|
|
141
|
+
value: {
|
|
142
|
+
candidates: [{ content: { parts: [{ text: 'First part' }] } }],
|
|
143
|
+
},
|
|
126
144
|
};
|
|
127
145
|
abortController.abort();
|
|
128
146
|
yield {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
type: StreamEventType.CHUNK,
|
|
148
|
+
value: {
|
|
149
|
+
candidates: [
|
|
150
|
+
{
|
|
151
|
+
content: {
|
|
152
|
+
parts: [{ text: 'Second part - should not be processed' }],
|
|
153
|
+
},
|
|
133
154
|
},
|
|
134
|
-
|
|
135
|
-
|
|
155
|
+
],
|
|
156
|
+
},
|
|
136
157
|
};
|
|
137
158
|
})();
|
|
138
159
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -172,84 +193,103 @@ describe('Turn', () => {
|
|
|
172
193
|
it('should handle function calls with undefined name or args', async () => {
|
|
173
194
|
const mockResponseStream = (async function* () {
|
|
174
195
|
yield {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
},
|
|
180
206
|
};
|
|
181
207
|
})();
|
|
182
208
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
183
209
|
const events = [];
|
|
184
|
-
const
|
|
185
|
-
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)) {
|
|
186
211
|
events.push(event);
|
|
187
212
|
}
|
|
188
213
|
expect(events.length).toBe(3);
|
|
214
|
+
// Assertions for each specific tool call event
|
|
189
215
|
const event1 = events[0];
|
|
190
|
-
expect(event1.
|
|
191
|
-
expect(event1.value).toEqual(expect.objectContaining({
|
|
216
|
+
expect(event1.value).toMatchObject({
|
|
192
217
|
callId: 'fc1',
|
|
193
218
|
name: 'undefined_tool_name',
|
|
194
219
|
args: { arg1: 'val1' },
|
|
195
|
-
|
|
196
|
-
}));
|
|
197
|
-
expect(turn.pendingToolCalls[0]).toEqual(event1.value);
|
|
220
|
+
});
|
|
198
221
|
const event2 = events[1];
|
|
199
|
-
expect(event2.
|
|
200
|
-
expect(event2.value).toEqual(expect.objectContaining({
|
|
222
|
+
expect(event2.value).toMatchObject({
|
|
201
223
|
callId: 'fc2',
|
|
202
224
|
name: 'tool2',
|
|
203
225
|
args: {},
|
|
204
|
-
|
|
205
|
-
}));
|
|
206
|
-
expect(turn.pendingToolCalls[1]).toEqual(event2.value);
|
|
226
|
+
});
|
|
207
227
|
const event3 = events[2];
|
|
208
|
-
expect(event3.
|
|
209
|
-
expect(event3.value).toEqual(expect.objectContaining({
|
|
228
|
+
expect(event3.value).toMatchObject({
|
|
210
229
|
callId: 'fc3',
|
|
211
230
|
name: 'undefined_tool_name',
|
|
212
231
|
args: {},
|
|
213
|
-
|
|
214
|
-
}));
|
|
215
|
-
expect(turn.pendingToolCalls[2]).toEqual(event3.value);
|
|
216
|
-
expect(turn.getDebugResponses().length).toBe(1);
|
|
232
|
+
});
|
|
217
233
|
});
|
|
218
234
|
it('should yield finished event when response has finish reason', async () => {
|
|
219
235
|
const mockResponseStream = (async function* () {
|
|
220
236
|
yield {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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,
|
|
225
251
|
},
|
|
226
|
-
|
|
252
|
+
},
|
|
227
253
|
};
|
|
228
254
|
})();
|
|
229
255
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
230
256
|
const events = [];
|
|
231
|
-
const
|
|
232
|
-
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)) {
|
|
233
258
|
events.push(event);
|
|
234
259
|
}
|
|
235
260
|
expect(events).toEqual([
|
|
236
261
|
{ type: GeminiEventType.Content, value: 'Partial response' },
|
|
237
|
-
{
|
|
262
|
+
{
|
|
263
|
+
type: GeminiEventType.Finished,
|
|
264
|
+
value: {
|
|
265
|
+
reason: 'STOP',
|
|
266
|
+
usageMetadata: {
|
|
267
|
+
promptTokenCount: 17,
|
|
268
|
+
candidatesTokenCount: 50,
|
|
269
|
+
cachedContentTokenCount: 10,
|
|
270
|
+
thoughtsTokenCount: 5,
|
|
271
|
+
toolUsePromptTokenCount: 2,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
238
275
|
]);
|
|
239
276
|
});
|
|
240
277
|
it('should yield finished event for MAX_TOKENS finish reason', async () => {
|
|
241
278
|
const mockResponseStream = (async function* () {
|
|
242
279
|
yield {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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',
|
|
249
290
|
},
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
],
|
|
291
|
+
],
|
|
292
|
+
},
|
|
253
293
|
};
|
|
254
294
|
})();
|
|
255
295
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -263,18 +303,24 @@ describe('Turn', () => {
|
|
|
263
303
|
type: GeminiEventType.Content,
|
|
264
304
|
value: 'This is a long response that was cut off...',
|
|
265
305
|
},
|
|
266
|
-
{
|
|
306
|
+
{
|
|
307
|
+
type: GeminiEventType.Finished,
|
|
308
|
+
value: { reason: 'MAX_TOKENS', usageMetadata: undefined },
|
|
309
|
+
},
|
|
267
310
|
]);
|
|
268
311
|
});
|
|
269
312
|
it('should yield finished event for SAFETY finish reason', async () => {
|
|
270
313
|
const mockResponseStream = (async function* () {
|
|
271
314
|
yield {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
315
|
+
type: StreamEventType.CHUNK,
|
|
316
|
+
value: {
|
|
317
|
+
candidates: [
|
|
318
|
+
{
|
|
319
|
+
content: { parts: [{ text: 'Content blocked' }] },
|
|
320
|
+
finishReason: 'SAFETY',
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
},
|
|
278
324
|
};
|
|
279
325
|
})();
|
|
280
326
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -285,18 +331,26 @@ describe('Turn', () => {
|
|
|
285
331
|
}
|
|
286
332
|
expect(events).toEqual([
|
|
287
333
|
{ type: GeminiEventType.Content, value: 'Content blocked' },
|
|
288
|
-
{
|
|
334
|
+
{
|
|
335
|
+
type: GeminiEventType.Finished,
|
|
336
|
+
value: { reason: 'SAFETY', usageMetadata: undefined },
|
|
337
|
+
},
|
|
289
338
|
]);
|
|
290
339
|
});
|
|
291
|
-
it('should
|
|
340
|
+
it('should yield finished event with undefined reason when there is no finish reason', async () => {
|
|
292
341
|
const mockResponseStream = (async function* () {
|
|
293
342
|
yield {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
+
},
|
|
300
354
|
};
|
|
301
355
|
})();
|
|
302
356
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -311,25 +365,30 @@ describe('Turn', () => {
|
|
|
311
365
|
value: 'Response without finish reason',
|
|
312
366
|
},
|
|
313
367
|
]);
|
|
314
|
-
// No Finished event should be emitted
|
|
315
368
|
});
|
|
316
369
|
it('should handle multiple responses with different finish reasons', async () => {
|
|
317
370
|
const mockResponseStream = (async function* () {
|
|
318
371
|
yield {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
},
|
|
325
381
|
};
|
|
326
382
|
yield {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
383
|
+
value: {
|
|
384
|
+
type: StreamEventType.CHUNK,
|
|
385
|
+
candidates: [
|
|
386
|
+
{
|
|
387
|
+
content: { parts: [{ text: 'Second part' }] },
|
|
388
|
+
finishReason: 'OTHER',
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
},
|
|
333
392
|
};
|
|
334
393
|
})();
|
|
335
394
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -341,26 +400,32 @@ describe('Turn', () => {
|
|
|
341
400
|
expect(events).toEqual([
|
|
342
401
|
{ type: GeminiEventType.Content, value: 'First part' },
|
|
343
402
|
{ type: GeminiEventType.Content, value: 'Second part' },
|
|
344
|
-
{
|
|
403
|
+
{
|
|
404
|
+
type: GeminiEventType.Finished,
|
|
405
|
+
value: { reason: 'OTHER', usageMetadata: undefined },
|
|
406
|
+
},
|
|
345
407
|
]);
|
|
346
408
|
});
|
|
347
409
|
it('should yield citation and finished events when response has citationMetadata', async () => {
|
|
348
410
|
const mockResponseStream = (async function* () {
|
|
349
411
|
yield {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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',
|
|
360
426
|
},
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
],
|
|
427
|
+
],
|
|
428
|
+
},
|
|
364
429
|
};
|
|
365
430
|
})();
|
|
366
431
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -374,30 +439,36 @@ describe('Turn', () => {
|
|
|
374
439
|
type: GeminiEventType.Citation,
|
|
375
440
|
value: 'Citations:\n(Source 1 Title) https://example.com/source1',
|
|
376
441
|
},
|
|
377
|
-
{
|
|
442
|
+
{
|
|
443
|
+
type: GeminiEventType.Finished,
|
|
444
|
+
value: { reason: 'STOP', usageMetadata: undefined },
|
|
445
|
+
},
|
|
378
446
|
]);
|
|
379
447
|
});
|
|
380
448
|
it('should yield a single citation event for multiple citations in one response', async () => {
|
|
381
449
|
const mockResponseStream = (async function* () {
|
|
382
450
|
yield {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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',
|
|
397
469
|
},
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
],
|
|
470
|
+
],
|
|
471
|
+
},
|
|
401
472
|
};
|
|
402
473
|
})();
|
|
403
474
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -411,26 +482,32 @@ describe('Turn', () => {
|
|
|
411
482
|
type: GeminiEventType.Citation,
|
|
412
483
|
value: 'Citations:\n(Title1) https://example.com/source1\n(Title2) https://example.com/source2',
|
|
413
484
|
},
|
|
414
|
-
{
|
|
485
|
+
{
|
|
486
|
+
type: GeminiEventType.Finished,
|
|
487
|
+
value: { reason: 'STOP', usageMetadata: undefined },
|
|
488
|
+
},
|
|
415
489
|
]);
|
|
416
490
|
});
|
|
417
491
|
it('should not yield citation event if there is no finish reason', async () => {
|
|
418
492
|
const mockResponseStream = (async function* () {
|
|
419
493
|
yield {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
430
508
|
},
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
],
|
|
509
|
+
],
|
|
510
|
+
},
|
|
434
511
|
};
|
|
435
512
|
})();
|
|
436
513
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -441,30 +518,33 @@ describe('Turn', () => {
|
|
|
441
518
|
expect(events).toEqual([
|
|
442
519
|
{ type: GeminiEventType.Content, value: 'Some text.' },
|
|
443
520
|
]);
|
|
444
|
-
// No Citation
|
|
521
|
+
// No Citation event (but we do get a Finished event with undefined reason)
|
|
445
522
|
expect(events.some((e) => e.type === GeminiEventType.Citation)).toBe(false);
|
|
446
523
|
});
|
|
447
524
|
it('should ignore citations without a URI', async () => {
|
|
448
525
|
const mockResponseStream = (async function* () {
|
|
449
526
|
yield {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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',
|
|
464
545
|
},
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
],
|
|
546
|
+
],
|
|
547
|
+
},
|
|
468
548
|
};
|
|
469
549
|
})();
|
|
470
550
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
@@ -478,7 +558,10 @@ describe('Turn', () => {
|
|
|
478
558
|
type: GeminiEventType.Citation,
|
|
479
559
|
value: 'Citations:\n(Good Source) https://example.com/source1',
|
|
480
560
|
},
|
|
481
|
-
{
|
|
561
|
+
{
|
|
562
|
+
type: GeminiEventType.Finished,
|
|
563
|
+
value: { reason: 'STOP', usageMetadata: undefined },
|
|
564
|
+
},
|
|
482
565
|
]);
|
|
483
566
|
});
|
|
484
567
|
it('should not crash when cancelled request has malformed error', async () => {
|
|
@@ -500,6 +583,26 @@ describe('Turn', () => {
|
|
|
500
583
|
expect(events).toEqual([{ type: GeminiEventType.UserCancelled }]);
|
|
501
584
|
expect(reportError).not.toHaveBeenCalled();
|
|
502
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
|
+
});
|
|
503
606
|
});
|
|
504
607
|
describe('getDebugResponses', () => {
|
|
505
608
|
it('should return collected debug responses', async () => {
|
|
@@ -510,8 +613,8 @@ describe('Turn', () => {
|
|
|
510
613
|
functionCalls: [{ name: 'debugTool' }],
|
|
511
614
|
};
|
|
512
615
|
const mockResponseStream = (async function* () {
|
|
513
|
-
yield resp1;
|
|
514
|
-
yield resp2;
|
|
616
|
+
yield { type: StreamEventType.CHUNK, value: resp1 };
|
|
617
|
+
yield { type: StreamEventType.CHUNK, value: resp2 };
|
|
515
618
|
})();
|
|
516
619
|
mockSendMessageStream.mockResolvedValue(mockResponseStream);
|
|
517
620
|
const reqParts = [{ text: 'Hi' }];
|