@ai-sdk/google 4.0.0-canary.63 → 4.0.0-canary.65
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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +510 -335
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.js +43 -28
- package/dist/internal/index.js.map +1 -1
- package/docs/15-google.mdx +72 -16
- package/package.json +1 -1
- package/src/convert-to-google-messages.ts +20 -2
- package/src/google-language-model.ts +5 -4
- package/src/google-prompt.ts +5 -1
- package/src/interactions/build-google-interactions-stream-transform.ts +273 -154
- package/src/interactions/convert-to-google-interactions-input.ts +57 -143
- package/src/interactions/extract-google-interactions-sources.ts +3 -3
- package/src/interactions/google-interactions-api.ts +179 -115
- package/src/interactions/google-interactions-language-model-options.ts +61 -0
- package/src/interactions/google-interactions-language-model.ts +100 -38
- package/src/interactions/google-interactions-prompt.ts +189 -114
- package/src/interactions/map-google-interactions-finish-reason.ts +3 -5
- package/src/interactions/parse-google-interactions-outputs.ts +69 -63
- package/src/interactions/prepare-google-interactions-tools.ts +1 -1
- package/src/interactions/stream-google-interactions.ts +1 -1
- package/src/interactions/synthesize-google-interactions-agent-stream.ts +1 -1
|
@@ -67,16 +67,16 @@ type OpenBlockState =
|
|
|
67
67
|
kind: 'function_call';
|
|
68
68
|
id: string;
|
|
69
69
|
toolCallId: string;
|
|
70
|
-
toolName: string
|
|
71
|
-
arguments: Record<string, unknown>;
|
|
72
|
-
signature?: string;
|
|
70
|
+
toolName: string;
|
|
73
71
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* `
|
|
72
|
+
* Accumulator for partial JSON arguments. Arguments stream as a
|
|
73
|
+
* sequence of `arguments_delta` substrings on `step.delta`; each one is
|
|
74
|
+
* appended verbatim and surfaced as a `tool-input-delta`. On
|
|
75
|
+
* `step.stop` the accumulated string is parsed to recover the full
|
|
76
|
+
* arguments object for the final `tool-call` event.
|
|
78
77
|
*/
|
|
79
|
-
|
|
78
|
+
argumentsAccum: string;
|
|
79
|
+
signature?: string;
|
|
80
80
|
}
|
|
81
81
|
| {
|
|
82
82
|
kind: 'builtin_tool_call';
|
|
@@ -97,14 +97,22 @@ type OpenBlockState =
|
|
|
97
97
|
isError?: boolean;
|
|
98
98
|
resultEmitted: boolean;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* A `model_output` step whose inner content-block kind has not yet been
|
|
102
|
+
* disambiguated. `step.start` may arrive bare (`{type: 'model_output'}`,
|
|
103
|
+
* no content payload); the first `step.delta` reveals whether the block
|
|
104
|
+
* is text or image. The block opens in this transitional state and swaps
|
|
105
|
+
* to `text` / `image` on the first matching delta.
|
|
106
|
+
*/
|
|
107
|
+
| { kind: 'pending_model_output'; id: string }
|
|
100
108
|
| { kind: 'unknown'; id: string };
|
|
101
109
|
|
|
102
110
|
/**
|
|
103
111
|
* Builds a `TransformStream<ParseResult<GoogleInteractionsEvent>, LanguageModelV4StreamPart>`
|
|
104
|
-
* over the
|
|
112
|
+
* over the Interactions API SSE event stream.
|
|
105
113
|
*
|
|
106
114
|
* Surfaces text + thought (reasoning), function_call, image, built-in tool
|
|
107
|
-
* call/result
|
|
115
|
+
* call/result steps, and `text_annotation` -> `source` parts.
|
|
108
116
|
*/
|
|
109
117
|
export function buildGoogleInteractionsStreamTransform({
|
|
110
118
|
warnings,
|
|
@@ -118,10 +126,9 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
118
126
|
/**
|
|
119
127
|
* Defensive fallback for service tier read from the `x-gemini-service-tier`
|
|
120
128
|
* HTTP response header. The Interactions API surfaces the applied tier in
|
|
121
|
-
* the `interaction.
|
|
129
|
+
* the `interaction.completed` event body (see `service_tier` below); this
|
|
122
130
|
* parameter exists so we still surface a tier if the API later starts
|
|
123
|
-
* sending the header
|
|
124
|
-
* 1adfb76d2d).
|
|
131
|
+
* sending the header.
|
|
125
132
|
*/
|
|
126
133
|
serviceTier?: string;
|
|
127
134
|
}): TransformStream<
|
|
@@ -135,10 +142,10 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
135
142
|
let hasFunctionCall = false;
|
|
136
143
|
|
|
137
144
|
/*
|
|
138
|
-
* Per-index open
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
145
|
+
* Per-index open step slots. The Interactions API frames concurrent steps
|
|
146
|
+
* (e.g. text alongside thought) by `index`; we track each open slot
|
|
147
|
+
* independently so a text delta at index N never collides with a thought
|
|
148
|
+
* delta at index M.
|
|
142
149
|
*/
|
|
143
150
|
const openBlocks = new Map<number, OpenBlockState>();
|
|
144
151
|
|
|
@@ -178,17 +185,17 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
178
185
|
const eventType = (value as { event_type?: string }).event_type;
|
|
179
186
|
|
|
180
187
|
switch (eventType) {
|
|
181
|
-
case 'interaction.
|
|
188
|
+
case 'interaction.created': {
|
|
182
189
|
const event = value as Extract<
|
|
183
190
|
GoogleInteractionsEvent,
|
|
184
|
-
{ event_type: 'interaction.
|
|
191
|
+
{ event_type: 'interaction.created' }
|
|
185
192
|
>;
|
|
186
193
|
const interaction = event.interaction;
|
|
187
194
|
/*
|
|
188
195
|
* The Interactions API returns `id: ""` (empty string) on streaming
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
196
|
+
* events when running with `store: false` — there is no server-side
|
|
197
|
+
* record. Treat empty string the same as missing so providerMetadata
|
|
198
|
+
* stays clean.
|
|
192
199
|
*/
|
|
193
200
|
interactionId =
|
|
194
201
|
interaction?.id != null && interaction.id.length > 0
|
|
@@ -214,12 +221,12 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
214
221
|
break;
|
|
215
222
|
}
|
|
216
223
|
|
|
217
|
-
case '
|
|
224
|
+
case 'step.start': {
|
|
218
225
|
const event = value as Extract<
|
|
219
226
|
GoogleInteractionsEvent,
|
|
220
|
-
{ event_type: '
|
|
227
|
+
{ event_type: 'step.start' }
|
|
221
228
|
>;
|
|
222
|
-
const
|
|
229
|
+
const step = event.step as
|
|
223
230
|
| {
|
|
224
231
|
type?: string;
|
|
225
232
|
id?: string;
|
|
@@ -227,118 +234,163 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
227
234
|
name?: string;
|
|
228
235
|
arguments?: Record<string, unknown>;
|
|
229
236
|
signature?: string;
|
|
237
|
+
summary?: Array<{ type?: string; text?: string }>;
|
|
230
238
|
result?: unknown;
|
|
231
239
|
is_error?: boolean;
|
|
232
|
-
|
|
240
|
+
content?: Array<{
|
|
241
|
+
type?: string;
|
|
242
|
+
text?: string;
|
|
243
|
+
data?: string;
|
|
244
|
+
mime_type?: string;
|
|
245
|
+
uri?: string;
|
|
246
|
+
annotations?: Array<GoogleInteractionsAnnotation>;
|
|
247
|
+
}>;
|
|
233
248
|
}
|
|
234
249
|
| undefined;
|
|
235
250
|
const index = event.index;
|
|
236
251
|
const blockId = `${interactionId ?? 'interaction'}:${index}`;
|
|
252
|
+
const stepType = step?.type;
|
|
237
253
|
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
if (stepType === 'model_output') {
|
|
255
|
+
/*
|
|
256
|
+
* `step.start` for a `model_output` step often carries only the
|
|
257
|
+
* type discriminator — content/image payloads then arrive on
|
|
258
|
+
* subsequent `step.delta` events. Open in a transitional
|
|
259
|
+
* `pending_model_output` state; the first delta promotes it to
|
|
260
|
+
* either `text` (and emits `text-start`) or `image`.
|
|
261
|
+
*
|
|
262
|
+
* `step.content[0]` may also arrive populated as a hint; when
|
|
263
|
+
* present, promote eagerly.
|
|
264
|
+
*/
|
|
265
|
+
const initial = step?.content?.[0] as
|
|
266
|
+
| {
|
|
267
|
+
type?: string;
|
|
268
|
+
text?: string;
|
|
269
|
+
data?: string;
|
|
270
|
+
mime_type?: string;
|
|
271
|
+
uri?: string;
|
|
272
|
+
annotations?: Array<GoogleInteractionsAnnotation>;
|
|
273
|
+
}
|
|
274
|
+
| undefined;
|
|
275
|
+
if (initial?.type === 'text') {
|
|
276
|
+
openBlocks.set(index, {
|
|
277
|
+
kind: 'text',
|
|
278
|
+
id: blockId,
|
|
279
|
+
emittedSourceKeys: new Set<string>(),
|
|
280
|
+
});
|
|
281
|
+
controller.enqueue({ type: 'text-start', id: blockId });
|
|
245
282
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
283
|
+
const initialSources = annotationsToSources({
|
|
284
|
+
annotations: initial.annotations,
|
|
285
|
+
generateId,
|
|
286
|
+
});
|
|
287
|
+
for (const source of initialSources) {
|
|
288
|
+
const key = sourceKey(source);
|
|
289
|
+
if (emittedSourceKeys.has(key)) continue;
|
|
290
|
+
emittedSourceKeys.add(key);
|
|
291
|
+
controller.enqueue(source);
|
|
292
|
+
}
|
|
293
|
+
} else if (initial?.type === 'image') {
|
|
294
|
+
openBlocks.set(index, {
|
|
295
|
+
kind: 'image',
|
|
296
|
+
id: blockId,
|
|
297
|
+
...(initial.data != null ? { data: initial.data } : {}),
|
|
298
|
+
...(initial.mime_type != null
|
|
299
|
+
? { mimeType: initial.mime_type }
|
|
300
|
+
: {}),
|
|
301
|
+
...(initial.uri != null ? { uri: initial.uri } : {}),
|
|
302
|
+
});
|
|
303
|
+
} else {
|
|
304
|
+
openBlocks.set(index, {
|
|
305
|
+
kind: 'pending_model_output',
|
|
306
|
+
id: blockId,
|
|
307
|
+
});
|
|
256
308
|
}
|
|
257
|
-
} else if (
|
|
258
|
-
const
|
|
259
|
-
data?: string;
|
|
260
|
-
mime_type?: string;
|
|
261
|
-
uri?: string;
|
|
262
|
-
};
|
|
263
|
-
openBlocks.set(index, {
|
|
264
|
-
kind: 'image',
|
|
265
|
-
id: blockId,
|
|
266
|
-
...(img.data != null ? { data: img.data } : {}),
|
|
267
|
-
...(img.mime_type != null ? { mimeType: img.mime_type } : {}),
|
|
268
|
-
...(img.uri != null ? { uri: img.uri } : {}),
|
|
269
|
-
});
|
|
270
|
-
} else if (block?.type === 'thought') {
|
|
271
|
-
const signature = (block as { signature?: string }).signature;
|
|
309
|
+
} else if (stepType === 'thought') {
|
|
310
|
+
const signature = step?.signature;
|
|
272
311
|
openBlocks.set(index, {
|
|
273
312
|
kind: 'reasoning',
|
|
274
313
|
id: blockId,
|
|
275
314
|
...(signature != null ? { signature } : {}),
|
|
276
315
|
});
|
|
277
316
|
controller.enqueue({ type: 'reasoning-start', id: blockId });
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
317
|
+
/*
|
|
318
|
+
* A `thought` step's initial `summary[]` may already contain text
|
|
319
|
+
* items on `step.start` — emit those as reasoning deltas so the
|
|
320
|
+
* consumer's reasoning buffer is up to date before any delta
|
|
321
|
+
* arrives.
|
|
322
|
+
*/
|
|
323
|
+
if (Array.isArray(step?.summary)) {
|
|
324
|
+
for (const item of step.summary) {
|
|
325
|
+
if (item?.type === 'text' && typeof item.text === 'string') {
|
|
326
|
+
controller.enqueue({
|
|
327
|
+
type: 'reasoning-delta',
|
|
328
|
+
id: blockId,
|
|
329
|
+
delta: item.text,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} else if (stepType === 'function_call') {
|
|
335
|
+
const toolCallId = step?.id ?? blockId;
|
|
336
|
+
const toolName = step?.name ?? 'unknown';
|
|
281
337
|
hasFunctionCall = true;
|
|
282
338
|
const state: Extract<OpenBlockState, { kind: 'function_call' }> = {
|
|
283
339
|
kind: 'function_call',
|
|
284
340
|
id: blockId,
|
|
285
341
|
toolCallId,
|
|
286
|
-
toolName
|
|
287
|
-
|
|
288
|
-
...(
|
|
289
|
-
startEmitted: false,
|
|
342
|
+
toolName,
|
|
343
|
+
argumentsAccum: '',
|
|
344
|
+
...(step?.signature != null ? { signature: step.signature } : {}),
|
|
290
345
|
};
|
|
291
346
|
openBlocks.set(index, state);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
});
|
|
298
|
-
state.startEmitted = true;
|
|
299
|
-
}
|
|
347
|
+
controller.enqueue({
|
|
348
|
+
type: 'tool-input-start',
|
|
349
|
+
id: toolCallId,
|
|
350
|
+
toolName,
|
|
351
|
+
});
|
|
300
352
|
} else if (
|
|
301
|
-
|
|
302
|
-
BUILTIN_TOOL_CALL_TYPES.has(
|
|
353
|
+
stepType != null &&
|
|
354
|
+
BUILTIN_TOOL_CALL_TYPES.has(stepType)
|
|
303
355
|
) {
|
|
304
356
|
const toolName =
|
|
305
|
-
|
|
306
|
-
? (
|
|
307
|
-
: builtinToolNameFromCallType(
|
|
308
|
-
const toolCallId =
|
|
357
|
+
stepType === 'mcp_server_tool_call'
|
|
358
|
+
? (step?.name ?? 'mcp_server_tool')
|
|
359
|
+
: builtinToolNameFromCallType(stepType);
|
|
360
|
+
const toolCallId = step?.id ?? blockId;
|
|
309
361
|
const state: Extract<
|
|
310
362
|
OpenBlockState,
|
|
311
363
|
{ kind: 'builtin_tool_call' }
|
|
312
364
|
> = {
|
|
313
365
|
kind: 'builtin_tool_call',
|
|
314
366
|
id: blockId,
|
|
315
|
-
blockType:
|
|
367
|
+
blockType: stepType,
|
|
316
368
|
toolCallId,
|
|
317
369
|
toolName,
|
|
318
|
-
arguments:
|
|
370
|
+
arguments: step?.arguments ?? {},
|
|
319
371
|
callEmitted: false,
|
|
320
372
|
};
|
|
321
373
|
openBlocks.set(index, state);
|
|
322
374
|
} else if (
|
|
323
|
-
|
|
324
|
-
BUILTIN_TOOL_RESULT_TYPES.has(
|
|
375
|
+
stepType != null &&
|
|
376
|
+
BUILTIN_TOOL_RESULT_TYPES.has(stepType)
|
|
325
377
|
) {
|
|
326
378
|
const toolName =
|
|
327
|
-
|
|
328
|
-
? (
|
|
329
|
-
: builtinToolNameFromResultType(
|
|
330
|
-
const callId =
|
|
379
|
+
stepType === 'mcp_server_tool_result'
|
|
380
|
+
? (step?.name ?? 'mcp_server_tool')
|
|
381
|
+
: builtinToolNameFromResultType(stepType);
|
|
382
|
+
const callId = step?.call_id ?? blockId;
|
|
331
383
|
const state: Extract<
|
|
332
384
|
OpenBlockState,
|
|
333
385
|
{ kind: 'builtin_tool_result' }
|
|
334
386
|
> = {
|
|
335
387
|
kind: 'builtin_tool_result',
|
|
336
388
|
id: blockId,
|
|
337
|
-
blockType:
|
|
389
|
+
blockType: stepType,
|
|
338
390
|
callId,
|
|
339
391
|
toolName,
|
|
340
|
-
result:
|
|
341
|
-
...(
|
|
392
|
+
result: step?.result ?? null,
|
|
393
|
+
...(step?.is_error != null ? { isError: step.is_error } : {}),
|
|
342
394
|
resultEmitted: false,
|
|
343
395
|
};
|
|
344
396
|
openBlocks.set(index, state);
|
|
@@ -348,14 +400,84 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
348
400
|
break;
|
|
349
401
|
}
|
|
350
402
|
|
|
351
|
-
case '
|
|
403
|
+
case 'step.delta': {
|
|
352
404
|
const event = value as Extract<
|
|
353
405
|
GoogleInteractionsEvent,
|
|
354
|
-
{ event_type: '
|
|
406
|
+
{ event_type: 'step.delta' }
|
|
355
407
|
>;
|
|
356
|
-
|
|
408
|
+
let open = openBlocks.get(event.index);
|
|
357
409
|
if (open == null) break;
|
|
358
410
|
|
|
411
|
+
const dtype = (event.delta as { type?: string } | undefined)?.type;
|
|
412
|
+
|
|
413
|
+
/*
|
|
414
|
+
* Promote a pending model_output block to `text` on the first
|
|
415
|
+
* text-shaped delta. Image deltas are emitted inline below — a
|
|
416
|
+
* model_output step can interleave text and image deltas, so the
|
|
417
|
+
* text "open block" stays in place across image emissions instead
|
|
418
|
+
* of being swapped for an image state.
|
|
419
|
+
*/
|
|
420
|
+
if (open.kind === 'pending_model_output') {
|
|
421
|
+
if (
|
|
422
|
+
dtype === 'text' ||
|
|
423
|
+
dtype === 'text_annotation' ||
|
|
424
|
+
dtype === 'text_annotation_delta'
|
|
425
|
+
) {
|
|
426
|
+
const promoted: Extract<OpenBlockState, { kind: 'text' }> = {
|
|
427
|
+
kind: 'text',
|
|
428
|
+
id: open.id,
|
|
429
|
+
emittedSourceKeys: new Set<string>(),
|
|
430
|
+
};
|
|
431
|
+
openBlocks.set(event.index, promoted);
|
|
432
|
+
open = promoted;
|
|
433
|
+
controller.enqueue({ type: 'text-start', id: promoted.id });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/*
|
|
438
|
+
* Image deltas inside `model_output` carry the full payload in a
|
|
439
|
+
* single chunk (no per-byte streaming). Emit the `file` part as
|
|
440
|
+
* soon as the delta arrives so it surfaces regardless of whether
|
|
441
|
+
* a text block is currently open at the same index.
|
|
442
|
+
*/
|
|
443
|
+
if (
|
|
444
|
+
dtype === 'image' &&
|
|
445
|
+
(open.kind === 'pending_model_output' ||
|
|
446
|
+
open.kind === 'text' ||
|
|
447
|
+
open.kind === 'image')
|
|
448
|
+
) {
|
|
449
|
+
const img = event.delta as
|
|
450
|
+
| { data?: string; mime_type?: string; uri?: string }
|
|
451
|
+
| undefined;
|
|
452
|
+
const google: Record<string, string> = {};
|
|
453
|
+
if (interactionId != null) google.interactionId = interactionId;
|
|
454
|
+
const providerMetadata =
|
|
455
|
+
Object.keys(google).length > 0 ? { google } : undefined;
|
|
456
|
+
if (img?.data != null && img.data.length > 0) {
|
|
457
|
+
controller.enqueue({
|
|
458
|
+
type: 'file',
|
|
459
|
+
mediaType: img.mime_type ?? 'image/png',
|
|
460
|
+
data: { type: 'data', data: img.data },
|
|
461
|
+
...(providerMetadata ? { providerMetadata } : {}),
|
|
462
|
+
});
|
|
463
|
+
} else if (img?.uri != null && img.uri.length > 0) {
|
|
464
|
+
controller.enqueue({
|
|
465
|
+
type: 'file',
|
|
466
|
+
mediaType: img.mime_type ?? 'image/png',
|
|
467
|
+
data: { type: 'url', url: new URL(img.uri) },
|
|
468
|
+
...(providerMetadata ? { providerMetadata } : {}),
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
// The file part was emitted inline; clear any data on an
|
|
472
|
+
// eagerly-promoted image OpenBlockState so the `step.stop`
|
|
473
|
+
// handler does not emit a duplicate.
|
|
474
|
+
if (open.kind === 'image') {
|
|
475
|
+
open.data = undefined;
|
|
476
|
+
open.uri = undefined;
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
|
|
359
481
|
const delta = event.delta as
|
|
360
482
|
| {
|
|
361
483
|
type?: string;
|
|
@@ -363,8 +485,13 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
363
485
|
signature?: string;
|
|
364
486
|
content?: { type?: string; text?: string };
|
|
365
487
|
id?: string;
|
|
366
|
-
|
|
367
|
-
|
|
488
|
+
/*
|
|
489
|
+
* `arguments` carries different shapes per delta kind:
|
|
490
|
+
* - `type: 'arguments_delta'` → `string` (partial JSON)
|
|
491
|
+
* - `type: '<builtin>_tool_call'` → `Record<string, unknown>`
|
|
492
|
+
* The branch handler reads it with the matching type.
|
|
493
|
+
*/
|
|
494
|
+
arguments?: Record<string, unknown> | string;
|
|
368
495
|
annotations?: Array<GoogleInteractionsAnnotation>;
|
|
369
496
|
call_id?: string;
|
|
370
497
|
result?: unknown;
|
|
@@ -372,6 +499,7 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
372
499
|
data?: string;
|
|
373
500
|
mime_type?: string;
|
|
374
501
|
uri?: string;
|
|
502
|
+
name?: string;
|
|
375
503
|
}
|
|
376
504
|
| undefined;
|
|
377
505
|
|
|
@@ -386,7 +514,8 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
386
514
|
}
|
|
387
515
|
} else if (
|
|
388
516
|
open.kind === 'text' &&
|
|
389
|
-
delta?.type === 'text_annotation'
|
|
517
|
+
(delta?.type === 'text_annotation' ||
|
|
518
|
+
delta?.type === 'text_annotation_delta')
|
|
390
519
|
) {
|
|
391
520
|
const sources = annotationsToSources({
|
|
392
521
|
annotations: delta.annotations,
|
|
@@ -400,14 +529,6 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
400
529
|
controller.enqueue(source);
|
|
401
530
|
}
|
|
402
531
|
} else if (open.kind === 'image' && delta?.type === 'image') {
|
|
403
|
-
/*
|
|
404
|
-
* `image` ContentDelta carries the entire image payload as a
|
|
405
|
-
* complete object (`data` base64 + `mime_type`, or `uri`) per
|
|
406
|
-
* `googleapis/js-genai`
|
|
407
|
-
* `src/interactions/resources/interactions.ts`
|
|
408
|
-
* `ContentDelta.Image`. Accumulate the latest snapshot; emit the
|
|
409
|
-
* file stream part on `content.stop`.
|
|
410
|
-
*/
|
|
411
532
|
if (delta.data != null) open.data = delta.data;
|
|
412
533
|
if (delta.mime_type != null) open.mimeType = delta.mime_type;
|
|
413
534
|
if (delta.uri != null) open.uri = delta.uri;
|
|
@@ -429,47 +550,44 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
429
550
|
}
|
|
430
551
|
} else if (
|
|
431
552
|
open.kind === 'function_call' &&
|
|
432
|
-
delta?.type === '
|
|
553
|
+
delta?.type === 'arguments_delta'
|
|
433
554
|
) {
|
|
434
555
|
/*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
* `
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* stringified args at content.stop.
|
|
442
|
-
*
|
|
443
|
-
* The `name` typically arrives here (not on `content.start`), so
|
|
444
|
-
* defer `tool-input-start` emission until we observe it.
|
|
556
|
+
* Partial JSON arguments arrive as `arguments_delta` events.
|
|
557
|
+
* The partial JSON string lives in `delta.arguments` (a string,
|
|
558
|
+
* not the parsed object — the `arguments_delta` name applies to
|
|
559
|
+
* the discriminator only). Append to the accumulator and surface
|
|
560
|
+
* each chunk as a `tool-input-delta`; the full arguments object
|
|
561
|
+
* is emitted at `step.stop`.
|
|
445
562
|
*/
|
|
563
|
+
const slice =
|
|
564
|
+
typeof delta.arguments === 'string' ? delta.arguments : '';
|
|
565
|
+
if (slice.length > 0) {
|
|
566
|
+
open.argumentsAccum += slice;
|
|
567
|
+
controller.enqueue({
|
|
568
|
+
type: 'tool-input-delta',
|
|
569
|
+
id: open.toolCallId,
|
|
570
|
+
delta: slice,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
446
573
|
if (delta.id != null) {
|
|
447
574
|
open.toolCallId = delta.id;
|
|
448
575
|
}
|
|
449
|
-
if (delta.name != null) {
|
|
450
|
-
open.toolName = delta.name;
|
|
451
|
-
}
|
|
452
|
-
if (delta.arguments != null) {
|
|
453
|
-
open.arguments = delta.arguments;
|
|
454
|
-
}
|
|
455
576
|
if (delta.signature != null) {
|
|
456
577
|
open.signature = delta.signature;
|
|
457
578
|
}
|
|
458
|
-
if (!open.startEmitted && open.toolName != null) {
|
|
459
|
-
controller.enqueue({
|
|
460
|
-
type: 'tool-input-start',
|
|
461
|
-
id: open.toolCallId,
|
|
462
|
-
toolName: open.toolName,
|
|
463
|
-
});
|
|
464
|
-
open.startEmitted = true;
|
|
465
|
-
}
|
|
466
579
|
hasFunctionCall = true;
|
|
467
580
|
} else if (
|
|
468
581
|
open.kind === 'builtin_tool_call' &&
|
|
469
582
|
delta?.type === open.blockType
|
|
470
583
|
) {
|
|
471
584
|
if (delta.id != null) open.toolCallId = delta.id;
|
|
472
|
-
if (
|
|
585
|
+
if (
|
|
586
|
+
delta.arguments != null &&
|
|
587
|
+
typeof delta.arguments === 'object'
|
|
588
|
+
) {
|
|
589
|
+
open.arguments = delta.arguments;
|
|
590
|
+
}
|
|
473
591
|
if (
|
|
474
592
|
delta.name != null &&
|
|
475
593
|
open.blockType === 'mcp_server_tool_call'
|
|
@@ -493,10 +611,10 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
493
611
|
break;
|
|
494
612
|
}
|
|
495
613
|
|
|
496
|
-
case '
|
|
614
|
+
case 'step.stop': {
|
|
497
615
|
const event = value as Extract<
|
|
498
616
|
GoogleInteractionsEvent,
|
|
499
|
-
{ event_type: '
|
|
617
|
+
{ event_type: 'step.stop' }
|
|
500
618
|
>;
|
|
501
619
|
const open = openBlocks.get(event.index);
|
|
502
620
|
if (open == null) break;
|
|
@@ -543,20 +661,8 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
543
661
|
});
|
|
544
662
|
}
|
|
545
663
|
} else if (open.kind === 'function_call') {
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
if (!open.startEmitted) {
|
|
549
|
-
controller.enqueue({
|
|
550
|
-
type: 'tool-input-start',
|
|
551
|
-
id: open.toolCallId,
|
|
552
|
-
toolName,
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
controller.enqueue({
|
|
556
|
-
type: 'tool-input-delta',
|
|
557
|
-
id: open.toolCallId,
|
|
558
|
-
delta: argsJson,
|
|
559
|
-
});
|
|
664
|
+
const accumulated =
|
|
665
|
+
open.argumentsAccum.length > 0 ? open.argumentsAccum : '{}';
|
|
560
666
|
controller.enqueue({
|
|
561
667
|
type: 'tool-input-end',
|
|
562
668
|
id: open.toolCallId,
|
|
@@ -569,8 +675,8 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
569
675
|
controller.enqueue({
|
|
570
676
|
type: 'tool-call',
|
|
571
677
|
toolCallId: open.toolCallId,
|
|
572
|
-
toolName,
|
|
573
|
-
input:
|
|
678
|
+
toolName: open.toolName,
|
|
679
|
+
input: accumulated,
|
|
574
680
|
...(providerMetadata ? { providerMetadata } : {}),
|
|
575
681
|
});
|
|
576
682
|
} else if (open.kind === 'builtin_tool_call' && !open.callEmitted) {
|
|
@@ -613,19 +719,32 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
613
719
|
break;
|
|
614
720
|
}
|
|
615
721
|
|
|
616
|
-
case 'interaction.status_update':
|
|
722
|
+
case 'interaction.status_update':
|
|
723
|
+
case 'interaction.in_progress':
|
|
724
|
+
case 'interaction.requires_action': {
|
|
617
725
|
const event = value as Extract<
|
|
618
726
|
GoogleInteractionsEvent,
|
|
619
|
-
{
|
|
727
|
+
{
|
|
728
|
+
event_type:
|
|
729
|
+
| 'interaction.status_update'
|
|
730
|
+
| 'interaction.in_progress'
|
|
731
|
+
| 'interaction.requires_action';
|
|
732
|
+
}
|
|
620
733
|
>;
|
|
621
|
-
|
|
734
|
+
if (event.status != null) {
|
|
735
|
+
finishStatus = event.status;
|
|
736
|
+
} else if (eventType === 'interaction.requires_action') {
|
|
737
|
+
finishStatus = 'requires_action';
|
|
738
|
+
} else {
|
|
739
|
+
finishStatus = 'in_progress';
|
|
740
|
+
}
|
|
622
741
|
break;
|
|
623
742
|
}
|
|
624
743
|
|
|
625
|
-
case 'interaction.
|
|
744
|
+
case 'interaction.completed': {
|
|
626
745
|
const event = value as Extract<
|
|
627
746
|
GoogleInteractionsEvent,
|
|
628
|
-
{ event_type: 'interaction.
|
|
747
|
+
{ event_type: 'interaction.completed' }
|
|
629
748
|
>;
|
|
630
749
|
const interaction = event.interaction as {
|
|
631
750
|
id?: string;
|
|
@@ -644,7 +763,7 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
644
763
|
}
|
|
645
764
|
/*
|
|
646
765
|
* The Interactions API surfaces the applied service tier on
|
|
647
|
-
* `interaction.
|
|
766
|
+
* `interaction.completed.interaction.service_tier` (NOT on the
|
|
648
767
|
* `x-gemini-service-tier` HTTP header that `:generateContent`
|
|
649
768
|
* uses). Body wins over header fallback.
|
|
650
769
|
*/
|