@tangle-network/agent-core 1.0.1 → 1.0.3

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,30 +1,36 @@
1
1
  /**
2
- * SSE Utilities
3
- *
4
- * The one Server-Sent Events wire-format implementation: frame parsing and
5
- * framing, plus the JSON event layer and stream usage extraction built on it.
6
- * Everything here is pure ECMAScript over `ReadableStream`/`TextDecoder`
7
- * globals and imports nothing, so Node, browser and Cloudflare Worker bundles
8
- * can take this subpath without pulling a dependency.
9
- *
10
- * Frame parser guarantees (WHATWG Server-Sent Events, §9.2 "Interpreting an
11
- * event stream"):
12
- * - Buffers frames that straddle chunk boundaries; `push()` emits only
13
- * complete frames and `flush()` dispatches a trailing frame whose
14
- * terminating blank line never arrived.
15
- * - Accepts LF and CRLF line terminators interchangeably. Bare CR is
16
- * intentionally not treated as a terminator: no known emitter uses
17
- * it, and supporting it would risk splitting payloads that
18
- * legitimately contain CR.
19
- * - Joins multi-line `data:` fields with "\n" (§9.2.6 step 3).
20
- * - Skips comment lines (a leading ":") — the heartbeat mechanism.
21
- * - Ignores `retry:` and unrecognized fields.
22
- * - Strips one leading U+FEFF (BOM) from the stream.
23
- * - Applies the `event:`/`id:` state of a frame only to that frame.
24
- *
25
- * The frame layer does no JSON parsing, reconnect/`Last-Event-ID` tracking or
26
- * event-type filtering; `SSEChunkParser` adds JSON payload handling on top.
2
+ * Part-text suffix coding. A harness restates a text or reasoning part's whole
3
+ * text on every `message.part.updated` frame, so an N-byte part costs O(N²)
4
+ * bytes to store or send (a Discovery root stream: 209 KB per 106-byte line).
5
+ * A `PartTextCoder` carries a frame whose text extends the part's last text in
6
+ * its stream as the suffix alone. A client asking with `?partText=suffix` gets
7
+ * frames marked `"partText": "suffix" | "full"`; `decodePartTextFrame` restores them.
27
8
  */
9
+ /** The query parameter that asks for suffixes, and the field a coded frame carries. */
10
+ export declare const PART_TEXT = "partText";
11
+ export declare const PART_TEXT_SUFFIX = "suffix";
12
+ export type PartTextMode = typeof PART_TEXT_SUFFIX | "full";
13
+ type Part = Record<string, unknown>;
14
+ /** A frame's part, in either producer shape: `{ part }` or `{ properties: { part } }`. */
15
+ export declare function locatePart(data: unknown): Part | null;
16
+ /** A frame's text or reasoning part with a string id and text. */
17
+ export declare function growingTextPart(data: unknown): (Part & {
18
+ id: string;
19
+ text: string;
20
+ }) | null;
21
+ /** What one stream has carried for each part. */
22
+ export declare class PartTextCoder {
23
+ #private;
24
+ /** Records `text`; the suffix to carry when it extends the part's last text. */
25
+ encode(id: string, text: string): string | undefined;
26
+ /** Records and returns the whole text a carried value stands for. */
27
+ decode(id: string, mode: PartTextMode, carried: string): string;
28
+ text(id: string): string | undefined;
29
+ }
30
+ /** One outgoing frame's JSON data, suffix-coded and marked; other frames pass. */
31
+ export declare function encodePartTextFrame(coder: PartTextCoder, event: string | undefined, data: string): string;
32
+ /** Restores a parsed frame `encodePartTextFrame` marked, in place; others pass. */
33
+ export declare function decodePartTextFrame(coder: PartTextCoder, frame: Part): void;
28
34
  /** Raw SSE event data from stream */
29
35
  export interface SSEEventData {
30
36
  type: string;
@@ -110,16 +116,42 @@ export declare class SSEStreamSilentError extends Error {
110
116
  readonly idleMs: number;
111
117
  constructor(idleMs: number);
112
118
  }
119
+ export type SSEFragment = {
120
+ type: "field";
121
+ field: string | undefined;
122
+ prefix: string;
123
+ } | {
124
+ type: "value";
125
+ value: string;
126
+ } | {
127
+ type: "line-end" | "frame-end";
128
+ ending: string;
129
+ };
113
130
  /**
114
- * Incremental SSE frame parser. Feed decoded text via `push()`; call
115
- * `flush()` when the stream ends.
131
+ * Incremental SSE frame parser. Feed decoded text via push(); call flush()
132
+ * when the stream ends. Complete frames and bounded fragments share one
133
+ * scanner and one parser lifetime.
116
134
  */
117
135
  export declare class SSEFrameParser {
118
- private buffer;
136
+ private prefix;
137
+ private started;
138
+ private cr;
139
+ private bomChecked;
140
+ /**
141
+ * Yield raw field prefixes, values and line/frame endings without retaining
142
+ * a complete line or frame. Comments and unknown fields survive; one initial
143
+ * BOM is consumed and CRLF remains intact. Pass final=true once at EOF.
144
+ * Drain each generator before supplying the next chunk. An instance owns
145
+ * either fragments or push/flush; mixing those modes loses collected state.
146
+ */
147
+ fragments(chunk: string, final?: boolean): Generator<SSEFragment>;
148
+ private value;
149
+ private end;
150
+ private field;
151
+ private line;
119
152
  private frameEvent;
120
153
  private frameId;
121
154
  private frameDataLines;
122
- private bomChecked;
123
155
  /**
124
156
  * Append a decoded text chunk and return every frame the chunk
125
157
  * completes. Frame state survives across calls: one frame can
@@ -132,7 +164,7 @@ export declare class SSEFrameParser {
132
164
  * Well-behaved servers end frames with "\n\n", but some close early.
133
165
  */
134
166
  flush(): SSEFrame[];
135
- private processLines;
167
+ private collect;
136
168
  /**
137
169
  * Dispatch the accumulated frame. A frame with no `data:` field (an
138
170
  * `event:`/`id:`-only frame, or a bare comment) produces nothing, and
@@ -177,13 +209,12 @@ export interface ParsedSSEEvent<T = unknown> {
177
209
  * on stream end. Frames whose trimmed data is empty produce no event.
178
210
  */
179
211
  export declare class SSEChunkParser<T = unknown> {
180
- private readonly frames;
181
212
  private readonly options;
213
+ private readonly frames;
182
214
  constructor(options?: SSEParserOptions<T>);
183
215
  push(chunk: string): ParsedSSEEvent<T>[];
184
216
  flush(): ParsedSSEEvent<T>[];
185
217
  private toEvents;
186
- private toEvent;
187
218
  }
188
219
  /**
189
220
  * Parse SSE stream into typed events via async iteration.
@@ -282,4 +313,5 @@ export declare function createUsageCallback<T extends Record<string, unknown>>(f
282
313
  getUsage: () => StreamUsageAccumulator;
283
314
  extractor: StreamUsageExtractor;
284
315
  };
316
+ export {};
285
317
  //# sourceMappingURL=index.d.ts.map
@@ -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/part-text.ts","../../src/sse/index.ts"],"mappings":";;;;;;;;;qBAUa;qBACA;YACD,sBAAsB;KAC7B,OAAO;;wBAMI,WAAW,gBAAgB;;wBAQ3B,gBAAgB,iBAAiB;EAAS;EAAY;;;qBASzD;;;EAIX,OAAO,YAAY;;EAOnB,OAAO,YAAY,MAAM,cAAc;EAMvC,KAAK;;;wBAMS,oBAAoB,OAAO,eAAe,2BAA2B;;wBAiBrE,oBAAoB,OAAO,eAAe,OAAO;;iBC5ChD;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
@@ -1,3 +1,72 @@
1
+ // src/sse/part-text.ts
2
+ /**
3
+ * Part-text suffix coding. A harness restates a text or reasoning part's whole
4
+ * text on every `message.part.updated` frame, so an N-byte part costs O(N²)
5
+ * bytes to store or send (a Discovery root stream: 209 KB per 106-byte line).
6
+ * A `PartTextCoder` carries a frame whose text extends the part's last text in
7
+ * its stream as the suffix alone. A client asking with `?partText=suffix` gets
8
+ * frames marked `"partText": "suffix" | "full"`; `decodePartTextFrame` restores them.
9
+ */
10
+ /** The query parameter that asks for suffixes, and the field a coded frame carries. */
11
+ const PART_TEXT = "partText";
12
+ const PART_TEXT_SUFFIX = "suffix";
13
+ const isObject = (value) => typeof value === "object" && value !== null;
14
+ /** A frame's part, in either producer shape: `{ part }` or `{ properties: { part } }`. */
15
+ function locatePart(data) {
16
+ if (!isObject(data)) return null;
17
+ const nested = isObject(data.properties) ? data.properties.part : void 0;
18
+ if (isObject(nested)) return nested;
19
+ return isObject(data.part) ? data.part : null;
20
+ }
21
+ /** A frame's text or reasoning part with a string id and text. */
22
+ function growingTextPart(data) {
23
+ const part = locatePart(data);
24
+ if (part?.type !== "text" && part?.type !== "reasoning") return null;
25
+ return typeof part.id === "string" && typeof part.text === "string" ? part : null;
26
+ }
27
+ /** What one stream has carried for each part. */
28
+ var PartTextCoder = class {
29
+ #texts = /* @__PURE__ */ new Map();
30
+ /** Records `text`; the suffix to carry when it extends the part's last text. */
31
+ encode(id, text) {
32
+ const previous = this.#texts.get(id);
33
+ this.#texts.set(id, text);
34
+ return previous !== void 0 && text.startsWith(previous) ? text.slice(previous.length) : void 0;
35
+ }
36
+ /** Records and returns the whole text a carried value stands for. */
37
+ decode(id, mode, carried) {
38
+ const text = mode === "suffix" ? (this.#texts.get(id) ?? "") + carried : carried;
39
+ this.#texts.set(id, text);
40
+ return text;
41
+ }
42
+ text(id) {
43
+ return this.#texts.get(id);
44
+ }
45
+ };
46
+ /** One outgoing frame's JSON data, suffix-coded and marked; other frames pass. */
47
+ function encodePartTextFrame(coder, event, data) {
48
+ if (event !== "message.part.updated" && !data.includes("message.part.updated")) return data;
49
+ let frame;
50
+ try {
51
+ frame = JSON.parse(data);
52
+ } catch {
53
+ return data;
54
+ }
55
+ const part = growingTextPart(frame);
56
+ if (!part) return data;
57
+ const suffix = coder.encode(part.id, part.text);
58
+ if (suffix !== void 0) part.text = suffix;
59
+ frame[PART_TEXT] = suffix === void 0 ? "full" : PART_TEXT_SUFFIX;
60
+ return JSON.stringify(frame);
61
+ }
62
+ /** Restores a parsed frame `encodePartTextFrame` marked, in place; others pass. */
63
+ function decodePartTextFrame(coder, frame) {
64
+ const mode = frame[PART_TEXT];
65
+ if (mode !== "suffix" && mode !== "full") return;
66
+ delete frame[PART_TEXT];
67
+ const part = growingTextPart(frame);
68
+ if (part) part.text = coder.decode(part.id, mode, part.text);
69
+ }
1
70
  // src/sse/index.ts
2
71
  /**
3
72
  * Parse raw SSE data string into structured event data.
@@ -50,30 +119,115 @@ var SSEStreamSilentError = class extends Error {
50
119
  }
51
120
  };
52
121
  /**
53
- * Incremental SSE frame parser. Feed decoded text via `push()`; call
54
- * `flush()` when the stream ends.
122
+ * Incremental SSE frame parser. Feed decoded text via push(); call flush()
123
+ * when the stream ends. Complete frames and bounded fragments share one
124
+ * scanner and one parser lifetime.
55
125
  */
56
126
  var SSEFrameParser = class {
57
- buffer = "";
127
+ prefix = "";
128
+ started = false;
129
+ cr = false;
130
+ bomChecked = false;
131
+ /**
132
+ * Yield raw field prefixes, values and line/frame endings without retaining
133
+ * a complete line or frame. Comments and unknown fields survive; one initial
134
+ * BOM is consumed and CRLF remains intact. Pass final=true once at EOF.
135
+ * Drain each generator before supplying the next chunk. An instance owns
136
+ * either fragments or push/flush; mixing those modes loses collected state.
137
+ */
138
+ *fragments(chunk, final = false) {
139
+ let text = chunk;
140
+ if (!this.bomChecked && text.length) {
141
+ this.bomChecked = true;
142
+ if (text.charCodeAt(0) === 65279) text = text.slice(1);
143
+ }
144
+ if (this.cr) {
145
+ text = `\r${text}`;
146
+ this.cr = false;
147
+ }
148
+ let cursor = 0;
149
+ for (;;) {
150
+ const newline = text.indexOf("\n", cursor);
151
+ const end = newline < 0 ? text.length : newline;
152
+ const trailingCr = text[end - 1] === "\r" && end > cursor;
153
+ const value = text.slice(cursor, trailingCr ? end - 1 : end);
154
+ yield* this.value(value);
155
+ if (newline < 0) {
156
+ this.cr = trailingCr;
157
+ break;
158
+ }
159
+ yield* this.end(trailingCr ? "\r\n" : "\n");
160
+ cursor = newline + 1;
161
+ }
162
+ if (final) {
163
+ if (this.started || this.prefix || this.cr) yield* this.end(this.cr ? "\r" : "");
164
+ yield {
165
+ type: "frame-end",
166
+ ending: ""
167
+ };
168
+ this.cr = false;
169
+ }
170
+ }
171
+ *value(value) {
172
+ let cursor = 0;
173
+ while (!this.started && cursor < value.length) {
174
+ const next = value[cursor++];
175
+ if (this.prefix.endsWith(":")) {
176
+ const prefix = this.prefix + (next === " " ? next : "");
177
+ yield {
178
+ type: "field",
179
+ field: this.prefix.slice(0, -1),
180
+ prefix
181
+ };
182
+ this.started = true;
183
+ if (next !== " ") cursor--;
184
+ } else {
185
+ this.prefix += next;
186
+ if (![
187
+ "data:",
188
+ "event:",
189
+ "id:",
190
+ "retry:"
191
+ ].some((name) => name.startsWith(this.prefix))) {
192
+ yield {
193
+ type: "field",
194
+ field: void 0,
195
+ prefix: this.prefix
196
+ };
197
+ this.started = true;
198
+ }
199
+ }
200
+ }
201
+ if (cursor < value.length) yield {
202
+ type: "value",
203
+ value: value.slice(cursor)
204
+ };
205
+ }
206
+ *end(ending) {
207
+ if (!this.started && this.prefix) yield {
208
+ type: "field",
209
+ field: this.prefix.endsWith(":") ? this.prefix.slice(0, -1) : void 0,
210
+ prefix: this.prefix
211
+ };
212
+ yield {
213
+ type: this.started || this.prefix ? "line-end" : "frame-end",
214
+ ending
215
+ };
216
+ this.prefix = "";
217
+ this.started = false;
218
+ }
219
+ field;
220
+ line = "";
58
221
  frameEvent = "";
59
222
  frameId;
60
223
  frameDataLines = null;
61
- bomChecked = false;
62
224
  /**
63
225
  * Append a decoded text chunk and return every frame the chunk
64
226
  * completes. Frame state survives across calls: one frame can
65
227
  * straddle any number of chunks.
66
228
  */
67
229
  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);
230
+ return this.collect(this.fragments(chunk));
77
231
  }
78
232
  /**
79
233
  * End-of-stream: process the unterminated trailing line, then
@@ -81,35 +235,21 @@ var SSEFrameParser = class {
81
235
  * Well-behaved servers end frames with "\n\n", but some close early.
82
236
  */
83
237
  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;
238
+ return this.collect(this.fragments("", true));
90
239
  }
91
- processLines(lines) {
240
+ collect(fragments) {
92
241
  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();
242
+ for (const fragment of fragments) if (fragment.type === "field") this.field = fragment.field;
243
+ else if (fragment.type === "value" && this.field !== void 0) this.line += fragment.value;
244
+ else if (fragment.type === "line-end") {
245
+ if (this.field === "data") (this.frameDataLines ??= []).push(this.line);
246
+ if (this.field === "event") this.frameEvent = this.line.trim();
247
+ if (this.field === "id") this.frameId = this.line.trim();
248
+ this.field = void 0;
249
+ this.line = "";
250
+ } else if (fragment.type === "frame-end") {
251
+ const frame = this.dispatch();
252
+ if (frame) frames.push(frame);
113
253
  }
114
254
  return frames;
115
255
  }
@@ -119,10 +259,9 @@ var SSEFrameParser = class {
119
259
  * its field state is consumed and reset either way.
120
260
  */
121
261
  dispatch() {
122
- const dataLines = this.frameDataLines;
123
- const frame = dataLines ? {
262
+ const frame = this.frameDataLines ? {
124
263
  event: detached(this.frameEvent),
125
- data: dataLines.join("\n"),
264
+ data: this.frameDataLines.join("\n"),
126
265
  id: this.frameId ? detached(this.frameId) : void 0
127
266
  } : null;
128
267
  this.frameEvent = "";
@@ -190,8 +329,8 @@ async function* streamSSEFrames(body, options) {
190
329
  * on stream end. Frames whose trimmed data is empty produce no event.
191
330
  */
192
331
  var SSEChunkParser = class {
193
- frames = new SSEFrameParser();
194
332
  options;
333
+ frames = new SSEFrameParser();
195
334
  constructor(options = {}) {
196
335
  this.options = options;
197
336
  }
@@ -202,85 +341,47 @@ var SSEChunkParser = class {
202
341
  return this.toEvents(this.frames.flush());
203
342
  }
204
343
  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;
344
+ return frames.flatMap((frame) => toSSEEvents(frame, this.options));
211
345
  }
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;
346
+ };
347
+ function toSSEEvents(frame, options) {
348
+ const rawData = frame.data.trim();
349
+ if (!rawData) return [];
350
+ let data;
351
+ if (options.transform) {
352
+ const transformed = options.transform(rawData);
353
+ if (transformed === null) {
354
+ if (options.logMalformed) console.warn("SSE transform returned null:", rawData.slice(0, 100));
355
+ return [];
228
356
  }
229
- return {
230
- data,
231
- rawData,
232
- eventId: frame.id,
233
- eventType: frame.event || void 0
234
- };
357
+ data = transformed;
358
+ } else try {
359
+ data = JSON.parse(rawData);
360
+ } catch {
361
+ if (options.logMalformed) console.warn("SSE parse failed:", rawData.slice(0, 100));
362
+ data = rawData;
235
363
  }
236
- };
364
+ return [{
365
+ data,
366
+ rawData,
367
+ eventId: frame.id,
368
+ eventType: frame.event || void 0
369
+ }];
370
+ }
237
371
  /**
238
372
  * Parse SSE stream into typed events via async iteration.
239
373
  */
240
374
  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
- }
375
+ for await (const frame of streamSSEFrames(stream)) yield* toSSEEvents(frame, options);
263
376
  }
264
377
  /**
265
378
  * Consume SSE stream with callbacks.
266
379
  */
267
380
  async function consumeSSEStream(stream, onEvent, onError, options = {}) {
268
- let eventCount = 0;
269
381
  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}`);
382
+ for await (const event of parseSSEStream(stream, options)) onEvent(event);
275
383
  } 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);
384
+ onError(error instanceof Error ? error : new Error(String(error)));
284
385
  }
285
386
  }
286
387
  /** Event types that contain token information (Anthropic SDK format) */
@@ -425,6 +526,6 @@ function createUsageCallback(forward) {
425
526
  extractor
426
527
  };
427
528
  }
428
- export { SSEChunkParser, SSEFrameParser, SSEStreamSilentError, StreamUsageExtractor, consumeSSEStream, createStreamUsageExtractor, createUsageCallback, encodeSSEFrame, parseSSEData, parseSSEStream, streamSSEFrames };
529
+ export { PART_TEXT, PART_TEXT_SUFFIX, PartTextCoder, SSEChunkParser, SSEFrameParser, SSEStreamSilentError, StreamUsageExtractor, consumeSSEStream, createStreamUsageExtractor, createUsageCallback, decodePartTextFrame, encodePartTextFrame, encodeSSEFrame, growingTextPart, locatePart, parseSSEData, parseSSEStream, streamSSEFrames };
429
530
 
430
531
  //# sourceMappingURL=index.js.map
@@ -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/part-text.ts","../../src/sse/index.ts"],"sourcesContent":["/**\n * Part-text suffix coding. A harness restates a text or reasoning part's whole\n * text on every `message.part.updated` frame, so an N-byte part costs O(N²)\n * bytes to store or send (a Discovery root stream: 209 KB per 106-byte line).\n * A `PartTextCoder` carries a frame whose text extends the part's last text in\n * its stream as the suffix alone. A client asking with `?partText=suffix` gets\n * frames marked `\"partText\": \"suffix\" | \"full\"`; `decodePartTextFrame` restores them.\n */\n\n/** The query parameter that asks for suffixes, and the field a coded frame carries. */\nexport const PART_TEXT = \"partText\";\nexport const PART_TEXT_SUFFIX = \"suffix\";\nexport type PartTextMode = typeof PART_TEXT_SUFFIX | \"full\";\ntype Part = Record<string, unknown>;\n\nconst isObject = (value: unknown): value is Part =>\n typeof value === \"object\" && value !== null;\n\n/** A frame's part, in either producer shape: `{ part }` or `{ properties: { part } }`. */\nexport function locatePart(data: unknown): Part | null {\n if (!isObject(data)) return null;\n const nested = isObject(data.properties) ? data.properties.part : undefined;\n if (isObject(nested)) return nested;\n return isObject(data.part) ? data.part : null;\n}\n\n/** A frame's text or reasoning part with a string id and text. */\nexport function growingTextPart(data: unknown): (Part & { id: string; text: string }) | null {\n const part = locatePart(data);\n if (part?.type !== \"text\" && part?.type !== \"reasoning\") return null;\n return typeof part.id === \"string\" && typeof part.text === \"string\"\n ? (part as Part & { id: string; text: string })\n : null;\n}\n\n/** What one stream has carried for each part. */\nexport class PartTextCoder {\n readonly #texts = new Map<string, string>();\n\n /** Records `text`; the suffix to carry when it extends the part's last text. */\n encode(id: string, text: string): string | undefined {\n const previous = this.#texts.get(id);\n this.#texts.set(id, text);\n return previous !== undefined && text.startsWith(previous) ? text.slice(previous.length) : undefined;\n }\n\n /** Records and returns the whole text a carried value stands for. */\n decode(id: string, mode: PartTextMode, carried: string): string {\n const text = mode === PART_TEXT_SUFFIX ? (this.#texts.get(id) ?? \"\") + carried : carried;\n this.#texts.set(id, text);\n return text;\n }\n\n text(id: string): string | undefined {\n return this.#texts.get(id);\n }\n}\n\n/** One outgoing frame's JSON data, suffix-coded and marked; other frames pass. */\nexport function encodePartTextFrame(coder: PartTextCoder, event: string | undefined, data: string): string {\n if (event !== \"message.part.updated\" && !data.includes(\"message.part.updated\")) return data;\n let frame: Part;\n try {\n frame = JSON.parse(data) as Part;\n } catch {\n return data;\n }\n const part = growingTextPart(frame);\n if (!part) return data;\n const suffix = coder.encode(part.id, part.text);\n if (suffix !== undefined) part.text = suffix;\n frame[PART_TEXT] = suffix === undefined ? \"full\" : PART_TEXT_SUFFIX;\n return JSON.stringify(frame);\n}\n\n/** Restores a parsed frame `encodePartTextFrame` marked, in place; others pass. */\nexport function decodePartTextFrame(coder: PartTextCoder, frame: Part): void {\n const mode = frame[PART_TEXT];\n if (mode !== PART_TEXT_SUFFIX && mode !== \"full\") return;\n delete frame[PART_TEXT];\n const part = growingTextPart(frame);\n if (part) part.text = coder.decode(part.id, mode, part.text);\n}\n","/**\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 * `part-text.ts` codes agent part frames' growing text as suffixes.\n */\n\nexport * from \"./part-text.js\";\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.1",
3
+ "version": "1.0.3",
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",