@tangle-network/agent-core 1.0.0 → 1.0.2

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.
@@ -110,16 +110,42 @@ export declare class SSEStreamSilentError extends Error {
110
110
  readonly idleMs: number;
111
111
  constructor(idleMs: number);
112
112
  }
113
+ export type SSEFragment = {
114
+ type: "field";
115
+ field: string | undefined;
116
+ prefix: string;
117
+ } | {
118
+ type: "value";
119
+ value: string;
120
+ } | {
121
+ type: "line-end" | "frame-end";
122
+ ending: string;
123
+ };
113
124
  /**
114
- * Incremental SSE frame parser. Feed decoded text via `push()`; call
115
- * `flush()` when the stream ends.
125
+ * Incremental SSE frame parser. Feed decoded text via push(); call flush()
126
+ * when the stream ends. Complete frames and bounded fragments share one
127
+ * scanner and one parser lifetime.
116
128
  */
117
129
  export declare class SSEFrameParser {
118
- private buffer;
130
+ private prefix;
131
+ private started;
132
+ private cr;
133
+ private bomChecked;
134
+ /**
135
+ * Yield raw field prefixes, values and line/frame endings without retaining
136
+ * a complete line or frame. Comments and unknown fields survive; one initial
137
+ * BOM is consumed and CRLF remains intact. Pass final=true once at EOF.
138
+ * Drain each generator before supplying the next chunk. An instance owns
139
+ * either fragments or push/flush; mixing those modes loses collected state.
140
+ */
141
+ fragments(chunk: string, final?: boolean): Generator<SSEFragment>;
142
+ private value;
143
+ private end;
144
+ private field;
145
+ private line;
119
146
  private frameEvent;
120
147
  private frameId;
121
148
  private frameDataLines;
122
- private bomChecked;
123
149
  /**
124
150
  * Append a decoded text chunk and return every frame the chunk
125
151
  * completes. Frame state survives across calls: one frame can
@@ -132,7 +158,7 @@ export declare class SSEFrameParser {
132
158
  * Well-behaved servers end frames with "\n\n", but some close early.
133
159
  */
134
160
  flush(): SSEFrame[];
135
- private processLines;
161
+ private collect;
136
162
  /**
137
163
  * Dispatch the accumulated frame. A frame with no `data:` field (an
138
164
  * `event:`/`id:`-only frame, or a bare comment) produces nothing, and
@@ -177,13 +203,12 @@ export interface ParsedSSEEvent<T = unknown> {
177
203
  * on stream end. Frames whose trimmed data is empty produce no event.
178
204
  */
179
205
  export declare class SSEChunkParser<T = unknown> {
180
- private readonly frames;
181
206
  private readonly options;
207
+ private readonly frames;
182
208
  constructor(options?: SSEParserOptions<T>);
183
209
  push(chunk: string): ParsedSSEEvent<T>[];
184
210
  flush(): ParsedSSEEvent<T>[];
185
211
  private toEvents;
186
- private toEvent;
187
212
  }
188
213
  /**
189
214
  * Parse SSE stream into typed events via async iteration.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/sse/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6BiB;EACf;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EAEA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EAEA;IACE;IACA;IACA;IACA;;GAGD;;;;;;;;;;;wBAYa,aAAa,kBAAkB;;;;;;;;iBAsC9B;;EAEf;;EAEA;;EAEA;;;iBAee;;;;;;;EAOf,SAAS;;;;;;;;EAQT;;;;;;;;qBASW,6BAA6B;WACtB;WACT;EACG,YAAA;;;;;;qBAUD;UACH;UACA;UACA;UACA;UACA;;;;;;EAOR,KAAK,gBAAgB;;;;;;EAqBrB,SAAS;UASD;;;;;;UAoCA;;;;;;;wBAqBM,eAAe;EAC7B;EACA;EACA;;;;;;;;;;wBAiBqB,gBACrB,MAAM,eAAe,gCACrB,UAAU,mBACT,eAAe;iBA4DD,iBAAiB;;EAEhC;;EAEA,aAAa,gBAAgB;;iBAGd,eAAe;EAC9B,MAAM;EACN;EACA;EACA;;;;;;;qBAQW,eAAe;mBACT;mBACA;EAEL,YAAA,UAAS,iBAAiB;EAItC,KAAK,gBAAgB,eAAe;EAIpC,SAAS,eAAe;UAIhB;UASA;;;;;wBAuCa,eAAe,aACpC,QAAQ,eAAe,aACvB,UAAS,iBAAiB,KACzB,eAAe,eAAe;;;;wBA4CX,iBAAiB,aACrC,QAAQ,eAAe,aACvB,UAAU,OAAO,eAAe,aAChC,UAAU,OAAO,gBACjB,UAAS,iBAAiB,KACzB;;iBAuCc;EACf;EACA;EACA;EACA;;;iBAIe;;EAEf,OAAO;;EAEP;;EAEA;;EAEA;;EAEA,WAAW;;EAEX;;;;;;;;;;;;;;;;;;;qBA0BW;UACH;UACA;;;;;EAiBR,cAAc;;;;EAUd,aAAa,OAAO,eAAe;;;;;EAoDnC,YAAY;;;;EAeZ,WAAW,SAAS;;;;EAOpB;UAaQ;UAyCA;;;;;wBA4BM,8BAA8B;;;;;;;;;;;;;;wBAiB9B,oBAAoB,UAAU,yBAC5C,WAAW,OAAO;EAElB,WAAW,OAAO;EAClB,gBAAgB;EAChB,WAAW"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/sse/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6BiB;EACf;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EAEA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EAEA;IACE;IACA;IACA;IACA;;GAGD;;;;;;;;;;;wBAYa,aAAa,kBAAkB;;;;;;;;iBAsC9B;;EAEf;;EAEA;;EAEA;;;iBAee;;;;;;;EAOf,SAAS;;;;;;;;EAQT;;;;;;;;qBASW,6BAA6B;WACtB;WACT;EACG,YAAA;;YAMF;EAEN;EACA;EACA;;EAEA;EAAe;;EACf;EAAgC;;;;;;;qBAOzB;UACH;UACA;UACA;UACA;;;;;;;;EASP,UAAU,eAAe,kBAAgB,UAAU;UAgC3C;UA0BA;UAeD;UACA;UACA;UACA;UACA;;;;;;EAOR,KAAK,gBAAgB;;;;;;EAQrB,SAAS;UAID;;;;;;UA0BA;;;;;;;wBAoBM,eAAe;EAC7B;EACA;EACA;;;;;;;;;;wBAiBqB,gBACrB,MAAM,eAAe,gCACrB,UAAU,mBACT,eAAe;iBA4DD,iBAAiB;;EAEhC;;EAEA,aAAa,gBAAgB;;iBAGd,eAAe;EAC9B,MAAM;EACN;EACA;EACA;;;;;;;qBAQW,eAAe;mBAEG;mBADZ;EACY,YAAA,UAAS,iBAAiB;EAEvD,KAAK,gBAAgB,eAAe;EAIpC,SAAS,eAAe;UAIhB;;;;;wBA2Ca,eAAe,aACpC,QAAQ,eAAe,aACvB,UAAS,iBAAiB,KACzB,eAAe,eAAe;;;;wBASX,iBAAiB,aACrC,QAAQ,eAAe,aACvB,UAAU,OAAO,eAAe,aAChC,UAAU,OAAO,gBACjB,UAAS,iBAAiB,KACzB;;iBAcc;EACf;EACA;EACA;EACA;;;iBAIe;;EAEf,OAAO;;EAEP;;EAEA;;EAEA;;EAEA,WAAW;;EAEX;;;;;;;;;;;;;;;;;;;qBAuBW;UACH;UACA;;;;;EAiBR,cAAc;;;;EAUd,aAAa,OAAO,eAAe;;;;;EAoDnC,YAAY;;;;EAeZ,WAAW,SAAS;;;;EAOpB;UAaQ;UAyCA;;;;;wBA4BM,8BAA8B;;;;;;;;;;;;;;wBAiB9B,oBAAoB,UAAU,yBAC5C,WAAW,OAAO;EAElB,WAAW,OAAO;EAClB,gBAAgB;EAChB,WAAW"}
package/dist/sse/index.js CHANGED
@@ -50,30 +50,115 @@ var SSEStreamSilentError = class extends Error {
50
50
  }
51
51
  };
52
52
  /**
53
- * Incremental SSE frame parser. Feed decoded text via `push()`; call
54
- * `flush()` when the stream ends.
53
+ * Incremental SSE frame parser. Feed decoded text via push(); call flush()
54
+ * when the stream ends. Complete frames and bounded fragments share one
55
+ * scanner and one parser lifetime.
55
56
  */
56
57
  var SSEFrameParser = class {
57
- buffer = "";
58
+ prefix = "";
59
+ started = false;
60
+ cr = false;
61
+ bomChecked = false;
62
+ /**
63
+ * Yield raw field prefixes, values and line/frame endings without retaining
64
+ * a complete line or frame. Comments and unknown fields survive; one initial
65
+ * BOM is consumed and CRLF remains intact. Pass final=true once at EOF.
66
+ * Drain each generator before supplying the next chunk. An instance owns
67
+ * either fragments or push/flush; mixing those modes loses collected state.
68
+ */
69
+ *fragments(chunk, final = false) {
70
+ let text = chunk;
71
+ if (!this.bomChecked && text.length) {
72
+ this.bomChecked = true;
73
+ if (text.charCodeAt(0) === 65279) text = text.slice(1);
74
+ }
75
+ if (this.cr) {
76
+ text = `\r${text}`;
77
+ this.cr = false;
78
+ }
79
+ let cursor = 0;
80
+ for (;;) {
81
+ const newline = text.indexOf("\n", cursor);
82
+ const end = newline < 0 ? text.length : newline;
83
+ const trailingCr = text[end - 1] === "\r" && end > cursor;
84
+ const value = text.slice(cursor, trailingCr ? end - 1 : end);
85
+ yield* this.value(value);
86
+ if (newline < 0) {
87
+ this.cr = trailingCr;
88
+ break;
89
+ }
90
+ yield* this.end(trailingCr ? "\r\n" : "\n");
91
+ cursor = newline + 1;
92
+ }
93
+ if (final) {
94
+ if (this.started || this.prefix || this.cr) yield* this.end(this.cr ? "\r" : "");
95
+ yield {
96
+ type: "frame-end",
97
+ ending: ""
98
+ };
99
+ this.cr = false;
100
+ }
101
+ }
102
+ *value(value) {
103
+ let cursor = 0;
104
+ while (!this.started && cursor < value.length) {
105
+ const next = value[cursor++];
106
+ if (this.prefix.endsWith(":")) {
107
+ const prefix = this.prefix + (next === " " ? next : "");
108
+ yield {
109
+ type: "field",
110
+ field: this.prefix.slice(0, -1),
111
+ prefix
112
+ };
113
+ this.started = true;
114
+ if (next !== " ") cursor--;
115
+ } else {
116
+ this.prefix += next;
117
+ if (![
118
+ "data:",
119
+ "event:",
120
+ "id:",
121
+ "retry:"
122
+ ].some((name) => name.startsWith(this.prefix))) {
123
+ yield {
124
+ type: "field",
125
+ field: void 0,
126
+ prefix: this.prefix
127
+ };
128
+ this.started = true;
129
+ }
130
+ }
131
+ }
132
+ if (cursor < value.length) yield {
133
+ type: "value",
134
+ value: value.slice(cursor)
135
+ };
136
+ }
137
+ *end(ending) {
138
+ if (!this.started && this.prefix) yield {
139
+ type: "field",
140
+ field: this.prefix.endsWith(":") ? this.prefix.slice(0, -1) : void 0,
141
+ prefix: this.prefix
142
+ };
143
+ yield {
144
+ type: this.started || this.prefix ? "line-end" : "frame-end",
145
+ ending
146
+ };
147
+ this.prefix = "";
148
+ this.started = false;
149
+ }
150
+ field;
151
+ line = "";
58
152
  frameEvent = "";
59
153
  frameId;
60
154
  frameDataLines = null;
61
- bomChecked = false;
62
155
  /**
63
156
  * Append a decoded text chunk and return every frame the chunk
64
157
  * completes. Frame state survives across calls: one frame can
65
158
  * straddle any number of chunks.
66
159
  */
67
160
  push(chunk) {
68
- let text = chunk;
69
- if (!this.bomChecked && text.length > 0) {
70
- this.bomChecked = true;
71
- if (text.charCodeAt(0) === 65279) text = text.slice(1);
72
- }
73
- this.buffer += text;
74
- const lines = this.buffer.split("\n");
75
- this.buffer = lines.pop() ?? "";
76
- return this.processLines(lines);
161
+ return this.collect(this.fragments(chunk));
77
162
  }
78
163
  /**
79
164
  * End-of-stream: process the unterminated trailing line, then
@@ -81,35 +166,21 @@ var SSEFrameParser = class {
81
166
  * Well-behaved servers end frames with "\n\n", but some close early.
82
167
  */
83
168
  flush() {
84
- const rest = this.buffer;
85
- this.buffer = "";
86
- const frames = rest.length > 0 ? this.processLines([rest]) : [];
87
- const dangling = this.dispatch();
88
- if (dangling) frames.push(dangling);
89
- return frames;
169
+ return this.collect(this.fragments("", true));
90
170
  }
91
- processLines(lines) {
171
+ collect(fragments) {
92
172
  const frames = [];
93
- for (const rawLine of lines) {
94
- const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
95
- if (line === "") {
96
- const frame = this.dispatch();
97
- if (frame) frames.push(frame);
98
- continue;
99
- }
100
- if (line.startsWith(":")) continue;
101
- if (line.startsWith("data:")) {
102
- let value = line.slice(5);
103
- if (value.startsWith(" ")) value = value.slice(1);
104
- if (this.frameDataLines === null) this.frameDataLines = [];
105
- this.frameDataLines.push(value);
106
- continue;
107
- }
108
- if (line.startsWith("event:")) {
109
- this.frameEvent = line.slice(6).trim();
110
- continue;
111
- }
112
- if (line.startsWith("id:")) this.frameId = line.slice(3).trim();
173
+ for (const fragment of fragments) if (fragment.type === "field") this.field = fragment.field;
174
+ else if (fragment.type === "value" && this.field !== void 0) this.line += fragment.value;
175
+ else if (fragment.type === "line-end") {
176
+ if (this.field === "data") (this.frameDataLines ??= []).push(this.line);
177
+ if (this.field === "event") this.frameEvent = this.line.trim();
178
+ if (this.field === "id") this.frameId = this.line.trim();
179
+ this.field = void 0;
180
+ this.line = "";
181
+ } else if (fragment.type === "frame-end") {
182
+ const frame = this.dispatch();
183
+ if (frame) frames.push(frame);
113
184
  }
114
185
  return frames;
115
186
  }
@@ -119,10 +190,9 @@ var SSEFrameParser = class {
119
190
  * its field state is consumed and reset either way.
120
191
  */
121
192
  dispatch() {
122
- const dataLines = this.frameDataLines;
123
- const frame = dataLines ? {
193
+ const frame = this.frameDataLines ? {
124
194
  event: detached(this.frameEvent),
125
- data: dataLines.join("\n"),
195
+ data: this.frameDataLines.join("\n"),
126
196
  id: this.frameId ? detached(this.frameId) : void 0
127
197
  } : null;
128
198
  this.frameEvent = "";
@@ -190,8 +260,8 @@ async function* streamSSEFrames(body, options) {
190
260
  * on stream end. Frames whose trimmed data is empty produce no event.
191
261
  */
192
262
  var SSEChunkParser = class {
193
- frames = new SSEFrameParser();
194
263
  options;
264
+ frames = new SSEFrameParser();
195
265
  constructor(options = {}) {
196
266
  this.options = options;
197
267
  }
@@ -202,85 +272,47 @@ var SSEChunkParser = class {
202
272
  return this.toEvents(this.frames.flush());
203
273
  }
204
274
  toEvents(frames) {
205
- const events = [];
206
- for (const frame of frames) {
207
- const event = this.toEvent(frame);
208
- if (event) events.push(event);
209
- }
210
- return events;
275
+ return frames.flatMap((frame) => toSSEEvents(frame, this.options));
211
276
  }
212
- toEvent(frame) {
213
- const rawData = frame.data.trim();
214
- if (!rawData) return null;
215
- let data;
216
- if (this.options.transform) {
217
- const transformed = this.options.transform(rawData);
218
- if (transformed === null) {
219
- if (this.options.logMalformed) console.warn("SSE transform returned null:", rawData.slice(0, 100));
220
- return null;
221
- }
222
- data = transformed;
223
- } else try {
224
- data = JSON.parse(rawData);
225
- } catch {
226
- if (this.options.logMalformed) console.warn("SSE parse failed:", rawData.slice(0, 100));
227
- data = rawData;
277
+ };
278
+ function toSSEEvents(frame, options) {
279
+ const rawData = frame.data.trim();
280
+ if (!rawData) return [];
281
+ let data;
282
+ if (options.transform) {
283
+ const transformed = options.transform(rawData);
284
+ if (transformed === null) {
285
+ if (options.logMalformed) console.warn("SSE transform returned null:", rawData.slice(0, 100));
286
+ return [];
228
287
  }
229
- return {
230
- data,
231
- rawData,
232
- eventId: frame.id,
233
- eventType: frame.event || void 0
234
- };
288
+ data = transformed;
289
+ } else try {
290
+ data = JSON.parse(rawData);
291
+ } catch {
292
+ if (options.logMalformed) console.warn("SSE parse failed:", rawData.slice(0, 100));
293
+ data = rawData;
235
294
  }
236
- };
295
+ return [{
296
+ data,
297
+ rawData,
298
+ eventId: frame.id,
299
+ eventType: frame.event || void 0
300
+ }];
301
+ }
237
302
  /**
238
303
  * Parse SSE stream into typed events via async iteration.
239
304
  */
240
305
  async function* parseSSEStream(stream, options = {}) {
241
- const reader = stream.getReader();
242
- const decoder = new TextDecoder();
243
- const parser = new SSEChunkParser(options);
244
- let chunkCount = 0;
245
- let totalBytes = 0;
246
- try {
247
- while (true) {
248
- const { done, value } = await reader.read();
249
- if (done) {
250
- console.log(`[agent-core:sse] Stream reader done after ${chunkCount} chunks, ${totalBytes} bytes`);
251
- break;
252
- }
253
- chunkCount++;
254
- totalBytes += value?.length ?? 0;
255
- const chunk = decoder.decode(value, { stream: true });
256
- if (chunkCount <= 3 || chunkCount % 10 === 0) console.log(`[agent-core:sse] Chunk ${chunkCount}: ${value?.length ?? 0} bytes, preview: ${chunk.slice(0, 100).replace(/\n/g, "\\n")}`);
257
- for (const event of parser.push(chunk)) yield event;
258
- }
259
- for (const event of parser.flush()) yield event;
260
- } finally {
261
- reader.releaseLock();
262
- }
306
+ for await (const frame of streamSSEFrames(stream)) yield* toSSEEvents(frame, options);
263
307
  }
264
308
  /**
265
309
  * Consume SSE stream with callbacks.
266
310
  */
267
311
  async function consumeSSEStream(stream, onEvent, onError, options = {}) {
268
- let eventCount = 0;
269
312
  try {
270
- for await (const event of parseSSEStream(stream, options)) {
271
- eventCount++;
272
- onEvent(event);
273
- }
274
- console.log(`[agent-core:sse] Stream ended normally, total events: ${eventCount}`);
313
+ for await (const event of parseSSEStream(stream, options)) onEvent(event);
275
314
  } catch (error) {
276
- const normalizedError = error instanceof Error ? error : new Error(String(error));
277
- if (normalizedError.name === "AbortError" || normalizedError.message.toLowerCase().includes("abort")) {
278
- console.log(`[agent-core:sse] Stream aborted normally after ${eventCount} events`);
279
- onError(normalizedError);
280
- return;
281
- }
282
- console.error(`[agent-core:sse] Stream error after ${eventCount} events:`, normalizedError);
283
- onError(normalizedError);
315
+ onError(error instanceof Error ? error : new Error(String(error)));
284
316
  }
285
317
  }
286
318
  /** Event types that contain token information (Anthropic SDK format) */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/sse/index.ts"],"sourcesContent":["/**\n * SSE Utilities\n *\n * The one Server-Sent Events wire-format implementation: frame parsing and\n * framing, plus the JSON event layer and stream usage extraction built on it.\n * Everything here is pure ECMAScript over `ReadableStream`/`TextDecoder`\n * globals and imports nothing, so Node, browser and Cloudflare Worker bundles\n * can take this subpath without pulling a dependency.\n *\n * Frame parser guarantees (WHATWG Server-Sent Events, §9.2 \"Interpreting an\n * event stream\"):\n * - Buffers frames that straddle chunk boundaries; `push()` emits only\n * complete frames and `flush()` dispatches a trailing frame whose\n * terminating blank line never arrived.\n * - Accepts LF and CRLF line terminators interchangeably. Bare CR is\n * intentionally not treated as a terminator: no known emitter uses\n * it, and supporting it would risk splitting payloads that\n * legitimately contain CR.\n * - Joins multi-line `data:` fields with \"\\n\" (§9.2.6 step 3).\n * - Skips comment lines (a leading \":\") — the heartbeat mechanism.\n * - Ignores `retry:` and unrecognized fields.\n * - Strips one leading U+FEFF (BOM) from the stream.\n * - Applies the `event:`/`id:` state of a frame only to that frame.\n *\n * The frame layer does no JSON parsing, reconnect/`Last-Event-ID` tracking or\n * event-type filtering; `SSEChunkParser` adds JSON payload handling on top.\n */\n\n/** Raw SSE event data from stream */\nexport interface SSEEventData {\n type: string;\n // Tool events\n toolName?: string;\n name?: string;\n callId?: string;\n input?: unknown;\n arguments?: unknown;\n output?: unknown;\n result?: unknown;\n durationMs?: number;\n // Error events\n error?: string;\n message?: string;\n code?: string;\n // Status events\n status?: string;\n // Token/LLM events\n tokens?: number;\n model?: string;\n // Usage fields (Anthropic format)\n inputTokens?: number;\n outputTokens?: number;\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n // Usage object (some providers wrap in usage field)\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n // Generic\n [key: string]: unknown;\n}\n\n/**\n * Parse raw SSE data string into structured event data.\n *\n * Handles the standard SSE format:\n * ```\n * event: eventType\n * data: {\"json\": \"payload\"}\n * ```\n */\nexport function parseSSEData(rawData: string): SSEEventData | null {\n try {\n const lines = rawData.split(\"\\n\");\n let eventType: string | undefined;\n let data: string | undefined;\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n eventType = line.slice(7).trim();\n } else if (line.startsWith(\"data: \")) {\n data = line.slice(6);\n }\n }\n\n if (data) {\n const parsed = JSON.parse(data);\n return {\n type: eventType || parsed.type || \"unknown\",\n ...parsed,\n };\n }\n } catch {\n // Ignore parse errors (heartbeats, malformed events)\n }\n return null;\n}\n\n// ============================================================================\n// Frame Parsing and Framing\n// ============================================================================\n\n/**\n * One parsed SSE frame: the fields accumulated between blank lines.\n *\n * `event` and `id` share no memory with the chunk the frame arrived in, so a\n * consumer can keep them as long as it keeps its events. `data` can share\n * that memory: parse it, and keep what the parse returns.\n */\nexport interface SSEFrame {\n /** Value of the `event:` field; \"\" when the frame carries none. */\n event: string;\n /** `data:` field lines joined with \"\\n\". */\n data: string;\n /** Value of the `id:` field; undefined when the frame carries none. */\n id?: string;\n}\n\n/**\n * A copy of `value` that shares no memory with the string it was cut from.\n *\n * V8 can return a short substring of a long string as a view that keeps the\n * whole string alive. Consumers keep event names and ids as long as they keep\n * events, so a view would keep every chunk its frame arrived in alive.\n */\nfunction detached(value: string): string {\n return value === \"\" ? value : (JSON.parse(JSON.stringify(value)) as string);\n}\n\n/** Options for {@link streamSSEFrames}. */\nexport interface SSEStreamOptions {\n /**\n * Aborts the read loop. When the signal fires, the generator throws a\n * `DOMException` named \"AbortError\" so consumers can distinguish a\n * cancellation from a clean end-of-stream without inspecting signal\n * state.\n */\n signal?: AbortSignal;\n /**\n * The longest silence the stream may keep, read before each chunk read.\n * Any bytes reset it, comment heartbeats included, because a heartbeat is\n * the producer saying the connection is still live. A read that outlasts it\n * throws {@link SSEStreamSilentError}. `undefined` waits without bound, which\n * suits a producer that promises no heartbeat.\n */\n idleMs?: () => number | undefined;\n}\n\n/**\n * Thrown when a stream sends no bytes for longer than its `idleMs`. A\n * producer that heartbeats would have written in that time, so the\n * connection is dead even though it never closed: a half-open TCP connection\n * or a stalled proxy hold the socket open while nothing arrives.\n */\nexport class SSEStreamSilentError extends Error {\n override readonly name = \"SSEStreamSilentError\";\n readonly idleMs: number;\n constructor(idleMs: number) {\n super(`SSE stream sent no bytes for ${idleMs} ms`);\n this.idleMs = idleMs;\n }\n}\n\n/**\n * Incremental SSE frame parser. Feed decoded text via `push()`; call\n * `flush()` when the stream ends.\n */\nexport class SSEFrameParser {\n private buffer = \"\";\n private frameEvent = \"\";\n private frameId: string | undefined;\n private frameDataLines: string[] | null = null;\n private bomChecked = false;\n\n /**\n * Append a decoded text chunk and return every frame the chunk\n * completes. Frame state survives across calls: one frame can\n * straddle any number of chunks.\n */\n push(chunk: string): SSEFrame[] {\n let text = chunk;\n if (!this.bomChecked && text.length > 0) {\n this.bomChecked = true;\n // The spec's decode step strips one leading BOM. Without this the\n // first field line of a BOM'd stream is never recognized.\n if (text.charCodeAt(0) === 0xfeff) text = text.slice(1);\n }\n this.buffer += text;\n const lines = this.buffer.split(\"\\n\");\n // The last element holds a line whose terminator has not arrived\n // yet; keep it for the next push.\n this.buffer = lines.pop() ?? \"\";\n return this.processLines(lines);\n }\n\n /**\n * End-of-stream: process the unterminated trailing line, then\n * dispatch a dangling frame that never got its blank line.\n * Well-behaved servers end frames with \"\\n\\n\", but some close early.\n */\n flush(): SSEFrame[] {\n const rest = this.buffer;\n this.buffer = \"\";\n const frames = rest.length > 0 ? this.processLines([rest]) : [];\n const dangling = this.dispatch();\n if (dangling) frames.push(dangling);\n return frames;\n }\n\n private processLines(lines: string[]): SSEFrame[] {\n const frames: SSEFrame[] = [];\n for (const rawLine of lines) {\n const line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\n if (line === \"\") {\n const frame = this.dispatch();\n if (frame) frames.push(frame);\n continue;\n }\n // Comment lines (heartbeats) carry no fields.\n if (line.startsWith(\":\")) continue;\n if (line.startsWith(\"data:\")) {\n // The spec removes one optional leading space from the value.\n let value = line.slice(5);\n if (value.startsWith(\" \")) value = value.slice(1);\n if (this.frameDataLines === null) this.frameDataLines = [];\n this.frameDataLines.push(value);\n continue;\n }\n if (line.startsWith(\"event:\")) {\n this.frameEvent = line.slice(6).trim();\n continue;\n }\n if (line.startsWith(\"id:\")) {\n this.frameId = line.slice(3).trim();\n }\n // `retry:` and unrecognized fields carry no event payload.\n }\n return frames;\n }\n\n /**\n * Dispatch the accumulated frame. A frame with no `data:` field (an\n * `event:`/`id:`-only frame, or a bare comment) produces nothing, and\n * its field state is consumed and reset either way.\n */\n private dispatch(): SSEFrame | null {\n const dataLines = this.frameDataLines;\n const frame: SSEFrame | null = dataLines\n ? {\n event: detached(this.frameEvent),\n data: dataLines.join(\"\\n\"),\n id: this.frameId ? detached(this.frameId) : undefined,\n }\n : null;\n this.frameEvent = \"\";\n this.frameId = undefined;\n this.frameDataLines = null;\n return frame;\n }\n}\n\n/**\n * Serialize one frame to its wire form. Multi-line `data` becomes one\n * `data:` line per source line so the payload survives the trip without\n * premature frame dispatch.\n */\nexport function encodeSSEFrame(frame: {\n data: string;\n event?: string;\n id?: string;\n}): string {\n let out = \"\";\n if (frame.event !== undefined) out += `event: ${frame.event}\\n`;\n if (frame.id !== undefined) out += `id: ${frame.id}\\n`;\n for (const line of frame.data.split(\"\\n\")) out += `data: ${line}\\n`;\n return `${out}\\n`;\n}\n\n/**\n * Read a streaming SSE response body into parsed frames.\n *\n * @throws `TypeError` when the body is missing.\n * @throws `DOMException` with `name === \"AbortError\"` on cancellation —\n * including the WHATWG-fetch variant where an aborted body resolves\n * `done: true` instead of rejecting the read.\n */\nexport async function* streamSSEFrames(\n body: ReadableStream<Uint8Array> | null | undefined,\n options?: SSEStreamOptions,\n): AsyncGenerator<SSEFrame> {\n if (!body) {\n throw new TypeError(\"SSE stream has no response body\");\n }\n const reader = body.getReader();\n const decoder = new TextDecoder();\n const parser = new SSEFrameParser();\n const { signal, idleMs } = options ?? {};\n const read = (): Promise<ReadableStreamReadResult<Uint8Array>> => {\n const limit = idleMs?.();\n if (limit === undefined) return reader.read();\n let timer: ReturnType<typeof setTimeout> | undefined;\n return Promise.race([\n reader.read(),\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new SSEStreamSilentError(limit)),\n limit,\n );\n }),\n ]).finally(() => clearTimeout(timer));\n };\n let completed = false;\n try {\n for (;;) {\n if (signal?.aborted) {\n throw new DOMException(\"SSE stream aborted\", \"AbortError\");\n }\n const { done, value } = await read();\n if (done) {\n if (signal?.aborted) {\n throw new DOMException(\"SSE stream aborted\", \"AbortError\");\n }\n break;\n }\n for (const frame of parser.push(\n decoder.decode(value, { stream: true }),\n )) {\n yield frame;\n }\n }\n // A final decode() flushes a partial multi-byte character left in\n // the decoder; flush() then dispatches a dangling frame.\n for (const frame of parser.push(decoder.decode())) yield frame;\n for (const frame of parser.flush()) yield frame;\n completed = true;\n } finally {\n // Returning early (a consumer that breaks on a terminal event)\n // closes the generator but does not by itself cancel a locked fetch\n // body. Cancel before releasing so the connection is not leaked\n // until the server times out.\n if (!completed) await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n}\n\n// ============================================================================\n// JSON Event Parsing (chunked SSE)\n// ============================================================================\n\nexport interface SSEParserOptions<T = unknown> {\n /** Log malformed data lines (default: false) */\n logMalformed?: boolean;\n /** Custom data transformer */\n transform?: (raw: string) => T | null;\n}\n\nexport interface ParsedSSEEvent<T = unknown> {\n data: T;\n rawData: string;\n eventId?: string;\n eventType?: string;\n}\n\n/**\n * Incremental JSON event parser for chunked streams, framed by\n * {@link SSEFrameParser}. Feed decoded string chunks via push(); call flush()\n * on stream end. Frames whose trimmed data is empty produce no event.\n */\nexport class SSEChunkParser<T = unknown> {\n private readonly frames = new SSEFrameParser();\n private readonly options: SSEParserOptions<T>;\n\n constructor(options: SSEParserOptions<T> = {}) {\n this.options = options;\n }\n\n push(chunk: string): ParsedSSEEvent<T>[] {\n return this.toEvents(this.frames.push(chunk));\n }\n\n flush(): ParsedSSEEvent<T>[] {\n return this.toEvents(this.frames.flush());\n }\n\n private toEvents(frames: SSEFrame[]): ParsedSSEEvent<T>[] {\n const events: ParsedSSEEvent<T>[] = [];\n for (const frame of frames) {\n const event = this.toEvent(frame);\n if (event) events.push(event);\n }\n return events;\n }\n\n private toEvent(frame: SSEFrame): ParsedSSEEvent<T> | null {\n const rawData = frame.data.trim();\n if (!rawData) {\n return null;\n }\n\n let data: T;\n if (this.options.transform) {\n const transformed = this.options.transform(rawData);\n if (transformed === null) {\n if (this.options.logMalformed) {\n console.warn(\"SSE transform returned null:\", rawData.slice(0, 100));\n }\n return null;\n }\n data = transformed;\n } else {\n try {\n data = JSON.parse(rawData) as T;\n } catch {\n if (this.options.logMalformed) {\n console.warn(\"SSE parse failed:\", rawData.slice(0, 100));\n }\n data = rawData as unknown as T;\n }\n }\n\n return {\n data,\n rawData,\n eventId: frame.id,\n eventType: frame.event || undefined,\n };\n }\n}\n\n/**\n * Parse SSE stream into typed events via async iteration.\n */\nexport async function* parseSSEStream<T = unknown>(\n stream: ReadableStream<Uint8Array>,\n options: SSEParserOptions<T> = {},\n): AsyncGenerator<ParsedSSEEvent<T>> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n const parser = new SSEChunkParser<T>(options);\n let chunkCount = 0;\n let totalBytes = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n console.log(\n `[agent-core:sse] Stream reader done after ${chunkCount} chunks, ${totalBytes} bytes`,\n );\n break;\n }\n\n chunkCount++;\n totalBytes += value?.length ?? 0;\n const chunk = decoder.decode(value, { stream: true });\n\n // Log every chunk for debugging\n if (chunkCount <= 3 || chunkCount % 10 === 0) {\n console.log(\n `[agent-core:sse] Chunk ${chunkCount}: ${value?.length ?? 0} bytes, preview: ${chunk.slice(0, 100).replace(/\\n/g, \"\\\\n\")}`,\n );\n }\n\n for (const event of parser.push(chunk)) {\n yield event;\n }\n }\n\n for (const event of parser.flush()) {\n yield event;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Consume SSE stream with callbacks.\n */\nexport async function consumeSSEStream<T = unknown>(\n stream: ReadableStream<Uint8Array>,\n onEvent: (event: ParsedSSEEvent<T>) => void,\n onError: (error: Error) => void,\n options: SSEParserOptions<T> = {},\n): Promise<void> {\n let eventCount = 0;\n try {\n for await (const event of parseSSEStream<T>(stream, options)) {\n eventCount++;\n onEvent(event);\n }\n // Stream finished normally\n console.log(\n `[agent-core:sse] Stream ended normally, total events: ${eventCount}`,\n );\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error));\n const isAbortError =\n normalizedError.name === \"AbortError\" ||\n normalizedError.message.toLowerCase().includes(\"abort\");\n\n if (isAbortError) {\n console.log(\n `[agent-core:sse] Stream aborted normally after ${eventCount} events`,\n );\n onError(normalizedError);\n return;\n }\n\n console.error(\n `[agent-core:sse] Stream error after ${eventCount} events:`,\n normalizedError,\n );\n onError(normalizedError);\n }\n}\n\n// ============================================================================\n// Stream Usage Extraction\n// ============================================================================\n\n/** Token usage extracted from stream */\nexport interface StreamTokenUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n}\n\n/** Accumulated usage from a stream session */\nexport interface StreamUsageAccumulator {\n /** Total tokens from all events */\n usage: StreamTokenUsage;\n /** Number of LLM response events */\n responseCount: number;\n /** Number of tool calls */\n toolCallCount: number;\n /** Model ID if detected from events */\n modelId?: string;\n /** Start time of accumulation */\n startedAt: Date;\n /** Duration in ms (set on finalize) */\n durationMs?: number;\n}\n\n/** Event types that contain token information (Anthropic SDK format) */\nconst TOKEN_EVENTS = new Set([\"message.stop\", \"content_block_stop\"]);\n\n/** Event types for tool calls - now handled via message.part.updated */\nconst _TOOL_CALL_EVENTS = new Set<string>();\n\n/**\n * StreamUsageExtractor - Accumulates token usage from SSE events.\n *\n * Usage:\n * ```typescript\n * const extractor = createStreamUsageExtractor();\n *\n * // In your stream callback\n * for await (const event of stream) {\n * extractor.processEvent(event);\n * // ... handle event\n * }\n *\n * const usage = extractor.finalize();\n * billingService.recordUsage({ usage: usage.usage, ... });\n * ```\n */\nexport class StreamUsageExtractor {\n private accumulator: StreamUsageAccumulator;\n private finalized = false;\n\n constructor() {\n this.accumulator = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n },\n responseCount: 0,\n toolCallCount: 0,\n startedAt: new Date(),\n };\n }\n\n /**\n * Process a raw SSE string (event: xxx\\ndata: {...}).\n */\n processRawSSE(raw: string): void {\n const parsed = parseSSEData(raw);\n if (parsed) {\n this.processEvent(parsed);\n }\n }\n\n /**\n * Process a parsed SSE event object.\n */\n processEvent(event: SSEEventData | Record<string, unknown>): void {\n if (this.finalized) {\n throw new Error(\"StreamUsageExtractor already finalized\");\n }\n\n const eventType = (event as SSEEventData).type ?? event.type;\n\n // Handle message.part.updated (canonical event for all part types)\n if (eventType === \"message.part.updated\") {\n const part = (event as { part?: { type?: string; tool?: string } }).part;\n const partType = part?.type;\n\n // Text and reasoning parts count as LLM responses\n if (partType === \"text\" || partType === \"reasoning\") {\n this.extractTokens(event);\n this.accumulator.responseCount++;\n }\n\n // Tool parts count as tool calls\n if (partType === \"tool\" && part?.tool) {\n this.accumulator.toolCallCount++;\n }\n\n return;\n }\n\n // Extract token counts from Anthropic SDK format events\n if (TOKEN_EVENTS.has(eventType as string)) {\n this.extractTokens(event);\n this.accumulator.responseCount++;\n }\n\n // Extract model ID if present\n if (!this.accumulator.modelId && event.model) {\n this.accumulator.modelId = String(event.model);\n }\n\n // Handle usage summary events (some providers send final usage)\n if (eventType === \"usage\" || event.usage) {\n this.extractUsageSummary(event);\n }\n\n // Handle message_stop with usage (Anthropic format)\n if (eventType === \"message_stop\" && event.usage) {\n this.extractUsageSummary(event);\n }\n }\n\n /**\n * Finalize and return accumulated usage.\n * Sets duration and marks extractor as complete.\n */\n finalize(): StreamUsageAccumulator {\n if (this.finalized) {\n return this.accumulator;\n }\n\n this.finalized = true;\n this.accumulator.durationMs =\n Date.now() - this.accumulator.startedAt.getTime();\n\n return this.accumulator;\n }\n\n /**\n * Get current accumulated usage (without finalizing).\n */\n current(): Readonly<StreamUsageAccumulator> {\n return this.accumulator;\n }\n\n /**\n * Reset the extractor for reuse.\n */\n reset(): void {\n this.accumulator = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n },\n responseCount: 0,\n toolCallCount: 0,\n startedAt: new Date(),\n };\n this.finalized = false;\n }\n\n private extractTokens(event: Record<string, unknown>): void {\n // Direct token count (simple format)\n if (typeof event.tokens === \"number\") {\n this.accumulator.usage.outputTokens += event.tokens;\n }\n\n // Output tokens (explicit)\n if (typeof event.outputTokens === \"number\") {\n this.accumulator.usage.outputTokens += event.outputTokens;\n }\n if (typeof event.output_tokens === \"number\") {\n this.accumulator.usage.outputTokens += event.output_tokens;\n }\n\n // Input tokens (usually from final summary)\n if (typeof event.inputTokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n event.inputTokens,\n );\n }\n if (typeof event.input_tokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n event.input_tokens,\n );\n }\n\n // Cache tokens (Anthropic format)\n if (typeof event.cache_read_input_tokens === \"number\") {\n this.accumulator.usage.cacheReadTokens =\n (this.accumulator.usage.cacheReadTokens ?? 0) +\n event.cache_read_input_tokens;\n }\n if (typeof event.cache_creation_input_tokens === \"number\") {\n this.accumulator.usage.cacheWriteTokens =\n (this.accumulator.usage.cacheWriteTokens ?? 0) +\n event.cache_creation_input_tokens;\n }\n }\n\n private extractUsageSummary(event: Record<string, unknown>): void {\n const usage = (event.usage as Record<string, unknown>) ?? event;\n\n if (typeof usage.input_tokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n usage.input_tokens,\n );\n }\n if (typeof usage.output_tokens === \"number\") {\n this.accumulator.usage.outputTokens = Math.max(\n this.accumulator.usage.outputTokens,\n usage.output_tokens,\n );\n }\n if (typeof usage.cache_read_input_tokens === \"number\") {\n this.accumulator.usage.cacheReadTokens = usage.cache_read_input_tokens;\n }\n if (typeof usage.cache_creation_input_tokens === \"number\") {\n this.accumulator.usage.cacheWriteTokens =\n usage.cache_creation_input_tokens;\n }\n }\n}\n\n/**\n * Create a new StreamUsageExtractor.\n */\nexport function createStreamUsageExtractor(): StreamUsageExtractor {\n return new StreamUsageExtractor();\n}\n\n/**\n * Create a callback wrapper that extracts usage while forwarding events.\n *\n * Usage:\n * ```typescript\n * const { callback, getUsage } = createUsageCallback((event) => {\n * // your event handler\n * });\n *\n * await orchestrator.executeAgentStream(projectRef, request, callback);\n * const usage = getUsage();\n * ```\n */\nexport function createUsageCallback<T extends Record<string, unknown>>(\n forward?: (event: T) => void,\n): {\n callback: (event: T) => void;\n getUsage: () => StreamUsageAccumulator;\n extractor: StreamUsageExtractor;\n} {\n const extractor = createStreamUsageExtractor();\n\n return {\n callback: (event: T) => {\n extractor.processEvent(event);\n forward?.(event);\n },\n getUsage: () => extractor.finalize(),\n extractor,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/sse/index.ts"],"sourcesContent":["/**\n * SSE Utilities\n *\n * The one Server-Sent Events wire-format implementation: frame parsing and\n * framing, plus the JSON event layer and stream usage extraction built on it.\n * Everything here is pure ECMAScript over `ReadableStream`/`TextDecoder`\n * globals and imports nothing, so Node, browser and Cloudflare Worker bundles\n * can take this subpath without pulling a dependency.\n *\n * Frame parser guarantees (WHATWG Server-Sent Events, §9.2 \"Interpreting an\n * event stream\"):\n * - Buffers frames that straddle chunk boundaries; `push()` emits only\n * complete frames and `flush()` dispatches a trailing frame whose\n * terminating blank line never arrived.\n * - Accepts LF and CRLF line terminators interchangeably. Bare CR is\n * intentionally not treated as a terminator: no known emitter uses\n * it, and supporting it would risk splitting payloads that\n * legitimately contain CR.\n * - Joins multi-line `data:` fields with \"\\n\" (§9.2.6 step 3).\n * - Skips comment lines (a leading \":\") — the heartbeat mechanism.\n * - Ignores `retry:` and unrecognized fields.\n * - Strips one leading U+FEFF (BOM) from the stream.\n * - Applies the `event:`/`id:` state of a frame only to that frame.\n *\n * The frame layer does no JSON parsing, reconnect/`Last-Event-ID` tracking or\n * event-type filtering; `SSEChunkParser` adds JSON payload handling on top.\n */\n\n/** Raw SSE event data from stream */\nexport interface SSEEventData {\n type: string;\n // Tool events\n toolName?: string;\n name?: string;\n callId?: string;\n input?: unknown;\n arguments?: unknown;\n output?: unknown;\n result?: unknown;\n durationMs?: number;\n // Error events\n error?: string;\n message?: string;\n code?: string;\n // Status events\n status?: string;\n // Token/LLM events\n tokens?: number;\n model?: string;\n // Usage fields (Anthropic format)\n inputTokens?: number;\n outputTokens?: number;\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n // Usage object (some providers wrap in usage field)\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n // Generic\n [key: string]: unknown;\n}\n\n/**\n * Parse raw SSE data string into structured event data.\n *\n * Handles the standard SSE format:\n * ```\n * event: eventType\n * data: {\"json\": \"payload\"}\n * ```\n */\nexport function parseSSEData(rawData: string): SSEEventData | null {\n try {\n const lines = rawData.split(\"\\n\");\n let eventType: string | undefined;\n let data: string | undefined;\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n eventType = line.slice(7).trim();\n } else if (line.startsWith(\"data: \")) {\n data = line.slice(6);\n }\n }\n\n if (data) {\n const parsed = JSON.parse(data);\n return {\n type: eventType || parsed.type || \"unknown\",\n ...parsed,\n };\n }\n } catch {\n // Ignore parse errors (heartbeats, malformed events)\n }\n return null;\n}\n\n// ============================================================================\n// Frame Parsing and Framing\n// ============================================================================\n\n/**\n * One parsed SSE frame: the fields accumulated between blank lines.\n *\n * `event` and `id` share no memory with the chunk the frame arrived in, so a\n * consumer can keep them as long as it keeps its events. `data` can share\n * that memory: parse it, and keep what the parse returns.\n */\nexport interface SSEFrame {\n /** Value of the `event:` field; \"\" when the frame carries none. */\n event: string;\n /** `data:` field lines joined with \"\\n\". */\n data: string;\n /** Value of the `id:` field; undefined when the frame carries none. */\n id?: string;\n}\n\n/**\n * A copy of `value` that shares no memory with the string it was cut from.\n *\n * V8 can return a short substring of a long string as a view that keeps the\n * whole string alive. Consumers keep event names and ids as long as they keep\n * events, so a view would keep every chunk its frame arrived in alive.\n */\nfunction detached(value: string): string {\n return value === \"\" ? value : (JSON.parse(JSON.stringify(value)) as string);\n}\n\n/** Options for {@link streamSSEFrames}. */\nexport interface SSEStreamOptions {\n /**\n * Aborts the read loop. When the signal fires, the generator throws a\n * `DOMException` named \"AbortError\" so consumers can distinguish a\n * cancellation from a clean end-of-stream without inspecting signal\n * state.\n */\n signal?: AbortSignal;\n /**\n * The longest silence the stream may keep, read before each chunk read.\n * Any bytes reset it, comment heartbeats included, because a heartbeat is\n * the producer saying the connection is still live. A read that outlasts it\n * throws {@link SSEStreamSilentError}. `undefined` waits without bound, which\n * suits a producer that promises no heartbeat.\n */\n idleMs?: () => number | undefined;\n}\n\n/**\n * Thrown when a stream sends no bytes for longer than its `idleMs`. A\n * producer that heartbeats would have written in that time, so the\n * connection is dead even though it never closed: a half-open TCP connection\n * or a stalled proxy hold the socket open while nothing arrives.\n */\nexport class SSEStreamSilentError extends Error {\n override readonly name = \"SSEStreamSilentError\";\n readonly idleMs: number;\n constructor(idleMs: number) {\n super(`SSE stream sent no bytes for ${idleMs} ms`);\n this.idleMs = idleMs;\n }\n}\n\nexport type SSEFragment =\n | {\n type: \"field\";\n field: string | undefined;\n prefix: string;\n }\n | { type: \"value\"; value: string }\n | { type: \"line-end\" | \"frame-end\"; ending: string };\n\n/**\n * Incremental SSE frame parser. Feed decoded text via push(); call flush()\n * when the stream ends. Complete frames and bounded fragments share one\n * scanner and one parser lifetime.\n */\nexport class SSEFrameParser {\n private prefix = \"\";\n private started = false;\n private cr = false;\n private bomChecked = false;\n\n /**\n * Yield raw field prefixes, values and line/frame endings without retaining\n * a complete line or frame. Comments and unknown fields survive; one initial\n * BOM is consumed and CRLF remains intact. Pass final=true once at EOF.\n * Drain each generator before supplying the next chunk. An instance owns\n * either fragments or push/flush; mixing those modes loses collected state.\n */\n *fragments(chunk: string, final = false): Generator<SSEFragment> {\n let text = chunk;\n if (!this.bomChecked && text.length) {\n this.bomChecked = true;\n if (text.charCodeAt(0) === 0xfeff) text = text.slice(1);\n }\n if (this.cr) {\n text = `\\r${text}`;\n this.cr = false;\n }\n let cursor = 0;\n for (;;) {\n const newline = text.indexOf(\"\\n\", cursor);\n const end = newline < 0 ? text.length : newline;\n const trailingCr = text[end - 1] === \"\\r\" && end > cursor;\n const value = text.slice(cursor, trailingCr ? end - 1 : end);\n yield* this.value(value);\n if (newline < 0) {\n this.cr = trailingCr;\n break;\n }\n yield* this.end(trailingCr ? \"\\r\\n\" : \"\\n\");\n cursor = newline + 1;\n }\n if (final) {\n if (this.started || this.prefix || this.cr)\n yield* this.end(this.cr ? \"\\r\" : \"\");\n yield { type: \"frame-end\", ending: \"\" };\n this.cr = false;\n }\n }\n\n private *value(value: string): Generator<SSEFragment> {\n let cursor = 0;\n while (!this.started && cursor < value.length) {\n const next = value[cursor++]!;\n if (this.prefix.endsWith(\":\")) {\n // The spec removes one optional leading space from the field value.\n const prefix = this.prefix + (next === \" \" ? next : \"\");\n yield { type: \"field\", field: this.prefix.slice(0, -1), prefix };\n this.started = true;\n if (next !== \" \") cursor--;\n } else {\n this.prefix += next;\n if (\n ![\"data:\", \"event:\", \"id:\", \"retry:\"].some((name) =>\n name.startsWith(this.prefix),\n )\n ) {\n yield { type: \"field\", field: undefined, prefix: this.prefix };\n this.started = true;\n }\n }\n }\n if (cursor < value.length)\n yield { type: \"value\", value: value.slice(cursor) };\n }\n\n private *end(ending: string): Generator<SSEFragment> {\n if (!this.started && this.prefix)\n yield {\n type: \"field\",\n field: this.prefix.endsWith(\":\") ? this.prefix.slice(0, -1) : undefined,\n prefix: this.prefix,\n };\n yield {\n type: this.started || this.prefix ? \"line-end\" : \"frame-end\",\n ending,\n };\n this.prefix = \"\";\n this.started = false;\n }\n\n private field: string | undefined;\n private line = \"\";\n private frameEvent = \"\";\n private frameId: string | undefined;\n private frameDataLines: string[] | null = null;\n\n /**\n * Append a decoded text chunk and return every frame the chunk\n * completes. Frame state survives across calls: one frame can\n * straddle any number of chunks.\n */\n push(chunk: string): SSEFrame[] {\n return this.collect(this.fragments(chunk));\n }\n /**\n * End-of-stream: process the unterminated trailing line, then\n * dispatch a dangling frame that never got its blank line.\n * Well-behaved servers end frames with \"\\n\\n\", but some close early.\n */\n flush(): SSEFrame[] {\n return this.collect(this.fragments(\"\", true));\n }\n\n private collect(fragments: Iterable<SSEFragment>): SSEFrame[] {\n const frames: SSEFrame[] = [];\n for (const fragment of fragments) {\n // Comment and unknown-field fragments survive raw mode, but carry no frame payload.\n if (fragment.type === \"field\") this.field = fragment.field;\n else if (fragment.type === \"value\" && this.field !== undefined)\n this.line += fragment.value;\n else if (fragment.type === \"line-end\") {\n if (this.field === \"data\") (this.frameDataLines ??= []).push(this.line);\n if (this.field === \"event\") this.frameEvent = this.line.trim();\n if (this.field === \"id\") this.frameId = this.line.trim();\n this.field = undefined;\n this.line = \"\";\n } else if (fragment.type === \"frame-end\") {\n const frame = this.dispatch();\n if (frame) frames.push(frame);\n }\n }\n return frames;\n }\n\n /**\n * Dispatch the accumulated frame. A frame with no `data:` field (an\n * `event:`/`id:`-only frame, or a bare comment) produces nothing, and\n * its field state is consumed and reset either way.\n */\n private dispatch(): SSEFrame | null {\n const frame = this.frameDataLines\n ? {\n event: detached(this.frameEvent),\n data: this.frameDataLines.join(\"\\n\"),\n id: this.frameId ? detached(this.frameId) : undefined,\n }\n : null;\n this.frameEvent = \"\";\n this.frameId = undefined;\n this.frameDataLines = null;\n return frame;\n }\n}\n\n/**\n * Serialize one frame to its wire form. Multi-line `data` becomes one\n * `data:` line per source line so the payload survives the trip without\n * premature frame dispatch.\n */\nexport function encodeSSEFrame(frame: {\n data: string;\n event?: string;\n id?: string;\n}): string {\n let out = \"\";\n if (frame.event !== undefined) out += `event: ${frame.event}\\n`;\n if (frame.id !== undefined) out += `id: ${frame.id}\\n`;\n for (const line of frame.data.split(\"\\n\")) out += `data: ${line}\\n`;\n return `${out}\\n`;\n}\n\n/**\n * Read a streaming SSE response body into parsed frames.\n *\n * @throws `TypeError` when the body is missing.\n * @throws `DOMException` with `name === \"AbortError\"` on cancellation —\n * including the WHATWG-fetch variant where an aborted body resolves\n * `done: true` instead of rejecting the read.\n */\nexport async function* streamSSEFrames(\n body: ReadableStream<Uint8Array> | null | undefined,\n options?: SSEStreamOptions,\n): AsyncGenerator<SSEFrame> {\n if (!body) {\n throw new TypeError(\"SSE stream has no response body\");\n }\n const reader = body.getReader();\n const decoder = new TextDecoder();\n const parser = new SSEFrameParser();\n const { signal, idleMs } = options ?? {};\n const read = (): Promise<ReadableStreamReadResult<Uint8Array>> => {\n const limit = idleMs?.();\n if (limit === undefined) return reader.read();\n let timer: ReturnType<typeof setTimeout> | undefined;\n return Promise.race([\n reader.read(),\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new SSEStreamSilentError(limit)),\n limit,\n );\n }),\n ]).finally(() => clearTimeout(timer));\n };\n let completed = false;\n try {\n for (;;) {\n if (signal?.aborted) {\n throw new DOMException(\"SSE stream aborted\", \"AbortError\");\n }\n const { done, value } = await read();\n if (done) {\n if (signal?.aborted) {\n throw new DOMException(\"SSE stream aborted\", \"AbortError\");\n }\n break;\n }\n for (const frame of parser.push(\n decoder.decode(value, { stream: true }),\n )) {\n yield frame;\n }\n }\n // A final decode() flushes a partial multi-byte character left in\n // the decoder; flush() then dispatches a dangling frame.\n for (const frame of parser.push(decoder.decode())) yield frame;\n for (const frame of parser.flush()) yield frame;\n completed = true;\n } finally {\n // Returning early (a consumer that breaks on a terminal event)\n // closes the generator but does not by itself cancel a locked fetch\n // body. Cancel before releasing so the connection is not leaked\n // until the server times out.\n if (!completed) await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n}\n\n// ============================================================================\n// JSON Event Parsing (chunked SSE)\n// ============================================================================\n\nexport interface SSEParserOptions<T = unknown> {\n /** Log malformed data lines (default: false) */\n logMalformed?: boolean;\n /** Custom data transformer */\n transform?: (raw: string) => T | null;\n}\n\nexport interface ParsedSSEEvent<T = unknown> {\n data: T;\n rawData: string;\n eventId?: string;\n eventType?: string;\n}\n\n/**\n * Incremental JSON event parser for chunked streams, framed by\n * {@link SSEFrameParser}. Feed decoded string chunks via push(); call flush()\n * on stream end. Frames whose trimmed data is empty produce no event.\n */\nexport class SSEChunkParser<T = unknown> {\n private readonly frames = new SSEFrameParser();\n constructor(private readonly options: SSEParserOptions<T> = {}) {}\n\n push(chunk: string): ParsedSSEEvent<T>[] {\n return this.toEvents(this.frames.push(chunk));\n }\n\n flush(): ParsedSSEEvent<T>[] {\n return this.toEvents(this.frames.flush());\n }\n\n private toEvents(frames: SSEFrame[]): ParsedSSEEvent<T>[] {\n return frames.flatMap((frame) => toSSEEvents(frame, this.options));\n }\n}\n\nfunction toSSEEvents<T>(\n frame: SSEFrame,\n options: SSEParserOptions<T>,\n): ParsedSSEEvent<T>[] {\n const rawData = frame.data.trim();\n if (!rawData) {\n return [];\n }\n\n let data: T;\n if (options.transform) {\n const transformed = options.transform(rawData);\n if (transformed === null) {\n if (options.logMalformed) {\n console.warn(\"SSE transform returned null:\", rawData.slice(0, 100));\n }\n return [];\n }\n data = transformed;\n } else {\n try {\n data = JSON.parse(rawData) as T;\n } catch {\n if (options.logMalformed) {\n console.warn(\"SSE parse failed:\", rawData.slice(0, 100));\n }\n data = rawData as unknown as T;\n }\n }\n\n return [\n { data, rawData, eventId: frame.id, eventType: frame.event || undefined },\n ];\n}\n\n/**\n * Parse SSE stream into typed events via async iteration.\n */\nexport async function* parseSSEStream<T = unknown>(\n stream: ReadableStream<Uint8Array>,\n options: SSEParserOptions<T> = {},\n): AsyncGenerator<ParsedSSEEvent<T>> {\n for await (const frame of streamSSEFrames(stream)) {\n yield* toSSEEvents(frame, options);\n }\n}\n\n/**\n * Consume SSE stream with callbacks.\n */\nexport async function consumeSSEStream<T = unknown>(\n stream: ReadableStream<Uint8Array>,\n onEvent: (event: ParsedSSEEvent<T>) => void,\n onError: (error: Error) => void,\n options: SSEParserOptions<T> = {},\n): Promise<void> {\n try {\n for await (const event of parseSSEStream<T>(stream, options))\n onEvent(event);\n } catch (error) {\n onError(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n// ============================================================================\n// Stream Usage Extraction\n// ============================================================================\n\n/** Token usage extracted from stream */\nexport interface StreamTokenUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n}\n\n/** Accumulated usage from a stream session */\nexport interface StreamUsageAccumulator {\n /** Total tokens from all events */\n usage: StreamTokenUsage;\n /** Number of LLM response events */\n responseCount: number;\n /** Number of tool calls */\n toolCallCount: number;\n /** Model ID if detected from events */\n modelId?: string;\n /** Start time of accumulation */\n startedAt: Date;\n /** Duration in ms (set on finalize) */\n durationMs?: number;\n}\n\n/** Event types that contain token information (Anthropic SDK format) */\nconst TOKEN_EVENTS = new Set([\"message.stop\", \"content_block_stop\"]);\n\n/**\n * StreamUsageExtractor - Accumulates token usage from SSE events.\n *\n * Usage:\n * ```typescript\n * const extractor = createStreamUsageExtractor();\n *\n * // In your stream callback\n * for await (const event of stream) {\n * extractor.processEvent(event);\n * // ... handle event\n * }\n *\n * const usage = extractor.finalize();\n * billingService.recordUsage({ usage: usage.usage, ... });\n * ```\n */\nexport class StreamUsageExtractor {\n private accumulator: StreamUsageAccumulator;\n private finalized = false;\n\n constructor() {\n this.accumulator = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n },\n responseCount: 0,\n toolCallCount: 0,\n startedAt: new Date(),\n };\n }\n\n /**\n * Process a raw SSE string (event: xxx\\ndata: {...}).\n */\n processRawSSE(raw: string): void {\n const parsed = parseSSEData(raw);\n if (parsed) {\n this.processEvent(parsed);\n }\n }\n\n /**\n * Process a parsed SSE event object.\n */\n processEvent(event: SSEEventData | Record<string, unknown>): void {\n if (this.finalized) {\n throw new Error(\"StreamUsageExtractor already finalized\");\n }\n\n const eventType = (event as SSEEventData).type ?? event.type;\n\n // Handle message.part.updated (canonical event for all part types)\n if (eventType === \"message.part.updated\") {\n const part = (event as { part?: { type?: string; tool?: string } }).part;\n const partType = part?.type;\n\n // Text and reasoning parts count as LLM responses\n if (partType === \"text\" || partType === \"reasoning\") {\n this.extractTokens(event);\n this.accumulator.responseCount++;\n }\n\n // Tool parts count as tool calls\n if (partType === \"tool\" && part?.tool) {\n this.accumulator.toolCallCount++;\n }\n\n return;\n }\n\n // Extract token counts from Anthropic SDK format events\n if (TOKEN_EVENTS.has(eventType as string)) {\n this.extractTokens(event);\n this.accumulator.responseCount++;\n }\n\n // Extract model ID if present\n if (!this.accumulator.modelId && event.model) {\n this.accumulator.modelId = String(event.model);\n }\n\n // Handle usage summary events (some providers send final usage)\n if (eventType === \"usage\" || event.usage) {\n this.extractUsageSummary(event);\n }\n\n // Handle message_stop with usage (Anthropic format)\n if (eventType === \"message_stop\" && event.usage) {\n this.extractUsageSummary(event);\n }\n }\n\n /**\n * Finalize and return accumulated usage.\n * Sets duration and marks extractor as complete.\n */\n finalize(): StreamUsageAccumulator {\n if (this.finalized) {\n return this.accumulator;\n }\n\n this.finalized = true;\n this.accumulator.durationMs =\n Date.now() - this.accumulator.startedAt.getTime();\n\n return this.accumulator;\n }\n\n /**\n * Get current accumulated usage (without finalizing).\n */\n current(): Readonly<StreamUsageAccumulator> {\n return this.accumulator;\n }\n\n /**\n * Reset the extractor for reuse.\n */\n reset(): void {\n this.accumulator = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n },\n responseCount: 0,\n toolCallCount: 0,\n startedAt: new Date(),\n };\n this.finalized = false;\n }\n\n private extractTokens(event: Record<string, unknown>): void {\n // Direct token count (simple format)\n if (typeof event.tokens === \"number\") {\n this.accumulator.usage.outputTokens += event.tokens;\n }\n\n // Output tokens (explicit)\n if (typeof event.outputTokens === \"number\") {\n this.accumulator.usage.outputTokens += event.outputTokens;\n }\n if (typeof event.output_tokens === \"number\") {\n this.accumulator.usage.outputTokens += event.output_tokens;\n }\n\n // Input tokens (usually from final summary)\n if (typeof event.inputTokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n event.inputTokens,\n );\n }\n if (typeof event.input_tokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n event.input_tokens,\n );\n }\n\n // Cache tokens (Anthropic format)\n if (typeof event.cache_read_input_tokens === \"number\") {\n this.accumulator.usage.cacheReadTokens =\n (this.accumulator.usage.cacheReadTokens ?? 0) +\n event.cache_read_input_tokens;\n }\n if (typeof event.cache_creation_input_tokens === \"number\") {\n this.accumulator.usage.cacheWriteTokens =\n (this.accumulator.usage.cacheWriteTokens ?? 0) +\n event.cache_creation_input_tokens;\n }\n }\n\n private extractUsageSummary(event: Record<string, unknown>): void {\n const usage = (event.usage as Record<string, unknown>) ?? event;\n\n if (typeof usage.input_tokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n usage.input_tokens,\n );\n }\n if (typeof usage.output_tokens === \"number\") {\n this.accumulator.usage.outputTokens = Math.max(\n this.accumulator.usage.outputTokens,\n usage.output_tokens,\n );\n }\n if (typeof usage.cache_read_input_tokens === \"number\") {\n this.accumulator.usage.cacheReadTokens = usage.cache_read_input_tokens;\n }\n if (typeof usage.cache_creation_input_tokens === \"number\") {\n this.accumulator.usage.cacheWriteTokens =\n usage.cache_creation_input_tokens;\n }\n }\n}\n\n/**\n * Create a new StreamUsageExtractor.\n */\nexport function createStreamUsageExtractor(): StreamUsageExtractor {\n return new StreamUsageExtractor();\n}\n\n/**\n * Create a callback wrapper that extracts usage while forwarding events.\n *\n * Usage:\n * ```typescript\n * const { callback, getUsage } = createUsageCallback((event) => {\n * // your event handler\n * });\n *\n * await orchestrator.executeAgentStream(projectRef, request, callback);\n * const usage = getUsage();\n * ```\n */\nexport function createUsageCallback<T extends Record<string, unknown>>(\n forward?: (event: T) => void,\n): {\n callback: (event: T) => void;\n getUsage: () => StreamUsageAccumulator;\n extractor: StreamUsageExtractor;\n} {\n const extractor = createStreamUsageExtractor();\n\n return {\n callback: (event: T) => {\n extractor.processEvent(event);\n forward?.(event);\n },\n getUsage: () => extractor.finalize(),\n extractor,\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-core",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Core transport interfaces, event utilities, auth, SSE parsing, and resilience primitives for talking to Tangle agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -154,7 +154,7 @@
154
154
  "LICENSE"
155
155
  ],
156
156
  "dependencies": {
157
- "@tangle-network/agent-interface": "^5.8.1",
157
+ "@tangle-network/agent-interface": "^5.10.2",
158
158
  "@tangle-network/agent-trace-contract": "^2.1.0",
159
159
  "zod": "^4.6.6"
160
160
  },