@tetherto/mdk-ui-agent 0.0.0 → 0.12.1

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 (217) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +310 -0
  3. package/dist/branding.d.ts +36 -0
  4. package/dist/branding.d.ts.map +1 -0
  5. package/dist/branding.js +37 -0
  6. package/dist/branding.js.map +1 -0
  7. package/dist/components/approval-card.d.ts +46 -0
  8. package/dist/components/approval-card.d.ts.map +1 -0
  9. package/dist/components/approval-card.js +84 -0
  10. package/dist/components/approval-card.js.map +1 -0
  11. package/dist/components/assistant-message.d.ts +25 -0
  12. package/dist/components/assistant-message.d.ts.map +1 -0
  13. package/dist/components/assistant-message.js +49 -0
  14. package/dist/components/assistant-message.js.map +1 -0
  15. package/dist/components/co-pilot-panel.d.ts +17 -0
  16. package/dist/components/co-pilot-panel.d.ts.map +1 -0
  17. package/dist/components/co-pilot-panel.js +33 -0
  18. package/dist/components/co-pilot-panel.js.map +1 -0
  19. package/dist/components/code-block.d.ts +17 -0
  20. package/dist/components/code-block.d.ts.map +1 -0
  21. package/dist/components/code-block.js +40 -0
  22. package/dist/components/code-block.js.map +1 -0
  23. package/dist/components/collapsible-answer.d.ts +26 -0
  24. package/dist/components/collapsible-answer.d.ts.map +1 -0
  25. package/dist/components/collapsible-answer.js +30 -0
  26. package/dist/components/collapsible-answer.js.map +1 -0
  27. package/dist/components/composer.d.ts +31 -0
  28. package/dist/components/composer.d.ts.map +1 -0
  29. package/dist/components/composer.js +82 -0
  30. package/dist/components/composer.js.map +1 -0
  31. package/dist/components/conversation-list.d.ts +21 -0
  32. package/dist/components/conversation-list.d.ts.map +1 -0
  33. package/dist/components/conversation-list.js +42 -0
  34. package/dist/components/conversation-list.js.map +1 -0
  35. package/dist/components/icons.d.ts +88 -0
  36. package/dist/components/icons.d.ts.map +1 -0
  37. package/dist/components/icons.js +112 -0
  38. package/dist/components/icons.js.map +1 -0
  39. package/dist/components/launcher-fab.d.ts +8 -0
  40. package/dist/components/launcher-fab.d.ts.map +1 -0
  41. package/dist/components/launcher-fab.js +6 -0
  42. package/dist/components/launcher-fab.js.map +1 -0
  43. package/dist/components/leaked-tool-call-notice.d.ts +18 -0
  44. package/dist/components/leaked-tool-call-notice.d.ts.map +1 -0
  45. package/dist/components/leaked-tool-call-notice.js +14 -0
  46. package/dist/components/leaked-tool-call-notice.js.map +1 -0
  47. package/dist/components/markdown-link.d.ts +17 -0
  48. package/dist/components/markdown-link.d.ts.map +1 -0
  49. package/dist/components/markdown-link.js +18 -0
  50. package/dist/components/markdown-link.js.map +1 -0
  51. package/dist/components/markdown-text.d.ts +26 -0
  52. package/dist/components/markdown-text.d.ts.map +1 -0
  53. package/dist/components/markdown-text.js +32 -0
  54. package/dist/components/markdown-text.js.map +1 -0
  55. package/dist/components/message-list.d.ts +24 -0
  56. package/dist/components/message-list.d.ts.map +1 -0
  57. package/dist/components/message-list.js +92 -0
  58. package/dist/components/message-list.js.map +1 -0
  59. package/dist/components/no-tools-note.d.ts +26 -0
  60. package/dist/components/no-tools-note.d.ts.map +1 -0
  61. package/dist/components/no-tools-note.js +27 -0
  62. package/dist/components/no-tools-note.js.map +1 -0
  63. package/dist/components/panel-header.d.ts +26 -0
  64. package/dist/components/panel-header.d.ts.map +1 -0
  65. package/dist/components/panel-header.js +13 -0
  66. package/dist/components/panel-header.js.map +1 -0
  67. package/dist/components/rehype-highlight-subset.d.ts +17 -0
  68. package/dist/components/rehype-highlight-subset.d.ts.map +1 -0
  69. package/dist/components/rehype-highlight-subset.js +77 -0
  70. package/dist/components/rehype-highlight-subset.js.map +1 -0
  71. package/dist/components/system-message.d.ts +15 -0
  72. package/dist/components/system-message.d.ts.map +1 -0
  73. package/dist/components/system-message.js +12 -0
  74. package/dist/components/system-message.js.map +1 -0
  75. package/dist/components/tool-chip.d.ts +19 -0
  76. package/dist/components/tool-chip.d.ts.map +1 -0
  77. package/dist/components/tool-chip.js +32 -0
  78. package/dist/components/tool-chip.js.map +1 -0
  79. package/dist/components/user-message.d.ts +9 -0
  80. package/dist/components/user-message.d.ts.map +1 -0
  81. package/dist/components/user-message.js +7 -0
  82. package/dist/components/user-message.js.map +1 -0
  83. package/dist/core/conversation-store.d.ts +107 -0
  84. package/dist/core/conversation-store.d.ts.map +1 -0
  85. package/dist/core/conversation-store.js +254 -0
  86. package/dist/core/conversation-store.js.map +1 -0
  87. package/dist/core/error-text.d.ts +19 -0
  88. package/dist/core/error-text.d.ts.map +1 -0
  89. package/dist/core/error-text.js +35 -0
  90. package/dist/core/error-text.js.map +1 -0
  91. package/dist/core/events.d.ts +98 -0
  92. package/dist/core/events.d.ts.map +1 -0
  93. package/dist/core/events.js +50 -0
  94. package/dist/core/events.js.map +1 -0
  95. package/dist/core/format.d.ts +23 -0
  96. package/dist/core/format.d.ts.map +1 -0
  97. package/dist/core/format.js +49 -0
  98. package/dist/core/format.js.map +1 -0
  99. package/dist/core/index.d.ts +27 -0
  100. package/dist/core/index.d.ts.map +1 -0
  101. package/dist/core/index.js +20 -0
  102. package/dist/core/index.js.map +1 -0
  103. package/dist/core/markdown.d.ts +26 -0
  104. package/dist/core/markdown.d.ts.map +1 -0
  105. package/dist/core/markdown.js +49 -0
  106. package/dist/core/markdown.js.map +1 -0
  107. package/dist/core/prose.d.ts +27 -0
  108. package/dist/core/prose.d.ts.map +1 -0
  109. package/dist/core/prose.js +79 -0
  110. package/dist/core/prose.js.map +1 -0
  111. package/dist/core/small-talk.d.ts +15 -0
  112. package/dist/core/small-talk.d.ts.map +1 -0
  113. package/dist/core/small-talk.js +42 -0
  114. package/dist/core/small-talk.js.map +1 -0
  115. package/dist/core/sse.d.ts +108 -0
  116. package/dist/core/sse.d.ts.map +1 -0
  117. package/dist/core/sse.js +236 -0
  118. package/dist/core/sse.js.map +1 -0
  119. package/dist/core/tool-label.d.ts +16 -0
  120. package/dist/core/tool-label.d.ts.map +1 -0
  121. package/dist/core/tool-label.js +31 -0
  122. package/dist/core/tool-label.js.map +1 -0
  123. package/dist/core/transport.d.ts +71 -0
  124. package/dist/core/transport.d.ts.map +1 -0
  125. package/dist/core/transport.js +147 -0
  126. package/dist/core/transport.js.map +1 -0
  127. package/dist/core/turn.d.ts +87 -0
  128. package/dist/core/turn.d.ts.map +1 -0
  129. package/dist/core/turn.js +232 -0
  130. package/dist/core/turn.js.map +1 -0
  131. package/dist/core/uuid.d.ts +21 -0
  132. package/dist/core/uuid.d.ts.map +1 -0
  133. package/dist/core/uuid.js +54 -0
  134. package/dist/core/uuid.js.map +1 -0
  135. package/dist/entries/chat-page.d.ts +28 -0
  136. package/dist/entries/chat-page.d.ts.map +1 -0
  137. package/dist/entries/chat-page.js +25 -0
  138. package/dist/entries/chat-page.js.map +1 -0
  139. package/dist/entries/co-pilot.d.ts +41 -0
  140. package/dist/entries/co-pilot.d.ts.map +1 -0
  141. package/dist/entries/co-pilot.js +39 -0
  142. package/dist/entries/co-pilot.js.map +1 -0
  143. package/dist/entries/lazy-panel.d.ts +15 -0
  144. package/dist/entries/lazy-panel.d.ts.map +1 -0
  145. package/dist/entries/lazy-panel.js +19 -0
  146. package/dist/entries/lazy-panel.js.map +1 -0
  147. package/dist/hooks/index.d.ts +7 -0
  148. package/dist/hooks/index.d.ts.map +1 -0
  149. package/dist/hooks/index.js +4 -0
  150. package/dist/hooks/index.js.map +1 -0
  151. package/dist/hooks/use-agent-chat.d.ts +46 -0
  152. package/dist/hooks/use-agent-chat.d.ts.map +1 -0
  153. package/dist/hooks/use-agent-chat.js +288 -0
  154. package/dist/hooks/use-agent-chat.js.map +1 -0
  155. package/dist/hooks/use-agent-config.d.ts +29 -0
  156. package/dist/hooks/use-agent-config.d.ts.map +1 -0
  157. package/dist/hooks/use-agent-config.js +45 -0
  158. package/dist/hooks/use-agent-config.js.map +1 -0
  159. package/dist/hooks/use-conversations.d.ts +19 -0
  160. package/dist/hooks/use-conversations.d.ts.map +1 -0
  161. package/dist/hooks/use-conversations.js +19 -0
  162. package/dist/hooks/use-conversations.js.map +1 -0
  163. package/dist/index.d.ts +28 -0
  164. package/dist/index.d.ts.map +1 -0
  165. package/dist/index.js +26 -0
  166. package/dist/index.js.map +1 -0
  167. package/dist/styles.css +1 -0
  168. package/package.json +105 -1
  169. package/src/branding.ts +40 -0
  170. package/src/components/approval-card.tsx +141 -0
  171. package/src/components/assistant-message.tsx +140 -0
  172. package/src/components/co-pilot-panel.tsx +109 -0
  173. package/src/components/code-block.tsx +63 -0
  174. package/src/components/collapsible-answer.tsx +55 -0
  175. package/src/components/composer.tsx +147 -0
  176. package/src/components/conversation-list.tsx +141 -0
  177. package/src/components/icons.tsx +189 -0
  178. package/src/components/launcher-fab.tsx +16 -0
  179. package/src/components/leaked-tool-call-notice.tsx +29 -0
  180. package/src/components/markdown-link.tsx +30 -0
  181. package/src/components/markdown-text.tsx +58 -0
  182. package/src/components/message-list.tsx +164 -0
  183. package/src/components/no-tools-note.tsx +33 -0
  184. package/src/components/panel-header.tsx +86 -0
  185. package/src/components/rehype-highlight-subset.ts +84 -0
  186. package/src/components/system-message.tsx +19 -0
  187. package/src/components/tool-chip.tsx +58 -0
  188. package/src/components/user-message.tsx +18 -0
  189. package/src/core/conversation-store.ts +342 -0
  190. package/src/core/error-text.ts +36 -0
  191. package/src/core/events.ts +127 -0
  192. package/src/core/format.ts +59 -0
  193. package/src/core/index.ts +115 -0
  194. package/src/core/markdown.ts +52 -0
  195. package/src/core/prose.ts +78 -0
  196. package/src/core/small-talk.ts +45 -0
  197. package/src/core/sse.ts +269 -0
  198. package/src/core/tool-label.ts +33 -0
  199. package/src/core/transport.ts +208 -0
  200. package/src/core/turn.ts +299 -0
  201. package/src/core/uuid.ts +59 -0
  202. package/src/entries/chat-page.tsx +56 -0
  203. package/src/entries/co-pilot.tsx +84 -0
  204. package/src/entries/lazy-panel.tsx +19 -0
  205. package/src/hooks/index.ts +8 -0
  206. package/src/hooks/use-agent-chat.ts +387 -0
  207. package/src/hooks/use-agent-config.ts +70 -0
  208. package/src/hooks/use-conversations.ts +39 -0
  209. package/src/index.ts +30 -0
  210. package/src/styles/_approval.scss +68 -0
  211. package/src/styles/_composer.scss +45 -0
  212. package/src/styles/_conversations.scss +160 -0
  213. package/src/styles/_messages.scss +444 -0
  214. package/src/styles/_mixins.scss +27 -0
  215. package/src/styles/_panel.scss +147 -0
  216. package/src/styles/_tokens.scss +30 -0
  217. package/src/styles.scss +16 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"small-talk.js","sourceRoot":"","sources":["../../src/core/small-talk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,yFAAyF;AACzF,MAAM,cAAc,GAAG;IACrB,oFAAoF;IACpF,iDAAiD;IACjD,gHAAgH;IAChH,yCAAyC;IACzC,kFAAkF;CACnF,CAAA;AAED;;;GAGG;AACH,MAAM,WAAW,GACb,wQAAwQ,CAAA;AAE5Q,qFAAqF;AACrF,MAAM,SAAS,GAAG,CAAC,CAAA;AAEnB,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,UAAU,GAAG,IAAI;SACpB,WAAW,EAAE;SACb,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,iDAAiD;SACvE,IAAI,EAAE,CAAA;IAET,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACzC,IAAI,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAA;IAC9C,IAAI,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,SAAS;QAAE,OAAO,KAAK,CAAA;IAE5D,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAA;AACnE,CAAC"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * SSE reading for the agent message route.
3
+ *
4
+ * `EventSource` is unusable here: the message is a POST body and `EventSource`
5
+ * is GET-only. So this is `fetch` + a body-stream reader, which is also what the
6
+ * producer's own docs prescribe.
7
+ *
8
+ * The gateway writes `event: <type>\ndata: <json>\n\n` and nothing else — no
9
+ * `id:`, no `retry:`, and no heartbeat comment frames. The `event:` line is
10
+ * redundant (`data.type` carries the same value) so only `data:` is parsed. The
11
+ * rest of the parser is nonetheless written against the spec rather than against
12
+ * that one producer: CRLF line endings and multi-line `data:` are both legal, and
13
+ * a proxy that rewrites line endings would otherwise silently end every turn as a
14
+ * truncation.
15
+ */
16
+ import type { WireEvent } from './events';
17
+ /** Synthesized locally, never sent by the gateway. See `readAgentStream`. */
18
+ export declare const ERR_STREAM_TRUNCATED = "ERR_AGENT_STREAM_TRUNCATED";
19
+ /** Synthesized locally: the socket stayed open but nothing arrived. See `readAgentStream`. */
20
+ export declare const ERR_STREAM_IDLE = "ERR_AGENT_STREAM_IDLE";
21
+ /**
22
+ * How long a turn may say nothing between events before the connection is
23
+ * assumed lost.
24
+ *
25
+ * The gateway sends no heartbeats, so silence is the only symptom of a wedged
26
+ * agent, a half-closed proxy or a laptop resumed from sleep. Once events are
27
+ * flowing, 30s of nothing is already far outside what a working turn does: the
28
+ * producer emits a frame per token.
29
+ */
30
+ export declare const STREAM_IDLE_TIMEOUT_MS = 30000;
31
+ /**
32
+ * The same limit before the first event, which is a different wait entirely.
33
+ *
34
+ * Nothing is emitted until the producer's first model call returns a routing
35
+ * decision, and a local model may think for a long time before it answers a
36
+ * single character: measured against a 4B thinking model on this hardware, the
37
+ * first event arrived at 61s. Held to the producer's own per-call budget
38
+ * (`REQUEST_TIMEOUT_MS`, 120s in backend/core/agent/src/constants.js) so a turn
39
+ * the server is still legitimately working on is never abandoned here first —
40
+ * whatever the model does, the server gives up before this does and says why.
41
+ */
42
+ export declare const FIRST_EVENT_IDLE_TIMEOUT_MS = 120000;
43
+ /**
44
+ * The same limit while an approval is outstanding, where silence is legitimate:
45
+ * the producer is waiting on a human, and only auto-rejects after its own
46
+ * `agent.approvalTimeoutMs`. Five minutes is that default plus room to spare, so
47
+ * the stream is never given up on before the server itself would have resumed it.
48
+ */
49
+ export declare const APPROVAL_IDLE_TIMEOUT_MS = 300000;
50
+ /** Thrown out of `readFrames` when the deadline expires; converted to an event above. */
51
+ export declare class StreamIdleError extends Error {
52
+ constructor();
53
+ }
54
+ export type ReadFramesOptions = {
55
+ /**
56
+ * Milliseconds of silence tolerated before the next read is given up on, read
57
+ * fresh before every read so the caller can widen it mid-stream. `0` disables
58
+ * the deadline.
59
+ */
60
+ idleTimeoutMs?: () => number;
61
+ };
62
+ /**
63
+ * Pulls the `data:` payload out of one frame.
64
+ *
65
+ * A frame may carry several `data:` lines, which the spec joins with newlines;
66
+ * anything else (`event:`, `id:`, a `:` comment) is ignored. Returns null for a
67
+ * frame with no data at all.
68
+ */
69
+ export declare function parseFrame(frame: string): unknown;
70
+ /**
71
+ * Yields each decoded `data:` payload, in order, as frames arrive.
72
+ *
73
+ * The abort signal is handled here rather than left to `fetch`. Aborting a fetch
74
+ * whose headers have already arrived is only specified to error the body stream
75
+ * — cancelling the reader is what actually unblocks a `read()` that is parked
76
+ * waiting for the next token, which is where an abort almost always lands.
77
+ */
78
+ export declare function readFrames(stream: ReadableStream<Uint8Array>, signal?: AbortSignal, options?: ReadFramesOptions): AsyncGenerator<unknown>;
79
+ export type ReadAgentStreamOptions = {
80
+ /** Silence tolerated between events. Defaults to {@link STREAM_IDLE_TIMEOUT_MS}; `0` disables it. */
81
+ idleTimeoutMs?: number;
82
+ /** Silence tolerated while an approval is outstanding. Defaults to {@link APPROVAL_IDLE_TIMEOUT_MS}. */
83
+ approvalIdleTimeoutMs?: number;
84
+ /** Silence tolerated before the first event. Defaults to {@link FIRST_EVENT_IDLE_TIMEOUT_MS}. */
85
+ firstEventIdleTimeoutMs?: number;
86
+ };
87
+ /**
88
+ * Turns a live response into the guaranteed-well-formed event sequence the rest
89
+ * of the package is written against. Four contract obligations live here:
90
+ *
91
+ * 1. Unknown or malformed events are dropped, not thrown on (invariant 5).
92
+ * 2. Nothing is emitted after the terminal event (invariant 1), so a
93
+ * misbehaving producer cannot reopen a finished turn.
94
+ * 3. Every stream ends with exactly one terminal event. The gateway's stream
95
+ * route calls `rep.hijack()`, so a failure after the headers are sent just
96
+ * closes the socket — no `error` frame, no `done`. Left alone that reads as a
97
+ * turn that never finishes, so an EOF with no terminal event is converted
98
+ * into one here. An abort is excluded: the operator stopping the turn is not
99
+ * a failure, and nothing should be reported back to them as one.
100
+ * 4. A socket that stays open but stops delivering ends the same way. Without
101
+ * that, a consumer awaiting the next event parks forever and the panel stays
102
+ * busy until the page is reloaded. The deadline is per phase, because how much
103
+ * silence is normal is not constant across a turn: wide before the first event
104
+ * (the model is still thinking), wide again while an approval is outstanding
105
+ * (the producer is waiting on a human), narrow in between.
106
+ */
107
+ export declare function readAgentStream(stream: ReadableStream<Uint8Array>, signal?: AbortSignal, options?: ReadAgentStreamOptions): AsyncGenerator<WireEvent>;
108
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/core/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAQzC,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,+BAA+B,CAAA;AAChE,8FAA8F;AAC9F,eAAO,MAAM,eAAe,0BAA0B,CAAA;AAEtD;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAA;AAE5C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,SAAU,CAAA;AAElD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,SAAU,CAAA;AAE/C,yFAAyF;AACzF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,cAGC;CACF;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,MAAM,CAAA;CAC7B,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAejD;AAiCD;;;;;;;GAOG;AACH,wBAAuB,UAAU,CAC/B,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,iBAAsB,GAC9B,cAAc,CAAC,OAAO,CAAC,CA8CzB;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,qGAAqG;IACrG,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,wGAAwG;IACxG,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,iGAAiG;IACjG,uBAAuB,CAAC,EAAE,MAAM,CAAA;CACjC,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAuB,eAAe,CACpC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,sBAA2B,GACnC,cAAc,CAAC,SAAS,CAAC,CA0C3B"}
@@ -0,0 +1,236 @@
1
+ /**
2
+ * SSE reading for the agent message route.
3
+ *
4
+ * `EventSource` is unusable here: the message is a POST body and `EventSource`
5
+ * is GET-only. So this is `fetch` + a body-stream reader, which is also what the
6
+ * producer's own docs prescribe.
7
+ *
8
+ * The gateway writes `event: <type>\ndata: <json>\n\n` and nothing else — no
9
+ * `id:`, no `retry:`, and no heartbeat comment frames. The `event:` line is
10
+ * redundant (`data.type` carries the same value) so only `data:` is parsed. The
11
+ * rest of the parser is nonetheless written against the spec rather than against
12
+ * that one producer: CRLF line endings and multi-line `data:` are both legal, and
13
+ * a proxy that rewrites line endings would otherwise silently end every turn as a
14
+ * truncation.
15
+ */
16
+ import { EVENT, isTerminal, isWireEvent } from './events';
17
+ /** A blank line ends a frame; either line ending is legal (spec §7). */
18
+ const FRAME_SEPARATOR = /\r?\n\r?\n/;
19
+ const LINE_SEPARATOR = /\r?\n/;
20
+ const DATA_PREFIX = 'data:';
21
+ /** Synthesized locally, never sent by the gateway. See `readAgentStream`. */
22
+ export const ERR_STREAM_TRUNCATED = 'ERR_AGENT_STREAM_TRUNCATED';
23
+ /** Synthesized locally: the socket stayed open but nothing arrived. See `readAgentStream`. */
24
+ export const ERR_STREAM_IDLE = 'ERR_AGENT_STREAM_IDLE';
25
+ /**
26
+ * How long a turn may say nothing between events before the connection is
27
+ * assumed lost.
28
+ *
29
+ * The gateway sends no heartbeats, so silence is the only symptom of a wedged
30
+ * agent, a half-closed proxy or a laptop resumed from sleep. Once events are
31
+ * flowing, 30s of nothing is already far outside what a working turn does: the
32
+ * producer emits a frame per token.
33
+ */
34
+ export const STREAM_IDLE_TIMEOUT_MS = 30_000;
35
+ /**
36
+ * The same limit before the first event, which is a different wait entirely.
37
+ *
38
+ * Nothing is emitted until the producer's first model call returns a routing
39
+ * decision, and a local model may think for a long time before it answers a
40
+ * single character: measured against a 4B thinking model on this hardware, the
41
+ * first event arrived at 61s. Held to the producer's own per-call budget
42
+ * (`REQUEST_TIMEOUT_MS`, 120s in backend/core/agent/src/constants.js) so a turn
43
+ * the server is still legitimately working on is never abandoned here first —
44
+ * whatever the model does, the server gives up before this does and says why.
45
+ */
46
+ export const FIRST_EVENT_IDLE_TIMEOUT_MS = 120_000;
47
+ /**
48
+ * The same limit while an approval is outstanding, where silence is legitimate:
49
+ * the producer is waiting on a human, and only auto-rejects after its own
50
+ * `agent.approvalTimeoutMs`. Five minutes is that default plus room to spare, so
51
+ * the stream is never given up on before the server itself would have resumed it.
52
+ */
53
+ export const APPROVAL_IDLE_TIMEOUT_MS = 300_000;
54
+ /** Thrown out of `readFrames` when the deadline expires; converted to an event above. */
55
+ export class StreamIdleError extends Error {
56
+ constructor() {
57
+ super(ERR_STREAM_IDLE);
58
+ this.name = 'StreamIdleError';
59
+ }
60
+ }
61
+ /**
62
+ * Pulls the `data:` payload out of one frame.
63
+ *
64
+ * A frame may carry several `data:` lines, which the spec joins with newlines;
65
+ * anything else (`event:`, `id:`, a `:` comment) is ignored. Returns null for a
66
+ * frame with no data at all.
67
+ */
68
+ export function parseFrame(frame) {
69
+ const data = frame
70
+ .split(LINE_SEPARATOR)
71
+ .filter((line) => line.startsWith(DATA_PREFIX))
72
+ .map((line) => line.slice(DATA_PREFIX.length).trim());
73
+ if (data.length === 0)
74
+ return null;
75
+ try {
76
+ return JSON.parse(data.join('\n'));
77
+ }
78
+ catch {
79
+ // A frame we cannot parse is indistinguishable from an event type we do not
80
+ // know about, and invariant 5 says ignore those rather than throw.
81
+ return null;
82
+ }
83
+ }
84
+ const IDLE = Symbol('idle');
85
+ /**
86
+ * One `read()`, given up on after `timeoutMs` of silence.
87
+ *
88
+ * The abandoned read is left attached to a rejection handler on purpose: the
89
+ * caller's `finally` cancels the reader straight after, and an unobserved
90
+ * rejection from a read parked on a socket we just cancelled would surface as a
91
+ * global unhandled rejection.
92
+ */
93
+ async function readWithDeadline(reader, timeoutMs) {
94
+ const read = reader.read();
95
+ if (timeoutMs <= 0)
96
+ return read;
97
+ read.catch(() => { });
98
+ let timer;
99
+ try {
100
+ return await Promise.race([
101
+ read,
102
+ new Promise((resolve) => {
103
+ timer = setTimeout(() => resolve(IDLE), timeoutMs);
104
+ }),
105
+ ]);
106
+ }
107
+ finally {
108
+ clearTimeout(timer);
109
+ }
110
+ }
111
+ /**
112
+ * Yields each decoded `data:` payload, in order, as frames arrive.
113
+ *
114
+ * The abort signal is handled here rather than left to `fetch`. Aborting a fetch
115
+ * whose headers have already arrived is only specified to error the body stream
116
+ * — cancelling the reader is what actually unblocks a `read()` that is parked
117
+ * waiting for the next token, which is where an abort almost always lands.
118
+ */
119
+ export async function* readFrames(stream, signal, options = {}) {
120
+ const reader = stream.getReader();
121
+ const decoder = new TextDecoder();
122
+ let buffer = '';
123
+ const cancel = () => {
124
+ reader.cancel().catch(() => {
125
+ // Already closed or errored; there is nothing left to release.
126
+ });
127
+ };
128
+ if (signal?.aborted)
129
+ cancel();
130
+ signal?.addEventListener('abort', cancel, { once: true });
131
+ const drain = function* () {
132
+ for (;;) {
133
+ const match = FRAME_SEPARATOR.exec(buffer);
134
+ if (!match)
135
+ return;
136
+ const frame = buffer.slice(0, match.index);
137
+ buffer = buffer.slice(match.index + match[0].length);
138
+ const payload = parseFrame(frame);
139
+ if (payload !== null)
140
+ yield payload;
141
+ }
142
+ };
143
+ try {
144
+ for (;;) {
145
+ const outcome = await readWithDeadline(reader, options.idleTimeoutMs?.() ?? 0);
146
+ if (outcome === IDLE)
147
+ throw new StreamIdleError();
148
+ const { value, done } = outcome;
149
+ if (done)
150
+ break;
151
+ buffer += decoder.decode(value, { stream: true });
152
+ yield* drain();
153
+ }
154
+ // Flush whatever the decoder was holding from an incomplete multibyte
155
+ // sequence, then the final frame if it arrived without its blank line.
156
+ buffer += decoder.decode();
157
+ yield* drain();
158
+ const tail = parseFrame(buffer);
159
+ if (tail !== null)
160
+ yield tail;
161
+ }
162
+ finally {
163
+ signal?.removeEventListener('abort', cancel);
164
+ cancel();
165
+ }
166
+ }
167
+ /**
168
+ * Turns a live response into the guaranteed-well-formed event sequence the rest
169
+ * of the package is written against. Four contract obligations live here:
170
+ *
171
+ * 1. Unknown or malformed events are dropped, not thrown on (invariant 5).
172
+ * 2. Nothing is emitted after the terminal event (invariant 1), so a
173
+ * misbehaving producer cannot reopen a finished turn.
174
+ * 3. Every stream ends with exactly one terminal event. The gateway's stream
175
+ * route calls `rep.hijack()`, so a failure after the headers are sent just
176
+ * closes the socket — no `error` frame, no `done`. Left alone that reads as a
177
+ * turn that never finishes, so an EOF with no terminal event is converted
178
+ * into one here. An abort is excluded: the operator stopping the turn is not
179
+ * a failure, and nothing should be reported back to them as one.
180
+ * 4. A socket that stays open but stops delivering ends the same way. Without
181
+ * that, a consumer awaiting the next event parks forever and the panel stays
182
+ * busy until the page is reloaded. The deadline is per phase, because how much
183
+ * silence is normal is not constant across a turn: wide before the first event
184
+ * (the model is still thinking), wide again while an approval is outstanding
185
+ * (the producer is waiting on a human), narrow in between.
186
+ */
187
+ export async function* readAgentStream(stream, signal, options = {}) {
188
+ const streamingTimeout = options.idleTimeoutMs ?? STREAM_IDLE_TIMEOUT_MS;
189
+ const approvalTimeout = options.approvalIdleTimeoutMs ?? APPROVAL_IDLE_TIMEOUT_MS;
190
+ const firstEventTimeout = options.firstEventIdleTimeoutMs ?? FIRST_EVENT_IDLE_TIMEOUT_MS;
191
+ let terminalSeen = false;
192
+ let awaitingApproval = false;
193
+ let seenAnyEvent = false;
194
+ let idle = false;
195
+ const idleTimeoutMs = () => {
196
+ if (streamingTimeout <= 0)
197
+ return 0;
198
+ if (awaitingApproval)
199
+ return approvalTimeout;
200
+ if (!seenAnyEvent)
201
+ return firstEventTimeout;
202
+ return streamingTimeout;
203
+ };
204
+ try {
205
+ for await (const payload of readFrames(stream, signal, { idleTimeoutMs })) {
206
+ if (!isWireEvent(payload))
207
+ continue;
208
+ if (terminalSeen)
209
+ continue;
210
+ seenAnyEvent = true;
211
+ // An approval pauses the producer; its result is what releases it.
212
+ if (payload.type === EVENT.PENDING_APPROVAL)
213
+ awaitingApproval = true;
214
+ if (payload.type === EVENT.TOOL_RESULT)
215
+ awaitingApproval = false;
216
+ if (isTerminal(payload))
217
+ terminalSeen = true;
218
+ yield payload;
219
+ }
220
+ }
221
+ catch (error) {
222
+ if (!(error instanceof StreamIdleError))
223
+ throw error;
224
+ idle = true;
225
+ }
226
+ if (!terminalSeen && !signal?.aborted) {
227
+ yield {
228
+ type: EVENT.ERROR,
229
+ error: idle ? ERR_STREAM_IDLE : ERR_STREAM_TRUNCATED,
230
+ turnId: '',
231
+ seq: Number.MAX_SAFE_INTEGER,
232
+ ts: 0,
233
+ };
234
+ }
235
+ }
236
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/core/sse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Human labels for tool chips.
3
+ *
4
+ * Tools are supplied by whichever MCP server the operator points the agent at,
5
+ * so no name can be hardcoded here — `summarize_site` and `get_device` happen to
6
+ * come from the reference site tools, but another deployment ships another set
7
+ * entirely. Callers pass a `toolLabels` map for the names they know; everything
8
+ * else falls back to a readable form of the identifier.
9
+ */
10
+ export type ToolLabels = Record<string, string>;
11
+ /** `summarize_site` -> `Summarize site`. */
12
+ export declare function humanizeToolName(name: string): string;
13
+ export declare function toolLabel(name: string, labels?: ToolLabels): string;
14
+ /** `420` -> `0.4s`, so a chip reads `Site status · 0.4s`. */
15
+ export declare function formatDuration(durationMs: number | undefined): string | null;
16
+ //# sourceMappingURL=tool-label.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-label.d.ts","sourceRoot":"","sources":["../../src/core/tool-label.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE/C,4CAA4C;AAC5C,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,CAEnE;AAED,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAK5E"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Human labels for tool chips.
3
+ *
4
+ * Tools are supplied by whichever MCP server the operator points the agent at,
5
+ * so no name can be hardcoded here — `summarize_site` and `get_device` happen to
6
+ * come from the reference site tools, but another deployment ships another set
7
+ * entirely. Callers pass a `toolLabels` map for the names they know; everything
8
+ * else falls back to a readable form of the identifier.
9
+ */
10
+ /** `summarize_site` -> `Summarize site`. */
11
+ export function humanizeToolName(name) {
12
+ const words = name
13
+ .replaceAll(/[_-]+/g, ' ')
14
+ .replaceAll(/([a-z\d])([A-Z])/g, '$1 $2')
15
+ .trim();
16
+ if (words.length === 0)
17
+ return name;
18
+ return words.charAt(0).toUpperCase() + words.slice(1).toLowerCase();
19
+ }
20
+ export function toolLabel(name, labels) {
21
+ return labels?.[name] ?? humanizeToolName(name);
22
+ }
23
+ /** `420` -> `0.4s`, so a chip reads `Site status · 0.4s`. */
24
+ export function formatDuration(durationMs) {
25
+ if (durationMs === undefined || !Number.isFinite(durationMs))
26
+ return null;
27
+ const seconds = durationMs / 1000;
28
+ // Tenths stay readable up to a minute; past that the decimal is noise.
29
+ return seconds < 60 ? `${seconds.toFixed(1)}s` : `${Math.round(seconds)}s`;
30
+ }
31
+ //# sourceMappingURL=tool-label.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-label.js","sourceRoot":"","sources":["../../src/core/tool-label.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,4CAA4C;AAC5C,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,KAAK,GAAG,IAAI;SACf,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC;SACzB,UAAU,CAAC,mBAAmB,EAAE,OAAO,CAAC;SACxC,IAAI,EAAE,CAAA;IACT,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,MAAmB;IACzD,OAAO,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAA;AACjD,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,cAAc,CAAC,UAA8B;IAC3D,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAA;IACzE,MAAM,OAAO,GAAG,UAAU,GAAG,IAAI,CAAA;IACjC,uEAAuE;IACvE,OAAO,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAA;AAC5E,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The four agent routes, as declared in `backend/apps/agent/mdk-plugin.json`.
3
+ *
4
+ * POST /agent/sessions -> { sessionId }
5
+ * POST /agent/sessions/:id/messages -> text/event-stream
6
+ * POST /agent/sessions/:id/approvals/:approvalId -> { approvalId, approved }
7
+ * DELETE /agent/sessions/:id -> { sessionId, deleted }
8
+ *
9
+ * Note the approval route is session-scoped. `backend/core/agent/docs/CONTRACT.md`
10
+ * documents it as `/agent/turns/{turnId}/approvals/{approvalId}`; that is stale —
11
+ * `turnId` appears in no URL.
12
+ *
13
+ * The gateway sends no CORS headers, and the stream route calls `rep.hijack()` so
14
+ * no CORS hook could decorate it even if one existed. Callers must be same-origin
15
+ * (served from `staticRootPath`) or proxied (`/agent` in the dev server).
16
+ */
17
+ import type { WireEvent } from './events';
18
+ import type { ReadAgentStreamOptions } from './sse';
19
+ export declare const AGENT_ROUTE_PREFIX = "/agent";
20
+ /** The `ERR_*` codes these routes can return. */
21
+ export declare const AGENT_ERROR: {
22
+ readonly SESSION_NOT_FOUND: 'ERR_AGENT_SESSION_NOT_FOUND';
23
+ readonly TURN_ACTIVE: 'ERR_AGENT_TURN_ACTIVE';
24
+ readonly TEXT_REQUIRED: 'ERR_AGENT_MESSAGE_TEXT_REQUIRED';
25
+ readonly UNAVAILABLE: 'ERR_AGENT_UNAVAILABLE';
26
+ readonly APPROVAL_NOT_FOUND: 'ERR_AGENT_APPROVAL_NOT_FOUND';
27
+ };
28
+ export type AgentErrorCode = (typeof AGENT_ERROR)[keyof typeof AGENT_ERROR];
29
+ export type TokenSource = () => string | null | undefined | Promise<string | null | undefined>;
30
+ export type AgentTransportOptions = {
31
+ /** Gateway origin. Empty string means relative URLs — i.e. same-origin or proxied. */
32
+ baseUrl?: string;
33
+ getToken?: TokenSource;
34
+ /** Injected in tests; defaults to the global `fetch`. */
35
+ fetchImpl?: typeof fetch;
36
+ };
37
+ /** A gateway error carrying the `ERR_*` code the UI branches on. */
38
+ export declare class AgentApiError extends Error {
39
+ readonly status: number;
40
+ readonly code: string;
41
+ constructor(status: number, code: string, message?: string);
42
+ is(code: AgentErrorCode): boolean;
43
+ }
44
+ /** Identity comes from the token, so the request body is ignored by the gateway. */
45
+ export declare function createSession(options?: AgentTransportOptions): Promise<string>;
46
+ export declare function deleteSession(sessionId: string, options?: AgentTransportOptions): Promise<void>;
47
+ /**
48
+ * Answers a paused approval. The decision travels on its own request while the
49
+ * message stream stays open — the stream resumes on its own once this lands.
50
+ *
51
+ * The gateway tests `approved === true`, so anything else is a rejection. It is
52
+ * passed through as a real boolean rather than coerced, to keep that explicit.
53
+ *
54
+ * A decision that arrives after the approval timed out, or a second decision for
55
+ * the same approval, is a 404 `ERR_AGENT_APPROVAL_NOT_FOUND` — the two are
56
+ * deliberately indistinguishable. Callers should treat it as "already settled",
57
+ * not as a failure.
58
+ */
59
+ export declare function decideApproval(sessionId: string, approvalId: string, approved: boolean, options?: AgentTransportOptions): Promise<void>;
60
+ export type StreamTurnOptions = AgentTransportOptions & ReadAgentStreamOptions & {
61
+ signal?: AbortSignal;
62
+ };
63
+ /**
64
+ * Sends one message and yields its events until the turn ends.
65
+ *
66
+ * Only one turn may be in flight per session; a second send while this is
67
+ * running — including while it is paused on an approval, since a paused turn
68
+ * still holds the session busy — throws `ERR_AGENT_TURN_ACTIVE`.
69
+ */
70
+ export declare function streamTurn(sessionId: string, text: string, options?: StreamTurnOptions): AsyncGenerator<WireEvent>;
71
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/core/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,OAAO,CAAA;AAGnD,eAAO,MAAM,kBAAkB,WAAW,CAAA;AAE1C,iDAAiD;AACjD,eAAO,MAAM,WAAW;aACtB,iBAAiB,EAAE,6BAA6B;aAChD,WAAW,EAAE,uBAAuB;aACpC,aAAa,EAAE,iCAAiC;aAChD,WAAW,EAAE,uBAAuB;aACpC,kBAAkB,EAAE,8BAA8B;CAC1C,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAA;AAE3E,MAAM,MAAM,WAAW,GAAG,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;AAE9F,MAAM,MAAM,qBAAqB,GAAG;IAClC,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB,CAAA;AAED,oEAAoE;AACpE,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB,YAAY,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAKzD;IAED,EAAE,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAEhC;CACF;AAyDD,oFAAoF;AACpF,wBAAsB,aAAa,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxF;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,OAAO,EACjB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,sBAAsB,GAAG;IAC/E,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAED;;;;;;GAMG;AACH,wBAAuB,UAAU,CAC/B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,iBAAsB,GAC9B,cAAc,CAAC,SAAS,CAAC,CA0B3B"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * The four agent routes, as declared in `backend/apps/agent/mdk-plugin.json`.
3
+ *
4
+ * POST /agent/sessions -> { sessionId }
5
+ * POST /agent/sessions/:id/messages -> text/event-stream
6
+ * POST /agent/sessions/:id/approvals/:approvalId -> { approvalId, approved }
7
+ * DELETE /agent/sessions/:id -> { sessionId, deleted }
8
+ *
9
+ * Note the approval route is session-scoped. `backend/core/agent/docs/CONTRACT.md`
10
+ * documents it as `/agent/turns/{turnId}/approvals/{approvalId}`; that is stale —
11
+ * `turnId` appears in no URL.
12
+ *
13
+ * The gateway sends no CORS headers, and the stream route calls `rep.hijack()` so
14
+ * no CORS hook could decorate it even if one existed. Callers must be same-origin
15
+ * (served from `staticRootPath`) or proxied (`/agent` in the dev server).
16
+ */
17
+ import { readAgentStream } from './sse';
18
+ export const AGENT_ROUTE_PREFIX = '/agent';
19
+ /** The `ERR_*` codes these routes can return. */
20
+ export const AGENT_ERROR = {
21
+ SESSION_NOT_FOUND: 'ERR_AGENT_SESSION_NOT_FOUND',
22
+ TURN_ACTIVE: 'ERR_AGENT_TURN_ACTIVE',
23
+ TEXT_REQUIRED: 'ERR_AGENT_MESSAGE_TEXT_REQUIRED',
24
+ UNAVAILABLE: 'ERR_AGENT_UNAVAILABLE',
25
+ APPROVAL_NOT_FOUND: 'ERR_AGENT_APPROVAL_NOT_FOUND',
26
+ };
27
+ /** A gateway error carrying the `ERR_*` code the UI branches on. */
28
+ export class AgentApiError extends Error {
29
+ status;
30
+ code;
31
+ constructor(status, code, message) {
32
+ super(message ?? code);
33
+ this.name = 'AgentApiError';
34
+ this.status = status;
35
+ this.code = code;
36
+ }
37
+ is(code) {
38
+ return this.code === code;
39
+ }
40
+ }
41
+ // Trimmed with a scan rather than /\/+$/: the regex backtracks quadratically on a long run
42
+ // of trailing slashes, which CodeQL flags as a polynomial ReDoS (js/polynomial-redos).
43
+ function stripTrailingSlashes(value) {
44
+ let end = value.length;
45
+ while (end > 0 && value[end - 1] === '/')
46
+ end--;
47
+ return value.slice(0, end);
48
+ }
49
+ function joinUrl(baseUrl, path) {
50
+ if (!baseUrl)
51
+ return path;
52
+ return `${stripTrailingSlashes(baseUrl)}${path}`;
53
+ }
54
+ async function buildHeaders(options, extra) {
55
+ const headers = new Headers(extra);
56
+ const token = await options.getToken?.();
57
+ if (token)
58
+ headers.set('authorization', `Bearer ${token}`);
59
+ return headers;
60
+ }
61
+ /**
62
+ * Both error body shapes, reduced to a code.
63
+ *
64
+ * Normal routes answer `{ statusCode, error, message }`; the streaming route
65
+ * answers `{ statusCode, message }` with no `error` key at all. Either way the
66
+ * `ERR_*` code is in `message`. `ERR_AGENT_UNAVAILABLE` additionally has its
67
+ * reason concatenated in (`"ERR_AGENT_UNAVAILABLE: config.agent missing"`), so
68
+ * the code is only the part before the colon.
69
+ */
70
+ async function toApiError(response) {
71
+ let message = response.statusText;
72
+ try {
73
+ const body = (await response.json());
74
+ if (typeof body?.message === 'string')
75
+ message = body.message;
76
+ }
77
+ catch {
78
+ // Non-JSON body (or none). The status alone still identifies the failure.
79
+ }
80
+ const code = message.split(':')[0]?.trim() || `HTTP_${response.status}`;
81
+ return new AgentApiError(response.status, code, message);
82
+ }
83
+ async function requestJson(path, init, options) {
84
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
85
+ const response = await fetchImpl(joinUrl(options.baseUrl, path), init);
86
+ if (!response.ok)
87
+ throw await toApiError(response);
88
+ return (await response.json());
89
+ }
90
+ /** Identity comes from the token, so the request body is ignored by the gateway. */
91
+ export async function createSession(options = {}) {
92
+ const headers = await buildHeaders(options, { 'content-type': 'application/json' });
93
+ const body = await requestJson(`${AGENT_ROUTE_PREFIX}/sessions`, { method: 'POST', headers, body: '{}' }, options);
94
+ return body.sessionId;
95
+ }
96
+ export async function deleteSession(sessionId, options = {}) {
97
+ const headers = await buildHeaders(options);
98
+ await requestJson(`${AGENT_ROUTE_PREFIX}/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE', headers }, options);
99
+ }
100
+ /**
101
+ * Answers a paused approval. The decision travels on its own request while the
102
+ * message stream stays open — the stream resumes on its own once this lands.
103
+ *
104
+ * The gateway tests `approved === true`, so anything else is a rejection. It is
105
+ * passed through as a real boolean rather than coerced, to keep that explicit.
106
+ *
107
+ * A decision that arrives after the approval timed out, or a second decision for
108
+ * the same approval, is a 404 `ERR_AGENT_APPROVAL_NOT_FOUND` — the two are
109
+ * deliberately indistinguishable. Callers should treat it as "already settled",
110
+ * not as a failure.
111
+ */
112
+ export async function decideApproval(sessionId, approvalId, approved, options = {}) {
113
+ const headers = await buildHeaders(options, { 'content-type': 'application/json' });
114
+ await requestJson(`${AGENT_ROUTE_PREFIX}/sessions/${encodeURIComponent(sessionId)}`
115
+ + `/approvals/${encodeURIComponent(approvalId)}`, { method: 'POST', headers, body: JSON.stringify({ approved }) }, options);
116
+ }
117
+ /**
118
+ * Sends one message and yields its events until the turn ends.
119
+ *
120
+ * Only one turn may be in flight per session; a second send while this is
121
+ * running — including while it is paused on an approval, since a paused turn
122
+ * still holds the session busy — throws `ERR_AGENT_TURN_ACTIVE`.
123
+ */
124
+ export async function* streamTurn(sessionId, text, options = {}) {
125
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
126
+ const headers = await buildHeaders(options, {
127
+ 'content-type': 'application/json',
128
+ 'accept': 'text/event-stream',
129
+ });
130
+ const response = await fetchImpl(joinUrl(options.baseUrl, `${AGENT_ROUTE_PREFIX}/sessions/${encodeURIComponent(sessionId)}/messages`), { method: 'POST', headers, body: JSON.stringify({ text }), signal: options.signal });
131
+ // 404 / 409 / 400 are all raised before the SSE headers go out, so they arrive
132
+ // as an ordinary JSON body rather than as an event on the stream.
133
+ if (!response.ok)
134
+ throw await toApiError(response);
135
+ if (!response.body)
136
+ throw new AgentApiError(response.status, 'ERR_AGENT_STREAM_EMPTY');
137
+ yield* readAgentStream(response.body, options.signal, {
138
+ ...(options.idleTimeoutMs === undefined ? {} : { idleTimeoutMs: options.idleTimeoutMs }),
139
+ ...(options.approvalIdleTimeoutMs === undefined
140
+ ? {}
141
+ : { approvalIdleTimeoutMs: options.approvalIdleTimeoutMs }),
142
+ ...(options.firstEventIdleTimeoutMs === undefined
143
+ ? {}
144
+ : { firstEventIdleTimeoutMs: options.firstEventIdleTimeoutMs }),
145
+ });
146
+ }
147
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/core/transport.ts"],"names":[],"mappings":"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"}