@tangle-network/agent-core 0.13.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/server-timing/index.d.ts +29 -0
- package/dist/server-timing/index.d.ts.map +1 -0
- package/dist/server-timing/index.js +78 -0
- package/dist/server-timing/index.js.map +1 -0
- package/dist/sse/index.d.ts +123 -9
- package/dist/sse/index.d.ts.map +1 -1
- package/dist/sse/index.js +163 -35
- package/dist/sse/index.js.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Runtime primitives for talking to Tangle agents:
|
|
4
4
|
|
|
5
5
|
- **Auth** — issue and verify scoped access tokens for sidecar/session access.
|
|
6
|
-
- **SSE** — parse
|
|
6
|
+
- **SSE** — parse and frame event streams (`SSEFrameParser`, `streamSSEFrames`, `encodeSSEFrame`, `SSEChunkParser`, `parseSSEStream`); dependency-free, so Worker and browser bundles can take it.
|
|
7
7
|
- **Transport** — connection-manager and transport interfaces.
|
|
8
8
|
- **Resilience** — retries, timeouts, and circuit breakers.
|
|
9
9
|
- **Telemetry** — GenAI attribute and token-usage helpers.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-Timing header parse and format (W3C Server Timing; RFC 9110 §5.6.4
|
|
3
|
+
* quoted strings). Pure ECMAScript that imports nothing, so Node, browser and
|
|
4
|
+
* Cloudflare Worker bundles can take this subpath without a dependency.
|
|
5
|
+
*/
|
|
6
|
+
/** One metric of a `Server-Timing` header. */
|
|
7
|
+
export interface ServerTimingEntry {
|
|
8
|
+
name: string;
|
|
9
|
+
/** Milliseconds, only when the header carried a well-formed `dur`. */
|
|
10
|
+
dur?: number;
|
|
11
|
+
/** `desc`, unquoted and unescaped. */
|
|
12
|
+
desc?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse a `Server-Timing` header. Never throws. A metric with an invalid name
|
|
16
|
+
* is skipped; a missing or malformed `dur` stays absent, never zero. The first
|
|
17
|
+
* occurrence of a parameter wins, and repeated metrics stay separate entries.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseServerTiming(header: string | null | undefined): ServerTimingEntry[];
|
|
20
|
+
/** Duration-bearing metrics by name; a later metric of the same name wins. */
|
|
21
|
+
export declare function parseServerTimingDurations(header: string | null | undefined): Record<string, number>;
|
|
22
|
+
/**
|
|
23
|
+
* Format metrics as a `Server-Timing` header value, the inverse of
|
|
24
|
+
* {@link parseServerTiming}. Invalid names are skipped; `dur` rounds to 0.1 ms;
|
|
25
|
+
* `desc` is escaped as an RFC 9110 quoted string. A header carries at most 32
|
|
26
|
+
* metrics and each `desc` at most 120 characters.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatServerTiming(entries: readonly ServerTimingEntry[]): string;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/server-timing/index.ts"],"mappings":";;;;;;iBAOiB;EACf;;EAEA;;EAEA;;;;;;;wBAgCc,kBACd,oCACC;;wBAgCa,2BACd,oCACC;;;;;;;wBAca,mBACd,kBAAkB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/server-timing/index.ts
|
|
2
|
+
const TOKEN = /^[!#$%&'*+.^_`|~\w-]+$/;
|
|
3
|
+
const NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
4
|
+
const QUOTED = /^"(?:[^"\\]|\\[\s\S])*"$/;
|
|
5
|
+
const MAX_FORMAT_ENTRIES = 32;
|
|
6
|
+
const MAX_FORMAT_DESC = 120;
|
|
7
|
+
/** Split on `separator` outside quoted strings, skipping backslash-escaped characters inside them. */
|
|
8
|
+
function split(value, separator) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
let start = 0;
|
|
11
|
+
let quoted = false;
|
|
12
|
+
for (let i = 0; i < value.length; i++) {
|
|
13
|
+
const char = value[i];
|
|
14
|
+
if (quoted && char === "\\") i++;
|
|
15
|
+
else if (char === "\"") quoted = !quoted;
|
|
16
|
+
else if (!quoted && char === separator) {
|
|
17
|
+
parts.push(value.slice(start, i));
|
|
18
|
+
start = i + 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
parts.push(value.slice(start));
|
|
22
|
+
return parts;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse a `Server-Timing` header. Never throws. A metric with an invalid name
|
|
26
|
+
* is skipped; a missing or malformed `dur` stays absent, never zero. The first
|
|
27
|
+
* occurrence of a parameter wins, and repeated metrics stay separate entries.
|
|
28
|
+
*/
|
|
29
|
+
function parseServerTiming(header) {
|
|
30
|
+
if (!header) return [];
|
|
31
|
+
const entries = [];
|
|
32
|
+
for (const metric of split(header, ",")) {
|
|
33
|
+
const [rawName, ...params] = split(metric, ";");
|
|
34
|
+
const name = (rawName ?? "").trim();
|
|
35
|
+
if (!TOKEN.test(name)) continue;
|
|
36
|
+
const entry = { name };
|
|
37
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38
|
+
for (const param of params) {
|
|
39
|
+
const eq = param.indexOf("=");
|
|
40
|
+
const key = (eq < 0 ? param : param.slice(0, eq)).trim().toLowerCase();
|
|
41
|
+
if (seen.has(key)) continue;
|
|
42
|
+
seen.add(key);
|
|
43
|
+
if (eq < 0) continue;
|
|
44
|
+
let value = param.slice(eq + 1).trim();
|
|
45
|
+
if (value.startsWith("\"")) {
|
|
46
|
+
if (!QUOTED.test(value)) continue;
|
|
47
|
+
value = value.slice(1, -1).replace(/\\([\s\S])/g, "$1");
|
|
48
|
+
}
|
|
49
|
+
if (key === "desc") entry.desc = value;
|
|
50
|
+
else if (key === "dur" && NUMBER.test(value)) {
|
|
51
|
+
const dur = Number(value);
|
|
52
|
+
if (Number.isFinite(dur)) entry.dur = dur;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
entries.push(entry);
|
|
56
|
+
}
|
|
57
|
+
return entries;
|
|
58
|
+
}
|
|
59
|
+
/** Duration-bearing metrics by name; a later metric of the same name wins. */
|
|
60
|
+
function parseServerTimingDurations(header) {
|
|
61
|
+
return Object.fromEntries(parseServerTiming(header).flatMap(({ name, dur }) => dur === void 0 ? [] : [[name, dur]]));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Format metrics as a `Server-Timing` header value, the inverse of
|
|
65
|
+
* {@link parseServerTiming}. Invalid names are skipped; `dur` rounds to 0.1 ms;
|
|
66
|
+
* `desc` is escaped as an RFC 9110 quoted string. A header carries at most 32
|
|
67
|
+
* metrics and each `desc` at most 120 characters.
|
|
68
|
+
*/
|
|
69
|
+
function formatServerTiming(entries) {
|
|
70
|
+
return entries.filter((entry) => TOKEN.test(entry.name)).slice(0, MAX_FORMAT_ENTRIES).map((entry) => [
|
|
71
|
+
entry.name,
|
|
72
|
+
...entry.dur === void 0 ? [] : [`dur=${Math.round(entry.dur * 10) / 10}`],
|
|
73
|
+
...entry.desc === void 0 ? [] : [`desc="${entry.desc.slice(0, MAX_FORMAT_DESC).replace(/["\\]/g, "\\$&")}"`]
|
|
74
|
+
].join(";")).join(", ");
|
|
75
|
+
}
|
|
76
|
+
export { formatServerTiming, parseServerTiming, parseServerTimingDurations };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/server-timing/index.ts"],"sourcesContent":["/**\n * Server-Timing header parse and format (W3C Server Timing; RFC 9110 §5.6.4\n * quoted strings). Pure ECMAScript that imports nothing, so Node, browser and\n * Cloudflare Worker bundles can take this subpath without a dependency.\n */\n\n/** One metric of a `Server-Timing` header. */\nexport interface ServerTimingEntry {\n name: string;\n /** Milliseconds, only when the header carried a well-formed `dur`. */\n dur?: number;\n /** `desc`, unquoted and unescaped. */\n desc?: string;\n}\n\nconst TOKEN = /^[!#$%&'*+.^_`|~\\w-]+$/;\nconst NUMBER = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:e[+-]?\\d+)?$/i;\nconst QUOTED = /^\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"$/;\nconst MAX_FORMAT_ENTRIES = 32;\nconst MAX_FORMAT_DESC = 120;\n\n/** Split on `separator` outside quoted strings, skipping backslash-escaped characters inside them. */\nfunction split(value: string, separator: string): string[] {\n const parts: string[] = [];\n let start = 0;\n let quoted = false;\n for (let i = 0; i < value.length; i++) {\n const char = value[i];\n if (quoted && char === \"\\\\\") i++;\n else if (char === '\"') quoted = !quoted;\n else if (!quoted && char === separator) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(value.slice(start));\n return parts;\n}\n\n/**\n * Parse a `Server-Timing` header. Never throws. A metric with an invalid name\n * is skipped; a missing or malformed `dur` stays absent, never zero. The first\n * occurrence of a parameter wins, and repeated metrics stay separate entries.\n */\nexport function parseServerTiming(\n header: string | null | undefined,\n): ServerTimingEntry[] {\n if (!header) return [];\n const entries: ServerTimingEntry[] = [];\n for (const metric of split(header, \",\")) {\n const [rawName, ...params] = split(metric, \";\");\n const name = (rawName ?? \"\").trim();\n if (!TOKEN.test(name)) continue;\n const entry: ServerTimingEntry = { name };\n const seen = new Set<string>();\n for (const param of params) {\n const eq = param.indexOf(\"=\");\n const key = (eq < 0 ? param : param.slice(0, eq)).trim().toLowerCase();\n if (seen.has(key)) continue;\n seen.add(key);\n if (eq < 0) continue;\n let value = param.slice(eq + 1).trim();\n if (value.startsWith('\"')) {\n if (!QUOTED.test(value)) continue;\n value = value.slice(1, -1).replace(/\\\\([\\s\\S])/g, \"$1\");\n }\n if (key === \"desc\") entry.desc = value;\n else if (key === \"dur\" && NUMBER.test(value)) {\n const dur = Number(value);\n if (Number.isFinite(dur)) entry.dur = dur;\n }\n }\n entries.push(entry);\n }\n return entries;\n}\n\n/** Duration-bearing metrics by name; a later metric of the same name wins. */\nexport function parseServerTimingDurations(\n header: string | null | undefined,\n): Record<string, number> {\n return Object.fromEntries(\n parseServerTiming(header).flatMap(({ name, dur }) =>\n dur === undefined ? [] : [[name, dur] as const],\n ),\n );\n}\n\n/**\n * Format metrics as a `Server-Timing` header value, the inverse of\n * {@link parseServerTiming}. Invalid names are skipped; `dur` rounds to 0.1 ms;\n * `desc` is escaped as an RFC 9110 quoted string. A header carries at most 32\n * metrics and each `desc` at most 120 characters.\n */\nexport function formatServerTiming(\n entries: readonly ServerTimingEntry[],\n): string {\n return entries\n .filter((entry) => TOKEN.test(entry.name))\n .slice(0, MAX_FORMAT_ENTRIES)\n .map((entry) =>\n [\n entry.name,\n ...(entry.dur === undefined\n ? []\n : [`dur=${Math.round(entry.dur * 10) / 10}`]),\n ...(entry.desc === undefined\n ? []\n : [\n `desc=\"${entry.desc.slice(0, MAX_FORMAT_DESC).replace(/[\"\\\\]/g, \"\\\\$&\")}\"`,\n ]),\n ].join(\";\"),\n )\n .join(\", \");\n}\n"],"mappings":";AAeA,MAAM,QAAQ;AACd,MAAM,SAAS;AACf,MAAM,SAAS;AACf,MAAM,qBAAqB;AAC3B,MAAM,kBAAkB;;AAGxB,SAAS,MAAM,OAAe,WAA6B;CACzD,MAAM,QAAkB,CAAC;CACzB,IAAI,QAAQ;CACZ,IAAI,SAAS;CACb,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;EACnB,IAAI,UAAU,SAAS,MAAM;OACxB,IAAI,SAAS,MAAK,SAAS,CAAC;OAC5B,IAAI,CAAC,UAAU,SAAS,WAAW;GACtC,MAAM,KAAK,MAAM,MAAM,OAAO,CAAC,CAAC;GAChC,QAAQ,IAAI;EACd;CACF;CACA,MAAM,KAAK,MAAM,MAAM,KAAK,CAAC;CAC7B,OAAO;AACT;;;;;;AAOA,SAAgB,kBACd,QACqB;CACrB,IAAI,CAAC,QAAQ,OAAO,CAAC;CACrB,MAAM,UAA+B,CAAC;CACtC,KAAK,MAAM,UAAU,MAAM,QAAQ,GAAG,GAAG;EACvC,MAAM,CAAC,SAAS,GAAG,UAAU,MAAM,QAAQ,GAAG;EAC9C,MAAM,QAAQ,WAAW,GAAA,CAAI,KAAK;EAClC,IAAI,CAAC,MAAM,KAAK,IAAI,GAAG;EACvB,MAAM,QAA2B,EAAE,KAAK;EACxC,MAAM,uBAAO,IAAI,IAAY;EAC7B,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,KAAK,MAAM,QAAQ,GAAG;GAC5B,MAAM,OAAO,KAAK,IAAI,QAAQ,MAAM,MAAM,GAAG,EAAE,EAAA,CAAG,KAAK,CAAC,CAAC,YAAY;GACrE,IAAI,KAAK,IAAI,GAAG,GAAG;GACnB,KAAK,IAAI,GAAG;GACZ,IAAI,KAAK,GAAG;GACZ,IAAI,QAAQ,MAAM,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK;GACrC,IAAI,MAAM,WAAW,IAAG,GAAG;IACzB,IAAI,CAAC,OAAO,KAAK,KAAK,GAAG;IACzB,QAAQ,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,QAAQ,eAAe,IAAI;GACxD;GACA,IAAI,QAAQ,QAAQ,MAAM,OAAO;QAC5B,IAAI,QAAQ,SAAS,OAAO,KAAK,KAAK,GAAG;IAC5C,MAAM,MAAM,OAAO,KAAK;IACxB,IAAI,OAAO,SAAS,GAAG,GAAG,MAAM,MAAM;GACxC;EACF;EACA,QAAQ,KAAK,KAAK;CACpB;CACA,OAAO;AACT;;AAGA,SAAgB,2BACd,QACwB;CACxB,OAAO,OAAO,YACZ,kBAAkB,MAAM,CAAC,CAAC,SAAS,EAAE,MAAM,UACzC,QAAQ,KAAA,IAAY,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAU,CAChD,CACF;AACF;;;;;;;AAQA,SAAgB,mBACd,SACQ;CACR,OAAO,QACJ,QAAQ,UAAU,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,CACzC,MAAM,GAAG,kBAAkB,CAAC,CAC5B,KAAK,UACJ;EACE,MAAM;EACN,GAAI,MAAM,QAAQ,KAAA,IACd,CAAC,IACD,CAAC,OAAO,KAAK,MAAM,MAAM,MAAM,EAAE,IAAI,IAAI;EAC7C,GAAI,MAAM,SAAS,KAAA,IACf,CAAC,IACD,CACE,SAAS,MAAM,KAAK,MAAM,GAAG,eAAe,CAAC,CAAC,QAAQ,UAAU,MAAM,EAAE,EAC1E;CACN,CAAC,CAAC,KAAK,GAAG,CACZ,CAAC,CACA,KAAK,IAAI;AACd"}
|
package/dist/sse/index.d.ts
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SSE Utilities
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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.
|
|
6
27
|
*/
|
|
7
28
|
/** Raw SSE event data from stream */
|
|
8
29
|
export interface SSEEventData {
|
|
@@ -45,6 +66,99 @@ export interface SSEEventData {
|
|
|
45
66
|
* ```
|
|
46
67
|
*/
|
|
47
68
|
export declare function parseSSEData(rawData: string): SSEEventData | null;
|
|
69
|
+
/**
|
|
70
|
+
* One parsed SSE frame: the fields accumulated between blank lines.
|
|
71
|
+
*
|
|
72
|
+
* `event` and `id` share no memory with the chunk the frame arrived in, so a
|
|
73
|
+
* consumer can keep them as long as it keeps its events. `data` can share
|
|
74
|
+
* that memory: parse it, and keep what the parse returns.
|
|
75
|
+
*/
|
|
76
|
+
export interface SSEFrame {
|
|
77
|
+
/** Value of the `event:` field; "" when the frame carries none. */
|
|
78
|
+
event: string;
|
|
79
|
+
/** `data:` field lines joined with "\n". */
|
|
80
|
+
data: string;
|
|
81
|
+
/** Value of the `id:` field; undefined when the frame carries none. */
|
|
82
|
+
id?: string;
|
|
83
|
+
}
|
|
84
|
+
/** Options for {@link streamSSEFrames}. */
|
|
85
|
+
export interface SSEStreamOptions {
|
|
86
|
+
/**
|
|
87
|
+
* Aborts the read loop. When the signal fires, the generator throws a
|
|
88
|
+
* `DOMException` named "AbortError" so consumers can distinguish a
|
|
89
|
+
* cancellation from a clean end-of-stream without inspecting signal
|
|
90
|
+
* state.
|
|
91
|
+
*/
|
|
92
|
+
signal?: AbortSignal;
|
|
93
|
+
/**
|
|
94
|
+
* The longest silence the stream may keep, read before each chunk read.
|
|
95
|
+
* Any bytes reset it, comment heartbeats included, because a heartbeat is
|
|
96
|
+
* the producer saying the connection is still live. A read that outlasts it
|
|
97
|
+
* throws {@link SSEStreamSilentError}. `undefined` waits without bound, which
|
|
98
|
+
* suits a producer that promises no heartbeat.
|
|
99
|
+
*/
|
|
100
|
+
idleMs?: () => number | undefined;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Thrown when a stream sends no bytes for longer than its `idleMs`. A
|
|
104
|
+
* producer that heartbeats would have written in that time, so the
|
|
105
|
+
* connection is dead even though it never closed: a half-open TCP connection
|
|
106
|
+
* or a stalled proxy hold the socket open while nothing arrives.
|
|
107
|
+
*/
|
|
108
|
+
export declare class SSEStreamSilentError extends Error {
|
|
109
|
+
readonly name = "SSEStreamSilentError";
|
|
110
|
+
readonly idleMs: number;
|
|
111
|
+
constructor(idleMs: number);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Incremental SSE frame parser. Feed decoded text via `push()`; call
|
|
115
|
+
* `flush()` when the stream ends.
|
|
116
|
+
*/
|
|
117
|
+
export declare class SSEFrameParser {
|
|
118
|
+
private buffer;
|
|
119
|
+
private frameEvent;
|
|
120
|
+
private frameId;
|
|
121
|
+
private frameDataLines;
|
|
122
|
+
private bomChecked;
|
|
123
|
+
/**
|
|
124
|
+
* Append a decoded text chunk and return every frame the chunk
|
|
125
|
+
* completes. Frame state survives across calls: one frame can
|
|
126
|
+
* straddle any number of chunks.
|
|
127
|
+
*/
|
|
128
|
+
push(chunk: string): SSEFrame[];
|
|
129
|
+
/**
|
|
130
|
+
* End-of-stream: process the unterminated trailing line, then
|
|
131
|
+
* dispatch a dangling frame that never got its blank line.
|
|
132
|
+
* Well-behaved servers end frames with "\n\n", but some close early.
|
|
133
|
+
*/
|
|
134
|
+
flush(): SSEFrame[];
|
|
135
|
+
private processLines;
|
|
136
|
+
/**
|
|
137
|
+
* Dispatch the accumulated frame. A frame with no `data:` field (an
|
|
138
|
+
* `event:`/`id:`-only frame, or a bare comment) produces nothing, and
|
|
139
|
+
* its field state is consumed and reset either way.
|
|
140
|
+
*/
|
|
141
|
+
private dispatch;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Serialize one frame to its wire form. Multi-line `data` becomes one
|
|
145
|
+
* `data:` line per source line so the payload survives the trip without
|
|
146
|
+
* premature frame dispatch.
|
|
147
|
+
*/
|
|
148
|
+
export declare function encodeSSEFrame(frame: {
|
|
149
|
+
data: string;
|
|
150
|
+
event?: string;
|
|
151
|
+
id?: string;
|
|
152
|
+
}): string;
|
|
153
|
+
/**
|
|
154
|
+
* Read a streaming SSE response body into parsed frames.
|
|
155
|
+
*
|
|
156
|
+
* @throws `TypeError` when the body is missing.
|
|
157
|
+
* @throws `DOMException` with `name === "AbortError"` on cancellation —
|
|
158
|
+
* including the WHATWG-fetch variant where an aborted body resolves
|
|
159
|
+
* `done: true` instead of rejecting the read.
|
|
160
|
+
*/
|
|
161
|
+
export declare function streamSSEFrames(body: ReadableStream<Uint8Array> | null | undefined, options?: SSEStreamOptions): AsyncGenerator<SSEFrame>;
|
|
48
162
|
export interface SSEParserOptions<T = unknown> {
|
|
49
163
|
/** Log malformed data lines (default: false) */
|
|
50
164
|
logMalformed?: boolean;
|
|
@@ -58,18 +172,18 @@ export interface ParsedSSEEvent<T = unknown> {
|
|
|
58
172
|
eventType?: string;
|
|
59
173
|
}
|
|
60
174
|
/**
|
|
61
|
-
* Incremental
|
|
62
|
-
* Feed decoded string chunks via push(); call flush()
|
|
175
|
+
* Incremental JSON event parser for chunked streams, framed by
|
|
176
|
+
* {@link SSEFrameParser}. Feed decoded string chunks via push(); call flush()
|
|
177
|
+
* on stream end. Frames whose trimmed data is empty produce no event.
|
|
63
178
|
*/
|
|
64
179
|
export declare class SSEChunkParser<T = unknown> {
|
|
65
|
-
private
|
|
66
|
-
private
|
|
67
|
-
private options;
|
|
180
|
+
private readonly frames;
|
|
181
|
+
private readonly options;
|
|
68
182
|
constructor(options?: SSEParserOptions<T>);
|
|
69
183
|
push(chunk: string): ParsedSSEEvent<T>[];
|
|
70
184
|
flush(): ParsedSSEEvent<T>[];
|
|
71
|
-
private
|
|
72
|
-
private
|
|
185
|
+
private toEvents;
|
|
186
|
+
private toEvent;
|
|
73
187
|
}
|
|
74
188
|
/**
|
|
75
189
|
* Parse SSE stream into typed events via async iteration.
|
package/dist/sse/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/sse/index.ts"],"mappings":"
|
|
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"}
|
package/dist/sse/index.js
CHANGED
|
@@ -26,63 +26,191 @@ function parseSSEData(rawData) {
|
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* A copy of `value` that shares no memory with the string it was cut from.
|
|
30
|
+
*
|
|
31
|
+
* V8 can return a short substring of a long string as a view that keeps the
|
|
32
|
+
* whole string alive. Consumers keep event names and ids as long as they keep
|
|
33
|
+
* events, so a view would keep every chunk its frame arrived in alive.
|
|
31
34
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
function detached(value) {
|
|
36
|
+
return value === "" ? value : JSON.parse(JSON.stringify(value));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Thrown when a stream sends no bytes for longer than its `idleMs`. A
|
|
40
|
+
* producer that heartbeats would have written in that time, so the
|
|
41
|
+
* connection is dead even though it never closed: a half-open TCP connection
|
|
42
|
+
* or a stalled proxy hold the socket open while nothing arrives.
|
|
43
|
+
*/
|
|
44
|
+
var SSEStreamSilentError = class extends Error {
|
|
45
|
+
name = "SSEStreamSilentError";
|
|
46
|
+
idleMs;
|
|
47
|
+
constructor(idleMs) {
|
|
48
|
+
super(`SSE stream sent no bytes for ${idleMs} ms`);
|
|
49
|
+
this.idleMs = idleMs;
|
|
38
50
|
}
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Incremental SSE frame parser. Feed decoded text via `push()`; call
|
|
54
|
+
* `flush()` when the stream ends.
|
|
55
|
+
*/
|
|
56
|
+
var SSEFrameParser = class {
|
|
57
|
+
buffer = "";
|
|
58
|
+
frameEvent = "";
|
|
59
|
+
frameId;
|
|
60
|
+
frameDataLines = null;
|
|
61
|
+
bomChecked = false;
|
|
62
|
+
/**
|
|
63
|
+
* Append a decoded text chunk and return every frame the chunk
|
|
64
|
+
* completes. Frame state survives across calls: one frame can
|
|
65
|
+
* straddle any number of chunks.
|
|
66
|
+
*/
|
|
39
67
|
push(chunk) {
|
|
40
|
-
|
|
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;
|
|
41
74
|
const lines = this.buffer.split("\n");
|
|
42
75
|
this.buffer = lines.pop() ?? "";
|
|
43
76
|
return this.processLines(lines);
|
|
44
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* End-of-stream: process the unterminated trailing line, then
|
|
80
|
+
* dispatch a dangling frame that never got its blank line.
|
|
81
|
+
* Well-behaved servers end frames with "\n\n", but some close early.
|
|
82
|
+
*/
|
|
45
83
|
flush() {
|
|
46
|
-
const
|
|
84
|
+
const rest = this.buffer;
|
|
47
85
|
this.buffer = "";
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
this.currentEvent = {};
|
|
53
|
-
}
|
|
54
|
-
return events;
|
|
86
|
+
const frames = rest.length > 0 ? this.processLines([rest]) : [];
|
|
87
|
+
const dangling = this.dispatch();
|
|
88
|
+
if (dangling) frames.push(dangling);
|
|
89
|
+
return frames;
|
|
55
90
|
}
|
|
56
91
|
processLines(lines) {
|
|
57
|
-
const
|
|
92
|
+
const frames = [];
|
|
58
93
|
for (const rawLine of lines) {
|
|
59
94
|
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
60
|
-
if (line.startsWith(":")) continue;
|
|
61
95
|
if (line === "") {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
this.currentEvent = {};
|
|
96
|
+
const frame = this.dispatch();
|
|
97
|
+
if (frame) frames.push(frame);
|
|
65
98
|
continue;
|
|
66
99
|
}
|
|
67
|
-
if (line.startsWith("
|
|
68
|
-
|
|
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);
|
|
69
106
|
continue;
|
|
70
107
|
}
|
|
71
108
|
if (line.startsWith("event:")) {
|
|
72
|
-
this.
|
|
109
|
+
this.frameEvent = line.slice(6).trim();
|
|
73
110
|
continue;
|
|
74
111
|
}
|
|
75
|
-
if (line.startsWith("
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
112
|
+
if (line.startsWith("id:")) this.frameId = line.slice(3).trim();
|
|
113
|
+
}
|
|
114
|
+
return frames;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Dispatch the accumulated frame. A frame with no `data:` field (an
|
|
118
|
+
* `event:`/`id:`-only frame, or a bare comment) produces nothing, and
|
|
119
|
+
* its field state is consumed and reset either way.
|
|
120
|
+
*/
|
|
121
|
+
dispatch() {
|
|
122
|
+
const dataLines = this.frameDataLines;
|
|
123
|
+
const frame = dataLines ? {
|
|
124
|
+
event: detached(this.frameEvent),
|
|
125
|
+
data: dataLines.join("\n"),
|
|
126
|
+
id: this.frameId ? detached(this.frameId) : void 0
|
|
127
|
+
} : null;
|
|
128
|
+
this.frameEvent = "";
|
|
129
|
+
this.frameId = void 0;
|
|
130
|
+
this.frameDataLines = null;
|
|
131
|
+
return frame;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Serialize one frame to its wire form. Multi-line `data` becomes one
|
|
136
|
+
* `data:` line per source line so the payload survives the trip without
|
|
137
|
+
* premature frame dispatch.
|
|
138
|
+
*/
|
|
139
|
+
function encodeSSEFrame(frame) {
|
|
140
|
+
let out = "";
|
|
141
|
+
if (frame.event !== void 0) out += `event: ${frame.event}\n`;
|
|
142
|
+
if (frame.id !== void 0) out += `id: ${frame.id}\n`;
|
|
143
|
+
for (const line of frame.data.split("\n")) out += `data: ${line}\n`;
|
|
144
|
+
return `${out}\n`;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Read a streaming SSE response body into parsed frames.
|
|
148
|
+
*
|
|
149
|
+
* @throws `TypeError` when the body is missing.
|
|
150
|
+
* @throws `DOMException` with `name === "AbortError"` on cancellation —
|
|
151
|
+
* including the WHATWG-fetch variant where an aborted body resolves
|
|
152
|
+
* `done: true` instead of rejecting the read.
|
|
153
|
+
*/
|
|
154
|
+
async function* streamSSEFrames(body, options) {
|
|
155
|
+
if (!body) throw new TypeError("SSE stream has no response body");
|
|
156
|
+
const reader = body.getReader();
|
|
157
|
+
const decoder = new TextDecoder();
|
|
158
|
+
const parser = new SSEFrameParser();
|
|
159
|
+
const { signal, idleMs } = options ?? {};
|
|
160
|
+
const read = () => {
|
|
161
|
+
const limit = idleMs?.();
|
|
162
|
+
if (limit === void 0) return reader.read();
|
|
163
|
+
let timer;
|
|
164
|
+
return Promise.race([reader.read(), new Promise((_, reject) => {
|
|
165
|
+
timer = setTimeout(() => reject(new SSEStreamSilentError(limit)), limit);
|
|
166
|
+
})]).finally(() => clearTimeout(timer));
|
|
167
|
+
};
|
|
168
|
+
let completed = false;
|
|
169
|
+
try {
|
|
170
|
+
for (;;) {
|
|
171
|
+
if (signal?.aborted) throw new DOMException("SSE stream aborted", "AbortError");
|
|
172
|
+
const { done, value } = await read();
|
|
173
|
+
if (done) {
|
|
174
|
+
if (signal?.aborted) throw new DOMException("SSE stream aborted", "AbortError");
|
|
175
|
+
break;
|
|
79
176
|
}
|
|
177
|
+
for (const frame of parser.push(decoder.decode(value, { stream: true }))) yield frame;
|
|
178
|
+
}
|
|
179
|
+
for (const frame of parser.push(decoder.decode())) yield frame;
|
|
180
|
+
for (const frame of parser.flush()) yield frame;
|
|
181
|
+
completed = true;
|
|
182
|
+
} finally {
|
|
183
|
+
if (!completed) await reader.cancel().catch(() => void 0);
|
|
184
|
+
reader.releaseLock();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Incremental JSON event parser for chunked streams, framed by
|
|
189
|
+
* {@link SSEFrameParser}. Feed decoded string chunks via push(); call flush()
|
|
190
|
+
* on stream end. Frames whose trimmed data is empty produce no event.
|
|
191
|
+
*/
|
|
192
|
+
var SSEChunkParser = class {
|
|
193
|
+
frames = new SSEFrameParser();
|
|
194
|
+
options;
|
|
195
|
+
constructor(options = {}) {
|
|
196
|
+
this.options = options;
|
|
197
|
+
}
|
|
198
|
+
push(chunk) {
|
|
199
|
+
return this.toEvents(this.frames.push(chunk));
|
|
200
|
+
}
|
|
201
|
+
flush() {
|
|
202
|
+
return this.toEvents(this.frames.flush());
|
|
203
|
+
}
|
|
204
|
+
toEvents(frames) {
|
|
205
|
+
const events = [];
|
|
206
|
+
for (const frame of frames) {
|
|
207
|
+
const event = this.toEvent(frame);
|
|
208
|
+
if (event) events.push(event);
|
|
80
209
|
}
|
|
81
210
|
return events;
|
|
82
211
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const rawData = this.currentEvent.data.trim();
|
|
212
|
+
toEvent(frame) {
|
|
213
|
+
const rawData = frame.data.trim();
|
|
86
214
|
if (!rawData) return null;
|
|
87
215
|
let data;
|
|
88
216
|
if (this.options.transform) {
|
|
@@ -101,8 +229,8 @@ var SSEChunkParser = class {
|
|
|
101
229
|
return {
|
|
102
230
|
data,
|
|
103
231
|
rawData,
|
|
104
|
-
eventId:
|
|
105
|
-
eventType:
|
|
232
|
+
eventId: frame.id,
|
|
233
|
+
eventType: frame.event || void 0
|
|
106
234
|
};
|
|
107
235
|
}
|
|
108
236
|
};
|
|
@@ -297,6 +425,6 @@ function createUsageCallback(forward) {
|
|
|
297
425
|
extractor
|
|
298
426
|
};
|
|
299
427
|
}
|
|
300
|
-
export { SSEChunkParser, StreamUsageExtractor, consumeSSEStream, createStreamUsageExtractor, createUsageCallback, parseSSEData, parseSSEStream };
|
|
428
|
+
export { SSEChunkParser, SSEFrameParser, SSEStreamSilentError, StreamUsageExtractor, consumeSSEStream, createStreamUsageExtractor, createUsageCallback, encodeSSEFrame, parseSSEData, parseSSEStream, streamSSEFrames };
|
|
301
429
|
|
|
302
430
|
//# sourceMappingURL=index.js.map
|
package/dist/sse/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/sse/index.ts"],"sourcesContent":["/**\n * SSE Utilities\n *\n * Shared SSE parsing and stream usage extraction for the SDK.\n * Used by sdk-telemetry, sdk-memory, and other consumers.\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// Stream 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 SSE parser for chunked streams.\n * Feed decoded string chunks via push(); call flush() on stream end.\n */\nexport class SSEChunkParser<T = unknown> {\n private buffer = \"\";\n private currentEvent: { id?: string; event?: string; data?: string } = {};\n private options: SSEParserOptions<T>;\n\n constructor(options: SSEParserOptions<T> = {}) {\n this.options = options;\n }\n\n push(chunk: string): ParsedSSEEvent<T>[] {\n this.buffer += chunk;\n const lines = this.buffer.split(\"\\n\");\n this.buffer = lines.pop() ?? \"\";\n return this.processLines(lines);\n }\n\n flush(): ParsedSSEEvent<T>[] {\n const lines = this.buffer ? [this.buffer] : [];\n this.buffer = \"\";\n const events = this.processLines(lines);\n const finalEvent = this.parseCurrentEvent();\n if (finalEvent) {\n events.push(finalEvent);\n this.currentEvent = {};\n }\n return events;\n }\n\n private processLines(lines: string[]): ParsedSSEEvent<T>[] {\n const events: ParsedSSEEvent<T>[] = [];\n\n for (const rawLine of lines) {\n const line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\n\n if (line.startsWith(\":\")) {\n continue;\n }\n\n if (line === \"\") {\n const parsed = this.parseCurrentEvent();\n if (parsed) {\n events.push(parsed);\n }\n this.currentEvent = {};\n continue;\n }\n\n if (line.startsWith(\"id:\")) {\n this.currentEvent.id = line.slice(3).trim();\n continue;\n }\n\n if (line.startsWith(\"event:\")) {\n this.currentEvent.event = line.slice(6).trim();\n continue;\n }\n\n if (line.startsWith(\"data:\")) {\n let value = line.slice(5);\n if (value.startsWith(\" \")) {\n value = value.slice(1);\n }\n this.currentEvent.data =\n this.currentEvent.data !== undefined\n ? `${this.currentEvent.data}\\n${value}`\n : value;\n }\n }\n\n return events;\n }\n\n private parseCurrentEvent(): ParsedSSEEvent<T> | null {\n if (this.currentEvent.data === undefined) {\n return null;\n }\n\n const rawData = this.currentEvent.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: this.currentEvent.id,\n eventType: this.currentEvent.event,\n };\n }\n}\n\n/**\n * Parse SSE stream into typed events via async iteration.\n */\nexport async function* parseSSEStream<T = unknown>(\n stream: ReadableStream<Uint8Array>,\n options: SSEParserOptions<T> = {},\n): AsyncGenerator<ParsedSSEEvent<T>> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n const parser = new SSEChunkParser<T>(options);\n let chunkCount = 0;\n let totalBytes = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n console.log(\n `[agent-core:sse] Stream reader done after ${chunkCount} chunks, ${totalBytes} bytes`,\n );\n break;\n }\n\n chunkCount++;\n totalBytes += value?.length ?? 0;\n const chunk = decoder.decode(value, { stream: true });\n\n // Log every chunk for debugging\n if (chunkCount <= 3 || chunkCount % 10 === 0) {\n console.log(\n `[agent-core:sse] Chunk ${chunkCount}: ${value?.length ?? 0} bytes, preview: ${chunk.slice(0, 100).replace(/\\n/g, \"\\\\n\")}`,\n );\n }\n\n for (const event of parser.push(chunk)) {\n yield event;\n }\n }\n\n for (const event of parser.flush()) {\n yield event;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Consume SSE stream with callbacks.\n */\nexport async function consumeSSEStream<T = unknown>(\n stream: ReadableStream<Uint8Array>,\n onEvent: (event: ParsedSSEEvent<T>) => void,\n onError: (error: Error) => void,\n options: SSEParserOptions<T> = {},\n): Promise<void> {\n let eventCount = 0;\n try {\n for await (const event of parseSSEStream<T>(stream, options)) {\n eventCount++;\n onEvent(event);\n }\n // Stream finished normally\n console.log(\n `[agent-core:sse] Stream ended normally, total events: ${eventCount}`,\n );\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error));\n const isAbortError =\n normalizedError.name === \"AbortError\" ||\n normalizedError.message.toLowerCase().includes(\"abort\");\n\n if (isAbortError) {\n console.log(\n `[agent-core:sse] Stream aborted normally after ${eventCount} events`,\n );\n onError(normalizedError);\n return;\n }\n\n console.error(\n `[agent-core:sse] Stream error after ${eventCount} events:`,\n normalizedError,\n );\n onError(normalizedError);\n }\n}\n\n// ============================================================================\n// Stream Usage Extraction\n// ============================================================================\n\n/** Token usage extracted from stream */\nexport interface StreamTokenUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n}\n\n/** Accumulated usage from a stream session */\nexport interface StreamUsageAccumulator {\n /** Total tokens from all events */\n usage: StreamTokenUsage;\n /** Number of LLM response events */\n responseCount: number;\n /** Number of tool calls */\n toolCallCount: number;\n /** Model ID if detected from events */\n modelId?: string;\n /** Start time of accumulation */\n startedAt: Date;\n /** Duration in ms (set on finalize) */\n durationMs?: number;\n}\n\n/** Event types that contain token information (Anthropic SDK format) */\nconst TOKEN_EVENTS = new Set([\"message.stop\", \"content_block_stop\"]);\n\n/** Event types for tool calls - now handled via message.part.updated */\nconst _TOOL_CALL_EVENTS = new Set<string>();\n\n/**\n * StreamUsageExtractor - Accumulates token usage from SSE events.\n *\n * Usage:\n * ```typescript\n * const extractor = createStreamUsageExtractor();\n *\n * // In your stream callback\n * for await (const event of stream) {\n * extractor.processEvent(event);\n * // ... handle event\n * }\n *\n * const usage = extractor.finalize();\n * billingService.recordUsage({ usage: usage.usage, ... });\n * ```\n */\nexport class StreamUsageExtractor {\n private accumulator: StreamUsageAccumulator;\n private finalized = false;\n\n constructor() {\n this.accumulator = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n },\n responseCount: 0,\n toolCallCount: 0,\n startedAt: new Date(),\n };\n }\n\n /**\n * Process a raw SSE string (event: xxx\\ndata: {...}).\n */\n processRawSSE(raw: string): void {\n const parsed = parseSSEData(raw);\n if (parsed) {\n this.processEvent(parsed);\n }\n }\n\n /**\n * Process a parsed SSE event object.\n */\n processEvent(event: SSEEventData | Record<string, unknown>): void {\n if (this.finalized) {\n throw new Error(\"StreamUsageExtractor already finalized\");\n }\n\n const eventType = (event as SSEEventData).type ?? event.type;\n\n // Handle message.part.updated (canonical event for all part types)\n if (eventType === \"message.part.updated\") {\n const part = (event as { part?: { type?: string; tool?: string } }).part;\n const partType = part?.type;\n\n // Text and reasoning parts count as LLM responses\n if (partType === \"text\" || partType === \"reasoning\") {\n this.extractTokens(event);\n this.accumulator.responseCount++;\n }\n\n // Tool parts count as tool calls\n if (partType === \"tool\" && part?.tool) {\n this.accumulator.toolCallCount++;\n }\n\n return;\n }\n\n // Extract token counts from Anthropic SDK format events\n if (TOKEN_EVENTS.has(eventType as string)) {\n this.extractTokens(event);\n this.accumulator.responseCount++;\n }\n\n // Extract model ID if present\n if (!this.accumulator.modelId && event.model) {\n this.accumulator.modelId = String(event.model);\n }\n\n // Handle usage summary events (some providers send final usage)\n if (eventType === \"usage\" || event.usage) {\n this.extractUsageSummary(event);\n }\n\n // Handle message_stop with usage (Anthropic format)\n if (eventType === \"message_stop\" && event.usage) {\n this.extractUsageSummary(event);\n }\n }\n\n /**\n * Finalize and return accumulated usage.\n * Sets duration and marks extractor as complete.\n */\n finalize(): StreamUsageAccumulator {\n if (this.finalized) {\n return this.accumulator;\n }\n\n this.finalized = true;\n this.accumulator.durationMs =\n Date.now() - this.accumulator.startedAt.getTime();\n\n return this.accumulator;\n }\n\n /**\n * Get current accumulated usage (without finalizing).\n */\n current(): Readonly<StreamUsageAccumulator> {\n return this.accumulator;\n }\n\n /**\n * Reset the extractor for reuse.\n */\n reset(): void {\n this.accumulator = {\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n },\n responseCount: 0,\n toolCallCount: 0,\n startedAt: new Date(),\n };\n this.finalized = false;\n }\n\n private extractTokens(event: Record<string, unknown>): void {\n // Direct token count (simple format)\n if (typeof event.tokens === \"number\") {\n this.accumulator.usage.outputTokens += event.tokens;\n }\n\n // Output tokens (explicit)\n if (typeof event.outputTokens === \"number\") {\n this.accumulator.usage.outputTokens += event.outputTokens;\n }\n if (typeof event.output_tokens === \"number\") {\n this.accumulator.usage.outputTokens += event.output_tokens;\n }\n\n // Input tokens (usually from final summary)\n if (typeof event.inputTokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n event.inputTokens,\n );\n }\n if (typeof event.input_tokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n event.input_tokens,\n );\n }\n\n // Cache tokens (Anthropic format)\n if (typeof event.cache_read_input_tokens === \"number\") {\n this.accumulator.usage.cacheReadTokens =\n (this.accumulator.usage.cacheReadTokens ?? 0) +\n event.cache_read_input_tokens;\n }\n if (typeof event.cache_creation_input_tokens === \"number\") {\n this.accumulator.usage.cacheWriteTokens =\n (this.accumulator.usage.cacheWriteTokens ?? 0) +\n event.cache_creation_input_tokens;\n }\n }\n\n private extractUsageSummary(event: Record<string, unknown>): void {\n const usage = (event.usage as Record<string, unknown>) ?? event;\n\n if (typeof usage.input_tokens === \"number\") {\n this.accumulator.usage.inputTokens = Math.max(\n this.accumulator.usage.inputTokens,\n usage.input_tokens,\n );\n }\n if (typeof usage.output_tokens === \"number\") {\n this.accumulator.usage.outputTokens = Math.max(\n this.accumulator.usage.outputTokens,\n usage.output_tokens,\n );\n }\n if (typeof usage.cache_read_input_tokens === \"number\") {\n this.accumulator.usage.cacheReadTokens = usage.cache_read_input_tokens;\n }\n if (typeof usage.cache_creation_input_tokens === \"number\") {\n this.accumulator.usage.cacheWriteTokens =\n usage.cache_creation_input_tokens;\n }\n }\n}\n\n/**\n * Create a new StreamUsageExtractor.\n */\nexport function createStreamUsageExtractor(): StreamUsageExtractor {\n return new StreamUsageExtractor();\n}\n\n/**\n * Create a callback wrapper that extracts usage while forwarding events.\n *\n * Usage:\n * ```typescript\n * const { callback, getUsage } = createUsageCallback((event) => {\n * // your event handler\n * });\n *\n * await orchestrator.executeAgentStream(projectRef, request, callback);\n * const usage = getUsage();\n * ```\n */\nexport function createUsageCallback<T extends Record<string, unknown>>(\n forward?: (event: T) => void,\n): {\n callback: (event: T) => void;\n getUsage: () => StreamUsageAccumulator;\n extractor: StreamUsageExtractor;\n} {\n const extractor = createStreamUsageExtractor();\n\n return {\n callback: (event: T) => {\n extractor.processEvent(event);\n forward?.(event);\n },\n getUsage: () => extractor.finalize(),\n extractor,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/sse/index.ts"],"sourcesContent":["/**\n * SSE Utilities\n *\n * The one Server-Sent Events wire-format implementation: frame parsing and\n * framing, plus the JSON event layer and stream usage extraction built on it.\n * Everything here is pure ECMAScript over `ReadableStream`/`TextDecoder`\n * globals and imports nothing, so Node, browser and Cloudflare Worker bundles\n * can take this subpath without pulling a dependency.\n *\n * Frame parser guarantees (WHATWG Server-Sent Events, §9.2 \"Interpreting an\n * event stream\"):\n * - Buffers frames that straddle chunk boundaries; `push()` emits only\n * complete frames and `flush()` dispatches a trailing frame whose\n * terminating blank line never arrived.\n * - Accepts LF and CRLF line terminators interchangeably. Bare CR is\n * intentionally not treated as a terminator: no known emitter uses\n * it, and supporting it would risk splitting payloads that\n * legitimately contain CR.\n * - Joins multi-line `data:` fields with \"\\n\" (§9.2.6 step 3).\n * - Skips comment lines (a leading \":\") — the heartbeat mechanism.\n * - Ignores `retry:` and unrecognized fields.\n * - Strips one leading U+FEFF (BOM) from the stream.\n * - Applies the `event:`/`id:` state of a frame only to that frame.\n *\n * The frame layer does no JSON parsing, reconnect/`Last-Event-ID` tracking or\n * event-type filtering; `SSEChunkParser` adds JSON payload handling on top.\n */\n\n/** Raw SSE event data from stream */\nexport interface SSEEventData {\n type: string;\n // Tool events\n toolName?: string;\n name?: string;\n callId?: string;\n input?: unknown;\n arguments?: unknown;\n output?: unknown;\n result?: unknown;\n durationMs?: number;\n // Error events\n error?: string;\n message?: string;\n code?: string;\n // Status events\n status?: string;\n // Token/LLM events\n tokens?: number;\n model?: string;\n // Usage fields (Anthropic format)\n inputTokens?: number;\n outputTokens?: number;\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n // Usage object (some providers wrap in usage field)\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n // Generic\n [key: string]: unknown;\n}\n\n/**\n * Parse raw SSE data string into structured event data.\n *\n * Handles the standard SSE format:\n * ```\n * event: eventType\n * data: {\"json\": \"payload\"}\n * ```\n */\nexport function parseSSEData(rawData: string): SSEEventData | null {\n try {\n const lines = rawData.split(\"\\n\");\n let eventType: string | undefined;\n let data: string | undefined;\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n eventType = line.slice(7).trim();\n } else if (line.startsWith(\"data: \")) {\n data = line.slice(6);\n }\n }\n\n if (data) {\n const parsed = JSON.parse(data);\n return {\n type: eventType || parsed.type || \"unknown\",\n ...parsed,\n };\n }\n } catch {\n // Ignore parse errors (heartbeats, malformed events)\n }\n return null;\n}\n\n// ============================================================================\n// Frame Parsing and Framing\n// ============================================================================\n\n/**\n * One parsed SSE frame: the fields accumulated between blank lines.\n *\n * `event` and `id` share no memory with the chunk the frame arrived in, so a\n * consumer can keep them as long as it keeps its events. `data` can share\n * that memory: parse it, and keep what the parse returns.\n */\nexport interface SSEFrame {\n /** Value of the `event:` field; \"\" when the frame carries none. */\n event: string;\n /** `data:` field lines joined with \"\\n\". */\n data: string;\n /** Value of the `id:` field; undefined when the frame carries none. */\n id?: string;\n}\n\n/**\n * A copy of `value` that shares no memory with the string it was cut from.\n *\n * V8 can return a short substring of a long string as a view that keeps the\n * whole string alive. Consumers keep event names and ids as long as they keep\n * events, so a view would keep every chunk its frame arrived in alive.\n */\nfunction detached(value: string): string {\n return value === \"\" ? value : (JSON.parse(JSON.stringify(value)) as string);\n}\n\n/** Options for {@link streamSSEFrames}. */\nexport interface SSEStreamOptions {\n /**\n * Aborts the read loop. When the signal fires, the generator throws a\n * `DOMException` named \"AbortError\" so consumers can distinguish a\n * cancellation from a clean end-of-stream without inspecting signal\n * state.\n */\n signal?: AbortSignal;\n /**\n * The longest silence the stream may keep, read before each chunk read.\n * Any bytes reset it, comment heartbeats included, because a heartbeat is\n * the producer saying the connection is still live. A read that outlasts it\n * throws {@link SSEStreamSilentError}. `undefined` waits without bound, which\n * suits a producer that promises no heartbeat.\n */\n idleMs?: () => number | undefined;\n}\n\n/**\n * Thrown when a stream sends no bytes for longer than its `idleMs`. A\n * producer that heartbeats would have written in that time, so the\n * connection is dead even though it never closed: a half-open TCP connection\n * or a stalled proxy hold the socket open while nothing arrives.\n */\nexport class SSEStreamSilentError extends Error {\n override readonly name = \"SSEStreamSilentError\";\n readonly idleMs: number;\n constructor(idleMs: number) {\n super(`SSE stream sent no bytes for ${idleMs} ms`);\n this.idleMs = idleMs;\n }\n}\n\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":";;;;;;;;;;AA4EA,SAAgB,aAAa,SAAsC;CACjE,IAAI;EACF,MAAM,QAAQ,QAAQ,MAAM,IAAI;EAChC,IAAI;EACJ,IAAI;EAEJ,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,WAAW,SAAS,GAC3B,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK;OAC1B,IAAI,KAAK,WAAW,QAAQ,GACjC,OAAO,KAAK,MAAM,CAAC;EAIvB,IAAI,MAAM;GACR,MAAM,SAAS,KAAK,MAAM,IAAI;GAC9B,OAAO;IACL,MAAM,aAAa,OAAO,QAAQ;IAClC,GAAG;GACL;EACF;CACF,QAAQ,CAER;CACA,OAAO;AACT;;;;;;;;AA6BA,SAAS,SAAS,OAAuB;CACvC,OAAO,UAAU,KAAK,QAAS,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACjE;;;;;;;AA2BA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,OAAyB;CACzB;CACA,YAAY,QAAgB;EAC1B,MAAM,gCAAgC,OAAO,IAAI;EACjD,KAAK,SAAS;CAChB;AACF;;;;;AAMA,IAAa,iBAAb,MAA4B;CAC1B,SAAiB;CACjB,aAAqB;CACrB;CACA,iBAA0C;CAC1C,aAAqB;;;;;;CAOrB,KAAK,OAA2B;EAC9B,IAAI,OAAO;EACX,IAAI,CAAC,KAAK,cAAc,KAAK,SAAS,GAAG;GACvC,KAAK,aAAa;GAGlB,IAAI,KAAK,WAAW,CAAC,MAAM,OAAQ,OAAO,KAAK,MAAM,CAAC;EACxD;EACA,KAAK,UAAU;EACf,MAAM,QAAQ,KAAK,OAAO,MAAM,IAAI;EAGpC,KAAK,SAAS,MAAM,IAAI,KAAK;EAC7B,OAAO,KAAK,aAAa,KAAK;CAChC;;;;;;CAOA,QAAoB;EAClB,MAAM,OAAO,KAAK;EAClB,KAAK,SAAS;EACd,MAAM,SAAS,KAAK,SAAS,IAAI,KAAK,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;EAC9D,MAAM,WAAW,KAAK,SAAS;EAC/B,IAAI,UAAU,OAAO,KAAK,QAAQ;EAClC,OAAO;CACT;CAEA,aAAqB,OAA6B;EAChD,MAAM,SAAqB,CAAC;EAC5B,KAAK,MAAM,WAAW,OAAO;GAC3B,MAAM,OAAO,QAAQ,SAAS,IAAI,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI;GAC7D,IAAI,SAAS,IAAI;IACf,MAAM,QAAQ,KAAK,SAAS;IAC5B,IAAI,OAAO,OAAO,KAAK,KAAK;IAC5B;GACF;GAEA,IAAI,KAAK,WAAW,GAAG,GAAG;GAC1B,IAAI,KAAK,WAAW,OAAO,GAAG;IAE5B,IAAI,QAAQ,KAAK,MAAM,CAAC;IACxB,IAAI,MAAM,WAAW,GAAG,GAAG,QAAQ,MAAM,MAAM,CAAC;IAChD,IAAI,KAAK,mBAAmB,MAAM,KAAK,iBAAiB,CAAC;IACzD,KAAK,eAAe,KAAK,KAAK;IAC9B;GACF;GACA,IAAI,KAAK,WAAW,QAAQ,GAAG;IAC7B,KAAK,aAAa,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK;IACrC;GACF;GACA,IAAI,KAAK,WAAW,KAAK,GACvB,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK;EAGtC;EACA,OAAO;CACT;;;;;;CAOA,WAAoC;EAClC,MAAM,YAAY,KAAK;EACvB,MAAM,QAAyB,YAC3B;GACE,OAAO,SAAS,KAAK,UAAU;GAC/B,MAAM,UAAU,KAAK,IAAI;GACzB,IAAI,KAAK,UAAU,SAAS,KAAK,OAAO,IAAI,KAAA;EAC9C,IACA;EACJ,KAAK,aAAa;EAClB,KAAK,UAAU,KAAA;EACf,KAAK,iBAAiB;EACtB,OAAO;CACT;AACF;;;;;;AAOA,SAAgB,eAAe,OAIpB;CACT,IAAI,MAAM;CACV,IAAI,MAAM,UAAU,KAAA,GAAW,OAAO,UAAU,MAAM,MAAM;CAC5D,IAAI,MAAM,OAAO,KAAA,GAAW,OAAO,OAAO,MAAM,GAAG;CACnD,KAAK,MAAM,QAAQ,MAAM,KAAK,MAAM,IAAI,GAAG,OAAO,SAAS,KAAK;CAChE,OAAO,GAAG,IAAI;AAChB;;;;;;;;;AAUA,gBAAuB,gBACrB,MACA,SAC0B;CAC1B,IAAI,CAAC,MACH,MAAM,IAAI,UAAU,iCAAiC;CAEvD,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,UAAU,IAAI,YAAY;CAChC,MAAM,SAAS,IAAI,eAAe;CAClC,MAAM,EAAE,QAAQ,WAAW,WAAW,CAAC;CACvC,MAAM,aAA4D;EAChE,MAAM,QAAQ,SAAS;EACvB,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO,KAAK;EAC5C,IAAI;EACJ,OAAO,QAAQ,KAAK,CAClB,OAAO,KAAK,GACZ,IAAI,SAAgB,GAAG,WAAW;GAChC,QAAQ,iBACA,OAAO,IAAI,qBAAqB,KAAK,CAAC,GAC5C,KACF;EACF,CAAC,CACH,CAAC,CAAC,CAAC,cAAc,aAAa,KAAK,CAAC;CACtC;CACA,IAAI,YAAY;CAChB,IAAI;EACF,SAAS;GACP,IAAI,QAAQ,SACV,MAAM,IAAI,aAAa,sBAAsB,YAAY;GAE3D,MAAM,EAAE,MAAM,UAAU,MAAM,KAAK;GACnC,IAAI,MAAM;IACR,IAAI,QAAQ,SACV,MAAM,IAAI,aAAa,sBAAsB,YAAY;IAE3D;GACF;GACA,KAAK,MAAM,SAAS,OAAO,KACzB,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,CACxC,GACE,MAAM;EAEV;EAGA,KAAK,MAAM,SAAS,OAAO,KAAK,QAAQ,OAAO,CAAC,GAAG,MAAM;EACzD,KAAK,MAAM,SAAS,OAAO,MAAM,GAAG,MAAM;EAC1C,YAAY;CACd,UAAU;EAKR,IAAI,CAAC,WAAW,MAAM,OAAO,OAAO,CAAC,CAAC,YAAY,KAAA,CAAS;EAC3D,OAAO,YAAY;CACrB;AACF;;;;;;AAyBA,IAAa,iBAAb,MAAyC;CACvC,SAA0B,IAAI,eAAe;CAC7C;CAEA,YAAY,UAA+B,CAAC,GAAG;EAC7C,KAAK,UAAU;CACjB;CAEA,KAAK,OAAoC;EACvC,OAAO,KAAK,SAAS,KAAK,OAAO,KAAK,KAAK,CAAC;CAC9C;CAEA,QAA6B;EAC3B,OAAO,KAAK,SAAS,KAAK,OAAO,MAAM,CAAC;CAC1C;CAEA,SAAiB,QAAyC;EACxD,MAAM,SAA8B,CAAC;EACrC,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,QAAQ,KAAK,QAAQ,KAAK;GAChC,IAAI,OAAO,OAAO,KAAK,KAAK;EAC9B;EACA,OAAO;CACT;CAEA,QAAgB,OAA2C;EACzD,MAAM,UAAU,MAAM,KAAK,KAAK;EAChC,IAAI,CAAC,SACH,OAAO;EAGT,IAAI;EACJ,IAAI,KAAK,QAAQ,WAAW;GAC1B,MAAM,cAAc,KAAK,QAAQ,UAAU,OAAO;GAClD,IAAI,gBAAgB,MAAM;IACxB,IAAI,KAAK,QAAQ,cACf,QAAQ,KAAK,gCAAgC,QAAQ,MAAM,GAAG,GAAG,CAAC;IAEpE,OAAO;GACT;GACA,OAAO;EACT,OACE,IAAI;GACF,OAAO,KAAK,MAAM,OAAO;EAC3B,QAAQ;GACN,IAAI,KAAK,QAAQ,cACf,QAAQ,KAAK,qBAAqB,QAAQ,MAAM,GAAG,GAAG,CAAC;GAEzD,OAAO;EACT;EAGF,OAAO;GACL;GACA;GACA,SAAS,MAAM;GACf,WAAW,MAAM,SAAS,KAAA;EAC5B;CACF;AACF;;;;AAKA,gBAAuB,eACrB,QACA,UAA+B,CAAC,GACG;CACnC,MAAM,SAAS,OAAO,UAAU;CAChC,MAAM,UAAU,IAAI,YAAY;CAChC,MAAM,SAAS,IAAI,eAAkB,OAAO;CAC5C,IAAI,aAAa;CACjB,IAAI,aAAa;CAEjB,IAAI;EACF,OAAO,MAAM;GACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;IACR,QAAQ,IACN,6CAA6C,WAAW,WAAW,WAAW,OAChF;IACA;GACF;GAEA;GACA,cAAc,OAAO,UAAU;GAC/B,MAAM,QAAQ,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;GAGpD,IAAI,cAAc,KAAK,aAAa,OAAO,GACzC,QAAQ,IACN,0BAA0B,WAAW,IAAI,OAAO,UAAU,EAAE,mBAAmB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,OAAO,KAAK,GACzH;GAGF,KAAK,MAAM,SAAS,OAAO,KAAK,KAAK,GACnC,MAAM;EAEV;EAEA,KAAK,MAAM,SAAS,OAAO,MAAM,GAC/B,MAAM;CAEV,UAAU;EACR,OAAO,YAAY;CACrB;AACF;;;;AAKA,eAAsB,iBACpB,QACA,SACA,SACA,UAA+B,CAAC,GACjB;CACf,IAAI,aAAa;CACjB,IAAI;EACF,WAAW,MAAM,SAAS,eAAkB,QAAQ,OAAO,GAAG;GAC5D;GACA,QAAQ,KAAK;EACf;EAEA,QAAQ,IACN,yDAAyD,YAC3D;CACF,SAAS,OAAO;EACd,MAAM,kBACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;EAK1D,IAHE,gBAAgB,SAAS,gBACzB,gBAAgB,QAAQ,YAAY,CAAC,CAAC,SAAS,OAAO,GAEtC;GAChB,QAAQ,IACN,kDAAkD,WAAW,QAC/D;GACA,QAAQ,eAAe;GACvB;EACF;EAEA,QAAQ,MACN,uCAAuC,WAAW,WAClD,eACF;EACA,QAAQ,eAAe;CACzB;AACF;;AA+BA,MAAM,+BAAe,IAAI,IAAI,CAAC,gBAAgB,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;AAsBnE,IAAa,uBAAb,MAAkC;CAChC;CACA,YAAoB;CAEpB,cAAc;EACZ,KAAK,cAAc;GACjB,OAAO;IACL,aAAa;IACb,cAAc;GAChB;GACA,eAAe;GACf,eAAe;GACf,2BAAW,IAAI,KAAK;EACtB;CACF;;;;CAKA,cAAc,KAAmB;EAC/B,MAAM,SAAS,aAAa,GAAG;EAC/B,IAAI,QACF,KAAK,aAAa,MAAM;CAE5B;;;;CAKA,aAAa,OAAqD;EAChE,IAAI,KAAK,WACP,MAAM,IAAI,MAAM,wCAAwC;EAG1D,MAAM,YAAa,MAAuB,QAAQ,MAAM;EAGxD,IAAI,cAAc,wBAAwB;GACxC,MAAM,OAAQ,MAAsD;GACpE,MAAM,WAAW,MAAM;GAGvB,IAAI,aAAa,UAAU,aAAa,aAAa;IACnD,KAAK,cAAc,KAAK;IACxB,KAAK,YAAY;GACnB;GAGA,IAAI,aAAa,UAAU,MAAM,MAC/B,KAAK,YAAY;GAGnB;EACF;EAGA,IAAI,aAAa,IAAI,SAAmB,GAAG;GACzC,KAAK,cAAc,KAAK;GACxB,KAAK,YAAY;EACnB;EAGA,IAAI,CAAC,KAAK,YAAY,WAAW,MAAM,OACrC,KAAK,YAAY,UAAU,OAAO,MAAM,KAAK;EAI/C,IAAI,cAAc,WAAW,MAAM,OACjC,KAAK,oBAAoB,KAAK;EAIhC,IAAI,cAAc,kBAAkB,MAAM,OACxC,KAAK,oBAAoB,KAAK;CAElC;;;;;CAMA,WAAmC;EACjC,IAAI,KAAK,WACP,OAAO,KAAK;EAGd,KAAK,YAAY;EACjB,KAAK,YAAY,aACf,KAAK,IAAI,IAAI,KAAK,YAAY,UAAU,QAAQ;EAElD,OAAO,KAAK;CACd;;;;CAKA,UAA4C;EAC1C,OAAO,KAAK;CACd;;;;CAKA,QAAc;EACZ,KAAK,cAAc;GACjB,OAAO;IACL,aAAa;IACb,cAAc;GAChB;GACA,eAAe;GACf,eAAe;GACf,2BAAW,IAAI,KAAK;EACtB;EACA,KAAK,YAAY;CACnB;CAEA,cAAsB,OAAsC;EAE1D,IAAI,OAAO,MAAM,WAAW,UAC1B,KAAK,YAAY,MAAM,gBAAgB,MAAM;EAI/C,IAAI,OAAO,MAAM,iBAAiB,UAChC,KAAK,YAAY,MAAM,gBAAgB,MAAM;EAE/C,IAAI,OAAO,MAAM,kBAAkB,UACjC,KAAK,YAAY,MAAM,gBAAgB,MAAM;EAI/C,IAAI,OAAO,MAAM,gBAAgB,UAC/B,KAAK,YAAY,MAAM,cAAc,KAAK,IACxC,KAAK,YAAY,MAAM,aACvB,MAAM,WACR;EAEF,IAAI,OAAO,MAAM,iBAAiB,UAChC,KAAK,YAAY,MAAM,cAAc,KAAK,IACxC,KAAK,YAAY,MAAM,aACvB,MAAM,YACR;EAIF,IAAI,OAAO,MAAM,4BAA4B,UAC3C,KAAK,YAAY,MAAM,mBACpB,KAAK,YAAY,MAAM,mBAAmB,KAC3C,MAAM;EAEV,IAAI,OAAO,MAAM,gCAAgC,UAC/C,KAAK,YAAY,MAAM,oBACpB,KAAK,YAAY,MAAM,oBAAoB,KAC5C,MAAM;CAEZ;CAEA,oBAA4B,OAAsC;EAChE,MAAM,QAAS,MAAM,SAAqC;EAE1D,IAAI,OAAO,MAAM,iBAAiB,UAChC,KAAK,YAAY,MAAM,cAAc,KAAK,IACxC,KAAK,YAAY,MAAM,aACvB,MAAM,YACR;EAEF,IAAI,OAAO,MAAM,kBAAkB,UACjC,KAAK,YAAY,MAAM,eAAe,KAAK,IACzC,KAAK,YAAY,MAAM,cACvB,MAAM,aACR;EAEF,IAAI,OAAO,MAAM,4BAA4B,UAC3C,KAAK,YAAY,MAAM,kBAAkB,MAAM;EAEjD,IAAI,OAAO,MAAM,gCAAgC,UAC/C,KAAK,YAAY,MAAM,mBACrB,MAAM;CAEZ;AACF;;;;AAKA,SAAgB,6BAAmD;CACjE,OAAO,IAAI,qBAAqB;AAClC;;;;;;;;;;;;;;AAeA,SAAgB,oBACd,SAKA;CACA,MAAM,YAAY,2BAA2B;CAE7C,OAAO;EACL,WAAW,UAAa;GACtB,UAAU,aAAa,KAAK;GAC5B,UAAU,KAAK;EACjB;EACA,gBAAgB,UAAU,SAAS;EACnC;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
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",
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
"types": "./dist/telemetry/index.d.ts",
|
|
58
58
|
"default": "./dist/telemetry/index.js"
|
|
59
59
|
},
|
|
60
|
+
"./server-timing": {
|
|
61
|
+
"import": "./dist/server-timing/index.js",
|
|
62
|
+
"types": "./dist/server-timing/index.d.ts",
|
|
63
|
+
"default": "./dist/server-timing/index.js"
|
|
64
|
+
},
|
|
60
65
|
"./interactions": {
|
|
61
66
|
"import": "./dist/interactions/index.js",
|
|
62
67
|
"types": "./dist/interactions/index.d.ts",
|
|
@@ -114,6 +119,9 @@
|
|
|
114
119
|
"types/*": [
|
|
115
120
|
"src/types/*"
|
|
116
121
|
],
|
|
122
|
+
"server-timing": [
|
|
123
|
+
"src/server-timing/index.ts"
|
|
124
|
+
],
|
|
117
125
|
"interactions": [
|
|
118
126
|
"src/interactions/index.ts"
|
|
119
127
|
],
|