@copilotkit/aimock 1.27.3 → 1.29.0

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.
Files changed (56) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +36 -0
  4. package/dist/agui-types.d.ts.map +1 -1
  5. package/dist/config-loader.d.ts.map +1 -1
  6. package/dist/gemini.cjs +18 -8
  7. package/dist/gemini.cjs.map +1 -1
  8. package/dist/gemini.d.cts.map +1 -1
  9. package/dist/gemini.d.ts.map +1 -1
  10. package/dist/gemini.js +18 -8
  11. package/dist/gemini.js.map +1 -1
  12. package/dist/harmony.cjs +419 -0
  13. package/dist/harmony.cjs.map +1 -0
  14. package/dist/harmony.js +417 -0
  15. package/dist/harmony.js.map +1 -0
  16. package/dist/journal.cjs +10 -0
  17. package/dist/journal.cjs.map +1 -1
  18. package/dist/journal.d.cts +8 -0
  19. package/dist/journal.d.ts +8 -0
  20. package/dist/journal.js +10 -0
  21. package/dist/journal.js.map +1 -1
  22. package/dist/recorder.cjs +57 -13
  23. package/dist/recorder.cjs.map +1 -1
  24. package/dist/recorder.d.cts +6 -1
  25. package/dist/recorder.d.cts.map +1 -1
  26. package/dist/recorder.d.ts +6 -1
  27. package/dist/recorder.d.ts.map +1 -1
  28. package/dist/recorder.js +57 -13
  29. package/dist/recorder.js.map +1 -1
  30. package/dist/server.cjs +40 -10
  31. package/dist/server.cjs.map +1 -1
  32. package/dist/server.d.cts.map +1 -1
  33. package/dist/server.d.ts.map +1 -1
  34. package/dist/server.js +40 -10
  35. package/dist/server.js.map +1 -1
  36. package/dist/stream-collapse.cjs +219 -57
  37. package/dist/stream-collapse.cjs.map +1 -1
  38. package/dist/stream-collapse.d.cts +16 -0
  39. package/dist/stream-collapse.d.cts.map +1 -1
  40. package/dist/stream-collapse.d.ts +16 -0
  41. package/dist/stream-collapse.d.ts.map +1 -1
  42. package/dist/stream-collapse.js +219 -57
  43. package/dist/stream-collapse.js.map +1 -1
  44. package/dist/types.d.cts +9 -0
  45. package/dist/types.d.cts.map +1 -1
  46. package/dist/types.d.ts +9 -0
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/vector-types.d.cts.map +1 -1
  49. package/dist/vector-types.d.ts.map +1 -1
  50. package/dist/video.cjs +17 -1
  51. package/dist/video.cjs.map +1 -1
  52. package/dist/video.d.cts.map +1 -1
  53. package/dist/video.d.ts.map +1 -1
  54. package/dist/video.js +17 -1
  55. package/dist/video.js.map +1 -1
  56. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { isHarmonyContent, parseHarmonyContent } from "./harmony.js";
1
2
  import { crc32 } from "node:zlib";
2
3
 
3
4
  //#region src/stream-collapse.ts
@@ -10,6 +11,60 @@ import { crc32 } from "node:zlib";
10
11
  * text followed by tool calls.
11
12
  */
12
13
  /**
14
+ * Slice the first `max` UTF-16 code units of `s` for a diagnostic sample,
15
+ * trimming a trailing lone high-surrogate so the resulting sample never ends on
16
+ * a lone high surrogate (i.e. never mid-surrogate-pair).
17
+ */
18
+ function surrogateSafeSlice(s, max) {
19
+ let out = s.slice(0, max);
20
+ if (out.length > 0) {
21
+ const last = out.charCodeAt(out.length - 1);
22
+ if (last >= 55296 && last <= 56319) out = out.slice(0, -1);
23
+ }
24
+ return out;
25
+ }
26
+ /**
27
+ * Split a raw SSE body into per-event blocks.
28
+ *
29
+ * Events are delimited by a blank line. Real HTTP/SSE transports use CRLF
30
+ * (`\r\n`) line endings, so the inter-event delimiter is `\r\n\r\n` (which
31
+ * contains no `\n\n` substring) and each line ends with a trailing `\r`.
32
+ * Splitting on `/\r?\n\r?\n/` handles LF, CRLF, and mixed streams; per-line
33
+ * `\r` trimming happens in {@link splitSSELines}. Blank blocks are dropped.
34
+ */
35
+ function splitSSEEvents(body) {
36
+ return body.split(/\r?\n\r?\n/).filter((block) => block.trim().length > 0);
37
+ }
38
+ /**
39
+ * Split a single SSE event block into its lines, trimming a trailing `\r` so
40
+ * CRLF streams parse identically to LF streams.
41
+ */
42
+ function splitSSELines(block) {
43
+ return block.split("\n").map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
44
+ }
45
+ /**
46
+ * Extract the SSE `data` field from a single event block's lines.
47
+ *
48
+ * Per the SSE spec a single event may carry MULTIPLE `data:` lines; the field
49
+ * value is every data line's content joined with "\n". Collecting only the
50
+ * first `data:` line (e.g. via `.find`) corrupts payloads that a server split
51
+ * across lines. Callers MUST pass lines produced by {@link splitSSELines} so
52
+ * any trailing `\r` is already stripped. Returns the joined payload (with the
53
+ * leading "data:" prefix and one optional leading space stripped per line), or
54
+ * `undefined` when the block contains no `data:` line.
55
+ */
56
+ function extractSSEData(lines) {
57
+ const dataParts = [];
58
+ for (const line of lines) {
59
+ if (!line.startsWith("data:")) continue;
60
+ let part = line.slice(5);
61
+ if (part.startsWith(" ")) part = part.slice(1);
62
+ dataParts.push(part);
63
+ }
64
+ if (dataParts.length === 0) return void 0;
65
+ return dataParts.join("\n");
66
+ }
67
+ /**
13
68
  * Collapse OpenAI Chat Completions SSE stream into a single response.
14
69
  *
15
70
  * Format:
@@ -17,24 +72,28 @@ import { crc32 } from "node:zlib";
17
72
  * data: [DONE]\n\n
18
73
  */
19
74
  function collapseOpenAISSE(body) {
20
- const lines = body.split("\n\n").filter((l) => l.trim().length > 0);
75
+ const lines = splitSSEEvents(body);
21
76
  let content = "";
22
77
  let reasoning = "";
23
78
  const webSearchQueries = [];
24
79
  let droppedChunks = 0;
25
80
  let firstDroppedSample;
81
+ let harmonyUnparsed = false;
82
+ let harmonyNote;
26
83
  const toolCallMap = /* @__PURE__ */ new Map();
84
+ let nextSyntheticIndex = 1e6;
85
+ const idKeyMap = /* @__PURE__ */ new Map();
27
86
  for (const line of lines) {
28
- const dataLine = line.split("\n").find((l) => l.startsWith("data:"));
29
- if (!dataLine) continue;
30
- const payload = dataLine.slice(5).trim();
87
+ const data = extractSSEData(splitSSELines(line));
88
+ if (data === void 0) continue;
89
+ const payload = data.trim();
31
90
  if (payload === "[DONE]") continue;
32
91
  let parsed;
33
92
  try {
34
93
  parsed = JSON.parse(payload);
35
94
  } catch (err) {
36
95
  droppedChunks++;
37
- if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${payload.slice(0, 200)}`;
96
+ if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
38
97
  continue;
39
98
  }
40
99
  if (parsed.type === "response.reasoning_summary_text.delta" && typeof parsed.delta === "string") {
@@ -64,10 +123,20 @@ function collapseOpenAISSE(body) {
64
123
  if (typeof delta.content === "string") content += delta.content;
65
124
  const toolCalls = delta.tool_calls;
66
125
  if (toolCalls) for (const tc of toolCalls) {
67
- const index = tc.index;
68
126
  const fn = tc.function;
127
+ const rawId = typeof tc.id === "string" ? tc.id : void 0;
128
+ let index;
129
+ if (typeof tc.index === "number") index = tc.index;
130
+ else if (rawId !== void 0) {
131
+ const existing = idKeyMap.get(rawId);
132
+ if (existing !== void 0) index = existing;
133
+ else {
134
+ index = nextSyntheticIndex++;
135
+ idKeyMap.set(rawId, index);
136
+ }
137
+ } else index = nextSyntheticIndex++;
69
138
  if (!toolCallMap.has(index)) toolCallMap.set(index, {
70
- id: tc.id ?? "",
139
+ id: rawId ?? "",
71
140
  name: fn?.name ?? "",
72
141
  arguments: ""
73
142
  });
@@ -77,17 +146,33 @@ function collapseOpenAISSE(body) {
77
146
  if (fn?.arguments && typeof fn.arguments === "string") entry.arguments += fn.arguments;
78
147
  }
79
148
  }
80
- if (toolCallMap.size > 0) {
149
+ const harmonyToolCalls = [];
150
+ if (toolCallMap.size === 0 && isHarmonyContent(content)) {
151
+ const parsed = parseHarmonyContent(content);
152
+ if (parsed.failed) {
153
+ harmonyUnparsed = true;
154
+ harmonyNote = `harmony tokens present but unparseable; content preserved verbatim: ${surrogateSafeSlice(content, 200)}`;
155
+ } else {
156
+ content = parsed.content;
157
+ if (parsed.reasoning) reasoning += parsed.reasoning;
158
+ harmonyToolCalls.push(...parsed.toolCalls);
159
+ }
160
+ }
161
+ if (toolCallMap.size > 0 || harmonyToolCalls.length > 0) {
81
162
  const sorted = Array.from(toolCallMap.entries()).sort(([a], [b]) => a - b);
82
163
  return {
83
164
  ...content ? { content } : {},
84
- toolCalls: sorted.map(([, tc]) => ({
165
+ toolCalls: [...sorted.map(([, tc]) => ({
85
166
  name: tc.name,
86
167
  arguments: tc.arguments,
87
168
  ...tc.id ? { id: tc.id } : {}
88
- })),
169
+ })), ...harmonyToolCalls],
170
+ ...reasoning ? { reasoning } : {},
171
+ ...webSearchQueries.length > 0 ? { webSearches: webSearchQueries } : {},
89
172
  ...droppedChunks > 0 ? { droppedChunks } : {},
90
- ...firstDroppedSample ? { firstDroppedSample } : {}
173
+ ...firstDroppedSample ? { firstDroppedSample } : {},
174
+ ...harmonyUnparsed ? { harmonyUnparsed: true } : {},
175
+ ...harmonyNote ? { harmonyNote } : {}
91
176
  };
92
177
  }
93
178
  return {
@@ -95,7 +180,9 @@ function collapseOpenAISSE(body) {
95
180
  ...reasoning ? { reasoning } : {},
96
181
  ...webSearchQueries.length > 0 ? { webSearches: webSearchQueries } : {},
97
182
  ...droppedChunks > 0 ? { droppedChunks } : {},
98
- ...firstDroppedSample ? { firstDroppedSample } : {}
183
+ ...firstDroppedSample ? { firstDroppedSample } : {},
184
+ ...harmonyUnparsed ? { harmonyUnparsed: true } : {},
185
+ ...harmonyNote ? { harmonyNote } : {}
99
186
  };
100
187
  }
101
188
  /**
@@ -106,45 +193,56 @@ function collapseOpenAISSE(body) {
106
193
  * event: content_block_delta\ndata: {"delta":{"type":"text_delta","text":"Hello"}}\n\n
107
194
  */
108
195
  function collapseAnthropicSSE(body) {
109
- const blocks = body.split("\n\n").filter((b) => b.trim().length > 0);
196
+ const blocks = splitSSEEvents(body);
110
197
  let content = "";
111
198
  let reasoning = "";
112
199
  let droppedChunks = 0;
113
200
  let firstDroppedSample;
114
201
  const toolCallMap = /* @__PURE__ */ new Map();
202
+ let nextSyntheticIndex = 1e6;
203
+ let lastSyntheticIndex;
115
204
  for (const block of blocks) {
116
- const lines = block.split("\n");
205
+ const lines = splitSSELines(block);
117
206
  const eventLine = lines.find((l) => l.startsWith("event:"));
118
- const dataLine = lines.find((l) => l.startsWith("data:"));
119
- if (!dataLine) continue;
207
+ const data = extractSSEData(lines);
208
+ if (data === void 0) continue;
120
209
  const eventType = eventLine ? eventLine.slice(6).trim() : "";
121
- const payload = dataLine.slice(5).trim();
210
+ const payload = data.trim();
122
211
  let parsed;
123
212
  try {
124
213
  parsed = JSON.parse(payload);
125
214
  } catch (err) {
126
215
  droppedChunks++;
127
- if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${payload.slice(0, 200)}`;
216
+ if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
128
217
  continue;
129
218
  }
130
219
  if (eventType === "content_block_start") {
131
- const index = parsed.index;
132
220
  const contentBlock = parsed.content_block;
133
- if (contentBlock?.type === "tool_use") toolCallMap.set(index, {
134
- id: contentBlock.id ?? "",
135
- name: contentBlock.name ?? "",
136
- arguments: ""
137
- });
221
+ if (contentBlock?.type === "tool_use") {
222
+ let index;
223
+ if (typeof parsed.index === "number") index = parsed.index;
224
+ else index = nextSyntheticIndex++;
225
+ lastSyntheticIndex = index;
226
+ toolCallMap.set(index, {
227
+ id: contentBlock.id ?? "",
228
+ name: contentBlock.name ?? "",
229
+ arguments: ""
230
+ });
231
+ }
138
232
  }
139
233
  if (eventType === "content_block_delta") {
140
- const index = parsed.index;
141
234
  const delta = parsed.delta;
142
235
  if (!delta) continue;
143
236
  if (delta.type === "text_delta" && typeof delta.text === "string") content += delta.text;
144
237
  if (delta.type === "thinking_delta" && typeof delta.thinking === "string") reasoning += delta.thinking;
145
238
  if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
146
- const entry = toolCallMap.get(index);
239
+ const index = typeof parsed.index === "number" ? parsed.index : lastSyntheticIndex;
240
+ const entry = index !== void 0 ? toolCallMap.get(index) : void 0;
147
241
  if (entry) entry.arguments += delta.partial_json;
242
+ else {
243
+ droppedChunks++;
244
+ if (droppedChunks === 1) firstDroppedSample = `input_json_delta with no correlating tool_use start: ${surrogateSafeSlice(payload, 200)}`;
245
+ }
148
246
  }
149
247
  }
150
248
  }
@@ -176,7 +274,7 @@ function collapseAnthropicSSE(body) {
176
274
  * data: {"candidates":[{"content":{"parts":[{"text":"Hello"}]}}]}\n\n
177
275
  */
178
276
  function collapseGeminiSSE(body) {
179
- const lines = body.split("\n\n").filter((l) => l.trim().length > 0);
277
+ const lines = splitSSEEvents(body);
180
278
  let content = "";
181
279
  let reasoning = "";
182
280
  let droppedChunks = 0;
@@ -185,15 +283,15 @@ function collapseGeminiSSE(body) {
185
283
  let audioMimeType;
186
284
  const toolCalls = [];
187
285
  for (const line of lines) {
188
- const dataLine = line.split("\n").find((l) => l.startsWith("data:"));
189
- if (!dataLine) continue;
190
- const payload = dataLine.slice(5).trim();
286
+ const data = extractSSEData(splitSSELines(line));
287
+ if (data === void 0) continue;
288
+ const payload = data.trim();
191
289
  let parsed;
192
290
  try {
193
291
  parsed = JSON.parse(payload);
194
292
  } catch (err) {
195
293
  droppedChunks++;
196
- if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${payload.slice(0, 200)}`;
294
+ if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
197
295
  continue;
198
296
  }
199
297
  const candidates = parsed.candidates;
@@ -206,7 +304,7 @@ function collapseGeminiSSE(body) {
206
304
  const fc = part.functionCall;
207
305
  toolCalls.push({
208
306
  name: String(fc.name ?? ""),
209
- arguments: typeof fc.args === "string" ? fc.args : JSON.stringify(fc.args)
307
+ arguments: typeof fc.args === "string" ? fc.args : JSON.stringify(fc.args ?? {})
210
308
  });
211
309
  } else if (part.inlineData && typeof part.inlineData.mimeType === "string" && part.inlineData.mimeType.startsWith("audio/")) {
212
310
  const inlineData = part.inlineData;
@@ -218,6 +316,9 @@ function collapseGeminiSSE(body) {
218
316
  if (audioB64) return {
219
317
  audioB64,
220
318
  audioMimeType,
319
+ ...content ? { content } : {},
320
+ ...reasoning ? { reasoning } : {},
321
+ ...toolCalls.length > 0 ? { toolCalls } : {},
221
322
  ...droppedChunks > 0 ? { droppedChunks } : {},
222
323
  ...firstDroppedSample ? { firstDroppedSample } : {}
223
324
  };
@@ -243,12 +344,20 @@ function collapseGeminiSSE(body) {
243
344
  *
244
345
  * /api/generate format:
245
346
  * {"model":"llama3","response":"Hello","done":false}\n
347
+ *
348
+ * Open-weight gpt-oss served via Ollama streams harmony channel tokens inside
349
+ * `message.content` (just like the OpenAI SSE path), so after accumulation the
350
+ * content is run through the same fail-safe {@link parseHarmonyContent} gate to
351
+ * capture structured tool calls / reasoning instead of leaking raw tokens.
246
352
  */
247
353
  function collapseOllamaNDJSON(body) {
248
354
  const lines = body.split("\n").filter((l) => l.trim().length > 0);
249
355
  let content = "";
356
+ let reasoning = "";
250
357
  let droppedChunks = 0;
251
358
  let firstDroppedSample;
359
+ let harmonyUnparsed = false;
360
+ let harmonyNote;
252
361
  const toolCalls = [];
253
362
  for (const line of lines) {
254
363
  let parsed;
@@ -256,7 +365,7 @@ function collapseOllamaNDJSON(body) {
256
365
  parsed = JSON.parse(line.trim());
257
366
  } catch (err) {
258
367
  droppedChunks++;
259
- if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${line.trim().slice(0, 200)}`;
368
+ if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(line.trim(), 200)}`;
260
369
  continue;
261
370
  }
262
371
  const message = parsed.message;
@@ -266,21 +375,38 @@ function collapseOllamaNDJSON(body) {
266
375
  const fn = tc.function;
267
376
  if (fn) toolCalls.push({
268
377
  name: String(fn.name ?? ""),
269
- arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments)
378
+ arguments: typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {})
270
379
  });
271
380
  }
272
381
  } else if (typeof parsed.response === "string") content += parsed.response;
273
382
  }
383
+ if (toolCalls.length === 0 && isHarmonyContent(content)) {
384
+ const parsedHarmony = parseHarmonyContent(content);
385
+ if (parsedHarmony.failed) {
386
+ harmonyUnparsed = true;
387
+ harmonyNote = `harmony tokens present but unparseable; content preserved verbatim: ${surrogateSafeSlice(content, 200)}`;
388
+ } else {
389
+ content = parsedHarmony.content;
390
+ if (parsedHarmony.reasoning) reasoning += parsedHarmony.reasoning;
391
+ toolCalls.push(...parsedHarmony.toolCalls);
392
+ }
393
+ }
274
394
  if (toolCalls.length > 0) return {
275
395
  ...content ? { content } : {},
276
396
  toolCalls,
397
+ ...reasoning ? { reasoning } : {},
277
398
  ...droppedChunks > 0 ? { droppedChunks } : {},
278
- ...firstDroppedSample ? { firstDroppedSample } : {}
399
+ ...firstDroppedSample ? { firstDroppedSample } : {},
400
+ ...harmonyUnparsed ? { harmonyUnparsed: true } : {},
401
+ ...harmonyNote ? { harmonyNote } : {}
279
402
  };
280
403
  return {
281
404
  content,
405
+ ...reasoning ? { reasoning } : {},
282
406
  ...droppedChunks > 0 ? { droppedChunks } : {},
283
- ...firstDroppedSample ? { firstDroppedSample } : {}
407
+ ...firstDroppedSample ? { firstDroppedSample } : {},
408
+ ...harmonyUnparsed ? { harmonyUnparsed: true } : {},
409
+ ...harmonyNote ? { harmonyNote } : {}
284
410
  };
285
411
  }
286
412
  /**
@@ -290,24 +416,26 @@ function collapseOllamaNDJSON(body) {
290
416
  * event: content-delta\ndata: {"type":"content-delta","delta":{"message":{"content":{"text":"Hello"}}}}\n\n
291
417
  */
292
418
  function collapseCohereSSE(body) {
293
- const blocks = body.split("\n\n").filter((b) => b.trim().length > 0);
419
+ const blocks = splitSSEEvents(body);
294
420
  let content = "";
295
421
  let droppedChunks = 0;
296
422
  let firstDroppedSample;
297
423
  const toolCallMap = /* @__PURE__ */ new Map();
424
+ let nextSyntheticIndex = 1e6;
425
+ let lastStartKey;
298
426
  for (const block of blocks) {
299
- const lines = block.split("\n");
427
+ const lines = splitSSELines(block);
300
428
  const eventLine = lines.find((l) => l.startsWith("event:"));
301
- const dataLine = lines.find((l) => l.startsWith("data:"));
302
- if (!dataLine) continue;
429
+ const data = extractSSEData(lines);
430
+ if (data === void 0) continue;
303
431
  const eventType = eventLine ? eventLine.slice(6).trim() : "";
304
- const payload = dataLine.slice(5).trim();
432
+ const payload = data.trim();
305
433
  let parsed;
306
434
  try {
307
435
  parsed = JSON.parse(payload);
308
436
  } catch (err) {
309
437
  droppedChunks++;
310
- if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${payload.slice(0, 200)}`;
438
+ if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
311
439
  continue;
312
440
  }
313
441
  if (eventType === "content-delta") {
@@ -315,7 +443,10 @@ function collapseCohereSSE(body) {
315
443
  if (contentObj && typeof contentObj.text === "string") content += contentObj.text;
316
444
  }
317
445
  if (eventType === "tool-call-start") {
318
- const index = parsed.index;
446
+ let index;
447
+ if (typeof parsed.index === "number") index = parsed.index;
448
+ else index = nextSyntheticIndex++;
449
+ lastStartKey = index;
319
450
  const toolCalls = (parsed.delta?.message)?.tool_calls;
320
451
  if (toolCalls) {
321
452
  const fn = toolCalls.function;
@@ -327,13 +458,17 @@ function collapseCohereSSE(body) {
327
458
  }
328
459
  }
329
460
  if (eventType === "tool-call-delta") {
330
- const index = parsed.index;
461
+ const index = typeof parsed.index === "number" ? parsed.index : lastStartKey;
331
462
  const toolCalls = (parsed.delta?.message)?.tool_calls;
332
463
  if (toolCalls) {
333
464
  const fn = toolCalls.function;
334
465
  if (fn && typeof fn.arguments === "string") {
335
- const entry = toolCallMap.get(index);
466
+ const entry = index !== void 0 ? toolCallMap.get(index) : void 0;
336
467
  if (entry) entry.arguments += fn.arguments;
468
+ else {
469
+ droppedChunks++;
470
+ if (droppedChunks === 1) firstDroppedSample = `tool-call-delta with no correlating start: ${surrogateSafeSlice(payload, 200)}`;
471
+ }
337
472
  }
338
473
  }
339
474
  }
@@ -387,22 +522,43 @@ function decodeEventStreamFrames(buf) {
387
522
  };
388
523
  const headersStart = offset + 12;
389
524
  const headersEnd = headersStart + headersLength;
525
+ const payloadEnd = offset + totalLength - 4;
526
+ if (headersEnd > payloadEnd || headersEnd > buf.length) return {
527
+ frames,
528
+ truncated: true
529
+ };
390
530
  const headers = {};
391
531
  let hOffset = headersStart;
532
+ let headerOverrun = false;
392
533
  while (hOffset < headersEnd) {
534
+ if (hOffset + 1 > headersEnd) {
535
+ headerOverrun = true;
536
+ break;
537
+ }
393
538
  const nameLen = buf.readUInt8(hOffset);
394
539
  hOffset += 1;
540
+ if (hOffset + nameLen + 1 + 2 > headersEnd) {
541
+ headerOverrun = true;
542
+ break;
543
+ }
395
544
  const name = buf.subarray(hOffset, hOffset + nameLen).toString("utf8");
396
545
  hOffset += nameLen;
397
546
  hOffset += 1;
398
547
  const valueLen = buf.readUInt16BE(hOffset);
399
548
  hOffset += 2;
549
+ if (hOffset + valueLen > headersEnd) {
550
+ headerOverrun = true;
551
+ break;
552
+ }
400
553
  const value = buf.subarray(hOffset, hOffset + valueLen).toString("utf8");
401
554
  hOffset += valueLen;
402
555
  headers[name] = value;
403
556
  }
557
+ if (headerOverrun) return {
558
+ frames,
559
+ truncated: true
560
+ };
404
561
  const payloadStart = headersEnd;
405
- const payloadEnd = offset + totalLength - 4;
406
562
  const payload = buf.subarray(payloadStart, payloadEnd);
407
563
  const messageCrc = buf.readUInt32BE(offset + totalLength - 4);
408
564
  const computedMessageCrc = crc32(buf.subarray(offset, offset + totalLength - 4));
@@ -440,7 +596,7 @@ function collapseBedrockEventStream(body) {
440
596
  parsed = JSON.parse(frameStr);
441
597
  } catch (err) {
442
598
  droppedChunks++;
443
- if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${frameStr.slice(0, 200)}`;
599
+ if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(frameStr, 200)}`;
444
600
  continue;
445
601
  }
446
602
  if (parsed.type === "content_block_delta") {
@@ -448,9 +604,11 @@ function collapseBedrockEventStream(body) {
448
604
  if (delta?.type === "text_delta" && typeof delta.text === "string") content += delta.text;
449
605
  if (delta?.type === "input_json_delta" && typeof delta.partial_json === "string") {
450
606
  const index = parsed.index;
451
- if (index !== void 0) {
452
- const entry = toolCallMap.get(index);
453
- if (entry) entry.arguments += delta.partial_json;
607
+ const entry = index !== void 0 ? toolCallMap.get(index) : void 0;
608
+ if (entry) entry.arguments += delta.partial_json;
609
+ else {
610
+ droppedChunks++;
611
+ if (droppedChunks === 1) firstDroppedSample = `input_json_delta with no correlating tool_use start: ${surrogateSafeSlice(frameStr, 200)}`;
454
612
  }
455
613
  }
456
614
  continue;
@@ -486,9 +644,13 @@ function collapseBedrockEventStream(body) {
486
644
  if (typeof delta.text === "string") content += delta.text;
487
645
  if (typeof delta.toolUse === "object" && delta.toolUse !== null) {
488
646
  const toolUseDelta = delta.toolUse;
489
- if (typeof toolUseDelta.input === "string" && index !== void 0) {
490
- const entry = toolCallMap.get(index);
647
+ if (typeof toolUseDelta.input === "string") {
648
+ const entry = index !== void 0 ? toolCallMap.get(index) : void 0;
491
649
  if (entry) entry.arguments += toolUseDelta.input;
650
+ else {
651
+ droppedChunks++;
652
+ if (droppedChunks === 1) firstDroppedSample = `toolUse.input delta with no correlating tool_use start: ${surrogateSafeSlice(frameStr, 200)}`;
653
+ }
492
654
  }
493
655
  }
494
656
  }
@@ -518,22 +680,22 @@ function collapseBedrockEventStream(body) {
518
680
  * data: {"event_type":"interaction.complete","interaction":{"id":"...","usage":{...}}}\n\n
519
681
  */
520
682
  function collapseGeminiInteractionsSSE(body) {
521
- const lines = body.split("\n\n").filter((l) => l.trim().length > 0);
683
+ const lines = splitSSEEvents(body);
522
684
  let content = "";
523
685
  let reasoning = "";
524
686
  let droppedChunks = 0;
525
687
  let firstDroppedSample;
526
688
  const toolCalls = [];
527
689
  for (const line of lines) {
528
- const dataLine = line.split("\n").find((l) => l.startsWith("data:"));
529
- if (!dataLine) continue;
530
- const payload = dataLine.slice(5).trim();
690
+ const data = extractSSEData(splitSSELines(line));
691
+ if (data === void 0) continue;
692
+ const payload = data.trim();
531
693
  let parsed;
532
694
  try {
533
695
  parsed = JSON.parse(payload);
534
696
  } catch (err) {
535
697
  droppedChunks++;
536
- if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${payload.slice(0, 200)}`;
698
+ if (droppedChunks === 1) firstDroppedSample = `parse failed (${err instanceof Error ? err.message : "unknown"}): ${surrogateSafeSlice(payload, 200)}`;
537
699
  continue;
538
700
  }
539
701
  const eventType = parsed.event_type;