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