@ai-sdk/google 3.0.73 → 3.0.75
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.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +521 -340
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +521 -340
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.d.mts +1 -0
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.js +43 -28
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +43 -28
- package/dist/internal/index.mjs.map +1 -1
- package/docs/15-google-generative-ai.mdx +72 -16
- package/package.json +1 -1
- package/src/convert-to-google-generative-ai-messages.ts +20 -2
- package/src/google-generative-ai-language-model.ts +5 -4
- package/src/google-generative-ai-prompt.ts +5 -1
- package/src/interactions/build-google-interactions-stream-transform.ts +285 -154
- package/src/interactions/convert-to-google-interactions-input.ts +57 -133
- 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 +80 -74
- 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>, LanguageModelV3StreamPart>`
|
|
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,96 @@ 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: img.data,
|
|
461
|
+
...(providerMetadata ? { providerMetadata } : {}),
|
|
462
|
+
});
|
|
463
|
+
} else if (img?.uri != null && img.uri.length > 0) {
|
|
464
|
+
/*
|
|
465
|
+
* V3 `LanguageModelV3File` only supports inline data (`string` /
|
|
466
|
+
* `Uint8Array`). URL-only image outputs cannot be represented as
|
|
467
|
+
* a file stream part on the v3 spec; surface the URI through
|
|
468
|
+
* provider metadata so callers can still recover it.
|
|
469
|
+
*/
|
|
470
|
+
const uriProviderMetadata = {
|
|
471
|
+
google: {
|
|
472
|
+
...(interactionId != null ? { interactionId } : {}),
|
|
473
|
+
imageUri: img.uri,
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
controller.enqueue({
|
|
477
|
+
type: 'file',
|
|
478
|
+
mediaType: img.mime_type ?? 'image/png',
|
|
479
|
+
data: '',
|
|
480
|
+
providerMetadata: uriProviderMetadata,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
// The file part was emitted inline; clear any data on an
|
|
484
|
+
// eagerly-promoted image OpenBlockState so the `step.stop`
|
|
485
|
+
// handler does not emit a duplicate.
|
|
486
|
+
if (open.kind === 'image') {
|
|
487
|
+
open.data = undefined;
|
|
488
|
+
open.uri = undefined;
|
|
489
|
+
}
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
|
|
359
493
|
const delta = event.delta as
|
|
360
494
|
| {
|
|
361
495
|
type?: string;
|
|
@@ -363,8 +497,13 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
363
497
|
signature?: string;
|
|
364
498
|
content?: { type?: string; text?: string };
|
|
365
499
|
id?: string;
|
|
366
|
-
|
|
367
|
-
|
|
500
|
+
/*
|
|
501
|
+
* `arguments` carries different shapes per delta kind:
|
|
502
|
+
* - `type: 'arguments_delta'` → `string` (partial JSON)
|
|
503
|
+
* - `type: '<builtin>_tool_call'` → `Record<string, unknown>`
|
|
504
|
+
* The branch handler reads it with the matching type.
|
|
505
|
+
*/
|
|
506
|
+
arguments?: Record<string, unknown> | string;
|
|
368
507
|
annotations?: Array<GoogleInteractionsAnnotation>;
|
|
369
508
|
call_id?: string;
|
|
370
509
|
result?: unknown;
|
|
@@ -372,6 +511,7 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
372
511
|
data?: string;
|
|
373
512
|
mime_type?: string;
|
|
374
513
|
uri?: string;
|
|
514
|
+
name?: string;
|
|
375
515
|
}
|
|
376
516
|
| undefined;
|
|
377
517
|
|
|
@@ -386,7 +526,8 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
386
526
|
}
|
|
387
527
|
} else if (
|
|
388
528
|
open.kind === 'text' &&
|
|
389
|
-
delta?.type === 'text_annotation'
|
|
529
|
+
(delta?.type === 'text_annotation' ||
|
|
530
|
+
delta?.type === 'text_annotation_delta')
|
|
390
531
|
) {
|
|
391
532
|
const sources = annotationsToSources({
|
|
392
533
|
annotations: delta.annotations,
|
|
@@ -400,14 +541,6 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
400
541
|
controller.enqueue(source);
|
|
401
542
|
}
|
|
402
543
|
} 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
544
|
if (delta.data != null) open.data = delta.data;
|
|
412
545
|
if (delta.mime_type != null) open.mimeType = delta.mime_type;
|
|
413
546
|
if (delta.uri != null) open.uri = delta.uri;
|
|
@@ -429,47 +562,44 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
429
562
|
}
|
|
430
563
|
} else if (
|
|
431
564
|
open.kind === 'function_call' &&
|
|
432
|
-
delta?.type === '
|
|
565
|
+
delta?.type === 'arguments_delta'
|
|
433
566
|
) {
|
|
434
567
|
/*
|
|
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.
|
|
568
|
+
* Partial JSON arguments arrive as `arguments_delta` events.
|
|
569
|
+
* The partial JSON string lives in `delta.arguments` (a string,
|
|
570
|
+
* not the parsed object — the `arguments_delta` name applies to
|
|
571
|
+
* the discriminator only). Append to the accumulator and surface
|
|
572
|
+
* each chunk as a `tool-input-delta`; the full arguments object
|
|
573
|
+
* is emitted at `step.stop`.
|
|
445
574
|
*/
|
|
575
|
+
const slice =
|
|
576
|
+
typeof delta.arguments === 'string' ? delta.arguments : '';
|
|
577
|
+
if (slice.length > 0) {
|
|
578
|
+
open.argumentsAccum += slice;
|
|
579
|
+
controller.enqueue({
|
|
580
|
+
type: 'tool-input-delta',
|
|
581
|
+
id: open.toolCallId,
|
|
582
|
+
delta: slice,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
446
585
|
if (delta.id != null) {
|
|
447
586
|
open.toolCallId = delta.id;
|
|
448
587
|
}
|
|
449
|
-
if (delta.name != null) {
|
|
450
|
-
open.toolName = delta.name;
|
|
451
|
-
}
|
|
452
|
-
if (delta.arguments != null) {
|
|
453
|
-
open.arguments = delta.arguments;
|
|
454
|
-
}
|
|
455
588
|
if (delta.signature != null) {
|
|
456
589
|
open.signature = delta.signature;
|
|
457
590
|
}
|
|
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
591
|
hasFunctionCall = true;
|
|
467
592
|
} else if (
|
|
468
593
|
open.kind === 'builtin_tool_call' &&
|
|
469
594
|
delta?.type === open.blockType
|
|
470
595
|
) {
|
|
471
596
|
if (delta.id != null) open.toolCallId = delta.id;
|
|
472
|
-
if (
|
|
597
|
+
if (
|
|
598
|
+
delta.arguments != null &&
|
|
599
|
+
typeof delta.arguments === 'object'
|
|
600
|
+
) {
|
|
601
|
+
open.arguments = delta.arguments;
|
|
602
|
+
}
|
|
473
603
|
if (
|
|
474
604
|
delta.name != null &&
|
|
475
605
|
open.blockType === 'mcp_server_tool_call'
|
|
@@ -493,10 +623,10 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
493
623
|
break;
|
|
494
624
|
}
|
|
495
625
|
|
|
496
|
-
case '
|
|
626
|
+
case 'step.stop': {
|
|
497
627
|
const event = value as Extract<
|
|
498
628
|
GoogleInteractionsEvent,
|
|
499
|
-
{ event_type: '
|
|
629
|
+
{ event_type: 'step.stop' }
|
|
500
630
|
>;
|
|
501
631
|
const open = openBlocks.get(event.index);
|
|
502
632
|
if (open == null) break;
|
|
@@ -555,20 +685,8 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
555
685
|
});
|
|
556
686
|
}
|
|
557
687
|
} else if (open.kind === 'function_call') {
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
if (!open.startEmitted) {
|
|
561
|
-
controller.enqueue({
|
|
562
|
-
type: 'tool-input-start',
|
|
563
|
-
id: open.toolCallId,
|
|
564
|
-
toolName,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
controller.enqueue({
|
|
568
|
-
type: 'tool-input-delta',
|
|
569
|
-
id: open.toolCallId,
|
|
570
|
-
delta: argsJson,
|
|
571
|
-
});
|
|
688
|
+
const accumulated =
|
|
689
|
+
open.argumentsAccum.length > 0 ? open.argumentsAccum : '{}';
|
|
572
690
|
controller.enqueue({
|
|
573
691
|
type: 'tool-input-end',
|
|
574
692
|
id: open.toolCallId,
|
|
@@ -581,8 +699,8 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
581
699
|
controller.enqueue({
|
|
582
700
|
type: 'tool-call',
|
|
583
701
|
toolCallId: open.toolCallId,
|
|
584
|
-
toolName,
|
|
585
|
-
input:
|
|
702
|
+
toolName: open.toolName,
|
|
703
|
+
input: accumulated,
|
|
586
704
|
...(providerMetadata ? { providerMetadata } : {}),
|
|
587
705
|
});
|
|
588
706
|
} else if (open.kind === 'builtin_tool_call' && !open.callEmitted) {
|
|
@@ -625,19 +743,32 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
625
743
|
break;
|
|
626
744
|
}
|
|
627
745
|
|
|
628
|
-
case 'interaction.status_update':
|
|
746
|
+
case 'interaction.status_update':
|
|
747
|
+
case 'interaction.in_progress':
|
|
748
|
+
case 'interaction.requires_action': {
|
|
629
749
|
const event = value as Extract<
|
|
630
750
|
GoogleInteractionsEvent,
|
|
631
|
-
{
|
|
751
|
+
{
|
|
752
|
+
event_type:
|
|
753
|
+
| 'interaction.status_update'
|
|
754
|
+
| 'interaction.in_progress'
|
|
755
|
+
| 'interaction.requires_action';
|
|
756
|
+
}
|
|
632
757
|
>;
|
|
633
|
-
|
|
758
|
+
if (event.status != null) {
|
|
759
|
+
finishStatus = event.status;
|
|
760
|
+
} else if (eventType === 'interaction.requires_action') {
|
|
761
|
+
finishStatus = 'requires_action';
|
|
762
|
+
} else {
|
|
763
|
+
finishStatus = 'in_progress';
|
|
764
|
+
}
|
|
634
765
|
break;
|
|
635
766
|
}
|
|
636
767
|
|
|
637
|
-
case 'interaction.
|
|
768
|
+
case 'interaction.completed': {
|
|
638
769
|
const event = value as Extract<
|
|
639
770
|
GoogleInteractionsEvent,
|
|
640
|
-
{ event_type: 'interaction.
|
|
771
|
+
{ event_type: 'interaction.completed' }
|
|
641
772
|
>;
|
|
642
773
|
const interaction = event.interaction as {
|
|
643
774
|
id?: string;
|
|
@@ -656,7 +787,7 @@ export function buildGoogleInteractionsStreamTransform({
|
|
|
656
787
|
}
|
|
657
788
|
/*
|
|
658
789
|
* The Interactions API surfaces the applied service tier on
|
|
659
|
-
* `interaction.
|
|
790
|
+
* `interaction.completed.interaction.service_tier` (NOT on the
|
|
660
791
|
* `x-gemini-service-tier` HTTP header that `:generateContent`
|
|
661
792
|
* uses). Body wins over header fallback.
|
|
662
793
|
*/
|