@ably/ai-transport 0.0.1 → 0.2.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 (167) hide show
  1. package/README.md +114 -116
  2. package/dist/ably-ai-transport.js +1743 -961
  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 +117 -39
  7. package/dist/core/agent.d.ts +29 -0
  8. package/dist/core/codec/decoder.d.ts +20 -23
  9. package/dist/core/codec/encoder.d.ts +11 -8
  10. package/dist/core/codec/index.d.ts +1 -2
  11. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  12. package/dist/core/codec/types.d.ts +410 -101
  13. package/dist/core/transport/agent-session.d.ts +10 -0
  14. package/dist/core/transport/branch-chain.d.ts +43 -0
  15. package/dist/core/transport/client-session.d.ts +13 -0
  16. package/dist/core/transport/decode-fold.d.ts +47 -0
  17. package/dist/core/transport/headers.d.ts +97 -17
  18. package/dist/core/transport/index.d.ts +5 -3
  19. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  20. package/dist/core/transport/invocation.d.ts +74 -0
  21. package/dist/core/transport/load-conversation.d.ts +128 -0
  22. package/dist/core/transport/load-history.d.ts +39 -0
  23. package/dist/core/transport/pipe-stream.d.ts +9 -8
  24. package/dist/core/transport/run-manager.d.ts +78 -0
  25. package/dist/core/transport/tree.d.ts +435 -0
  26. package/dist/core/transport/types/agent.d.ts +353 -0
  27. package/dist/core/transport/types/client.d.ts +168 -0
  28. package/dist/core/transport/types/shared.d.ts +24 -0
  29. package/dist/core/transport/types/tree.d.ts +315 -0
  30. package/dist/core/transport/types/view.d.ts +222 -0
  31. package/dist/core/transport/types.d.ts +13 -402
  32. package/dist/core/transport/view.d.ts +354 -0
  33. package/dist/errors.d.ts +37 -9
  34. package/dist/index.d.ts +6 -6
  35. package/dist/logger.d.ts +12 -0
  36. package/dist/react/ably-ai-transport-react.js +1164 -645
  37. package/dist/react/ably-ai-transport-react.js.map +1 -1
  38. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  39. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  40. package/dist/react/contexts/client-session-context.d.ts +36 -0
  41. package/dist/react/contexts/client-session-provider.d.ts +53 -0
  42. package/dist/react/create-session-hooks.d.ts +116 -0
  43. package/dist/react/index.d.ts +16 -10
  44. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  45. package/dist/react/use-ably-messages.d.ts +20 -11
  46. package/dist/react/use-client-session.d.ts +81 -0
  47. package/dist/react/use-create-view.d.ts +23 -0
  48. package/dist/react/use-tree.d.ts +35 -0
  49. package/dist/react/use-view.d.ts +110 -0
  50. package/dist/utils.d.ts +32 -23
  51. package/dist/vercel/ably-ai-transport-vercel.js +2748 -1625
  52. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  53. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  54. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  55. package/dist/vercel/codec/decoder.d.ts +5 -18
  56. package/dist/vercel/codec/encoder.d.ts +6 -36
  57. package/dist/vercel/codec/events.d.ts +51 -0
  58. package/dist/vercel/codec/index.d.ts +24 -12
  59. package/dist/vercel/codec/reducer.d.ts +144 -0
  60. package/dist/vercel/codec/tool-transitions.d.ts +50 -0
  61. package/dist/vercel/index.d.ts +4 -2
  62. package/dist/vercel/react/ably-ai-transport-vercel-react.js +10298 -1410
  63. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  64. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +70 -1
  65. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  66. package/dist/vercel/react/contexts/chat-transport-context.d.ts +33 -0
  67. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +96 -0
  68. package/dist/vercel/react/index.d.ts +4 -0
  69. package/dist/vercel/react/use-chat-transport.d.ts +66 -21
  70. package/dist/vercel/react/use-message-sync.d.ts +31 -12
  71. package/dist/vercel/run-end-reason.d.ts +29 -0
  72. package/dist/vercel/transport/chat-transport.d.ts +71 -30
  73. package/dist/vercel/transport/index.d.ts +25 -18
  74. package/dist/vercel/transport/run-output-stream.d.ts +56 -0
  75. package/dist/version.d.ts +2 -0
  76. package/package.json +47 -34
  77. package/src/constants.ts +126 -47
  78. package/src/core/agent.ts +68 -0
  79. package/src/core/codec/decoder.ts +71 -98
  80. package/src/core/codec/encoder.ts +115 -58
  81. package/src/core/codec/index.ts +13 -6
  82. package/src/core/codec/lifecycle-tracker.ts +10 -9
  83. package/src/core/codec/types.ts +438 -106
  84. package/src/core/transport/agent-session.ts +1344 -0
  85. package/src/core/transport/branch-chain.ts +58 -0
  86. package/src/core/transport/client-session.ts +775 -0
  87. package/src/core/transport/decode-fold.ts +91 -0
  88. package/src/core/transport/headers.ts +182 -19
  89. package/src/core/transport/index.ts +29 -22
  90. package/src/core/transport/internal/bounded-map.ts +27 -0
  91. package/src/core/transport/invocation.ts +98 -0
  92. package/src/core/transport/load-conversation.ts +355 -0
  93. package/src/core/transport/load-history.ts +269 -0
  94. package/src/core/transport/pipe-stream.ts +58 -40
  95. package/src/core/transport/run-manager.ts +249 -0
  96. package/src/core/transport/tree.ts +1167 -0
  97. package/src/core/transport/types/agent.ts +407 -0
  98. package/src/core/transport/types/client.ts +211 -0
  99. package/src/core/transport/types/shared.ts +27 -0
  100. package/src/core/transport/types/tree.ts +344 -0
  101. package/src/core/transport/types/view.ts +259 -0
  102. package/src/core/transport/types.ts +13 -527
  103. package/src/core/transport/view.ts +1271 -0
  104. package/src/errors.ts +42 -9
  105. package/src/event-emitter.ts +3 -2
  106. package/src/index.ts +55 -39
  107. package/src/logger.ts +14 -1
  108. package/src/react/contexts/client-session-context.ts +41 -0
  109. package/src/react/contexts/client-session-provider.tsx +186 -0
  110. package/src/react/create-session-hooks.ts +141 -0
  111. package/src/react/index.ts +27 -10
  112. package/src/react/internal/use-resolved-session.ts +63 -0
  113. package/src/react/use-ably-messages.ts +47 -19
  114. package/src/react/use-client-session.ts +201 -0
  115. package/src/react/use-create-view.ts +72 -0
  116. package/src/react/use-tree.ts +84 -0
  117. package/src/react/use-view.ts +275 -0
  118. package/src/react/vite.config.ts +4 -1
  119. package/src/utils.ts +63 -45
  120. package/src/vercel/codec/decoder.ts +336 -255
  121. package/src/vercel/codec/encoder.ts +348 -196
  122. package/src/vercel/codec/events.ts +87 -0
  123. package/src/vercel/codec/index.ts +59 -14
  124. package/src/vercel/codec/reducer.ts +977 -0
  125. package/src/vercel/codec/tool-transitions.ts +122 -0
  126. package/src/vercel/index.ts +7 -3
  127. package/src/vercel/react/contexts/chat-transport-context.ts +41 -0
  128. package/src/vercel/react/contexts/chat-transport-provider.tsx +150 -0
  129. package/src/vercel/react/index.ts +13 -1
  130. package/src/vercel/react/use-chat-transport.ts +162 -42
  131. package/src/vercel/react/use-message-sync.ts +121 -22
  132. package/src/vercel/react/vite.config.ts +4 -2
  133. package/src/vercel/run-end-reason.ts +78 -0
  134. package/src/vercel/transport/chat-transport.ts +553 -113
  135. package/src/vercel/transport/index.ts +40 -28
  136. package/src/vercel/transport/run-output-stream.ts +170 -0
  137. package/src/version.ts +2 -0
  138. package/dist/core/transport/client-transport.d.ts +0 -10
  139. package/dist/core/transport/conversation-tree.d.ts +0 -9
  140. package/dist/core/transport/decode-history.d.ts +0 -41
  141. package/dist/core/transport/server-transport.d.ts +0 -7
  142. package/dist/core/transport/stream-router.d.ts +0 -19
  143. package/dist/core/transport/turn-manager.d.ts +0 -34
  144. package/dist/react/use-active-turns.d.ts +0 -8
  145. package/dist/react/use-client-transport.d.ts +0 -7
  146. package/dist/react/use-conversation-tree.d.ts +0 -20
  147. package/dist/react/use-edit.d.ts +0 -7
  148. package/dist/react/use-history.d.ts +0 -19
  149. package/dist/react/use-messages.d.ts +0 -7
  150. package/dist/react/use-regenerate.d.ts +0 -7
  151. package/dist/react/use-send.d.ts +0 -7
  152. package/dist/vercel/codec/accumulator.d.ts +0 -21
  153. package/src/core/transport/client-transport.ts +0 -959
  154. package/src/core/transport/conversation-tree.ts +0 -434
  155. package/src/core/transport/decode-history.ts +0 -337
  156. package/src/core/transport/server-transport.ts +0 -458
  157. package/src/core/transport/stream-router.ts +0 -118
  158. package/src/core/transport/turn-manager.ts +0 -147
  159. package/src/react/use-active-turns.ts +0 -61
  160. package/src/react/use-client-transport.ts +0 -37
  161. package/src/react/use-conversation-tree.ts +0 -71
  162. package/src/react/use-edit.ts +0 -24
  163. package/src/react/use-history.ts +0 -111
  164. package/src/react/use-messages.ts +0 -32
  165. package/src/react/use-regenerate.ts +0 -24
  166. package/src/react/use-send.ts +0 -25
  167. package/src/vercel/codec/accumulator.ts +0 -603
@@ -0,0 +1,344 @@
1
+ /** Conversation-tree types: nodes, the run-lifecycle event, output events, and the Tree contract. */
2
+
3
+ import type * as Ably from 'ably';
4
+
5
+ import type { CodecOutputEvent } from '../../codec/types.js';
6
+ import type { RunEndReason } from './shared.js';
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Run lifecycle events
10
+ // ---------------------------------------------------------------------------
11
+
12
+ /**
13
+ * Fields common to every {@link RunLifecycleEvent} arm.
14
+ */
15
+ interface RunLifecycleBase {
16
+ /** The run-id this lifecycle event concerns. */
17
+ runId: string;
18
+ /** The owning client's identity (Ably publisher `clientId`). */
19
+ clientId: string;
20
+ /**
21
+ * The invocation-id this lifecycle event was published under (wire
22
+ * `invocation-id`). Lets consumers correlate the run's lifecycle back to the
23
+ * invocation that drove it; on a run-start the Tree records it on the RunNode
24
+ * at first creation so an optimistic Run exposes the invocation synchronously.
25
+ * Empty string if the wire didn't carry an invocation-id.
26
+ */
27
+ invocationId: string;
28
+ }
29
+
30
+ /**
31
+ * A structured event describing a run starting, suspending, resuming, or
32
+ * ending. The `type` discriminator (`start` / `suspend` / `resume` / `end`) is
33
+ * the in-memory domain vocabulary and is intentionally distinct from the wire
34
+ * message names (`ai-run-start` / `ai-run-suspend` / `ai-run-resume` /
35
+ * `ai-run-end`) those events are decoded from.
36
+ */
37
+ export type RunLifecycleEvent =
38
+ | (RunLifecycleBase & {
39
+ type: 'start';
40
+ /**
41
+ * Ably channel serial of the run-start message, or `undefined` for an
42
+ * optimistic local event (no serial assigned yet). The Tree reads it to
43
+ * promote the Run's startSerial.
44
+ */
45
+ serial: string | undefined;
46
+ /** The codec-message-id of the parent message, if known. Omitted for root runs. */
47
+ parent?: string;
48
+ /**
49
+ * The codec-message-id of the user prompt being forked, when the run is an
50
+ * edit. Carried verbatim from the `fork-of` wire header.
51
+ */
52
+ forkOf?: string;
53
+ /**
54
+ * The codec-message-id of the assistant message this run regenerates, when
55
+ * the run is a regenerate continuation. Carried verbatim from the
56
+ * `msg-regenerate` wire header. The Tree treats regenerates
57
+ * as continuations (no `forkOf` at the Run level) — the View
58
+ * realises the replacement when materialising messages.
59
+ */
60
+ regenerates?: string;
61
+ })
62
+ | (RunLifecycleBase & {
63
+ type: 'suspend';
64
+ /**
65
+ * Ably channel serial of the run-suspend message, or `undefined` for an
66
+ * optimistic local event. The Tree reads it to set the Run's endSerial
67
+ * (a suspended run carries the serial at which it paused).
68
+ */
69
+ serial: string | undefined;
70
+ })
71
+ | (RunLifecycleBase & {
72
+ type: 'resume';
73
+ /**
74
+ * Ably channel serial of the run-resume message, or `undefined` for an
75
+ * optimistic local event. A resume re-enters an existing run; it does not
76
+ * promote the Run's startSerial (the original run-start owns that).
77
+ */
78
+ serial: string | undefined;
79
+ })
80
+ | (RunLifecycleBase & {
81
+ type: 'end';
82
+ /**
83
+ * Ably channel serial of the run-end message, or `undefined` for an
84
+ * optimistic local event. The Tree reads it to set the Run's endSerial.
85
+ */
86
+ serial: string | undefined;
87
+ /**
88
+ * Why the run ended — the terminal reason the Tree records as the
89
+ * RunNode's status: `complete`, `cancelled`, or `error`.
90
+ */
91
+ reason: RunEndReason;
92
+ });
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Conversation tree (branching history)
96
+ // ---------------------------------------------------------------------------
97
+
98
+ /** A node in the conversation tree, representing a single domain message. */
99
+ export interface MessageNode<TMessage> {
100
+ /** Discriminator — identifies this as a message node. */
101
+ kind: 'message';
102
+ /** The domain message. */
103
+ message: TMessage;
104
+ /** The codec-message-id of this node — primary key in the tree. */
105
+ codecMessageId: string;
106
+ /** Parent node's codec-message-id (parent), or undefined for root messages. */
107
+ parentId: string | undefined;
108
+ /** The codec-message-id this node forks from (fork-of), or undefined if first version. */
109
+ forkOf: string | undefined;
110
+ /** The transport-tier headers (`extras.ai.transport`) for this message: the run/stream/identity/branching headers set and read by the transport layer. Codec-tier headers (`extras.ai.codec`) are not included. */
111
+ headers: Record<string, string>;
112
+ /**
113
+ * Ably serial for this message. Lexicographically comparable for total order.
114
+ * Used to sort siblings deterministically regardless of delivery/history order.
115
+ * Absent for optimistic messages (set when the server relay arrives).
116
+ */
117
+ serial: string | undefined;
118
+ }
119
+
120
+ /**
121
+ * A node in the conversation tree, representing a single Run.
122
+ *
123
+ * A RunNode is keyed by its agent-minted `runId`. Each RunNode owns a per-Run
124
+ * codec {@link TProjection} folded from every event published under that
125
+ * run-id; the SDK extracts the per-message list via {@link Codec.getMessages}
126
+ * when it needs to render messages for that Run.
127
+ *
128
+ * A regenerate is a sibling reply run: it shares its input-node parent
129
+ * ({@link parentCodecMessageId}) with the original reply, so same-parent reply
130
+ * runs form the regenerate group with no `forkOf` involved. (Editing a prompt
131
+ * instead produces a sibling {@link InputNode} via that node's `forkOf`.)
132
+ */
133
+ export interface RunNode<TProjection> {
134
+ /** Discriminator — identifies this as a reply-run node within {@link ConversationNode}. */
135
+ kind: 'run';
136
+ /** The run-id of this Run — primary key in the tree. */
137
+ runId: string;
138
+ /**
139
+ * The codec-message-id this Run is rooted at — the `parent` header of the
140
+ * first observed message (or the run-start lifecycle event's `parent`
141
+ * field). This is the run's input node's codec-message-id: the user prompt
142
+ * the agent replied to. The Tree uses it for kind-blind reachability and to
143
+ * build the input→reply edge. `undefined` for the root Run.
144
+ */
145
+ parentCodecMessageId: string | undefined;
146
+ /**
147
+ * The node key of the node this Run replaces, or `undefined` if this Run is
148
+ * not a fork. Populated when the wire's `fork-of` header points at a
149
+ * codec-message-id that has been observed; the Tree resolves it through the
150
+ * codec-message-id → node-key index. Reply-run regenerate siblings do not
151
+ * use this (they group by shared parent) — it carries an explicit fork link
152
+ * when the wire stamps one.
153
+ */
154
+ forkOf: string | undefined;
155
+ /**
156
+ * The codec-message-id this Run regenerates, or `undefined` for non-regenerate
157
+ * Runs. Populated from the wire's `msg-regenerate` header (and the lifecycle
158
+ * event's `regenerates` field) verbatim — the Tree does not resolve it to a
159
+ * node key because the anchor is a message position, not a node.
160
+ *
161
+ * A regenerate run parents at the SAME input node as the reply it
162
+ * regenerates, so it joins that input's reply runs as a same-parent sibling;
163
+ * the message named by `regeneratesCodecMessageId` is replaced by this Run's
164
+ * content when the View materialises the chain into messages (Spec: AIT-CT13d).
165
+ */
166
+ regeneratesCodecMessageId: string | undefined;
167
+ /**
168
+ * Identity of the Ably client that started this Run, sourced from the
169
+ * `run-client-id` wire header (or the run-start lifecycle event's
170
+ * `clientId` field). Set once at Run creation and never updated; persists
171
+ * through the Run's lifecycle, including after `run-end`. Empty string if
172
+ * the wire didn't carry a client id.
173
+ */
174
+ clientId: string;
175
+ /**
176
+ * Run lifecycle status.
177
+ * - `'active'` — run-start observed, no terminal event yet.
178
+ * - `'suspended'` — run-suspend observed; the run is paused awaiting input
179
+ * and stays live (a continuation re-activates it). Not terminal.
180
+ * - {@link RunEndReason} — terminal state reflecting the run-end reason.
181
+ */
182
+ status: 'active' | 'suspended' | RunEndReason;
183
+ /** Per-Run codec projection. Folded by the Tree from every event published under this run-id. */
184
+ projection: TProjection;
185
+ /**
186
+ * The agent-minted invocationId observed for this Run (wire `invocation-id`).
187
+ * The agent mints it, so an optimistic Run starts with an empty id; it is
188
+ * adopted from the agent's `ai-run-start` (or set at creation when the Run is
189
+ * first seen from a wire event carrying one) and never reassigned thereafter.
190
+ * Empty string until run-start arrives, or if the wire didn't carry an
191
+ * invocation-id.
192
+ */
193
+ invocationId: string;
194
+ /** Ably serial of the first observed message tagged with this run-id. Absent for optimistic Runs. */
195
+ startSerial: string | undefined;
196
+ /** Ably serial of the run-end lifecycle event, if observed. */
197
+ endSerial: string | undefined;
198
+ }
199
+
200
+ /**
201
+ * A node in the conversation tree, representing a single user input (prompt).
202
+ *
203
+ * An input node owns the user's prompt for one turn. It is keyed by the
204
+ * client-owned `codec-message-id` and never carries a run-id — the agent mints
205
+ * the run-id for the reply, which becomes a separate {@link RunNode} parented to
206
+ * this input node. An edit of a prompt is a sibling input node (via `forkOf`).
207
+ *
208
+ * Like a {@link RunNode}, it carries its own per-input codec {@link TProjection}
209
+ * folded from the input event(s) published under its codec-message-id; the SDK
210
+ * extracts the per-message list via {@link Codec.getMessages} when rendering.
211
+ */
212
+ export interface InputNode<TProjection> {
213
+ /** Discriminator — identifies this as an input node within {@link ConversationNode}. */
214
+ kind: 'input';
215
+ /** The codec-message-id of this input — primary key in the tree. */
216
+ codecMessageId: string;
217
+ /**
218
+ * The codec-message-id of the node this input hangs off (its structural
219
+ * parent — the immediately preceding reply run on this chain), or `undefined`
220
+ * for the first input in a conversation. Used for kind-blind tree
221
+ * reachability alongside {@link RunNode.parentCodecMessageId}.
222
+ */
223
+ parentCodecMessageId: string | undefined;
224
+ /**
225
+ * The codec-message-id this input forks from when it is an edit of an earlier
226
+ * prompt, or `undefined` if it is the first version. Sibling input nodes
227
+ * (alternate prompts) share the same `forkOf` anchor.
228
+ */
229
+ forkOf: string | undefined;
230
+ /** Per-input codec projection. Folded by the Tree from every input event published under this codec-message-id. */
231
+ projection: TProjection;
232
+ /** Ably serial of the first observed message for this input. Absent for optimistic (locally-created) inputs. */
233
+ serial: string | undefined;
234
+ }
235
+
236
+ /**
237
+ * A node in the conversation tree: either a user {@link InputNode} or an agent
238
+ * {@link RunNode}. Narrow on `kind` (`'input'` vs `'run'`) before reading
239
+ * kind-specific fields.
240
+ */
241
+ export type ConversationNode<TProjection> = InputNode<TProjection> | RunNode<TProjection>;
242
+
243
+ /**
244
+ * Payload of the Tree's `output` event: the decoded agent outputs folded
245
+ * for a Run from a single inbound message, carrying the routing metadata a
246
+ * consumer needs to attribute or stream them.
247
+ */
248
+ export interface OutputEvent<TOutput extends CodecOutputEvent> {
249
+ /**
250
+ * The runId the outputs were folded into, or `undefined` when the fold was
251
+ * into a user input node (which carries no run-id — the agent mints run-ids).
252
+ * An input fold always has empty {@link events}; consumers route by
253
+ * {@link inputCodecMessageId}, not this.
254
+ */
255
+ runId: string | undefined;
256
+ /**
257
+ * The codec-message-id of the input event that triggered this run — the
258
+ * agent's `input-codec-message-id` header. This is the stable key the client
259
+ * owns from send time (before the agent mints the runId), so the output
260
+ * stream can attribute outputs to the request that produced them. Distinct
261
+ * from {@link runId}: causal (which input produced these outputs) rather than
262
+ * the run's own identity. `undefined` when the carrying message had no such
263
+ * header — e.g. a purely-optimistic local fold with no wire echo yet.
264
+ */
265
+ inputCodecMessageId: string | undefined;
266
+ /**
267
+ * The `codec-message-id` the outputs were published under, or `undefined`
268
+ * when the message carried none.
269
+ */
270
+ codecMessageId: string | undefined;
271
+ /**
272
+ * Ably channel serial of the message that carried the outputs, or
273
+ * `undefined` for an optimistic local fold (no serial assigned yet).
274
+ */
275
+ serial: string | undefined;
276
+ /**
277
+ * The decoded agent outputs from this message, in wire order. Empty when
278
+ * the folded message carried only inputs (e.g. an optimistic user
279
+ * message); the event still fires so consumers can observe that the Run's
280
+ * projection changed.
281
+ */
282
+ events: TOutput[];
283
+ }
284
+
285
+ /**
286
+ * Materializes a branching conversation tree from a flat oplog of Ably
287
+ * messages. Each turn is two nodes: a user {@link InputNode} keyed by its
288
+ * client-owned codec-message-id and an agent {@link RunNode} keyed by the
289
+ * agent-minted run-id, parented to the input node.
290
+ *
291
+ * The Tree owns the complete conversation state across every observed node.
292
+ * Each node holds a per-node codec {@link TProjection} which the Tree folds
293
+ * from inbound events. The View walks the parent chain to extract a flat
294
+ * message list for rendering.
295
+ */
296
+ export interface Tree<TOutput extends CodecOutputEvent, TProjection> {
297
+ /** Get a Run by runId, or undefined if not found. */
298
+ getRunNode(runId: string): RunNode<TProjection> | undefined;
299
+
300
+ /**
301
+ * Get the node that owns a given codec-message-id (via the Tree's
302
+ * codecMessageId index), or undefined if the codec-message-id hasn't been
303
+ * observed. The result is a {@link ConversationNode} union: narrow on `kind`
304
+ * (`'input'` vs `'run'`) before reading kind-specific fields.
305
+ */
306
+ getNodeByCodecMessageId(codecMessageId: string): ConversationNode<TProjection> | undefined;
307
+
308
+ /**
309
+ * Get the sibling group (both kinds) the node keyed by `key` belongs to:
310
+ * edit versions for an input node (forkOf-linked, same parent), regenerate
311
+ * runs for a reply run (same input-node parent). Ordered oldest-first by
312
+ * serial; a single-element array when the node has no siblings. Empty when
313
+ * `key` is unknown. Narrow each node on `kind` before reading kind-specific
314
+ * fields.
315
+ * @param key - The node key ({@link RunNode.runId} or {@link InputNode.codecMessageId}).
316
+ * @returns The ordered sibling nodes.
317
+ */
318
+ getSiblingNodes(key: string): ConversationNode<TProjection>[];
319
+
320
+ // --- Events ---
321
+
322
+ /**
323
+ * Subscribe to tree structural changes (Run insert, delete, sort-reorder,
324
+ * startSerial promotion, run-start metadata backfill). Does NOT fire on
325
+ * content-only folds (streaming chunks) or on run-end status changes —
326
+ * those flow through `output` and `run` respectively.
327
+ */
328
+ on(event: 'update', handler: () => void): () => void;
329
+
330
+ /** Subscribe to raw Ably messages arriving on the channel. */
331
+ on(event: 'ably-message', handler: (msg: Ably.InboundMessage) => void): () => void;
332
+
333
+ /** Subscribe to run lifecycle events (start, suspend, resume, and end). */
334
+ on(event: 'run', handler: (event: RunLifecycleEvent) => void): () => void;
335
+
336
+ /**
337
+ * Subscribe to decoded agent outputs as they are folded into a Run.
338
+ * Fires once per inbound message after its fold, carrying the message's
339
+ * output events plus routing metadata (runId, codec-message-id, serial).
340
+ * Fires with an empty `events` array for inputs-only folds so it can also
341
+ * serve as a projection-changed signal.
342
+ */
343
+ on(event: 'output', handler: (event: OutputEvent<TOutput>) => void): () => void;
344
+ }
@@ -0,0 +1,259 @@
1
+ /** View types: history pagination, branch selection, run info, and the windowed View contract. */
2
+
3
+ import type * as Ably from 'ably';
4
+
5
+ import type { CodecInputEvent, CodecMessage } from '../../codec/types.js';
6
+ import type { ActiveRun, SendOptions } from './client.js';
7
+ import type { RunEndReason } from './shared.js';
8
+ import type { RunLifecycleEvent } from './tree.js';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // History / pagination
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /** A page of raw history wires from the channel. Internal to View/decodeHistory. */
15
+ export interface HistoryPage {
16
+ /** Raw Ably messages that produced this page, in chronological order (oldest first). */
17
+ rawMessages: Ably.InboundMessage[];
18
+ /** Whether there are older pages available. */
19
+ hasNext(): boolean;
20
+ /** Fetch the next (older) page. Returns undefined if no more pages. */
21
+ next(): Promise<HistoryPage | undefined>;
22
+ }
23
+
24
+ /** Options for loading channel history. */
25
+ export interface LoadHistoryOptions {
26
+ /** Max messages per page. Default: 100. */
27
+ limit?: number;
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // View — windowed projection over the tree
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /**
35
+ * Projection-free, View-facing snapshot of a Run.
36
+ *
37
+ * Exposes the Run facts a UI consumer needs (`runId`, owner `clientId`,
38
+ * lifecycle `status`, `invocationId`) without leaking the codec's
39
+ * opaque per-Run projection or the Tree's structural fields. Callers
40
+ * that need the full Run record (parent / fork relationships, serials,
41
+ * projection) reach `session.tree.getRunNode(runId)` directly.
42
+ */
43
+ export interface RunInfo {
44
+ /** The Run's unique identifier. */
45
+ runId: string;
46
+ /**
47
+ * Identity of the Ably client that started this Run. Empty string
48
+ * when the wire didn't carry an owner client id.
49
+ */
50
+ clientId: string;
51
+ /**
52
+ * Run lifecycle status. `'active'` while the Run is streaming;
53
+ * `'suspended'` while it is paused awaiting input (still live, a
54
+ * continuation re-activates it); otherwise the {@link RunEndReason} the Run
55
+ * terminated with. Literal lifecycle vocabulary — UIs that want `'streaming'`
56
+ * rendering language translate at the component boundary.
57
+ */
58
+ status: 'active' | 'suspended' | RunEndReason;
59
+ /**
60
+ * The agent-minted `invocationId` observed for this Run, adopted from the
61
+ * wire `ai-run-start`. Stable across the Run's lifecycle once observed.
62
+ * Empty string until run-start arrives (the agent mints it, so an
63
+ * optimistic Run carries none) or when the wire didn't carry an
64
+ * invocation-id.
65
+ */
66
+ invocationId: string;
67
+ }
68
+
69
+ /**
70
+ * Bundle returned by {@link View.branchSelection} describing the
71
+ * sibling group anchored at a given codec-message-id.
72
+ *
73
+ * Total / always-defined — `view.branchSelection(id)` is safe to call
74
+ * for any message:
75
+ *
76
+ * - **Branch anchor (N ≥ 2 siblings)**: `siblings` carries every
77
+ * sibling Run's view of the anchor slot, `index` is the selected
78
+ * sibling's position, `selected === siblings[index]`,
79
+ * `hasSiblings: true`.
80
+ * - **Known non-anchor message**: `siblings = [thisMessage]`,
81
+ * `index: 0`, `selected: thisMessage`, `hasSiblings: false`.
82
+ * - **Unknown codec-message-id**: `siblings: []`, `index: 0`,
83
+ * `selected: undefined`, `hasSiblings: false`.
84
+ *
85
+ * Because `siblings` always contains the currently rendered message
86
+ * (for known ids), `siblings.length` is `1` for a plain bubble (not
87
+ * `0`) and the indexing space matches between read and write —
88
+ * passing `branch.index` back into {@link View.selectSibling} is a
89
+ * round-trip no-op.
90
+ */
91
+ export interface BranchSelection<TMessage> {
92
+ /** True when the codec-message-id is a branch anchor with more than one sibling. Equivalent to `siblings.length > 1`. */
93
+ hasSiblings: boolean;
94
+ /**
95
+ * The selected sibling and any alternatives, in tree-order (oldest
96
+ * first). Always contains the currently rendered message itself for
97
+ * known codec-message-ids; empty only when the id is unknown to the
98
+ * view.
99
+ */
100
+ siblings: TMessage[];
101
+ /** Index of the selected sibling within `siblings`. `0` when there is no real branching or the id is unknown. */
102
+ index: number;
103
+ /** Convenience reference to `siblings[index]`. `undefined` only when `siblings` is empty. */
104
+ selected: TMessage | undefined;
105
+ }
106
+
107
+ /**
108
+ * A paginated, branch-aware projection of the conversation tree.
109
+ *
110
+ * Returns only the visible portion of the selected branch. New live messages
111
+ * appear immediately; older messages are revealed progressively via
112
+ * `loadOlder()`. Events are scoped to the visible window — subscribers
113
+ * are only notified when the visible output changes.
114
+ */
115
+ export interface View<TInput extends CodecInputEvent, TMessage> {
116
+ /**
117
+ * The visible messages along the selected branch, each paired with its
118
+ * codec-message-id (see {@link CodecMessage}). Computed by walking the
119
+ * visible Run chain (newest to root) and concatenating each Run's
120
+ * `codec.getMessages(projection)` in chronological order.
121
+ *
122
+ * Correlate a message back to the transport — routing a continuation
123
+ * input, resolving a regenerate/edit target, looking up the owning Run —
124
+ * via its `codecMessageId`, which the SDK assigns and tracks
125
+ * independently of any identity the domain `message` may carry. Read the
126
+ * domain object from each entry's `message` field.
127
+ */
128
+ getMessages(): CodecMessage<TMessage>[];
129
+
130
+ /**
131
+ * Snapshot of the visible Runs along the selected branch, in
132
+ * chronological order — already filtered by this view's pagination
133
+ * window, branch selection, and regenerate substitution. The
134
+ * companion to {@link getMessages}: same scope, exposed as
135
+ * projection-free {@link RunInfo} so consumers can iterate Run
136
+ * identity (runId, clientId, status, invocationId) without touching
137
+ * the Tree.
138
+ */
139
+ runs(): RunInfo[];
140
+
141
+ /** Whether there are older Runs that can be loaded or revealed. */
142
+ hasOlder(): boolean;
143
+
144
+ /**
145
+ * Reveal older Runs. Loads from channel history if the tree doesn't have
146
+ * enough, then advances the pagination window by up to `limit` Runs.
147
+ * Emits 'update' when the visible list changes.
148
+ *
149
+ * The pagination unit is the **Run**, not the message. A single Run
150
+ * typically contributes more than one message to the flat list returned
151
+ * by {@link View.getMessages} (e.g. a user prompt + assistant reply
152
+ * pair). Revealing `limit` Runs may add 1..N messages each to the
153
+ * visible window.
154
+ * @param limit - Maximum number of older Runs to reveal. Defaults to 100.
155
+ */
156
+ loadOlder(limit?: number): Promise<void>;
157
+
158
+ // --- Run lookup ---
159
+
160
+ /**
161
+ * Look up the {@link RunInfo} for the Run that owns `codecMessageId`.
162
+ * For a user input node's codec-message-id, resolves to its
163
+ * currently-selected reply run. Returns `undefined` when the
164
+ * codec-message-id hasn't been observed by the view, or when it names
165
+ * an input node that has no reply run yet.
166
+ * @param codecMessageId - The codec-message-id to look up.
167
+ */
168
+ runOf(codecMessageId: string): RunInfo | undefined;
169
+
170
+ /**
171
+ * Direct lookup by Run id. Kept for symmetry with {@link runOf} so
172
+ * callers that hold a `runId` (e.g. cancel handlers) get a one-step
173
+ * lookup. Returns `undefined` when the Run hasn't been observed.
174
+ * @param runId - The Run id to look up.
175
+ */
176
+ run(runId: string): RunInfo | undefined;
177
+
178
+ // --- Branch navigation ---
179
+
180
+ /**
181
+ * Resolve the {@link BranchSelection} bundle anchored at
182
+ * `codecMessageId`. Always returns a safe object — see
183
+ * {@link BranchSelection} for the per-case shape.
184
+ *
185
+ * Per AITRFC-014, branch points are message-anchored: edit forks
186
+ * point at the user prompt's codec-message-id, regenerate forks
187
+ * point at the assistant message's codec-message-id.
188
+ * @param codecMessageId - The codec-message-id of the bubble being rendered.
189
+ */
190
+ branchSelection(codecMessageId: string): BranchSelection<TMessage>;
191
+
192
+ /**
193
+ * Select a sibling at the branch point anchored at
194
+ * `codecMessageId`. `index` is clamped to
195
+ * `[0, siblings.length - 1]`. Silent no-op when `codecMessageId`
196
+ * is not a branch anchor. Emits 'update' when the visible output
197
+ * changes.
198
+ * @param codecMessageId - The codec-message-id of the bubble being rendered.
199
+ * @param index - The index of the sibling to select.
200
+ */
201
+ selectSibling(codecMessageId: string, index: number): void;
202
+
203
+ // --- Write operations ---
204
+
205
+ /**
206
+ * Send one or more TInputs on the channel and fire a POST. Each TInput
207
+ * carries its own routing metadata (`parent` / `target` / `codecMessageId`)
208
+ * via the {@link CodecInputEvent} base; the SDK reads those fields
209
+ * directly without runtime classification.
210
+ *
211
+ * To send a fresh user message, wrap the domain message with
212
+ * {@link Codec.createUserMessage} and pass the result here, e.g.
213
+ * `view.send(codec.createUserMessage(message))`.
214
+ *
215
+ * Convention: a send containing at least one `UserMessage` is a
216
+ * fresh send (mints a new `runId`). A send containing only
217
+ * tool-resolution inputs is a continuation — pair with
218
+ * `options.runId` to extend a suspended run.
219
+ *
220
+ * The parent is auto-computed from this view's selected branch unless
221
+ * overridden. The HTTP POST is fire-and-forget — the returned stream is
222
+ * available immediately. If the POST fails, the error is surfaced via
223
+ * the session's `on("error")` and the stream is errored.
224
+ */
225
+ send(events: TInput | TInput[], options?: SendOptions): Promise<ActiveRun>;
226
+
227
+ /**
228
+ * Regenerate an assistant message. Mints a codec `Regenerate` input
229
+ * carrying `target` (the assistant codec-message-id being regenerated)
230
+ * and `parent` (the preceding user prompt's codec-message-id), both
231
+ * auto-computed from this view's branch — there are no new user inputs.
232
+ * The new reply run is not a `forkOf` fork; it continues the
233
+ * regenerated message's run, and the message-level replacement (the new
234
+ * assistant superseding the original) happens at projection-extraction
235
+ * time.
236
+ */
237
+ regenerate(messageId: string, options?: SendOptions): Promise<ActiveRun>;
238
+
239
+ /**
240
+ * Edit a user message. Creates a new run that forks the target message
241
+ * with replacement content. Automatically computes `forkOf` (the edited
242
+ * message) and `parent` from this view's branch.
243
+ */
244
+ edit(messageId: string, inputs: TInput | TInput[], options?: SendOptions): Promise<ActiveRun>;
245
+
246
+ // --- Observation ---
247
+
248
+ /** The visible message list changed (new visible node, branch switch, window shift). */
249
+ on(event: 'update', handler: () => void): () => void;
250
+
251
+ /** A raw Ably message arrived that corresponds to a visible node. */
252
+ on(event: 'ably-message', handler: (msg: Ably.InboundMessage) => void): () => void;
253
+
254
+ /** A run event occurred for a run with visible messages in the window. */
255
+ on(event: 'run', handler: (event: RunLifecycleEvent) => void): () => void;
256
+
257
+ /** Tear down the view — unsubscribe from tree events and clear internal state. */
258
+ close(): void;
259
+ }