@ably/ai-transport 0.1.0 → 0.3.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 (221) hide show
  1. package/README.md +93 -111
  2. package/dist/ably-ai-transport.js +2401 -1387
  3. package/dist/ably-ai-transport.js.map +1 -1
  4. package/dist/ably-ai-transport.umd.cjs +1 -1
  5. package/dist/ably-ai-transport.umd.cjs.map +1 -1
  6. package/dist/constants.d.ts +116 -42
  7. package/dist/core/agent.d.ts +44 -0
  8. package/dist/core/channel-options.d.ts +57 -0
  9. package/dist/core/codec/codec-event.d.ts +9 -0
  10. package/dist/core/codec/decoder.d.ts +24 -24
  11. package/dist/core/codec/define-codec.d.ts +100 -0
  12. package/dist/core/codec/encoder.d.ts +10 -12
  13. package/dist/core/codec/field-bag.d.ts +85 -0
  14. package/dist/core/codec/fields.d.ts +141 -0
  15. package/dist/core/codec/index.d.ts +8 -2
  16. package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
  17. package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
  18. package/dist/core/codec/input-descriptors.d.ts +281 -0
  19. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  20. package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
  21. package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
  22. package/dist/core/codec/output-descriptors.d.ts +237 -0
  23. package/dist/core/codec/types.d.ts +470 -119
  24. package/dist/core/codec/well-known-inputs.d.ts +52 -0
  25. package/dist/core/transport/agent-session.d.ts +10 -0
  26. package/dist/core/transport/agent-view.d.ts +296 -0
  27. package/dist/core/transport/client-session.d.ts +13 -0
  28. package/dist/core/transport/decode-fold.d.ts +55 -0
  29. package/dist/core/transport/headers.d.ts +121 -14
  30. package/dist/core/transport/index.d.ts +5 -6
  31. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  32. package/dist/core/transport/invocation.d.ts +74 -0
  33. package/dist/core/transport/load-history-pages.d.ts +71 -0
  34. package/dist/core/transport/load-history.d.ts +44 -0
  35. package/dist/core/transport/pipe-stream.d.ts +9 -9
  36. package/dist/core/transport/run-manager.d.ts +76 -0
  37. package/dist/core/transport/session-support.d.ts +55 -0
  38. package/dist/core/transport/tree.d.ts +523 -109
  39. package/dist/core/transport/types/agent.d.ts +375 -0
  40. package/dist/core/transport/types/client.d.ts +201 -0
  41. package/dist/core/transport/types/shared.d.ts +24 -0
  42. package/dist/core/transport/types/tree.d.ts +357 -0
  43. package/dist/core/transport/types/view.d.ts +249 -0
  44. package/dist/core/transport/types.d.ts +13 -553
  45. package/dist/core/transport/view.d.ts +390 -84
  46. package/dist/core/transport/wire-log.d.ts +102 -0
  47. package/dist/errors.d.ts +27 -10
  48. package/dist/index.d.ts +8 -9
  49. package/dist/logger.d.ts +12 -0
  50. package/dist/react/ably-ai-transport-react.js +1365 -1010
  51. package/dist/react/ably-ai-transport-react.js.map +1 -1
  52. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  53. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  54. package/dist/react/contexts/client-session-context.d.ts +37 -0
  55. package/dist/react/contexts/client-session-provider.d.ts +56 -0
  56. package/dist/react/create-session-hooks.d.ts +116 -0
  57. package/dist/react/index.d.ts +13 -12
  58. package/dist/react/internal/skipped-session.d.ts +8 -0
  59. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  60. package/dist/react/use-ably-messages.d.ts +17 -14
  61. package/dist/react/use-client-session.d.ts +81 -0
  62. package/dist/react/use-create-view.d.ts +14 -13
  63. package/dist/react/use-tree.d.ts +30 -15
  64. package/dist/react/use-view.d.ts +81 -50
  65. package/dist/utils.d.ts +48 -71
  66. package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
  67. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  68. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  69. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  70. package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
  71. package/dist/vercel/codec/events.d.ts +50 -0
  72. package/dist/vercel/codec/fields.d.ts +44 -0
  73. package/dist/vercel/codec/fold-content.d.ts +16 -0
  74. package/dist/vercel/codec/fold-data.d.ts +16 -0
  75. package/dist/vercel/codec/fold-input.d.ts +67 -0
  76. package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
  77. package/dist/vercel/codec/fold-text.d.ts +16 -0
  78. package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
  79. package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
  80. package/dist/vercel/codec/index.d.ts +7 -20
  81. package/dist/vercel/codec/inputs.d.ts +11 -0
  82. package/dist/vercel/codec/outputs.d.ts +11 -0
  83. package/dist/vercel/codec/reducer-state.d.ts +121 -0
  84. package/dist/vercel/codec/reducer.d.ts +62 -0
  85. package/dist/vercel/codec/tool-transitions.d.ts +2 -8
  86. package/dist/vercel/codec/wire-data.d.ts +34 -0
  87. package/dist/vercel/index.d.ts +5 -5
  88. package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
  89. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  90. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
  91. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  92. package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
  93. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
  94. package/dist/vercel/react/index.d.ts +1 -2
  95. package/dist/vercel/react/use-chat-transport.d.ts +30 -26
  96. package/dist/vercel/react/use-message-sync.d.ts +17 -30
  97. package/dist/vercel/run-end-reason.d.ts +84 -0
  98. package/dist/vercel/tool-part.d.ts +21 -0
  99. package/dist/vercel/transport/chat-transport.d.ts +41 -24
  100. package/dist/vercel/transport/index.d.ts +24 -20
  101. package/dist/vercel/transport/run-output-stream.d.ts +54 -0
  102. package/dist/version.d.ts +2 -0
  103. package/package.json +31 -24
  104. package/src/constants.ts +124 -51
  105. package/src/core/agent.ts +92 -0
  106. package/src/core/channel-options.ts +89 -0
  107. package/src/core/codec/codec-event.ts +27 -0
  108. package/src/core/codec/decoder.ts +202 -105
  109. package/src/core/codec/define-codec.ts +432 -0
  110. package/src/core/codec/encoder.ts +114 -107
  111. package/src/core/codec/field-bag.ts +142 -0
  112. package/src/core/codec/fields.ts +193 -0
  113. package/src/core/codec/index.ts +56 -6
  114. package/src/core/codec/input-descriptor-decoder.ts +97 -0
  115. package/src/core/codec/input-descriptor-encoder.ts +150 -0
  116. package/src/core/codec/input-descriptors.ts +373 -0
  117. package/src/core/codec/lifecycle-tracker.ts +10 -9
  118. package/src/core/codec/output-descriptor-decoder.ts +139 -0
  119. package/src/core/codec/output-descriptor-encoder.ts +101 -0
  120. package/src/core/codec/output-descriptors.ts +307 -0
  121. package/src/core/codec/types.ts +505 -126
  122. package/src/core/codec/well-known-inputs.ts +96 -0
  123. package/src/core/transport/agent-session.ts +1085 -0
  124. package/src/core/transport/agent-view.ts +738 -0
  125. package/src/core/transport/client-session.ts +780 -0
  126. package/src/core/transport/decode-fold.ts +101 -0
  127. package/src/core/transport/headers.ts +234 -22
  128. package/src/core/transport/index.ts +27 -27
  129. package/src/core/transport/internal/bounded-map.ts +27 -0
  130. package/src/core/transport/invocation.ts +98 -0
  131. package/src/core/transport/load-history-pages.ts +220 -0
  132. package/src/core/transport/load-history.ts +271 -0
  133. package/src/core/transport/pipe-stream.ts +63 -39
  134. package/src/core/transport/run-manager.ts +243 -0
  135. package/src/core/transport/session-support.ts +96 -0
  136. package/src/core/transport/tree.ts +1293 -308
  137. package/src/core/transport/types/agent.ts +434 -0
  138. package/src/core/transport/types/client.ts +247 -0
  139. package/src/core/transport/types/shared.ts +27 -0
  140. package/src/core/transport/types/tree.ts +393 -0
  141. package/src/core/transport/types/view.ts +288 -0
  142. package/src/core/transport/types.ts +13 -706
  143. package/src/core/transport/view.ts +1229 -450
  144. package/src/core/transport/wire-log.ts +189 -0
  145. package/src/errors.ts +29 -9
  146. package/src/event-emitter.ts +3 -2
  147. package/src/index.ts +86 -42
  148. package/src/logger.ts +14 -1
  149. package/src/react/contexts/client-session-context.ts +41 -0
  150. package/src/react/contexts/client-session-provider.tsx +222 -0
  151. package/src/react/create-session-hooks.ts +141 -0
  152. package/src/react/index.ts +24 -13
  153. package/src/react/internal/skipped-session.ts +62 -0
  154. package/src/react/internal/use-resolved-session.ts +63 -0
  155. package/src/react/use-ably-messages.ts +32 -22
  156. package/src/react/use-client-session.ts +178 -0
  157. package/src/react/use-create-view.ts +33 -29
  158. package/src/react/use-tree.ts +61 -30
  159. package/src/react/use-view.ts +138 -96
  160. package/src/utils.ts +83 -131
  161. package/src/vercel/codec/decode-lifecycle.ts +70 -0
  162. package/src/vercel/codec/events.ts +85 -0
  163. package/src/vercel/codec/fields.ts +58 -0
  164. package/src/vercel/codec/fold-content.ts +54 -0
  165. package/src/vercel/codec/fold-data.ts +46 -0
  166. package/src/vercel/codec/fold-input.ts +255 -0
  167. package/src/vercel/codec/fold-lifecycle.ts +85 -0
  168. package/src/vercel/codec/fold-text.ts +55 -0
  169. package/src/vercel/codec/fold-tool-input.ts +86 -0
  170. package/src/vercel/codec/fold-tool-output.ts +79 -0
  171. package/src/vercel/codec/index.ts +28 -21
  172. package/src/vercel/codec/inputs.ts +116 -0
  173. package/src/vercel/codec/outputs.ts +207 -0
  174. package/src/vercel/codec/reducer-state.ts +169 -0
  175. package/src/vercel/codec/reducer.ts +191 -0
  176. package/src/vercel/codec/tool-transitions.ts +3 -14
  177. package/src/vercel/codec/wire-data.ts +64 -0
  178. package/src/vercel/index.ts +7 -19
  179. package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
  180. package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
  181. package/src/vercel/react/index.ts +3 -5
  182. package/src/vercel/react/use-chat-transport.ts +44 -66
  183. package/src/vercel/react/use-message-sync.ts +75 -39
  184. package/src/vercel/run-end-reason.ts +157 -0
  185. package/src/vercel/tool-part.ts +25 -0
  186. package/src/vercel/transport/chat-transport.ts +380 -98
  187. package/src/vercel/transport/index.ts +38 -37
  188. package/src/vercel/transport/run-output-stream.ts +169 -0
  189. package/src/version.ts +2 -0
  190. package/dist/core/transport/client-transport.d.ts +0 -10
  191. package/dist/core/transport/decode-history.d.ts +0 -43
  192. package/dist/core/transport/server-transport.d.ts +0 -7
  193. package/dist/core/transport/stream-router.d.ts +0 -29
  194. package/dist/core/transport/turn-manager.d.ts +0 -37
  195. package/dist/react/contexts/transport-context.d.ts +0 -31
  196. package/dist/react/contexts/transport-provider.d.ts +0 -49
  197. package/dist/react/create-transport-hooks.d.ts +0 -124
  198. package/dist/react/use-active-turns.d.ts +0 -12
  199. package/dist/react/use-client-transport.d.ts +0 -80
  200. package/dist/vercel/codec/accumulator.d.ts +0 -21
  201. package/dist/vercel/codec/decoder.d.ts +0 -22
  202. package/dist/vercel/codec/encoder.d.ts +0 -41
  203. package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
  204. package/dist/vercel/tool-approvals.d.ts +0 -124
  205. package/dist/vercel/tool-events.d.ts +0 -26
  206. package/src/core/transport/client-transport.ts +0 -977
  207. package/src/core/transport/decode-history.ts +0 -485
  208. package/src/core/transport/server-transport.ts +0 -612
  209. package/src/core/transport/stream-router.ts +0 -136
  210. package/src/core/transport/turn-manager.ts +0 -165
  211. package/src/react/contexts/transport-context.ts +0 -37
  212. package/src/react/contexts/transport-provider.tsx +0 -164
  213. package/src/react/create-transport-hooks.ts +0 -144
  214. package/src/react/use-active-turns.ts +0 -72
  215. package/src/react/use-client-transport.ts +0 -197
  216. package/src/vercel/codec/accumulator.ts +0 -588
  217. package/src/vercel/codec/decoder.ts +0 -618
  218. package/src/vercel/codec/encoder.ts +0 -410
  219. package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
  220. package/src/vercel/tool-approvals.ts +0 -380
  221. package/src/vercel/tool-events.ts +0 -53
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Shared wire decode-and-apply engine.
3
+ *
4
+ * The client's live decode loop and the View's history replay both reconstruct
5
+ * the conversation Tree from the same raw Ably wire log. This module is the one
6
+ * place that classifies a wire message (run-lifecycle vs codec-decoded), parses
7
+ * or decodes it, and applies it to the Tree — so the two paths can never drift.
8
+ *
9
+ * The engine is exposed as a {@link WireApplier} binding one Tree to one
10
+ * decoder. A Tree has exactly one applier (the session constructs it and hands
11
+ * it to every View), so every route a wire message can arrive by — the live
12
+ * subscription, View history pagination, the agent's hydration walks — feeds
13
+ * the same decoder. The decoder's version-guarded stream trackers then make
14
+ * re-delivery across routes (an attach-boundary in-flight stream, a replayed
15
+ * history page) decode to nothing instead of double-folding. The delivery's
16
+ * `version.serial` is also threaded into the Tree, whose per-entry
17
+ * `decodedThrough` high-water-mark drops whole-wire replays that no decoder
18
+ * state can see (stateless discrete re-decodes).
19
+ */
20
+
21
+ import type * as Ably from 'ably';
22
+
23
+ import { HEADER_RUN_ID } from '../../constants.js';
24
+ import { getTransportHeaders } from '../../utils.js';
25
+ import type { CodecInputEvent, CodecOutputEvent, Decoder } from '../codec/types.js';
26
+ import { isRunLifecycleName, parseRunLifecycle } from './headers.js';
27
+ import type { TreeInternal } from './tree.js';
28
+ import type { RunLifecycleEvent } from './types.js';
29
+
30
+ /**
31
+ * The decode-and-apply engine for one Tree: a single codec decoder bound to a
32
+ * single Tree, shared by every route that feeds the Tree wire messages.
33
+ */
34
+ export interface WireApplier {
35
+ /**
36
+ * Apply one inbound wire message to the bound tree.
37
+ *
38
+ * Run-lifecycle messages are turned into a {@link RunLifecycleEvent} via
39
+ * {@link parseRunLifecycle} and applied with `applyRunLifecycle`; everything
40
+ * else is decoded with the bound decoder and applied with `applyMessage`,
41
+ * skipping wire-only carriers that decode to no events and carry no run-id
42
+ * (the eventual reply run is created later by its run-start).
43
+ *
44
+ * Does NOT emit the tree's `ably-message` event — the caller owns that,
45
+ * because the live loop emits per message while history replay emits in a
46
+ * batch once the whole page is applied. Returns the parsed lifecycle event
47
+ * so a live caller can run its own side-effects (resolving a pending
48
+ * run-start, surfacing an agent error); returns `undefined` for a
49
+ * codec-decoded message or a lifecycle message that carried no run-id.
50
+ * @param rawMsg - The inbound Ably wire message.
51
+ * @returns The parsed run-lifecycle event, or `undefined`.
52
+ */
53
+ apply(rawMsg: Ably.InboundMessage): RunLifecycleEvent | undefined;
54
+ }
55
+
56
+ /**
57
+ * Classify, decode, and apply one inbound wire message to the tree. See
58
+ * {@link WireApplier.apply} for the contract.
59
+ * @param tree - The tree to apply the message to.
60
+ * @param decoder - The codec decoder used for non-lifecycle messages.
61
+ * @param rawMsg - The inbound Ably wire message.
62
+ * @returns The parsed run-lifecycle event, or `undefined`.
63
+ */
64
+ const applyWireMessage = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection>(
65
+ tree: TreeInternal<TInput, TOutput, TProjection>,
66
+ decoder: Decoder<TInput, TOutput>,
67
+ rawMsg: Ably.InboundMessage,
68
+ ): RunLifecycleEvent | undefined => {
69
+ const headers = getTransportHeaders(rawMsg);
70
+ const serial = rawMsg.serial;
71
+ // Top-level timestamp — the message's create time on every delivery (an
72
+ // append's own receive time lives in `version.timestamp`). The retention
73
+ // clock is sound on this timeline because run-end, a fresh create published
74
+ // after every wire of its run, bounds the node's last activity.
75
+ const timestamp = rawMsg.timestamp;
76
+
77
+ if (isRunLifecycleName(rawMsg.name)) {
78
+ const event = parseRunLifecycle(rawMsg.name, headers, serial, timestamp);
79
+ if (event) tree.applyRunLifecycle(event);
80
+ return event;
81
+ }
82
+
83
+ const { inputs, outputs } = decoder.decode(rawMsg);
84
+ if (inputs.length > 0 || outputs.length > 0 || headers[HEADER_RUN_ID]) {
85
+ tree.applyMessage({ inputs, outputs }, headers, serial, timestamp, rawMsg.version.serial);
86
+ }
87
+ return undefined;
88
+ };
89
+
90
+ /**
91
+ * Bind a Tree and a decoder into the Tree's single {@link WireApplier}.
92
+ * @param tree - The tree the applier feeds.
93
+ * @param decoder - The codec decoder shared by every route into the tree.
94
+ * @returns The applier.
95
+ */
96
+ export const createWireApplier = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection>(
97
+ tree: TreeInternal<TInput, TOutput, TProjection>,
98
+ decoder: Decoder<TInput, TOutput>,
99
+ ): WireApplier => ({
100
+ apply: (rawMsg: Ably.InboundMessage): RunLifecycleEvent | undefined => applyWireMessage(tree, decoder, rawMsg),
101
+ });
@@ -1,50 +1,262 @@
1
1
  /**
2
2
  * Transport header builder.
3
3
  *
4
- * Single source of truth for which `x-ably-*` headers every transport
5
- * message carries. Used by the server transport (addMessages, streamResponse)
6
- * and will be used by the client transport (optimistic message stamping).
4
+ * Single source of truth for which transport headers every transport
5
+ * message carries. Used by the agent session (pipe) and by
6
+ * the client session (optimistic message stamping).
7
7
  */
8
8
 
9
+ import * as Ably from 'ably';
10
+
9
11
  import {
10
- HEADER_AMEND,
12
+ EVENT_RUN_END,
13
+ EVENT_RUN_RESUME,
14
+ EVENT_RUN_START,
15
+ EVENT_RUN_SUSPEND,
16
+ HEADER_CODEC_MESSAGE_ID,
17
+ HEADER_ERROR_CODE,
18
+ HEADER_ERROR_MESSAGE,
19
+ HEADER_EVENT_ID,
11
20
  HEADER_FORK_OF,
12
- HEADER_MSG_ID,
21
+ HEADER_INPUT_CLIENT_ID,
22
+ HEADER_INPUT_CODEC_MESSAGE_ID,
23
+ HEADER_INVOCATION_ID,
24
+ HEADER_MSG_REGENERATE,
13
25
  HEADER_PARENT,
14
26
  HEADER_ROLE,
15
- HEADER_TURN_CLIENT_ID,
16
- HEADER_TURN_ID,
27
+ HEADER_RUN_CLIENT_ID,
28
+ HEADER_RUN_ID,
29
+ HEADER_RUN_REASON,
17
30
  } from '../../constants.js';
31
+ import { ErrorCode } from '../../errors.js';
32
+ import type { RunEndReason, RunLifecycleEvent } from './types.js';
18
33
 
19
34
  /**
20
35
  * Build the standard transport header set for a message.
21
36
  * @param opts - The header values to include.
22
37
  * @param opts.role - Message role (e.g. "user", "assistant").
23
- * @param opts.turnId - Turn correlation ID.
24
- * @param opts.msgId - Message identity.
25
- * @param opts.turnClientId - ClientId of the turn initiator.
26
- * @param opts.parent - Preceding message's msg-id (for branching).
27
- * @param opts.forkOf - Forked message's msg-id (for edit/regen).
28
- * @param opts.amend - The msg-id of the existing message this message targets (cross-turn events).
29
- * @returns A headers record with the `x-ably-*` transport headers set.
38
+ * @param opts.runId - Run correlation ID, or `undefined` for a fresh client
39
+ * input (the agent mints run-ids, so it is not known synchronously). Omitted
40
+ * from the headers when undefined; a continuation still carries the known run-id.
41
+ * @param opts.codecMessageId - Message identity — the wire `codec-message-id` for this message.
42
+ * @param opts.runClientId - ClientId of the run initiator.
43
+ * @param opts.parent - Preceding message's codec-message-id (for branching).
44
+ * @param opts.forkOf - Forked user-prompt's codec-message-id (for edits — creates a Run-level fork sibling).
45
+ * @param opts.regenerates - Assistant codec-message-id this run regenerates. Stamps
46
+ * `msg-regenerate`. Distinct from `forkOf`: regenerate is a
47
+ * continuation of the prior run (no Run-level fork), with the message
48
+ * replacement resolved at projection extraction time.
49
+ * @param opts.invocationId - Agent-minted invocation id. Stamped by the agent on every event it publishes for the invocation (run lifecycle + outputs) so the client can observe it; not set by the client on the input.
50
+ * @param opts.inputClientId - ClientId of the input event (the `ai-input`) that
51
+ * drove the current invocation. The agent reads it from the publisher's
52
+ * Ably-level `clientId` on the matched input event and re-stamps it on its
53
+ * own publishes (run lifecycle + outputs). Differs from `runClientId` on
54
+ * continuation invocations driven by an input from a non-owner.
55
+ * @param opts.inputEventId - Per-event identifier. Set on each client-published user-prompt message; the invocation body's `inputEventIds` lists the ids the agent should look up.
56
+ * @param opts.inputCodecMessageId - The codec-message-id of the input event that
57
+ * triggered the current invocation (the one whose `event-id` matched the
58
+ * invocation's `inputEventId`). The agent re-stamps it on every event it
59
+ * publishes for the invocation (run lifecycle + outputs), mirroring
60
+ * `inputClientId`, so the client can correlate any of those events back to
61
+ * the originating input by the id it owned at send time.
62
+ * @returns A headers record with the transport headers set.
30
63
  */
31
64
  export const buildTransportHeaders = (opts: {
32
65
  role: string;
33
- turnId: string;
34
- msgId: string;
35
- turnClientId?: string;
66
+ runId?: string;
67
+ codecMessageId: string;
68
+ runClientId?: string;
36
69
  parent?: string;
37
70
  forkOf?: string;
38
- amend?: string;
71
+ regenerates?: string;
72
+ invocationId?: string;
73
+ inputClientId?: string;
74
+ inputCodecMessageId?: string;
75
+ inputEventId?: string;
39
76
  }): Record<string, string> => {
40
77
  const h: Record<string, string> = {
41
78
  [HEADER_ROLE]: opts.role,
42
- [HEADER_TURN_ID]: opts.turnId,
43
- [HEADER_MSG_ID]: opts.msgId,
79
+ [HEADER_CODEC_MESSAGE_ID]: opts.codecMessageId,
44
80
  };
45
- if (opts.turnClientId !== undefined) h[HEADER_TURN_CLIENT_ID] = opts.turnClientId;
81
+ if (opts.runId !== undefined) h[HEADER_RUN_ID] = opts.runId;
82
+ if (opts.runClientId !== undefined) h[HEADER_RUN_CLIENT_ID] = opts.runClientId;
46
83
  if (opts.parent) h[HEADER_PARENT] = opts.parent;
47
84
  if (opts.forkOf) h[HEADER_FORK_OF] = opts.forkOf;
48
- if (opts.amend) h[HEADER_AMEND] = opts.amend;
85
+ if (opts.regenerates) h[HEADER_MSG_REGENERATE] = opts.regenerates;
86
+ if (opts.invocationId) h[HEADER_INVOCATION_ID] = opts.invocationId;
87
+ if (opts.inputClientId !== undefined) h[HEADER_INPUT_CLIENT_ID] = opts.inputClientId;
88
+ if (opts.inputCodecMessageId !== undefined) h[HEADER_INPUT_CODEC_MESSAGE_ID] = opts.inputCodecMessageId;
89
+ if (opts.inputEventId) h[HEADER_EVENT_ID] = opts.inputEventId;
90
+ return h;
91
+ };
92
+
93
+ /**
94
+ * Build the transport header set for a run-lifecycle event (run-start,
95
+ * run-resume, run-suspend, run-end). Single source of truth for lifecycle
96
+ * header stamping, mirroring {@link buildTransportHeaders} for the
97
+ * message-carrier path. Every field except `runId`/`runClientId` is optional
98
+ * and omitted when not provided.
99
+ *
100
+ * A resume suppresses the structural `parent` / `forkOf` / `regenerates`
101
+ * headers — the caller passes them only for a fresh run-start. `reason` is
102
+ * stamped only on run-end.
103
+ * @param opts - The lifecycle header values to include.
104
+ * @param opts.runId - The run's id.
105
+ * @param opts.runClientId - ClientId of the run initiator (empty string when unknown).
106
+ * @param opts.parent - Structural parent codec-message-id (fresh run-start only).
107
+ * @param opts.forkOf - Forked user-prompt codec-message-id (fresh run-start only).
108
+ * @param opts.regenerates - Regenerated assistant codec-message-id (fresh run-start only).
109
+ * @param opts.invocationId - Agent-minted invocation id; carried on every lifecycle event.
110
+ * @param opts.inputClientId - ClientId of the triggering input event.
111
+ * @param opts.inputCodecMessageId - Codec-message-id of the triggering input event.
112
+ * @param opts.reason - Terminal reason; stamped on run-end only.
113
+ * @param opts.errorCode - Numeric error code stamped as `error-code` on
114
+ * run-end. Set only when the run ended in error and the agent supplied an
115
+ * error to surface; gives codec-agnostic consumers a baseline failure detail.
116
+ * @param opts.errorMessage - Error message stamped as `error-message` on
117
+ * run-end. Paired with `errorCode`; set under the same condition.
118
+ * @returns A headers record with the lifecycle headers set.
119
+ */
120
+ export const buildLifecycleHeaders = (opts: {
121
+ runId: string;
122
+ runClientId: string;
123
+ parent?: string;
124
+ forkOf?: string;
125
+ regenerates?: string;
126
+ invocationId?: string;
127
+ inputClientId?: string;
128
+ inputCodecMessageId?: string;
129
+ reason?: RunEndReason;
130
+ errorCode?: number;
131
+ errorMessage?: string;
132
+ }): Record<string, string> => {
133
+ const h: Record<string, string> = {
134
+ [HEADER_RUN_ID]: opts.runId,
135
+ [HEADER_RUN_CLIENT_ID]: opts.runClientId,
136
+ };
137
+ if (opts.reason !== undefined) h[HEADER_RUN_REASON] = opts.reason;
138
+ if (opts.parent !== undefined) h[HEADER_PARENT] = opts.parent;
139
+ if (opts.forkOf !== undefined) h[HEADER_FORK_OF] = opts.forkOf;
140
+ if (opts.regenerates !== undefined) h[HEADER_MSG_REGENERATE] = opts.regenerates;
141
+ if (opts.invocationId !== undefined) h[HEADER_INVOCATION_ID] = opts.invocationId;
142
+ if (opts.inputClientId !== undefined) h[HEADER_INPUT_CLIENT_ID] = opts.inputClientId;
143
+ if (opts.inputCodecMessageId !== undefined) h[HEADER_INPUT_CODEC_MESSAGE_ID] = opts.inputCodecMessageId;
144
+ if (opts.errorCode !== undefined) h[HEADER_ERROR_CODE] = String(opts.errorCode);
145
+ if (opts.errorMessage !== undefined) h[HEADER_ERROR_MESSAGE] = opts.errorMessage;
49
146
  return h;
50
147
  };
148
+
149
+ /** The four run-lifecycle Ably message names. */
150
+ type RunLifecycleName =
151
+ | typeof EVENT_RUN_START
152
+ | typeof EVENT_RUN_SUSPEND
153
+ | typeof EVENT_RUN_RESUME
154
+ | typeof EVENT_RUN_END;
155
+
156
+ /**
157
+ * Whether an Ably message `name` is one of the run-lifecycle event names
158
+ * (run-start / run-suspend / run-resume / run-end). Single source of truth for
159
+ * the classification both decode loops and the agent's history scan use to
160
+ * route lifecycle wires away from the codec decoder. Narrows `name` to a
161
+ * lifecycle name so callers can pass it straight to {@link parseRunLifecycle}.
162
+ * @param name - The inbound Ably message `name`, or undefined.
163
+ * @returns True when `name` is a run-lifecycle event name.
164
+ */
165
+ export const isRunLifecycleName = (name: string | undefined): name is RunLifecycleName =>
166
+ name === EVENT_RUN_START || name === EVENT_RUN_SUSPEND || name === EVENT_RUN_RESUME || name === EVENT_RUN_END;
167
+
168
+ /**
169
+ * Reconstruct the terminal `Ably.ErrorInfo` for a run that ended in error, from
170
+ * its run-end transport headers. Reads the `error-code` / `error-message`
171
+ * headers the agent stamps (see {@link buildLifecycleHeaders}); falls back to a
172
+ * generic code/message when a run ended in error without detail. Single source
173
+ * of truth for the header→ErrorInfo derivation, shared by the client session's
174
+ * `on('error')` emit and the Tree's `RunInfo.error`.
175
+ * @param headers - Transport headers from the inbound run-end message.
176
+ * @returns The reconstructed terminal error.
177
+ */
178
+ export const buildRunEndError = (headers: Record<string, string>): Ably.ErrorInfo => {
179
+ const codeRaw = headers[HEADER_ERROR_CODE];
180
+ const parsedCode = codeRaw === undefined ? Number.NaN : Number(codeRaw);
181
+ const code = Number.isFinite(parsedCode) ? parsedCode : ErrorCode.SessionSubscriptionError;
182
+ const message = headers[HEADER_ERROR_MESSAGE] ?? 'agent reported an error';
183
+ // 5-digit codes encode their HTTP status in the leading 3 digits; otherwise 500.
184
+ const statusCode = code >= 10000 && code < 60000 ? Math.floor(code / 100) : 500;
185
+ return new Ably.ErrorInfo(message, code, statusCode);
186
+ };
187
+
188
+ /**
189
+ * Parse an inbound run-lifecycle Ably message into a {@link RunLifecycleEvent}.
190
+ *
191
+ * Single source of truth for turning the wire run-lifecycle message `name`,
192
+ * transport headers, and channel serial into the structured lifecycle event
193
+ * the Tree consumes. Used by the client decode loop (live) and the View's
194
+ * history replay so both build the event identically.
195
+ * @param name - The inbound Ably message `name`.
196
+ * @param headers - Transport headers from the inbound Ably message.
197
+ * @param serial - Ably channel serial of the message, or `undefined` for an
198
+ * optimistic local event. Stamped onto the returned event.
199
+ * @param timestamp - Ably server timestamp (epoch ms) of the message, or
200
+ * `undefined` for an optimistic local event. Stamped onto the returned
201
+ * event; drives the Tree's event-log retention clock.
202
+ * @returns The lifecycle event, or `undefined` when `name` is not a
203
+ * run-lifecycle event name or the message carries no `run-id`.
204
+ */
205
+ export const parseRunLifecycle = (
206
+ name: string,
207
+ headers: Record<string, string>,
208
+ serial: string | undefined,
209
+ timestamp: number | undefined,
210
+ ): RunLifecycleEvent | undefined => {
211
+ const runId = headers[HEADER_RUN_ID];
212
+ if (!runId) return undefined;
213
+
214
+ const clientId = headers[HEADER_RUN_CLIENT_ID] ?? '';
215
+ const stamped = timestamp === undefined ? {} : { timestamp };
216
+
217
+ if (name === EVENT_RUN_START) {
218
+ const parent = headers[HEADER_PARENT];
219
+ const forkOf = headers[HEADER_FORK_OF];
220
+ const regenerates = headers[HEADER_MSG_REGENERATE];
221
+ return {
222
+ type: 'start',
223
+ runId,
224
+ clientId,
225
+ serial,
226
+ invocationId: headers[HEADER_INVOCATION_ID] ?? '',
227
+ ...stamped,
228
+ ...(parent !== undefined && { parent }),
229
+ ...(forkOf !== undefined && { forkOf }),
230
+ ...(regenerates !== undefined && { regenerates }),
231
+ };
232
+ }
233
+
234
+ if (name === EVENT_RUN_SUSPEND) {
235
+ return { type: 'suspend', runId, clientId, serial, invocationId: headers[HEADER_INVOCATION_ID] ?? '', ...stamped };
236
+ }
237
+
238
+ if (name === EVENT_RUN_RESUME) {
239
+ return { type: 'resume', runId, clientId, serial, invocationId: headers[HEADER_INVOCATION_ID] ?? '', ...stamped };
240
+ }
241
+
242
+ if (name === EVENT_RUN_END) {
243
+ // CAST: agent always writes a valid RunEndReason; default to 'complete' for robustness.
244
+ const reason = (headers[HEADER_RUN_REASON] ?? 'complete') as RunEndReason;
245
+ const invocationId = headers[HEADER_INVOCATION_ID] ?? '';
246
+ if (reason === 'error') {
247
+ return {
248
+ type: 'end',
249
+ runId,
250
+ clientId,
251
+ serial,
252
+ invocationId,
253
+ reason,
254
+ ...stamped,
255
+ error: buildRunEndError(headers),
256
+ };
257
+ }
258
+ return { type: 'end', runId, clientId, serial, invocationId, reason, ...stamped };
259
+ }
260
+
261
+ return undefined;
262
+ };
@@ -1,42 +1,42 @@
1
1
  // Shared types
2
2
  export type {
3
- ActiveTurn,
4
- AddMessageOptions,
5
- AddMessagesResult,
6
- CancelFilter,
3
+ ActiveRun,
4
+ AgentSession,
5
+ AgentSessionOptions,
6
+ BranchSelection,
7
7
  CancelRequest,
8
- ClientTransport,
9
- ClientTransportOptions,
10
- CloseOptions,
11
- EventsNode,
8
+ ClientSession,
9
+ ClientSessionOptions,
10
+ ConversationNode,
11
+ InputNode,
12
+ LoadConversationOptions,
12
13
  MessageNode,
13
- NewTurnOptions,
14
+ OutputEvent,
15
+ PipeOptions,
16
+ Run,
17
+ RunEndParams,
18
+ RunEndReason,
19
+ RunInfo,
20
+ RunLifecycleEvent,
21
+ RunNode,
22
+ RunNodeState,
23
+ RunRuntime,
24
+ RunView,
14
25
  SendOptions,
15
- ServerTransport,
16
- ServerTransportOptions,
17
- StreamResponseOptions,
18
26
  StreamResult,
19
27
  Tree,
20
- Turn,
21
- TurnEndReason,
22
- TurnLifecycleEvent,
23
28
  View,
24
29
  } from './types.js';
25
30
 
26
- // Deprecated aliases — intentional re-export of deprecated types for backwards compatibility.
27
- // eslint-disable-next-line @typescript-eslint/no-deprecated
28
- export type { EventNode } from './types.js';
29
- // eslint-disable-next-line @typescript-eslint/no-deprecated
30
- export type { TreeNode } from './types.js';
31
+ // Invocation
32
+ export type { InvocationData } from './invocation.js';
33
+ export { Invocation } from './invocation.js';
31
34
 
32
- // Internal tree interface (consumed by View implementations)
33
- export type { TreeInternal } from './tree.js';
35
+ // Agent session
36
+ export { createAgentSession } from './agent-session.js';
34
37
 
35
- // Server transport
36
- export { createServerTransport } from './server-transport.js';
37
-
38
- // Client transport
39
- export { createClientTransport } from './client-transport.js';
38
+ // Client session
39
+ export { createClientSession } from './client-session.js';
40
40
 
41
41
  // Header builder
42
42
  export { buildTransportHeaders } from './headers.js';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Helpers for FIFO-bounded `Map`s — Maps used as capacity-limited buffers that
3
+ * must evict their oldest entry when full.
4
+ */
5
+
6
+ /**
7
+ * Make room for a new key in a FIFO-bounded map: if `map` is at (or over)
8
+ * `limit` and does not already contain `key`, evict the oldest entry (insertion
9
+ * order) and return its key so the caller can log the eviction. Returns
10
+ * `undefined` when nothing was evicted (the key already exists, the map is
11
+ * below the limit, or it is empty).
12
+ *
13
+ * The caller performs the actual set/append afterwards — this only frees a
14
+ * slot — so it works for maps whose values are replaced and for maps whose
15
+ * values are appended-to lists.
16
+ * @param map - The bounded map to evict from.
17
+ * @param key - The key about to be added; an existing key never evicts.
18
+ * @param limit - The maximum number of entries the map may hold.
19
+ * @returns The evicted key, or `undefined` if nothing was evicted.
20
+ */
21
+ export const evictOldestIfFull = <K, V>(map: Map<K, V>, key: K, limit: number): K | undefined => {
22
+ if (map.has(key) || map.size < limit) return undefined;
23
+ const oldest = map.keys().next().value;
24
+ if (oldest === undefined) return undefined;
25
+ map.delete(oldest);
26
+ return oldest;
27
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Invocation — value object wrapping the JSON body that a client sends to
3
+ * an agent's HTTP endpoint to start a run.
4
+ *
5
+ * The data shape is the wire contract; the {@link Invocation} class is a
6
+ * runtime view of that data with the same fields. {@link Invocation.fromJSON}
7
+ * is the entry point used by agent handlers:
8
+ *
9
+ * ```ts
10
+ * const data = (await req.json()) as InvocationData;
11
+ * const invocation = Invocation.fromJSON(data);
12
+ * const run = session.createRun(invocation, { signal: req.signal });
13
+ * await run.start();
14
+ * await run.loadConversation(); // walk channel history into the session tree
15
+ * const messages = run.messages;
16
+ * ```
17
+ *
18
+ * The body carries only what the agent needs out-of-band before the channel
19
+ * is observable: the session/channel name and the `inputEventId` that triggered
20
+ * the invocation. The agent mints the `invocationId` itself (one per HTTP
21
+ * request) and returns it on the HTTP response, so it is not a body field. Run
22
+ * identity also lives on the channel: the agent mints the `runId` for a fresh
23
+ * run and reads the existing `runId` off the triggering input event for a
24
+ * continuation — so the body carries no run-id either. Per-message metadata —
25
+ * `clientId`, `parent`, `forkOf`, continuation status — likewise lives on the
26
+ * channel and is resolved by the agent from the triggering input event, not
27
+ * from the body. The `inputClientId` the agent re-stamps on its own publishes
28
+ * comes from the publisher's Ably `clientId` on the matched input event, not
29
+ * from a body field.
30
+ */
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Wire shape
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * Wire shape of a single invocation — the JSON body sent from the client
38
+ * transport's HTTP POST to the agent endpoint.
39
+ */
40
+ export interface InvocationData {
41
+ /**
42
+ * Identifier for the specific input event on the channel that triggered
43
+ * this invocation. The agent locates the event via the `event-id`
44
+ * header. Its wire headers carry the run-id for a continuation (absent for
45
+ * a fresh run), so run identity is resolved from the channel, not the body.
46
+ */
47
+ inputEventId: string;
48
+ /** Logical name of the session (chat) — used as the Ably channel name. */
49
+ sessionName: string;
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Runtime view
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Runtime view of an {@link InvocationData}. Constructed via
58
+ * {@link Invocation.fromJSON}. Read-only; carries no behaviour beyond
59
+ * exposing its fields.
60
+ */
61
+ // Spec: AIT-ST13
62
+ export class Invocation {
63
+ /**
64
+ * Identifier for the specific input event on the channel that triggered
65
+ * this invocation. Run identity is resolved from that event's wire headers
66
+ * (or minted), not from the body.
67
+ */
68
+ readonly inputEventId: string;
69
+ /** Logical name of the session (chat). Used as the Ably channel name. */
70
+ readonly sessionName: string;
71
+
72
+ private constructor(data: InvocationData) {
73
+ this.inputEventId = data.inputEventId;
74
+ this.sessionName = data.sessionName;
75
+ }
76
+
77
+ /**
78
+ * Build an Invocation from its JSON wire shape.
79
+ * @param data - Parsed JSON body matching {@link InvocationData}.
80
+ * @returns A new Invocation exposing the same fields.
81
+ */
82
+ static fromJSON(data: InvocationData): Invocation {
83
+ return new Invocation(data);
84
+ }
85
+
86
+ /**
87
+ * Serialise this invocation to its JSON wire shape — the body a client
88
+ * POSTs to the agent's endpoint to wake a run. Round-trips through
89
+ * {@link Invocation.fromJSON}.
90
+ * @returns The {@link InvocationData} carrying this invocation's identity.
91
+ */
92
+ toJSON(): InvocationData {
93
+ return {
94
+ inputEventId: this.inputEventId,
95
+ sessionName: this.sessionName,
96
+ };
97
+ }
98
+ }