@tangle-network/agent-core 1.0.2 → 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;
@@ -307,4 +313,5 @@ export declare function createUsageCallback<T extends Record<string, unknown>>(f
307
313
  getUsage: () => StreamUsageAccumulator;
308
314
  extractor: StreamUsageExtractor;
309
315
  };
316
+ export {};
310
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;;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"}
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.
@@ -457,6 +526,6 @@ function createUsageCallback(forward) {
457
526
  extractor
458
527
  };
459
528
  }
460
- 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 };
461
530
 
462
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\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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"}
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.2",
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",