@veryfront/ext-llm-google 0.1.1185 → 0.1.1189

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.
@@ -1,4 +1,24 @@
1
- import { parseSseChunk, readRecord, stringifyJsonValue, } from "veryfront/provider/shared";
1
+ import { mergeUsage, parseFinalSseChunk, parseSseChunk, ProviderRequestError, readRecord, stringifyJsonValue, } from "veryfront/provider/shared";
2
+ import { createGoogleToolCallCorrelationRegistry, GOOGLE_CODE_EXECUTION_TOOL_NAME, googleCodeExecutionInput, googleCodeExecutionOutput, readGoogleCodeExecutionResult, readGoogleExecutableCode, readGooglePartDataField, } from "./google-content-parts.js";
3
+ import { mergeGoogleGroundingMetadata } from "./google-grounding-metadata.js";
4
+ import { createGoogleProviderMetadata, MAX_GOOGLE_PROVIDER_METADATA_BYTES, readGoogleThoughtSignature, } from "./google-thought-signatures.js";
5
+ export const MAX_GOOGLE_SSE_CHUNK_BYTES = 8 * 1024 * 1024;
6
+ export const MAX_GOOGLE_SSE_BUFFER_CODE_UNITS = 8 * 1024 * 1024;
7
+ export const MAX_GOOGLE_RETAINED_STATE_BYTES = MAX_GOOGLE_PROVIDER_METADATA_BYTES;
8
+ export const MAX_GOOGLE_RETAINED_STATE_ITEMS = 8_192;
9
+ const GOOGLE_RETAINED_STATE_ENCODER = new TextEncoder();
10
+ const GOOGLE_CONTENT_FILTER_FINISH_REASONS = new Set([
11
+ "SAFETY",
12
+ "RECITATION",
13
+ "LANGUAGE",
14
+ "BLOCKLIST",
15
+ "PROHIBITED_CONTENT",
16
+ "SPII",
17
+ "IMAGE_SAFETY",
18
+ "IMAGE_PROHIBITED_CONTENT",
19
+ "IMAGE_RECITATION",
20
+ "ESCALATION",
21
+ ]);
2
22
  export function normalizeGoogleFinishReason(raw) {
3
23
  if (typeof raw !== "string") {
4
24
  return null;
@@ -8,10 +28,10 @@ export function normalizeGoogleFinishReason(raw) {
8
28
  return { unified: "stop", raw };
9
29
  case "MAX_TOKENS":
10
30
  return { unified: "length", raw };
11
- case "SAFETY":
12
- case "RECITATION":
13
- return { unified: "content-filter", raw };
14
31
  default:
32
+ if (GOOGLE_CONTENT_FILTER_FINISH_REASONS.has(raw)) {
33
+ return { unified: "content-filter", raw };
34
+ }
15
35
  return raw.toLowerCase();
16
36
  }
17
37
  }
@@ -21,20 +41,25 @@ export function extractGoogleUsage(payload) {
21
41
  if (!usage) {
22
42
  return undefined;
23
43
  }
24
- const inputTokens = usage.promptTokenCount;
25
- const outputTokens = usage.candidatesTokenCount;
26
- const totalTokens = usage.totalTokenCount;
27
- const cachedContentTokenCount = usage.cachedContentTokenCount;
28
- const thoughtsTokenCount = usage.thoughtsTokenCount;
29
- return {
30
- inputTokens: typeof inputTokens === "number" ? inputTokens : undefined,
31
- outputTokens: typeof outputTokens === "number" ? outputTokens : undefined,
32
- totalTokens: typeof totalTokens === "number" ? totalTokens : undefined,
33
- ...(typeof cachedContentTokenCount === "number"
34
- ? { cacheReadInputTokens: cachedContentTokenCount }
35
- : {}),
36
- ...(typeof thoughtsTokenCount === "number" ? { reasoningTokens: thoughtsTokenCount } : {}),
44
+ const readTokenCount = (value) => typeof value === "number" &&
45
+ Number.isFinite(value) &&
46
+ Number.isSafeInteger(value) &&
47
+ value >= 0
48
+ ? value
49
+ : undefined;
50
+ const inputTokens = readTokenCount(usage.promptTokenCount);
51
+ const outputTokens = readTokenCount(usage.candidatesTokenCount);
52
+ const totalTokens = readTokenCount(usage.totalTokenCount);
53
+ const cacheReadInputTokens = readTokenCount(usage.cachedContentTokenCount);
54
+ const reasoningTokens = readTokenCount(usage.thoughtsTokenCount);
55
+ const normalized = {
56
+ inputTokens,
57
+ outputTokens,
58
+ totalTokens,
59
+ ...(cacheReadInputTokens !== undefined ? { cacheReadInputTokens } : {}),
60
+ ...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
37
61
  };
62
+ return Object.values(normalized).some((value) => value !== undefined) ? normalized : undefined;
38
63
  }
39
64
  export function extractFirstGoogleCandidate(payload) {
40
65
  const record = readRecord(payload);
@@ -56,66 +81,320 @@ export function extractGoogleCandidateParts(payload) {
56
81
  return record ? [record] : [];
57
82
  });
58
83
  }
59
- export async function* streamGoogleCompatibleParts(stream) {
60
- const decoder = new TextDecoder();
84
+ function invalidGoogleStream(context, issue) {
85
+ return new ProviderRequestError({
86
+ provider: "google",
87
+ status: 200,
88
+ message: `${context.providerLabel ?? "google"} request failed: invalid successful stream (${issue})`,
89
+ retryable: false,
90
+ });
91
+ }
92
+ function mergeGoogleUsage(current, payload) {
93
+ const merged = mergeUsage(current, extractGoogleUsage(payload));
94
+ return merged && Object.keys(merged).length > 0 ? merged : undefined;
95
+ }
96
+ function isContentFilterFinishReason(value) {
97
+ return typeof value === "object" && value?.unified === "content-filter";
98
+ }
99
+ export async function* streamGoogleCompatibleParts(stream, context = {}) {
100
+ const decoder = new TextDecoder("utf-8", { fatal: true });
61
101
  let buffer = "";
62
- const seenToolCalls = new Set();
102
+ const seenToolCalls = new Map();
103
+ const seenCodeExecutions = new Map();
104
+ const seenCodeExecutionResults = new Map();
105
+ const seenAnonymousCodeExecutions = new Map();
106
+ const seenAnonymousCodeExecutionResults = new Map();
107
+ const pendingAnonymousCodeExecutions = [];
108
+ const toolCallRegistry = createGoogleToolCallCorrelationRegistry();
109
+ const rawAssistantParts = [];
110
+ let retainedStateBytes = 0;
111
+ let retainedStateItems = 0;
63
112
  let reasoningId = null;
113
+ let reasoningSignature;
64
114
  let reasoningIndex = 0;
65
115
  let finishReason = null;
66
116
  let usage;
67
- for await (const chunk of stream) {
68
- buffer += decoder.decode(chunk, { stream: true });
69
- const parsed = parseSseChunk(buffer);
70
- buffer = parsed.remainder;
71
- for (const event of parsed.events) {
72
- if (event === "[DONE]") {
73
- continue;
117
+ let groundingMetadata;
118
+ let sawCandidateEnvelope = false;
119
+ let sawOutput = false;
120
+ let sawFinishReason = false;
121
+ let sawDone = false;
122
+ const reserveRetainedItem = (issue) => {
123
+ if (retainedStateItems >= MAX_GOOGLE_RETAINED_STATE_ITEMS) {
124
+ throw invalidGoogleStream(context, `retained state exceeded ${MAX_GOOGLE_RETAINED_STATE_ITEMS} items (${issue})`);
125
+ }
126
+ retainedStateItems++;
127
+ };
128
+ const reserveRetainedBytes = (value, issue) => {
129
+ const bytes = GOOGLE_RETAINED_STATE_ENCODER.encode(value).byteLength;
130
+ if (bytes > MAX_GOOGLE_RETAINED_STATE_BYTES - retainedStateBytes) {
131
+ throw invalidGoogleStream(context, `retained state exceeded ${MAX_GOOGLE_RETAINED_STATE_BYTES} UTF-8 bytes (${issue})`);
132
+ }
133
+ retainedStateBytes += bytes;
134
+ };
135
+ const retainRawAssistantPart = (part) => {
136
+ let serialized;
137
+ try {
138
+ serialized = stringifyJsonValue(part);
139
+ }
140
+ catch {
141
+ throw invalidGoogleStream(context, "candidate part could not be retained safely");
142
+ }
143
+ reserveRetainedItem("raw candidate part");
144
+ reserveRetainedBytes(serialized, "raw candidate part");
145
+ return rawAssistantParts.push(part) - 1;
146
+ };
147
+ const reserveCorrelation = (issue, ...values) => {
148
+ reserveRetainedItem(issue);
149
+ for (const value of values)
150
+ reserveRetainedBytes(value, issue);
151
+ };
152
+ const mergeReplaySignature = (prior, thoughtSignature, changedIssue) => {
153
+ if (thoughtSignature !== undefined &&
154
+ prior.thoughtSignature !== undefined &&
155
+ thoughtSignature !== prior.thoughtSignature) {
156
+ throw invalidGoogleStream(context, changedIssue);
157
+ }
158
+ if (thoughtSignature !== undefined && prior.thoughtSignature === undefined) {
159
+ const previousRawPart = rawAssistantParts[prior.rawPartIndex];
160
+ if (!previousRawPart) {
161
+ throw invalidGoogleStream(context, "candidate replay history was inconsistent");
74
162
  }
75
- usage = extractGoogleUsage(event) ?? usage;
76
- const candidate = extractFirstGoogleCandidate(event);
77
- const normalizedFinishReason = normalizeGoogleFinishReason(candidate?.finishReason);
78
- if (normalizedFinishReason) {
79
- finishReason = normalizedFinishReason;
80
- }
81
- for (const [index, part] of extractGoogleCandidateParts(event).entries()) {
82
- const isThought = part.thought === true;
83
- if (isThought && typeof part.text === "string" && part.text.length > 0) {
84
- if (!reasoningId) {
85
- reasoningId = `reasoning-${reasoningIndex++}`;
86
- yield {
87
- type: "reasoning-start",
88
- id: reasoningId,
89
- };
90
- }
91
- yield {
92
- type: "reasoning-delta",
93
- id: reasoningId,
94
- delta: part.text,
95
- };
96
- continue;
97
- }
98
- if (reasoningId) {
163
+ reserveRetainedBytes(thoughtSignature, "replayed thought signature");
164
+ previousRawPart.thoughtSignature = thoughtSignature;
165
+ prior.thoughtSignature = thoughtSignature;
166
+ }
167
+ };
168
+ function* processEvent(event) {
169
+ if (event === "[DONE]") {
170
+ if (sawDone) {
171
+ throw invalidGoogleStream(context, "stream contained duplicate [DONE] markers");
172
+ }
173
+ sawDone = true;
174
+ return;
175
+ }
176
+ if (sawDone) {
177
+ throw invalidGoogleStream(context, "event appeared after [DONE]");
178
+ }
179
+ const record = readRecord(event);
180
+ if (!record) {
181
+ throw invalidGoogleStream(context, "event was not an object");
182
+ }
183
+ const usageRecord = readRecord(record.usageMetadata);
184
+ usage = mergeGoogleUsage(usage, record);
185
+ const promptFeedback = readRecord(record.promptFeedback);
186
+ const promptBlockReason = promptFeedback?.blockReason;
187
+ if (promptBlockReason !== undefined &&
188
+ (typeof promptBlockReason !== "string" || promptBlockReason.length === 0)) {
189
+ throw invalidGoogleStream(context, "prompt block reason was malformed");
190
+ }
191
+ if (typeof promptBlockReason === "string") {
192
+ if (sawFinishReason || sawDone) {
193
+ throw invalidGoogleStream(context, "stream contained duplicate terminal reasons");
194
+ }
195
+ finishReason = { unified: "content-filter", raw: promptBlockReason };
196
+ sawCandidateEnvelope = true;
197
+ sawFinishReason = true;
198
+ }
199
+ if (record.candidates === undefined) {
200
+ if (!usageRecord && typeof promptBlockReason !== "string") {
201
+ throw invalidGoogleStream(context, "event had neither candidates nor usage");
202
+ }
203
+ return;
204
+ }
205
+ if (!Array.isArray(record.candidates)) {
206
+ throw invalidGoogleStream(context, "candidates was not an array");
207
+ }
208
+ if (record.candidates.length === 0) {
209
+ if (!usageRecord && typeof promptBlockReason !== "string") {
210
+ throw invalidGoogleStream(context, "empty candidates event had no usage or block reason");
211
+ }
212
+ return;
213
+ }
214
+ if (record.candidates.length !== 1) {
215
+ throw invalidGoogleStream(context, "event contained multiple candidates");
216
+ }
217
+ if (typeof promptBlockReason === "string") {
218
+ throw invalidGoogleStream(context, "blocked event also contained a candidate");
219
+ }
220
+ const candidate = readRecord(record.candidates[0]);
221
+ if (!candidate) {
222
+ throw invalidGoogleStream(context, "first candidate was not an object");
223
+ }
224
+ if (sawFinishReason || sawDone) {
225
+ const issue = typeof candidate.finishReason === "string"
226
+ ? "stream contained duplicate terminal reasons"
227
+ : "candidate appeared after a terminal marker";
228
+ throw invalidGoogleStream(context, issue);
229
+ }
230
+ sawCandidateEnvelope = true;
231
+ if (candidate.groundingMetadata !== undefined) {
232
+ try {
233
+ groundingMetadata = mergeGoogleGroundingMetadata(groundingMetadata, candidate.groundingMetadata);
234
+ }
235
+ catch {
236
+ throw invalidGoogleStream(context, "candidate grounding metadata was malformed");
237
+ }
238
+ }
239
+ if (candidate.finishReason !== undefined &&
240
+ (typeof candidate.finishReason !== "string" || candidate.finishReason.length === 0)) {
241
+ throw invalidGoogleStream(context, "candidate finish reason was malformed");
242
+ }
243
+ const candidateFinishReason = typeof candidate.finishReason === "string"
244
+ ? candidate.finishReason
245
+ : undefined;
246
+ let parts = [];
247
+ if (candidate.content !== undefined) {
248
+ const content = readRecord(candidate.content);
249
+ if (!content) {
250
+ throw invalidGoogleStream(context, "candidate content was not an object");
251
+ }
252
+ if (content.parts !== undefined && !Array.isArray(content.parts)) {
253
+ throw invalidGoogleStream(context, "candidate content parts was not an array");
254
+ }
255
+ parts = Array.isArray(content.parts) ? content.parts : [];
256
+ }
257
+ else if (candidateFinishReason === undefined) {
258
+ throw invalidGoogleStream(context, "candidate had neither content nor a finish reason");
259
+ }
260
+ for (const [candidatePartIndex, rawPart] of parts.entries()) {
261
+ const part = readRecord(rawPart);
262
+ if (!part) {
263
+ throw invalidGoogleStream(context, "candidate content part was not an object");
264
+ }
265
+ if (part.thought !== undefined && typeof part.thought !== "boolean") {
266
+ throw invalidGoogleStream(context, "candidate thought marker was malformed");
267
+ }
268
+ let thoughtSignature;
269
+ try {
270
+ thoughtSignature = readGoogleThoughtSignature(part);
271
+ }
272
+ catch {
273
+ throw invalidGoogleStream(context, "candidate thought signature was malformed");
274
+ }
275
+ let dataField;
276
+ try {
277
+ dataField = readGooglePartDataField(part);
278
+ }
279
+ catch (error) {
280
+ const issue = error instanceof Error &&
281
+ (error.message.includes("unsupported") || error.message.includes("unknown"))
282
+ ? "candidate part contained an unsupported data field"
283
+ : "candidate part contained multiple data fields";
284
+ throw invalidGoogleStream(context, issue);
285
+ }
286
+ const partText = dataField === "text" && typeof part.text === "string"
287
+ ? part.text
288
+ : undefined;
289
+ if (dataField === "text" && partText === undefined) {
290
+ throw invalidGoogleStream(context, "candidate text part was malformed");
291
+ }
292
+ if (dataField === "text") {
293
+ retainRawAssistantPart(part);
294
+ }
295
+ if (partText !== undefined && part.thought === true) {
296
+ if (reasoningId &&
297
+ thoughtSignature !== undefined &&
298
+ reasoningSignature !== undefined &&
299
+ thoughtSignature !== reasoningSignature) {
99
300
  yield {
100
301
  type: "reasoning-end",
101
302
  id: reasoningId,
303
+ signature: reasoningSignature,
102
304
  };
103
305
  reasoningId = null;
306
+ reasoningSignature = undefined;
104
307
  }
105
- if (typeof part.text === "string" && part.text.length > 0) {
106
- yield { type: "text-delta", delta: part.text };
107
- continue;
308
+ if (!reasoningId && (partText.length > 0 || thoughtSignature !== undefined)) {
309
+ reasoningId = `reasoning-${reasoningIndex++}`;
310
+ yield { type: "reasoning-start", id: reasoningId };
311
+ }
312
+ if (thoughtSignature !== undefined) {
313
+ reasoningSignature = thoughtSignature;
314
+ }
315
+ if (reasoningId) {
316
+ sawOutput = true;
317
+ if (partText.length > 0) {
318
+ yield {
319
+ type: "reasoning-delta",
320
+ id: reasoningId,
321
+ delta: partText,
322
+ };
323
+ }
324
+ }
325
+ continue;
326
+ }
327
+ if (reasoningId) {
328
+ yield {
329
+ type: "reasoning-end",
330
+ id: reasoningId,
331
+ ...(reasoningSignature !== undefined ? { signature: reasoningSignature } : {}),
332
+ };
333
+ reasoningId = null;
334
+ reasoningSignature = undefined;
335
+ }
336
+ if (partText !== undefined) {
337
+ if (partText.length > 0) {
338
+ sawOutput = true;
339
+ yield { type: "text-delta", delta: partText };
108
340
  }
341
+ continue;
342
+ }
343
+ if (dataField === "functionCall") {
109
344
  const functionCall = readRecord(part.functionCall);
110
- if (typeof functionCall?.name !== "string") {
111
- continue;
345
+ if (!functionCall ||
346
+ typeof functionCall.name !== "string" ||
347
+ functionCall.name.length === 0) {
348
+ throw invalidGoogleStream(context, "candidate function call was malformed");
349
+ }
350
+ if (functionCall.id !== undefined &&
351
+ (typeof functionCall.id !== "string" || functionCall.id.length === 0)) {
352
+ throw invalidGoogleStream(context, "candidate function call id was malformed");
353
+ }
354
+ // Gemini omits `args` entirely for zero-parameter tool calls.
355
+ const functionCallArgs = functionCall.args === undefined
356
+ ? {}
357
+ : readRecord(functionCall.args);
358
+ if (!functionCallArgs) {
359
+ throw invalidGoogleStream(context, "candidate function call arguments were not an object");
360
+ }
361
+ let serializedInput;
362
+ try {
363
+ serializedInput = stringifyJsonValue(functionCallArgs);
112
364
  }
113
- const toolCallId = typeof functionCall.id === "string" ? functionCall.id : `tool-${index}`;
114
- if (seenToolCalls.has(toolCallId)) {
365
+ catch {
366
+ throw invalidGoogleStream(context, "candidate function call arguments were malformed");
367
+ }
368
+ const providerId = typeof functionCall.id === "string" ? functionCall.id : undefined;
369
+ const rawPartIndex = rawAssistantParts.length;
370
+ // Gemini may replay an anonymous function call in later candidate
371
+ // envelopes. Its candidate-part position is stable across those
372
+ // cumulative snapshots; our growing retained-output index is not.
373
+ const deduplicationKey = providerId ?? `anonymous-${candidatePartIndex}`;
374
+ const callShape = `${functionCall.name}\u0000${serializedInput}`;
375
+ const previousToolCall = seenToolCalls.get(deduplicationKey);
376
+ if (previousToolCall !== undefined) {
377
+ if (previousToolCall.callShape !== callShape) {
378
+ throw invalidGoogleStream(context, "candidate function call changed after emission");
379
+ }
380
+ mergeReplaySignature(previousToolCall, thoughtSignature, "candidate function call thought signature changed after emission");
115
381
  continue;
116
382
  }
117
- const serializedInput = stringifyJsonValue(functionCall.args ?? {});
118
- seenToolCalls.add(toolCallId);
383
+ let toolCallId;
384
+ try {
385
+ toolCallId = toolCallRegistry.registerFunctionCall(rawPartIndex, providerId);
386
+ }
387
+ catch {
388
+ throw invalidGoogleStream(context, "candidate tool call id was duplicated");
389
+ }
390
+ retainRawAssistantPart(part);
391
+ reserveCorrelation("function-call correlation", deduplicationKey, callShape, ...(thoughtSignature === undefined ? [] : [thoughtSignature]));
392
+ seenToolCalls.set(deduplicationKey, {
393
+ callShape,
394
+ rawPartIndex,
395
+ ...(thoughtSignature !== undefined ? { thoughtSignature } : {}),
396
+ });
397
+ sawOutput = true;
119
398
  yield {
120
399
  type: "tool-input-start",
121
400
  id: toolCallId,
@@ -132,27 +411,273 @@ export async function* streamGoogleCompatibleParts(stream) {
132
411
  toolName: functionCall.name,
133
412
  input: serializedInput,
134
413
  };
414
+ continue;
135
415
  }
416
+ if (dataField === "executableCode") {
417
+ let executableCode;
418
+ try {
419
+ executableCode = readGoogleExecutableCode(part.executableCode);
420
+ }
421
+ catch {
422
+ throw invalidGoogleStream(context, "candidate executable code was malformed");
423
+ }
424
+ const serializedInput = stringifyJsonValue(googleCodeExecutionInput(executableCode));
425
+ const callShape = serializedInput;
426
+ let toolCallId;
427
+ if (executableCode.providerId !== undefined) {
428
+ const prior = seenCodeExecutions.get(executableCode.providerId);
429
+ if (prior) {
430
+ if (prior.callShape !== callShape) {
431
+ throw invalidGoogleStream(context, "candidate executable code changed after emission");
432
+ }
433
+ mergeReplaySignature(prior, thoughtSignature, "candidate executable code thought signature changed after emission");
434
+ continue;
435
+ }
436
+ try {
437
+ toolCallId = toolCallRegistry.registerCodeExecution(executableCode.providerId);
438
+ }
439
+ catch {
440
+ throw invalidGoogleStream(context, "candidate executable code id was duplicated");
441
+ }
442
+ }
443
+ else {
444
+ const prior = seenAnonymousCodeExecutions.get(candidatePartIndex);
445
+ if (prior) {
446
+ if (prior.callShape === callShape) {
447
+ mergeReplaySignature(prior, thoughtSignature, "candidate executable code thought signature changed after emission");
448
+ continue;
449
+ }
450
+ if (!prior.settled) {
451
+ throw invalidGoogleStream(context, "candidate executable code changed after emission");
452
+ }
453
+ }
454
+ try {
455
+ toolCallId = toolCallRegistry.registerCodeExecution(undefined);
456
+ }
457
+ catch {
458
+ throw invalidGoogleStream(context, "candidate executable code id was duplicated");
459
+ }
460
+ }
461
+ const rawPartIndex = retainRawAssistantPart(part);
462
+ reserveCorrelation("code-execution correlation", callShape, ...(executableCode.providerId === undefined ? [] : [executableCode.providerId]), ...(thoughtSignature === undefined ? [] : [thoughtSignature]));
463
+ const pending = {
464
+ callShape,
465
+ rawPartIndex,
466
+ ...(thoughtSignature !== undefined ? { thoughtSignature } : {}),
467
+ settled: false,
468
+ };
469
+ if (executableCode.providerId === undefined) {
470
+ seenAnonymousCodeExecutions.set(candidatePartIndex, pending);
471
+ pendingAnonymousCodeExecutions.push(pending);
472
+ }
473
+ else {
474
+ seenCodeExecutions.set(executableCode.providerId, pending);
475
+ }
476
+ sawOutput = true;
477
+ yield {
478
+ type: "tool-input-start",
479
+ id: toolCallId,
480
+ toolName: GOOGLE_CODE_EXECUTION_TOOL_NAME,
481
+ providerExecuted: true,
482
+ };
483
+ yield {
484
+ type: "tool-input-delta",
485
+ id: toolCallId,
486
+ delta: serializedInput,
487
+ };
488
+ yield {
489
+ type: "tool-call",
490
+ toolCallId,
491
+ toolName: GOOGLE_CODE_EXECUTION_TOOL_NAME,
492
+ input: serializedInput,
493
+ providerExecuted: true,
494
+ };
495
+ continue;
496
+ }
497
+ let result;
498
+ try {
499
+ result = readGoogleCodeExecutionResult(part.codeExecutionResult);
500
+ }
501
+ catch {
502
+ throw invalidGoogleStream(context, "candidate code execution result was malformed");
503
+ }
504
+ const resultValue = googleCodeExecutionOutput(result);
505
+ const resultShape = stringifyJsonValue(resultValue);
506
+ let toolCallId;
507
+ if (result.providerId !== undefined) {
508
+ const priorResult = seenCodeExecutionResults.get(result.providerId);
509
+ if (priorResult) {
510
+ if (priorResult.resultShape !== resultShape) {
511
+ throw invalidGoogleStream(context, "candidate code execution result changed after emission");
512
+ }
513
+ mergeReplaySignature(priorResult, thoughtSignature, "candidate code execution result thought signature changed after emission");
514
+ continue;
515
+ }
516
+ try {
517
+ toolCallId = toolCallRegistry.resolveCodeExecutionResult(result.providerId);
518
+ }
519
+ catch {
520
+ throw invalidGoogleStream(context, "candidate code execution result did not match executable code");
521
+ }
522
+ }
523
+ else {
524
+ const pendingExecution = pendingAnonymousCodeExecutions[0];
525
+ if (!pendingExecution) {
526
+ const priorResult = seenAnonymousCodeExecutionResults.get(candidatePartIndex);
527
+ if (priorResult) {
528
+ if (priorResult.resultShape !== resultShape) {
529
+ throw invalidGoogleStream(context, "candidate code execution result changed after emission");
530
+ }
531
+ mergeReplaySignature(priorResult, thoughtSignature, "candidate code execution result thought signature changed after emission");
532
+ continue;
533
+ }
534
+ throw invalidGoogleStream(context, "candidate code execution result did not match executable code");
535
+ }
536
+ try {
537
+ toolCallId = toolCallRegistry.resolveCodeExecutionResult(undefined);
538
+ }
539
+ catch {
540
+ throw invalidGoogleStream(context, "candidate code execution result did not match executable code");
541
+ }
542
+ pendingExecution.settled = true;
543
+ pendingAnonymousCodeExecutions.shift();
544
+ }
545
+ const rawPartIndex = retainRawAssistantPart(part);
546
+ if (result.providerId !== undefined) {
547
+ reserveCorrelation("code-result correlation", result.providerId, resultShape, ...(thoughtSignature === undefined ? [] : [thoughtSignature]));
548
+ seenCodeExecutionResults.set(result.providerId, {
549
+ resultShape,
550
+ rawPartIndex,
551
+ ...(thoughtSignature !== undefined ? { thoughtSignature } : {}),
552
+ });
553
+ }
554
+ else {
555
+ const deduplicationKey = `anonymous-${candidatePartIndex}`;
556
+ reserveCorrelation("code-result correlation", deduplicationKey, resultShape, ...(thoughtSignature === undefined ? [] : [thoughtSignature]));
557
+ seenAnonymousCodeExecutionResults.set(candidatePartIndex, {
558
+ resultShape,
559
+ rawPartIndex,
560
+ ...(thoughtSignature !== undefined ? { thoughtSignature } : {}),
561
+ });
562
+ }
563
+ sawOutput = true;
564
+ yield result.isError
565
+ ? {
566
+ type: "tool-error",
567
+ toolCallId,
568
+ toolName: GOOGLE_CODE_EXECUTION_TOOL_NAME,
569
+ error: resultValue,
570
+ isError: true,
571
+ providerExecuted: true,
572
+ }
573
+ : {
574
+ type: "tool-result",
575
+ toolCallId,
576
+ toolName: GOOGLE_CODE_EXECUTION_TOOL_NAME,
577
+ result: resultValue,
578
+ providerExecuted: true,
579
+ };
580
+ }
581
+ if (candidateFinishReason !== undefined) {
582
+ if (sawFinishReason) {
583
+ throw invalidGoogleStream(context, "stream contained duplicate terminal reasons");
584
+ }
585
+ finishReason = normalizeGoogleFinishReason(candidateFinishReason);
586
+ sawFinishReason = true;
587
+ }
588
+ }
589
+ for await (const chunk of stream) {
590
+ if (!(chunk instanceof Uint8Array)) {
591
+ throw invalidGoogleStream(context, "stream chunk was not binary data");
592
+ }
593
+ if (chunk.byteLength > MAX_GOOGLE_SSE_CHUNK_BYTES) {
594
+ throw invalidGoogleStream(context, `SSE chunk exceeded ${MAX_GOOGLE_SSE_CHUNK_BYTES} bytes`);
595
+ }
596
+ let decodedChunk;
597
+ try {
598
+ decodedChunk = decoder.decode(chunk, { stream: true });
599
+ }
600
+ catch {
601
+ throw invalidGoogleStream(context, "stream contained invalid UTF-8");
136
602
  }
603
+ if (decodedChunk.length > MAX_GOOGLE_SSE_BUFFER_CODE_UNITS - buffer.length) {
604
+ throw invalidGoogleStream(context, `SSE buffer exceeded ${MAX_GOOGLE_SSE_BUFFER_CODE_UNITS} code units`);
605
+ }
606
+ buffer += decodedChunk;
607
+ let parsed;
608
+ try {
609
+ parsed = parseSseChunk(buffer);
610
+ }
611
+ catch {
612
+ throw invalidGoogleStream(context, "SSE event framing was malformed");
613
+ }
614
+ buffer = parsed.remainder;
615
+ for (const event of parsed.events) {
616
+ yield* processEvent(event);
617
+ }
618
+ }
619
+ let decodedTail;
620
+ try {
621
+ decodedTail = decoder.decode();
622
+ }
623
+ catch {
624
+ throw invalidGoogleStream(context, "stream contained invalid UTF-8");
137
625
  }
626
+ if (decodedTail.length > MAX_GOOGLE_SSE_BUFFER_CODE_UNITS - buffer.length) {
627
+ throw invalidGoogleStream(context, `SSE buffer exceeded ${MAX_GOOGLE_SSE_BUFFER_CODE_UNITS} code units`);
628
+ }
629
+ buffer += decodedTail;
138
630
  if (buffer.trim().length > 0) {
139
- const parsed = parseSseChunk(`${buffer}\n\n`);
631
+ let parsed;
632
+ try {
633
+ parsed = parseFinalSseChunk(buffer);
634
+ }
635
+ catch {
636
+ throw invalidGoogleStream(context, "trailing SSE event was malformed");
637
+ }
140
638
  for (const event of parsed.events) {
141
- if (event === "[DONE]") {
142
- continue;
143
- }
144
- usage = extractGoogleUsage(event) ?? usage;
639
+ yield* processEvent(event);
145
640
  }
146
641
  }
642
+ if (!sawCandidateEnvelope) {
643
+ throw invalidGoogleStream(context, "stream contained no candidate envelope");
644
+ }
645
+ if (!sawFinishReason && !sawDone) {
646
+ throw invalidGoogleStream(context, "stream ended before a terminal marker");
647
+ }
648
+ try {
649
+ toolCallRegistry.assertSettled();
650
+ }
651
+ catch {
652
+ throw invalidGoogleStream(context, "candidate executable code had no matching execution result");
653
+ }
654
+ const contentFiltered = isContentFilterFinishReason(finishReason);
655
+ if (!sawOutput && !contentFiltered) {
656
+ throw invalidGoogleStream(context, "stream contained no supported output content");
657
+ }
147
658
  if (reasoningId) {
148
659
  yield {
149
660
  type: "reasoning-end",
150
661
  id: reasoningId,
662
+ ...(reasoningSignature !== undefined ? { signature: reasoningSignature } : {}),
151
663
  };
152
664
  }
665
+ let providerMetadata;
666
+ try {
667
+ providerMetadata = createGoogleProviderMetadata(rawAssistantParts, groundingMetadata);
668
+ }
669
+ catch {
670
+ // The stream accounts for retained raw parts and correlation state under
671
+ // the same byte ceiling as replay metadata. The final owned snapshot also
672
+ // includes JSON container overhead and grounding metadata, so translate
673
+ // any remaining snapshot-boundary failure into the stream's typed error
674
+ // contract instead of leaking an implementation TypeError.
675
+ throw invalidGoogleStream(context, "provider metadata could not be retained safely");
676
+ }
153
677
  yield {
154
678
  type: "finish",
155
679
  finishReason,
156
680
  ...(usage ? { usage } : {}),
681
+ ...(providerMetadata ? { providerMetadata } : {}),
157
682
  };
158
683
  }