@tangle-network/agent-core 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/browser.d.ts +1 -1
  3. package/dist/auth/browser.js +1 -2
  4. package/dist/auth/index.d.ts +1 -7
  5. package/dist/auth/index.d.ts.map +1 -1
  6. package/dist/auth/index.js +4 -7
  7. package/dist/auth/index.js.map +1 -1
  8. package/dist/computer-use/index.d.ts +0 -6
  9. package/dist/computer-use/index.d.ts.map +1 -1
  10. package/dist/computer-use/index.js +3 -6
  11. package/dist/computer-use/index.js.map +1 -1
  12. package/dist/{errors-GuWACHTg.js → errors-B5KiSXnw.js} +2 -3
  13. package/dist/{errors-GuWACHTg.js.map → errors-B5KiSXnw.js.map} +1 -1
  14. package/dist/events/index.d.ts +1 -1
  15. package/dist/events/index.js +1 -2
  16. package/dist/{events-U7kxNUpM.js → events-C2vdiRD_.js} +4 -7
  17. package/dist/{events-U7kxNUpM.js.map → events-C2vdiRD_.js.map} +1 -1
  18. package/dist/{index-68is3nof.d.ts → index-AEKS1kB8.d.ts} +1 -9
  19. package/dist/index-AEKS1kB8.d.ts.map +1 -0
  20. package/dist/{index-Dq6SfGaX.d.ts → index-CevHCod7.d.ts} +1 -9
  21. package/dist/index-CevHCod7.d.ts.map +1 -0
  22. package/dist/{index-CKDeaS7l.d.ts → index-DWPciD1S.d.ts} +2 -8
  23. package/dist/index-DWPciD1S.d.ts.map +1 -0
  24. package/dist/{index-BFYASYJk.d.ts → index-DwvNnL6X.d.ts} +1 -3
  25. package/dist/index-DwvNnL6X.d.ts.map +1 -0
  26. package/dist/{index-CuBvSsBf.d.ts → index-OCnM4OoP.d.ts} +1 -7
  27. package/dist/index-OCnM4OoP.d.ts.map +1 -0
  28. package/dist/{index-CQPGv8jg.d.ts → index-v0euJYbX.d.ts} +10 -12
  29. package/dist/index-v0euJYbX.d.ts.map +1 -0
  30. package/dist/index.d.ts +7 -23
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +15 -23
  33. package/dist/index.js.map +1 -1
  34. package/dist/interactions/index.d.ts +1 -3
  35. package/dist/interactions/index.d.ts.map +1 -1
  36. package/dist/interactions/index.js +4 -7
  37. package/dist/interactions/index.js.map +1 -1
  38. package/dist/interactions/mcp-server.d.ts +1 -3
  39. package/dist/interactions/mcp-server.d.ts.map +1 -1
  40. package/dist/interactions/mcp-server.js +2 -3
  41. package/dist/{lifecycle-BljLcnY5.js → lifecycle-wjO8lqUZ.js} +2 -3
  42. package/dist/{lifecycle-BljLcnY5.js.map → lifecycle-wjO8lqUZ.js.map} +1 -1
  43. package/dist/node/index.d.ts +0 -2
  44. package/dist/node/index.d.ts.map +1 -1
  45. package/dist/node/index.js +1 -2
  46. package/dist/resilience/index.d.ts +1 -1
  47. package/dist/resilience/index.js +1 -2
  48. package/dist/{resilience-Cv5tqIDU.js → resilience-Bb1XDoKt.js} +5 -8
  49. package/dist/{resilience-Cv5tqIDU.js.map → resilience-Bb1XDoKt.js.map} +1 -1
  50. package/dist/sse/index.d.ts +123 -11
  51. package/dist/sse/index.d.ts.map +1 -1
  52. package/dist/sse/index.js +164 -37
  53. package/dist/sse/index.js.map +1 -1
  54. package/dist/telemetry/index.d.ts +1 -1
  55. package/dist/telemetry/index.js +1 -2
  56. package/dist/{telemetry--KwUEdTv.js → telemetry-CF4pOM9u.js} +5 -9
  57. package/dist/{telemetry--KwUEdTv.js.map → telemetry-CF4pOM9u.js.map} +1 -1
  58. package/dist/{tokens-browser-DCV3-WHc.d.ts → tokens-browser-CGLWUfZ7.d.ts} +5 -11
  59. package/dist/tokens-browser-CGLWUfZ7.d.ts.map +1 -0
  60. package/dist/{tools-DeZkIW3h.d.ts → tools-C3tUmLEu.d.ts} +1 -5
  61. package/dist/tools-C3tUmLEu.d.ts.map +1 -0
  62. package/dist/transport/index.d.ts +1 -1
  63. package/dist/transport/index.js +1 -2
  64. package/dist/{transport-DAfw3oL0.js → transport-BqEVpHWb.js} +5 -9
  65. package/dist/{transport-DAfw3oL0.js.map → transport-BqEVpHWb.js.map} +1 -1
  66. package/dist/types/index.d.ts +1 -1
  67. package/dist/types/index.js +1 -2
  68. package/dist/{types--u-Om3vE.js → types-BKMl10eN.js} +5 -8
  69. package/dist/{types--u-Om3vE.js.map → types-BKMl10eN.js.map} +1 -1
  70. package/dist/{types-DVN5qATk.js → types-UwiuyvMA.js} +2 -3
  71. package/dist/{types-DVN5qATk.js.map → types-UwiuyvMA.js.map} +1 -1
  72. package/package.json +9 -9
  73. package/dist/index-68is3nof.d.ts.map +0 -1
  74. package/dist/index-BFYASYJk.d.ts.map +0 -1
  75. package/dist/index-CKDeaS7l.d.ts.map +0 -1
  76. package/dist/index-CQPGv8jg.d.ts.map +0 -1
  77. package/dist/index-CuBvSsBf.d.ts.map +0 -1
  78. package/dist/index-Dq6SfGaX.d.ts.map +0 -1
  79. package/dist/tokens-browser-DCV3-WHc.d.ts.map +0 -1
  80. package/dist/tools-DeZkIW3h.d.ts.map +0 -1
@@ -1,4 +1,4 @@
1
- import { n as closeHttpServer, r as waitForSettled, t as SerializedTransportLifecycle } from "../lifecycle-BljLcnY5.js";
1
+ import { n as closeHttpServer, r as waitForSettled, t as SerializedTransportLifecycle } from "../lifecycle-wjO8lqUZ.js";
2
2
  import { z } from "zod";
3
3
  import { createHash, randomBytes, randomUUID } from "node:crypto";
4
4
  import { createServer } from "node:http";
@@ -6,7 +6,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
7
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
8
  import { isJSONRPCErrorResponse, isJSONRPCRequest, isJSONRPCResultResponse } from "@modelcontextprotocol/sdk/types.js";
9
- //#region src/interactions/mcp-server.ts
9
+ // src/interactions/mcp-server.ts
10
10
  const DEFAULT_INTERACTION_MCP_SERVER_NAME = "tangle-interaction";
11
11
  const MAX_MCP_BODY_BYTES = 4194304;
12
12
  const MAX_MCP_REPLAY_EVENTS = 1024;
@@ -363,7 +363,6 @@ var InteractionMcpServer = class {
363
363
  return generationId;
364
364
  }
365
365
  };
366
- //#endregion
367
366
  export { DEFAULT_INTERACTION_MCP_SERVER_NAME, InteractionMcpServer, interactionMcpToolName };
368
367
 
369
368
  //# sourceMappingURL=mcp-server.js.map
@@ -1,4 +1,4 @@
1
- //#region src/interactions/lifecycle.ts
1
+ // src/interactions/lifecycle.ts
2
2
  /** Serialize one transport's single-use start and stop operations. */
3
3
  var SerializedTransportLifecycle = class {
4
4
  state = "idle";
@@ -62,7 +62,6 @@ async function waitForSettled(promises, graceMs = 1e3) {
62
62
  if (timer) clearTimeout(timer);
63
63
  }
64
64
  }
65
- //#endregion
66
65
  export { closeHttpServer as n, waitForSettled as r, SerializedTransportLifecycle as t };
67
66
 
68
- //# sourceMappingURL=lifecycle-BljLcnY5.js.map
67
+ //# sourceMappingURL=lifecycle-wjO8lqUZ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle-BljLcnY5.js","names":[],"sources":["../src/interactions/lifecycle.ts"],"sourcesContent":["type LifecycleState = \"idle\" | \"starting\" | \"running\" | \"stopping\" | \"stopped\";\n\n/** Serialize one transport's single-use start and stop operations. */\nexport class SerializedTransportLifecycle {\n private state: LifecycleState = \"idle\";\n private tail: Promise<void> = Promise.resolve();\n\n start(open: () => Promise<void>): Promise<void> {\n return this.enqueue(async () => {\n if (this.state !== \"idle\") {\n throw new Error(`Interaction transport cannot start while ${this.state}`);\n }\n this.state = \"starting\";\n try {\n await open();\n this.state = \"running\";\n } catch (error) {\n this.state = \"stopped\";\n throw error;\n }\n });\n }\n\n stop(close: () => Promise<void>): Promise<void> {\n return this.enqueue(async () => {\n if (this.state === \"idle\" || this.state === \"stopped\") return;\n this.state = \"stopping\";\n try {\n await close();\n } finally {\n this.state = \"stopped\";\n }\n });\n }\n\n private enqueue(operation: () => Promise<void>): Promise<void> {\n const result = this.tail.then(operation);\n this.tail = result.catch(() => undefined);\n return result;\n }\n}\n\n/** Close active requests gracefully, then force-close a stuck local socket. */\nexport async function closeHttpServer(\n server: import(\"node:http\").Server,\n graceMs = 1_000,\n): Promise<void> {\n await new Promise<void>((resolve) => {\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n clearTimeout(forceClose);\n resolve();\n };\n const forceClose = setTimeout(() => {\n server.closeAllConnections?.();\n finish();\n }, graceMs);\n server.close(finish);\n server.closeIdleConnections?.();\n });\n}\n\n/** Give active work a bounded grace period before transport shutdown. */\nexport async function waitForSettled(\n promises: Iterable<Promise<unknown>>,\n graceMs = 1_000,\n): Promise<void> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n Promise.allSettled([...promises]),\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, graceMs);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n"],"mappings":";;AAGA,IAAa,+BAAb,MAA0C;CACxC,QAAgC;CAChC,OAA8B,QAAQ,QAAQ;CAE9C,MAAM,MAA0C;EAC9C,OAAO,KAAK,QAAQ,YAAY;GAC9B,IAAI,KAAK,UAAU,QACjB,MAAM,IAAI,MAAM,4CAA4C,KAAK,OAAO;GAE1E,KAAK,QAAQ;GACb,IAAI;IACF,MAAM,KAAK;IACX,KAAK,QAAQ;GACf,SAAS,OAAO;IACd,KAAK,QAAQ;IACb,MAAM;GACR;EACF,CAAC;CACH;CAEA,KAAK,OAA2C;EAC9C,OAAO,KAAK,QAAQ,YAAY;GAC9B,IAAI,KAAK,UAAU,UAAU,KAAK,UAAU,WAAW;GACvD,KAAK,QAAQ;GACb,IAAI;IACF,MAAM,MAAM;GACd,UAAU;IACR,KAAK,QAAQ;GACf;EACF,CAAC;CACH;CAEA,QAAgB,WAA+C;EAC7D,MAAM,SAAS,KAAK,KAAK,KAAK,SAAS;EACvC,KAAK,OAAO,OAAO,YAAY,KAAA,CAAS;EACxC,OAAO;CACT;AACF;;AAGA,eAAsB,gBACpB,QACA,UAAU,KACK;CACf,MAAM,IAAI,SAAe,YAAY;EACnC,IAAI,WAAW;EACf,MAAM,eAAe;GACnB,IAAI,UAAU;GACd,WAAW;GACX,aAAa,UAAU;GACvB,QAAQ;EACV;EACA,MAAM,aAAa,iBAAiB;GAClC,OAAO,sBAAsB;GAC7B,OAAO;EACT,GAAG,OAAO;EACV,OAAO,MAAM,MAAM;EACnB,OAAO,uBAAuB;CAChC,CAAC;AACH;;AAGA,eAAsB,eACpB,UACA,UAAU,KACK;CACf,IAAI;CACJ,IAAI;EACF,MAAM,QAAQ,KAAK,CACjB,QAAQ,WAAW,CAAC,GAAG,QAAQ,CAAC,GAChC,IAAI,SAAe,YAAY;GAC7B,QAAQ,WAAW,SAAS,OAAO;EACrC,CAAC,CACH,CAAC;CACH,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF"}
1
+ {"version":3,"file":"lifecycle-wjO8lqUZ.js","names":[],"sources":["../src/interactions/lifecycle.ts"],"sourcesContent":["type LifecycleState = \"idle\" | \"starting\" | \"running\" | \"stopping\" | \"stopped\";\n\n/** Serialize one transport's single-use start and stop operations. */\nexport class SerializedTransportLifecycle {\n private state: LifecycleState = \"idle\";\n private tail: Promise<void> = Promise.resolve();\n\n start(open: () => Promise<void>): Promise<void> {\n return this.enqueue(async () => {\n if (this.state !== \"idle\") {\n throw new Error(`Interaction transport cannot start while ${this.state}`);\n }\n this.state = \"starting\";\n try {\n await open();\n this.state = \"running\";\n } catch (error) {\n this.state = \"stopped\";\n throw error;\n }\n });\n }\n\n stop(close: () => Promise<void>): Promise<void> {\n return this.enqueue(async () => {\n if (this.state === \"idle\" || this.state === \"stopped\") return;\n this.state = \"stopping\";\n try {\n await close();\n } finally {\n this.state = \"stopped\";\n }\n });\n }\n\n private enqueue(operation: () => Promise<void>): Promise<void> {\n const result = this.tail.then(operation);\n this.tail = result.catch(() => undefined);\n return result;\n }\n}\n\n/** Close active requests gracefully, then force-close a stuck local socket. */\nexport async function closeHttpServer(\n server: import(\"node:http\").Server,\n graceMs = 1_000,\n): Promise<void> {\n await new Promise<void>((resolve) => {\n let finished = false;\n const finish = () => {\n if (finished) return;\n finished = true;\n clearTimeout(forceClose);\n resolve();\n };\n const forceClose = setTimeout(() => {\n server.closeAllConnections?.();\n finish();\n }, graceMs);\n server.close(finish);\n server.closeIdleConnections?.();\n });\n}\n\n/** Give active work a bounded grace period before transport shutdown. */\nexport async function waitForSettled(\n promises: Iterable<Promise<unknown>>,\n graceMs = 1_000,\n): Promise<void> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n Promise.allSettled([...promises]),\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, graceMs);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n"],"mappings":";;AAGA,IAAa,+BAAb,MAA0C;CACxC,QAAgC;CAChC,OAA8B,QAAQ,QAAQ;CAE9C,MAAM,MAA0C;EAC9C,OAAO,KAAK,QAAQ,YAAY;GAC9B,IAAI,KAAK,UAAU,QACjB,MAAM,IAAI,MAAM,4CAA4C,KAAK,OAAO;GAE1E,KAAK,QAAQ;GACb,IAAI;IACF,MAAM,KAAK;IACX,KAAK,QAAQ;GACf,SAAS,OAAO;IACd,KAAK,QAAQ;IACb,MAAM;GACR;EACF,CAAC;CACH;CAEA,KAAK,OAA2C;EAC9C,OAAO,KAAK,QAAQ,YAAY;GAC9B,IAAI,KAAK,UAAU,UAAU,KAAK,UAAU,WAAW;GACvD,KAAK,QAAQ;GACb,IAAI;IACF,MAAM,MAAM;GACd,UAAU;IACR,KAAK,QAAQ;GACf;EACF,CAAC;CACH;CAEA,QAAgB,WAA+C;EAC7D,MAAM,SAAS,KAAK,KAAK,KAAK,SAAS;EACvC,KAAK,OAAO,OAAO,YAAY,KAAA,CAAS;EACxC,OAAO;CACT;AACF;;AAGA,eAAsB,gBACpB,QACA,UAAU,KACK;CACf,MAAM,IAAI,SAAe,YAAY;EACnC,IAAI,WAAW;EACf,MAAM,eAAe;GACnB,IAAI,UAAU;GACd,WAAW;GACX,aAAa,UAAU;GACvB,QAAQ;EACV;EACA,MAAM,aAAa,iBAAiB;GAClC,OAAO,sBAAsB;GAC7B,OAAO;EACT,GAAG,OAAO;EACV,OAAO,MAAM,MAAM;EACnB,OAAO,uBAAuB;CAChC,CAAC;AACH;;AAGA,eAAsB,eACpB,UACA,UAAU,KACK;CACf,IAAI;CACJ,IAAI;EACF,MAAM,QAAQ,KAAK,CACjB,QAAQ,WAAW,CAAC,GAAG,QAAQ,CAAC,GAChC,IAAI,SAAe,YAAY;GAC7B,QAAQ,WAAW,SAAS,OAAO;EACrC,CAAC,CACH,CAAC;CACH,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF"}
@@ -1,7 +1,5 @@
1
- //#region src/node/index.d.ts
2
1
  /** Lowercase hex SHA-256 of a string or bytes. */
3
2
  export declare function sha256Hex(value: string | Uint8Array): string;
4
3
  /** Quote one argument for a POSIX shell: single quotes, embedded `'` as `'\''`. */
5
4
  export declare function shellQuote(value: string): string;
6
- //#endregion
7
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/node/index.ts"],"mappings":";;wBAOgB,UAAU,gBAAgB;;wBAK1B,WAAW"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/node/index.ts"],"mappings":";wBAOgB,UAAU,gBAAgB;;wBAK1B,WAAW"}
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- //#region src/node/index.ts
2
+ // src/node/index.ts
3
3
  /**
4
4
  * Node-only primitives. Kept off the root entry so browser bundles of
5
5
  * `@tangle-network/agent-core` never pull in `node:crypto`.
@@ -12,7 +12,6 @@ function sha256Hex(value) {
12
12
  function shellQuote(value) {
13
13
  return `'${value.replace(/'/g, `'\\''`)}'`;
14
14
  }
15
- //#endregion
16
15
  export { sha256Hex, shellQuote };
17
16
 
18
17
  //# sourceMappingURL=index.js.map
@@ -1,2 +1,2 @@
1
- import { a as ProtectionOptions, c as CircuitBreaker, d as CircuitOpenError, f as CircuitState, i as withTimeout, l as CircuitBreakerConfig, n as createTimeoutController, o as createProtectedFn, r as sleep, s as withProtection, t as TimeoutError, u as CircuitBreakerStats } from "../index-CKDeaS7l.js";
1
+ import { a as ProtectionOptions, c as CircuitBreaker, d as CircuitOpenError, f as CircuitState, i as withTimeout, l as CircuitBreakerConfig, n as createTimeoutController, o as createProtectedFn, r as sleep, s as withProtection, t as TimeoutError, u as CircuitBreakerStats } from "../index-DWPciD1S.js";
2
2
  export { CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerStats, CircuitOpenError, type CircuitState, type ProtectionOptions, TimeoutError, createProtectedFn, createTimeoutController, sleep, withProtection, withTimeout };
@@ -1,2 +1 @@
1
- import { a as sleep, c as CircuitOpenError, i as createTimeoutController, n as withProtection, o as withTimeout, r as TimeoutError, s as CircuitBreaker, t as createProtectedFn } from "../resilience-Cv5tqIDU.js";
2
- export { CircuitBreaker, CircuitOpenError, TimeoutError, createProtectedFn, createTimeoutController, sleep, withProtection, withTimeout };
1
+ export { s as CircuitBreaker, c as CircuitOpenError, r as TimeoutError, t as createProtectedFn, i as createTimeoutController, a as sleep, n as withProtection, o as withTimeout } from "../resilience-Bb1XDoKt.js";
@@ -1,5 +1,5 @@
1
- import { t as SDKError } from "./errors-GuWACHTg.js";
2
- //#region src/resilience/circuit-breaker.ts
1
+ import { t as SDKError } from "./errors-B5KiSXnw.js";
2
+ // src/resilience/circuit-breaker.ts
3
3
  /**
4
4
  * Circuit Breaker
5
5
  *
@@ -122,8 +122,7 @@ var CircuitBreaker = class {
122
122
  this.lastFailureTime = Date.now();
123
123
  }
124
124
  };
125
- //#endregion
126
- //#region src/resilience/timeout.ts
125
+ // src/resilience/timeout.ts
127
126
  /**
128
127
  * Timeout Utilities
129
128
  *
@@ -216,8 +215,7 @@ function armTimeout(ms, callback) {
216
215
  if (timer !== void 0) clearTimeout(timer);
217
216
  };
218
217
  }
219
- //#endregion
220
- //#region src/resilience/protection.ts
218
+ // src/resilience/protection.ts
221
219
  /**
222
220
  * Execute a function with timeout and circuit breaker protection.
223
221
  *
@@ -242,7 +240,6 @@ async function withProtection(fn, options = {}) {
242
240
  function createProtectedFn(fn, options) {
243
241
  return (...args) => withProtection(() => fn(...args), options);
244
242
  }
245
- //#endregion
246
243
  export { sleep as a, CircuitOpenError as c, createTimeoutController as i, withProtection as n, withTimeout as o, TimeoutError as r, CircuitBreaker as s, createProtectedFn as t };
247
244
 
248
- //# sourceMappingURL=resilience-Cv5tqIDU.js.map
245
+ //# sourceMappingURL=resilience-Bb1XDoKt.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resilience-Cv5tqIDU.js","names":[],"sources":["../src/resilience/circuit-breaker.ts","../src/resilience/timeout.ts","../src/resilience/protection.ts"],"sourcesContent":["/**\n * Circuit Breaker\n *\n * Implements the circuit breaker pattern for fail-fast behavior when\n * downstream services are unhealthy. Prevents cascade failures by\n * stopping requests to failing services.\n *\n * States:\n * - CLOSED: Normal operation, requests pass through\n * - OPEN: Service unhealthy, requests fail immediately\n * - HALF_OPEN: Testing if service recovered\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\nexport type CircuitState = \"closed\" | \"open\" | \"half-open\";\n\nexport interface CircuitBreakerConfig {\n /** Number of failures before opening circuit */\n failureThreshold: number;\n /** Time in ms to wait before testing recovery (half-open) */\n resetTimeoutMs: number;\n /** Number of successes in half-open state to close circuit */\n successThreshold: number;\n /** Optional: Only count these error types as failures */\n failureFilter?: (error: unknown) => boolean;\n}\n\nexport interface CircuitBreakerStats {\n state: CircuitState;\n failures: number;\n successes: number;\n lastFailureTime: number | null;\n totalFailures: number;\n totalSuccesses: number;\n totalRejected: number;\n}\n\nexport class CircuitOpenError extends SDKError {\n readonly circuitName: string;\n readonly resetAfterMs: number;\n\n constructor(name: string, resetAfterMs: number) {\n super(`Circuit breaker '${name}' is open`, {\n code: \"SERVER\",\n retryable: true,\n retryAfterMs: resetAfterMs,\n context: { circuitName: name },\n });\n this.name = \"CircuitOpenError\";\n this.circuitName = name;\n this.resetAfterMs = resetAfterMs;\n }\n}\n\nexport class CircuitBreaker {\n private state: CircuitState = \"closed\";\n private failures = 0;\n private successes = 0;\n private lastFailureTime: number | null = null;\n\n // Lifetime stats\n private totalFailures = 0;\n private totalSuccesses = 0;\n private totalRejected = 0;\n\n constructor(\n private readonly name: string,\n private readonly config: CircuitBreakerConfig,\n ) {\n if (config.failureThreshold < 1) {\n throw new Error(\"failureThreshold must be >= 1\");\n }\n if (config.resetTimeoutMs < 0) {\n throw new Error(\"resetTimeoutMs must be >= 0\");\n }\n if (config.successThreshold < 1) {\n throw new Error(\"successThreshold must be >= 1\");\n }\n }\n\n /**\n * Execute a function through the circuit breaker.\n * Throws CircuitOpenError if circuit is open.\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n // Check if we should transition from open to half-open\n if (this.state === \"open\") {\n const timeSinceFailure = this.lastFailureTime\n ? Date.now() - this.lastFailureTime\n : Number.POSITIVE_INFINITY;\n\n if (timeSinceFailure >= this.config.resetTimeoutMs) {\n this.state = \"half-open\";\n this.successes = 0;\n } else {\n this.totalRejected++;\n throw new CircuitOpenError(\n this.name,\n this.config.resetTimeoutMs - timeSinceFailure,\n );\n }\n }\n\n try {\n const result = await fn();\n this.onSuccess();\n return result;\n } catch (error) {\n this.onFailure(error);\n throw error;\n }\n }\n\n private onSuccess(): void {\n this.totalSuccesses++;\n\n if (this.state === \"half-open\") {\n this.successes++;\n if (this.successes >= this.config.successThreshold) {\n // Service recovered, close circuit\n this.state = \"closed\";\n this.failures = 0;\n this.successes = 0;\n }\n } else if (this.state === \"closed\") {\n // Reset failure count on success\n this.failures = 0;\n }\n }\n\n private onFailure(error: unknown): void {\n // Check if this error should count as a failure\n if (this.config.failureFilter && !this.config.failureFilter(error)) {\n return;\n }\n\n this.totalFailures++;\n this.failures++;\n this.lastFailureTime = Date.now();\n this.successes = 0;\n\n if (this.state === \"half-open\") {\n // Any failure in half-open returns to open\n this.state = \"open\";\n } else if (\n this.state === \"closed\" &&\n this.failures >= this.config.failureThreshold\n ) {\n // Threshold exceeded, open circuit\n this.state = \"open\";\n }\n }\n\n /** Get current circuit state */\n getState(): CircuitState {\n return this.state;\n }\n\n /** Get circuit statistics */\n getStats(): CircuitBreakerStats {\n return {\n state: this.state,\n failures: this.failures,\n successes: this.successes,\n lastFailureTime: this.lastFailureTime,\n totalFailures: this.totalFailures,\n totalSuccesses: this.totalSuccesses,\n totalRejected: this.totalRejected,\n };\n }\n\n /** Check if circuit is allowing requests */\n isAllowingRequests(): boolean {\n if (this.state === \"closed\" || this.state === \"half-open\") {\n return true;\n }\n\n // Check if reset timeout has passed\n const timeSinceFailure = this.lastFailureTime\n ? Date.now() - this.lastFailureTime\n : Number.POSITIVE_INFINITY;\n\n return timeSinceFailure >= this.config.resetTimeoutMs;\n }\n\n /** Manually reset the circuit to closed state */\n reset(): void {\n this.state = \"closed\";\n this.failures = 0;\n this.successes = 0;\n this.lastFailureTime = null;\n }\n\n /** Manually open the circuit */\n open(): void {\n this.state = \"open\";\n this.lastFailureTime = Date.now();\n }\n}\n","/**\n * Timeout Utilities\n *\n * Promise-based timeout handling with proper cleanup.\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\nexport class TimeoutError extends SDKError {\n readonly timeoutMs: number;\n\n constructor(message: string, timeoutMs: number) {\n super(message, {\n code: \"TIMEOUT\",\n retryable: true,\n context: { timeoutMs },\n });\n this.name = \"TimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Wrap a promise with a timeout.\n * Rejects with TimeoutError if the promise doesn't resolve in time.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message?: string,\n): Promise<T> {\n if (timeoutMs <= 0) {\n return promise;\n }\n\n let clearTimer: (() => void) | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n clearTimer = armTimeout(timeoutMs, () => {\n reject(\n new TimeoutError(\n message || `Operation timed out after ${timeoutMs}ms`,\n timeoutMs,\n ),\n );\n });\n });\n\n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimer?.();\n });\n}\n\n/**\n * Create an AbortController that auto-aborts after a timeout.\n * Returns the controller and a cleanup function.\n */\nexport function createTimeoutController(timeoutMs: number): {\n controller: AbortController;\n cleanup: () => void;\n} {\n const controller = new AbortController();\n let clearTimer: (() => void) | undefined;\n\n if (timeoutMs > 0) {\n clearTimer = armTimeout(timeoutMs, () => {\n controller.abort(new TimeoutError(\"Request timeout\", timeoutMs));\n });\n }\n\n return {\n controller,\n cleanup: () => {\n clearTimer?.();\n },\n };\n}\n\n/**\n * Sleep for a specified duration.\n * Optionally accepts an AbortSignal for cancellation.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n const onAbort = () => {\n clearTimer();\n signal?.removeEventListener(\"abort\", onAbort);\n reject(signal?.reason);\n };\n const clearTimer = armTimeout(ms, () => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n });\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/** Keep one absolute deadline; native timers overflow to 1ms beyond this chunk size. */\nfunction armTimeout(ms: number, callback: () => void): () => void {\n const deadline = Date.now() + Math.max(0, ms);\n if (!Number.isFinite(ms) || !Number.isSafeInteger(Math.ceil(deadline))) {\n throw new SDKError(\"Timeout must produce a finite safe deadline\", { code: \"VALIDATION\" });\n }\n let cleared = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const arm = () => {\n if (cleared) return;\n timer = setTimeout(() => {\n if (cleared) return;\n if (Date.now() >= deadline) callback();\n else arm();\n }, Math.min(2_147_483_647, Math.max(0, deadline - Date.now())));\n };\n arm();\n return () => {\n cleared = true;\n if (timer !== undefined) clearTimeout(timer);\n };\n}\n","/**\n * Combines timeout + circuit breaker into a single request guard.\n */\n\nimport type { CircuitBreaker } from \"./circuit-breaker.js\";\nimport { withTimeout } from \"./timeout.js\";\n\nexport interface ProtectionOptions {\n /** Timeout in milliseconds (0 or undefined = no timeout) */\n timeoutMs?: number;\n /** Circuit breaker instance (optional) */\n breaker?: CircuitBreaker;\n /** Operation name for error messages */\n operationName?: string;\n}\n\n/**\n * Execute a function with timeout and circuit breaker protection.\n *\n * Order of execution:\n * 1. Circuit breaker check (fail fast if open)\n * 2. Timeout wrapper\n * 3. Execute function\n * 4. Update circuit breaker state\n */\nexport async function withProtection<T>(\n fn: () => Promise<T>,\n options: ProtectionOptions = {},\n): Promise<T> {\n const { timeoutMs, breaker, operationName = \"operation\" } = options;\n\n const execute = async (): Promise<T> => {\n if (timeoutMs && timeoutMs > 0) {\n return withTimeout(fn(), timeoutMs, `${operationName} timed out`);\n }\n return fn();\n };\n\n if (breaker) {\n return breaker.execute(execute);\n }\n\n return execute();\n}\n\n/**\n * Create a protected function that always applies the same protection options.\n */\nexport function createProtectedFn<TArgs extends unknown[], TResult>(\n fn: (...args: TArgs) => Promise<TResult>,\n options: ProtectionOptions,\n): (...args: TArgs) => Promise<TResult> {\n return (...args: TArgs) => withProtection(() => fn(...args), options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resilience-Bb1XDoKt.js","names":[],"sources":["../src/resilience/circuit-breaker.ts","../src/resilience/timeout.ts","../src/resilience/protection.ts"],"sourcesContent":["/**\n * Circuit Breaker\n *\n * Implements the circuit breaker pattern for fail-fast behavior when\n * downstream services are unhealthy. Prevents cascade failures by\n * stopping requests to failing services.\n *\n * States:\n * - CLOSED: Normal operation, requests pass through\n * - OPEN: Service unhealthy, requests fail immediately\n * - HALF_OPEN: Testing if service recovered\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\nexport type CircuitState = \"closed\" | \"open\" | \"half-open\";\n\nexport interface CircuitBreakerConfig {\n /** Number of failures before opening circuit */\n failureThreshold: number;\n /** Time in ms to wait before testing recovery (half-open) */\n resetTimeoutMs: number;\n /** Number of successes in half-open state to close circuit */\n successThreshold: number;\n /** Optional: Only count these error types as failures */\n failureFilter?: (error: unknown) => boolean;\n}\n\nexport interface CircuitBreakerStats {\n state: CircuitState;\n failures: number;\n successes: number;\n lastFailureTime: number | null;\n totalFailures: number;\n totalSuccesses: number;\n totalRejected: number;\n}\n\nexport class CircuitOpenError extends SDKError {\n readonly circuitName: string;\n readonly resetAfterMs: number;\n\n constructor(name: string, resetAfterMs: number) {\n super(`Circuit breaker '${name}' is open`, {\n code: \"SERVER\",\n retryable: true,\n retryAfterMs: resetAfterMs,\n context: { circuitName: name },\n });\n this.name = \"CircuitOpenError\";\n this.circuitName = name;\n this.resetAfterMs = resetAfterMs;\n }\n}\n\nexport class CircuitBreaker {\n private state: CircuitState = \"closed\";\n private failures = 0;\n private successes = 0;\n private lastFailureTime: number | null = null;\n\n // Lifetime stats\n private totalFailures = 0;\n private totalSuccesses = 0;\n private totalRejected = 0;\n\n constructor(\n private readonly name: string,\n private readonly config: CircuitBreakerConfig,\n ) {\n if (config.failureThreshold < 1) {\n throw new Error(\"failureThreshold must be >= 1\");\n }\n if (config.resetTimeoutMs < 0) {\n throw new Error(\"resetTimeoutMs must be >= 0\");\n }\n if (config.successThreshold < 1) {\n throw new Error(\"successThreshold must be >= 1\");\n }\n }\n\n /**\n * Execute a function through the circuit breaker.\n * Throws CircuitOpenError if circuit is open.\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n // Check if we should transition from open to half-open\n if (this.state === \"open\") {\n const timeSinceFailure = this.lastFailureTime\n ? Date.now() - this.lastFailureTime\n : Number.POSITIVE_INFINITY;\n\n if (timeSinceFailure >= this.config.resetTimeoutMs) {\n this.state = \"half-open\";\n this.successes = 0;\n } else {\n this.totalRejected++;\n throw new CircuitOpenError(\n this.name,\n this.config.resetTimeoutMs - timeSinceFailure,\n );\n }\n }\n\n try {\n const result = await fn();\n this.onSuccess();\n return result;\n } catch (error) {\n this.onFailure(error);\n throw error;\n }\n }\n\n private onSuccess(): void {\n this.totalSuccesses++;\n\n if (this.state === \"half-open\") {\n this.successes++;\n if (this.successes >= this.config.successThreshold) {\n // Service recovered, close circuit\n this.state = \"closed\";\n this.failures = 0;\n this.successes = 0;\n }\n } else if (this.state === \"closed\") {\n // Reset failure count on success\n this.failures = 0;\n }\n }\n\n private onFailure(error: unknown): void {\n // Check if this error should count as a failure\n if (this.config.failureFilter && !this.config.failureFilter(error)) {\n return;\n }\n\n this.totalFailures++;\n this.failures++;\n this.lastFailureTime = Date.now();\n this.successes = 0;\n\n if (this.state === \"half-open\") {\n // Any failure in half-open returns to open\n this.state = \"open\";\n } else if (\n this.state === \"closed\" &&\n this.failures >= this.config.failureThreshold\n ) {\n // Threshold exceeded, open circuit\n this.state = \"open\";\n }\n }\n\n /** Get current circuit state */\n getState(): CircuitState {\n return this.state;\n }\n\n /** Get circuit statistics */\n getStats(): CircuitBreakerStats {\n return {\n state: this.state,\n failures: this.failures,\n successes: this.successes,\n lastFailureTime: this.lastFailureTime,\n totalFailures: this.totalFailures,\n totalSuccesses: this.totalSuccesses,\n totalRejected: this.totalRejected,\n };\n }\n\n /** Check if circuit is allowing requests */\n isAllowingRequests(): boolean {\n if (this.state === \"closed\" || this.state === \"half-open\") {\n return true;\n }\n\n // Check if reset timeout has passed\n const timeSinceFailure = this.lastFailureTime\n ? Date.now() - this.lastFailureTime\n : Number.POSITIVE_INFINITY;\n\n return timeSinceFailure >= this.config.resetTimeoutMs;\n }\n\n /** Manually reset the circuit to closed state */\n reset(): void {\n this.state = \"closed\";\n this.failures = 0;\n this.successes = 0;\n this.lastFailureTime = null;\n }\n\n /** Manually open the circuit */\n open(): void {\n this.state = \"open\";\n this.lastFailureTime = Date.now();\n }\n}\n","/**\n * Timeout Utilities\n *\n * Promise-based timeout handling with proper cleanup.\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\nexport class TimeoutError extends SDKError {\n readonly timeoutMs: number;\n\n constructor(message: string, timeoutMs: number) {\n super(message, {\n code: \"TIMEOUT\",\n retryable: true,\n context: { timeoutMs },\n });\n this.name = \"TimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Wrap a promise with a timeout.\n * Rejects with TimeoutError if the promise doesn't resolve in time.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message?: string,\n): Promise<T> {\n if (timeoutMs <= 0) {\n return promise;\n }\n\n let clearTimer: (() => void) | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n clearTimer = armTimeout(timeoutMs, () => {\n reject(\n new TimeoutError(\n message || `Operation timed out after ${timeoutMs}ms`,\n timeoutMs,\n ),\n );\n });\n });\n\n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimer?.();\n });\n}\n\n/**\n * Create an AbortController that auto-aborts after a timeout.\n * Returns the controller and a cleanup function.\n */\nexport function createTimeoutController(timeoutMs: number): {\n controller: AbortController;\n cleanup: () => void;\n} {\n const controller = new AbortController();\n let clearTimer: (() => void) | undefined;\n\n if (timeoutMs > 0) {\n clearTimer = armTimeout(timeoutMs, () => {\n controller.abort(new TimeoutError(\"Request timeout\", timeoutMs));\n });\n }\n\n return {\n controller,\n cleanup: () => {\n clearTimer?.();\n },\n };\n}\n\n/**\n * Sleep for a specified duration.\n * Optionally accepts an AbortSignal for cancellation.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n const onAbort = () => {\n clearTimer();\n signal?.removeEventListener(\"abort\", onAbort);\n reject(signal?.reason);\n };\n const clearTimer = armTimeout(ms, () => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n });\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/** Keep one absolute deadline; native timers overflow to 1ms beyond this chunk size. */\nfunction armTimeout(ms: number, callback: () => void): () => void {\n const deadline = Date.now() + Math.max(0, ms);\n if (!Number.isFinite(ms) || !Number.isSafeInteger(Math.ceil(deadline))) {\n throw new SDKError(\"Timeout must produce a finite safe deadline\", { code: \"VALIDATION\" });\n }\n let cleared = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const arm = () => {\n if (cleared) return;\n timer = setTimeout(() => {\n if (cleared) return;\n if (Date.now() >= deadline) callback();\n else arm();\n }, Math.min(2_147_483_647, Math.max(0, deadline - Date.now())));\n };\n arm();\n return () => {\n cleared = true;\n if (timer !== undefined) clearTimeout(timer);\n };\n}\n","/**\n * Combines timeout + circuit breaker into a single request guard.\n */\n\nimport type { CircuitBreaker } from \"./circuit-breaker.js\";\nimport { withTimeout } from \"./timeout.js\";\n\nexport interface ProtectionOptions {\n /** Timeout in milliseconds (0 or undefined = no timeout) */\n timeoutMs?: number;\n /** Circuit breaker instance (optional) */\n breaker?: CircuitBreaker;\n /** Operation name for error messages */\n operationName?: string;\n}\n\n/**\n * Execute a function with timeout and circuit breaker protection.\n *\n * Order of execution:\n * 1. Circuit breaker check (fail fast if open)\n * 2. Timeout wrapper\n * 3. Execute function\n * 4. Update circuit breaker state\n */\nexport async function withProtection<T>(\n fn: () => Promise<T>,\n options: ProtectionOptions = {},\n): Promise<T> {\n const { timeoutMs, breaker, operationName = \"operation\" } = options;\n\n const execute = async (): Promise<T> => {\n if (timeoutMs && timeoutMs > 0) {\n return withTimeout(fn(), timeoutMs, `${operationName} timed out`);\n }\n return fn();\n };\n\n if (breaker) {\n return breaker.execute(execute);\n }\n\n return execute();\n}\n\n/**\n * Create a protected function that always applies the same protection options.\n */\nexport function createProtectedFn<TArgs extends unknown[], TResult>(\n fn: (...args: TArgs) => Promise<TResult>,\n options: ProtectionOptions,\n): (...args: TArgs) => Promise<TResult> {\n return (...args: TArgs) => withProtection(() => fn(...args), options);\n}\n"],"mappings":"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"}
@@ -1,9 +1,29 @@
1
- //#region src/sse/index.d.ts
2
1
  /**
3
2
  * SSE Utilities
4
3
  *
5
- * Shared SSE parsing and stream usage extraction for the SDK.
6
- * Used by sdk-telemetry, sdk-memory, and other consumers.
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.
7
27
  */
8
28
  /** Raw SSE event data from stream */
9
29
  export interface SSEEventData {
@@ -46,6 +66,99 @@ export interface SSEEventData {
46
66
  * ```
47
67
  */
48
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>;
49
162
  export interface SSEParserOptions<T = unknown> {
50
163
  /** Log malformed data lines (default: false) */
51
164
  logMalformed?: boolean;
@@ -59,18 +172,18 @@ export interface ParsedSSEEvent<T = unknown> {
59
172
  eventType?: string;
60
173
  }
61
174
  /**
62
- * Incremental SSE parser for chunked streams.
63
- * Feed decoded string chunks via push(); call flush() on stream end.
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.
64
178
  */
65
179
  export declare class SSEChunkParser<T = unknown> {
66
- private buffer;
67
- private currentEvent;
68
- private options;
180
+ private readonly frames;
181
+ private readonly options;
69
182
  constructor(options?: SSEParserOptions<T>);
70
183
  push(chunk: string): ParsedSSEEvent<T>[];
71
184
  flush(): ParsedSSEEvent<T>[];
72
- private processLines;
73
- private parseCurrentEvent;
185
+ private toEvents;
186
+ private toEvent;
74
187
  }
75
188
  /**
76
189
  * Parse SSE stream into typed events via async iteration.
@@ -169,5 +282,4 @@ export declare function createUsageCallback<T extends Record<string, unknown>>(f
169
282
  getUsage: () => StreamUsageAccumulator;
170
283
  extractor: StreamUsageExtractor;
171
284
  };
172
- //#endregion
173
285
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/sse/index.ts"],"mappings":";;;;;;;;iBAQiB;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;iBA+B9B,iBAAiB;;EAEhC;;EAEA,aAAa,gBAAgB;;iBAGd,eAAe;EAC9B,MAAM;EACN;EACA;EACA;;;;;;qBAOW,eAAe;UAClB;UACA;UACA;EAER,YAAY,UAAS,iBAAiB;EAItC,KAAK,gBAAgB,eAAe;EAOpC,SAAS,eAAe;UAYhB;UA4CA;;;;;wBA2Ca,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;EAER;;;;EAeA,cAAc;;;;EAUd,aAAa,OAAO,eAAe;;;;;EAoDnC,YAAY;;;;EAeZ,WAAW,SAAS;;;;EAOpB;UAaQ;UAyCA;;;;;wBA4BM,8BAA8B;;;;;;;;;;;;;;wBAiB9B,oBAAoB,UAAU,yBAC5C,WAAW,OAAO;EAElB,WAAW,OAAO;EAClB,gBAAgB;EAChB,WAAW"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/sse/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6BiB;EACf;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EAEA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EAEA;IACE;IACA;IACA;IACA;;GAGD;;;;;;;;;;;wBAYa,aAAa,kBAAkB;;;;;;;;iBAsC9B;;EAEf;;EAEA;;EAEA;;;iBAee;;;;;;;EAOf,SAAS;;;;;;;;EAQT;;;;;;;;qBASW,6BAA6B;WACtB;WACT;EACG,YAAA;;;;;;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
@@ -1,4 +1,4 @@
1
- //#region src/sse/index.ts
1
+ // src/sse/index.ts
2
2
  /**
3
3
  * Parse raw SSE data string into structured event data.
4
4
  *
@@ -26,63 +26,191 @@ function parseSSEData(rawData) {
26
26
  return null;
27
27
  }
28
28
  /**
29
- * Incremental SSE parser for chunked streams.
30
- * Feed decoded string chunks via push(); call flush() on stream end.
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
- var SSEChunkParser = class {
33
- buffer = "";
34
- currentEvent = {};
35
- options;
36
- constructor(options = {}) {
37
- this.options = options;
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
- this.buffer += chunk;
68
+ let text = chunk;
69
+ if (!this.bomChecked && text.length > 0) {
70
+ this.bomChecked = true;
71
+ if (text.charCodeAt(0) === 65279) text = text.slice(1);
72
+ }
73
+ this.buffer += text;
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 lines = this.buffer ? [this.buffer] : [];
84
+ const rest = this.buffer;
47
85
  this.buffer = "";
48
- const events = this.processLines(lines);
49
- const finalEvent = this.parseCurrentEvent();
50
- if (finalEvent) {
51
- events.push(finalEvent);
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 events = [];
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 parsed = this.parseCurrentEvent();
63
- if (parsed) events.push(parsed);
64
- this.currentEvent = {};
96
+ const frame = this.dispatch();
97
+ if (frame) frames.push(frame);
65
98
  continue;
66
99
  }
67
- if (line.startsWith("id:")) {
68
- this.currentEvent.id = line.slice(3).trim();
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.currentEvent.event = line.slice(6).trim();
109
+ this.frameEvent = line.slice(6).trim();
73
110
  continue;
74
111
  }
75
- if (line.startsWith("data:")) {
76
- let value = line.slice(5);
77
- if (value.startsWith(" ")) value = value.slice(1);
78
- this.currentEvent.data = this.currentEvent.data !== void 0 ? `${this.currentEvent.data}\n${value}` : value;
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
- parseCurrentEvent() {
84
- if (this.currentEvent.data === void 0) return null;
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: this.currentEvent.id,
105
- eventType: this.currentEvent.event
232
+ eventId: frame.id,
233
+ eventType: frame.event || void 0
106
234
  };
107
235
  }
108
236
  };
@@ -297,7 +425,6 @@ function createUsageCallback(forward) {
297
425
  extractor
298
426
  };
299
427
  }
300
- //#endregion
301
- export { SSEChunkParser, StreamUsageExtractor, consumeSSEStream, createStreamUsageExtractor, createUsageCallback, parseSSEData, parseSSEStream };
428
+ export { SSEChunkParser, SSEFrameParser, SSEStreamSilentError, StreamUsageExtractor, consumeSSEStream, createStreamUsageExtractor, createUsageCallback, encodeSSEFrame, parseSSEData, parseSSEStream, streamSSEFrames };
302
429
 
303
430
  //# sourceMappingURL=index.js.map