@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,375 @@
1
+ import { Logger } from '../../../logger.js';
2
+ import { Codec, CodecInputEvent, CodecOutputEvent, WriteOptions } from '../../codec/types.js';
3
+ import { Invocation } from '../invocation.js';
4
+ import { CancelRequest, RunEndReason } from './shared.js';
5
+ import { MessageNode, Tree } from './tree.js';
6
+ /** Agent (server-side) session types: options, run runtime, and the Run / AgentSession contracts. */
7
+ import type * as Ably from 'ably';
8
+ import type * as AblyObjects from 'ably/liveobjects';
9
+ /** Options for creating an agent session. */
10
+ export interface AgentSessionOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
11
+ /**
12
+ * The Ably Realtime client. The caller owns its lifecycle —
13
+ * `session.close()` does not close the client.
14
+ */
15
+ client: Ably.Realtime;
16
+ /**
17
+ * The name of the channel to publish to. The session owns this channel —
18
+ * do not also resolve it elsewhere with conflicting channel options.
19
+ */
20
+ channelName: string;
21
+ /** The codec to use for encoding events and messages. */
22
+ codec: Codec<TInput, TOutput, TProjection, TMessage>;
23
+ /** Logger instance for diagnostic output. */
24
+ logger?: Logger;
25
+ /**
26
+ * Called with non-fatal session-level errors not scoped to any run.
27
+ * Examples: cancel listener subscription failure, channel attach errors,
28
+ * channel continuity loss (FAILED/SUSPENDED/DETACHED or re-attach with
29
+ * `resumed: false`).
30
+ */
31
+ onError?: (error: Ably.ErrorInfo) => void;
32
+ /**
33
+ * How long `Run.start()` will wait for the input event(s) tagged with
34
+ * the run's `invocationId` to arrive on the channel — across both the
35
+ * post-attach live subscription and the bounded history scan — before
36
+ * rejecting with `InputEventNotFound`. The rejection bubbles up to the
37
+ * developer's HTTP handler, which should surface it as a non-2xx response
38
+ * so the client's pending send fails.
39
+ * Default: 30000 (30 seconds).
40
+ */
41
+ inputEventLookupTimeoutMs?: number;
42
+ /**
43
+ * How far back in time `Run.start()` scans channel history for the
44
+ * triggering input event. Implements the lookback bound for the
45
+ * input-event scan — anything older than `Date.now() - inputEventLookbackMs`
46
+ * is treated as outside the lookup window.
47
+ *
48
+ * History is fetched with `untilAttach: true` so the scan composes
49
+ * with the live subscription by serial boundary — together they cover
50
+ * every message within `inputEventLookbackMs` of attach.
51
+ *
52
+ * Increase this for long-suspended runs whose continuation may arrive
53
+ * many minutes after the original publish. Decrease it for stricter
54
+ * recency.
55
+ *
56
+ * Default: 120000 (2 minutes).
57
+ */
58
+ inputEventLookbackMs?: number;
59
+ /**
60
+ * Extra Ably channel modes to request on the session's channel, on top of the
61
+ * modes AI Transport always needs. Pass `OBJECT_MODES` (or
62
+ * `['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH']`) to use Ably LiveObjects via
63
+ * {@link AgentSession.object}. Omit to attach with the default mode set.
64
+ *
65
+ * The session requests the union of these modes with the modes it always
66
+ * needs, so passing extra modes never drops the SDK's required modes. The
67
+ * connection's token/key capability must permit the requested operations,
68
+ * otherwise the server grants only the permitted subset.
69
+ */
70
+ channelModes?: readonly Ably.ChannelMode[];
71
+ }
72
+ /**
73
+ * Options for `Run.pipe` — per-operation overrides for the assistant message.
74
+ * @template TOutput - The codec output type carried by the stream; used by the `resolveWriteOptions` hook.
75
+ */
76
+ export interface PipeOptions<TOutput extends CodecOutputEvent> {
77
+ /** The codec-message-id of the immediately preceding message in this branch. */
78
+ parent?: string;
79
+ /** The codec-message-id of the message this response replaces (for regeneration). */
80
+ forkOf?: string;
81
+ /**
82
+ * Optional per-output hook invoked before each output is encoded. The
83
+ * returned {@link WriteOptions} (if any) override the stream's default
84
+ * headers and `codecMessageId` for that one encode call only; return `undefined`
85
+ * to use the stream defaults.
86
+ *
87
+ * Used to carry a subset of outputs within the stream to a different
88
+ * message (e.g. `tool-output-available` chunks that belong on a prior
89
+ * assistant message, stamped with `amend`). Must not be used
90
+ * for outputs that participate in the encoder's stream-append pipeline
91
+ * — streaming state (stream tracker, append ordering) is anchored to
92
+ * the stream's default identity and is not affected by per-output
93
+ * overrides.
94
+ * @param output - The output about to be encoded.
95
+ * @returns Per-write overrides for this output, or undefined.
96
+ */
97
+ resolveWriteOptions?: (output: TOutput) => WriteOptions | undefined;
98
+ }
99
+ /** The result of streaming a response through the encoder. */
100
+ export interface StreamResult {
101
+ /** Why the stream ended. */
102
+ reason: RunEndReason;
103
+ /**
104
+ * The error that caused the stream to fail, present when `reason` is
105
+ * `'error'`. This is the original error (e.g. from the LLM provider)
106
+ * preserved so the caller can inspect provider-specific fields. The
107
+ * run's `onError` callback also fires with a wrapped `Ably.ErrorInfo`
108
+ * (code `StreamError`) for standardized observability.
109
+ */
110
+ error?: Error;
111
+ }
112
+ /** Per-run runtime hooks, signal, and overrides supplied at `createRun()` time. */
113
+ export interface RunRuntime<TOutput extends CodecOutputEvent> {
114
+ /**
115
+ * Override the invocation id for this run. When omitted, the agent mints a
116
+ * fresh `crypto.randomUUID()` — the normal path (one per HTTP request).
117
+ * Supply a non-empty fixed value for deterministic ids in tests or
118
+ * in-process drivers; the empty string is not a valid override (it is the
119
+ * "unset" sentinel and does not fall through to minting).
120
+ */
121
+ invocationId?: string;
122
+ /**
123
+ * Override the run id for a FRESH run. When omitted, the agent mints a fresh
124
+ * `crypto.randomUUID()` — the normal path. A continuation IGNORES this: its
125
+ * run id is read from the triggering input event's wire headers, since a
126
+ * continuation re-enters a run that already exists. Supply a non-empty fixed
127
+ * value for deterministic ids in tests or in-process drivers; the empty
128
+ * string is not a valid override (it is the "unset" sentinel and does not
129
+ * fall through to minting).
130
+ */
131
+ runId?: string;
132
+ /**
133
+ * An external AbortSignal (typically the HTTP request's `req.signal`) that,
134
+ * when fired, cancels this run. This allows platform-level cancellation —
135
+ * request cancellation, serverless function timeout — to stop LLM generation
136
+ * and stream piping gracefully.
137
+ */
138
+ signal?: AbortSignal;
139
+ /**
140
+ * Called before each Ably message is published in this run.
141
+ * Mutate the Ably message in place to add custom headers under extras.ai.
142
+ */
143
+ onMessage?: (message: Ably.Message) => void;
144
+ /**
145
+ * Called when the run's stream is cancelled (by client cancel or server).
146
+ * Receives a write function to publish final outputs before the cancellation finalises.
147
+ */
148
+ onCancelled?: (write: (output: TOutput) => Promise<void>) => void | Promise<void>;
149
+ /**
150
+ * Called when a cancel message arrives matching this run.
151
+ * Return true to allow cancellation (fires `abortSignal`, stream cancels).
152
+ * Return false to reject (cancel ignored, stream continues).
153
+ * If not provided, all cancels are accepted.
154
+ */
155
+ onCancel?: (request: CancelRequest) => Promise<boolean>;
156
+ /**
157
+ * Called with non-fatal run-scoped errors that have no other delivery
158
+ * path. Fires in two scenarios:
159
+ * - Stream failures in `pipe` — the underlying error is also returned on
160
+ * `StreamResult.error`, but this callback delivers it wrapped as an
161
+ * `Ably.ErrorInfo` (code `StreamError`) for standardized observability.
162
+ * - Failures in the `onCancel` handler.
163
+ *
164
+ * Publish failures in `start` and `end`
165
+ * are not delivered here — those methods reject their returned promise
166
+ * with an `Ably.ErrorInfo`, and the caller should handle it at the await
167
+ * site. Run errors never render the session unusable, but the run may
168
+ * be in an inconsistent state; the caller should typically `end` it
169
+ * with reason `'error'`.
170
+ *
171
+ * Channel-wide events (e.g. continuity loss) are delivered via the
172
+ * session-level `onError` on {@link AgentSessionOptions}, not here.
173
+ */
174
+ onError?: (error: Ably.ErrorInfo) => void;
175
+ }
176
+ /**
177
+ * Read-only view exposed on a {@link Run} of the conversation messages
178
+ * this run was created with.
179
+ *
180
+ * TODO(AIT-771): when the agent rebuilds full conversation history from
181
+ * the channel, this should expose `RunNode[]`-shaped data to match the
182
+ * client side. Today it carries the flat input messages handed to the
183
+ * invocation.
184
+ */
185
+ export interface RunView<TMessage> {
186
+ /** Invocation input messages handed to this run; no branch awareness today. */
187
+ readonly messages: MessageNode<TMessage>[];
188
+ }
189
+ /** Options for {@link Run.loadConversation}. */
190
+ export interface LoadConversationOptions {
191
+ /**
192
+ * Maximum number of ANCESTOR reply RunNodes to walk back through the
193
+ * chain. Input nodes encountered alongside don't count toward the bound,
194
+ * and neither does the current run's own node (it is the conversation
195
+ * tail, not ancestor context). Default unbounded (walks to the
196
+ * conversation root).
197
+ *
198
+ * Set this to bound the LLM context window — `maxRuns: 5` returns the
199
+ * 5 most-recent prior reply runs and their associated input nodes
200
+ * (each bounded run's triggering input included, so the chain never
201
+ * starts assistant-first), in chronological order.
202
+ */
203
+ maxRuns?: number;
204
+ }
205
+ /**
206
+ * How a run terminates, passed to {@link Run.end}. Discriminated on `reason`:
207
+ * an `'error'` end may carry a terminal `error`; any other reason carries none.
208
+ */
209
+ export type RunEndParams = {
210
+ /** Why the run ended — any terminal reason other than `'error'`. */
211
+ reason: Exclude<RunEndReason, 'error'>;
212
+ } | {
213
+ /** The run ended in error. */
214
+ reason: 'error';
215
+ /**
216
+ * Optional terminal error to surface to clients. Omit to end in error
217
+ * without detail.
218
+ */
219
+ error?: Ably.ErrorInfo;
220
+ };
221
+ /**
222
+ * A server-side run with explicit lifecycle methods. Generic over the codec's
223
+ * output, projection, and message types. `TProjection` is retained for
224
+ * parameter symmetry with {@link AgentSession.createRun}; it does not
225
+ * appear in the Run's public surface today but keeps the type slot
226
+ * available for future per-Run projection accessors.
227
+ */
228
+ export interface Run<TOutput extends CodecOutputEvent, TProjection, TMessage> {
229
+ /** The run's unique identifier. */
230
+ readonly runId: string;
231
+ /**
232
+ * The invocation's unique identifier, minted by the agent when the run is
233
+ * created (one per HTTP request that invokes the agent). Readable
234
+ * synchronously — the application returns it on the HTTP response so the
235
+ * caller can observe it. The agent stamps it on every event it publishes
236
+ * for this invocation (run lifecycle + outputs).
237
+ */
238
+ readonly invocationId: string;
239
+ /** AbortSignal scoped to this run. Fires when a cancel event arrives for this runId. */
240
+ readonly abortSignal: AbortSignal;
241
+ /** Read-only view of the conversation messages associated with this run. */
242
+ readonly view: RunView<TMessage>;
243
+ /**
244
+ * The conversation messages this run should feed to the model.
245
+ *
246
+ * - Before {@link start} resolves: empty (no view contribution yet).
247
+ * - After {@link start}: the user-prompt messages looked up on the
248
+ * channel for this invocation.
249
+ * - After {@link loadConversation}: the full multi-turn conversation —
250
+ * all ancestor run messages followed by the current run's messages,
251
+ * oldest turn first. This is the value to pass to the LLM when the
252
+ * agent handles a reply in an ongoing conversation.
253
+ *
254
+ * Each access returns a fresh array — safe to mutate without affecting
255
+ * internal Run state.
256
+ */
257
+ readonly messages: TMessage[];
258
+ /**
259
+ * Publish the run's opening lifecycle event to the channel (run-start, or
260
+ * run-resume for a continuation). Must be called before any other run method
261
+ * (pipe, suspend, end).
262
+ */
263
+ start(): Promise<void>;
264
+ /**
265
+ * Pipe a ReadableStream through the encoder to the channel.
266
+ * Returns when the stream completes, is cancelled, or errors.
267
+ * Does NOT call end() — the caller must call end() after pipe returns.
268
+ */
269
+ pipe(stream: ReadableStream<TOutput>, options?: PipeOptions<TOutput>): Promise<StreamResult>;
270
+ /**
271
+ * Reconstruct the full multi-turn conversation by walking the ancestor
272
+ * run chain over the session's Tree, concatenating each ancestor's
273
+ * projection (oldest turn first) plus the current run's projection.
274
+ *
275
+ * Hydrates the Tree as needed from channel history if the chain from
276
+ * the run's structural-parent anchor isn't already fully present;
277
+ * subsequent reads of {@link Run.messages} re-walk the same Tree and
278
+ * reflect any further folds (e.g. live arrivals from concurrent runs).
279
+ * No cache: every call computes a fresh snapshot from the live Tree.
280
+ *
281
+ * Walks to the conversation root by default; bound the walk via the
282
+ * optional {@link LoadConversationOptions.maxRuns} cap. If channel
283
+ * retention has expired older turns, the walk stops at what is available.
284
+ * @param options - Optional walk bounds.
285
+ * @returns The conversation messages in chronological order, ready to pass to an LLM.
286
+ * @throws {Ably.ErrorInfo} `HistoryFetchFailed` — or the underlying Ably
287
+ * code when the failure carried one — when the history fetch fails after
288
+ * retries (the conversation is never silently truncated on fetch
289
+ * failure); `InvalidArgument` when the run's signal aborts.
290
+ */
291
+ loadConversation(options?: LoadConversationOptions): Promise<TMessage[]>;
292
+ /**
293
+ * Publish a run-suspend event to the channel and clean up, pausing the run
294
+ * without ending it. Call this instead of {@link Run.end} when the run is
295
+ * waiting on participant input (e.g. a client-side tool execution or a
296
+ * server-side tool approval): the run stays live and a later invocation can
297
+ * resume it under the same `runId`. Like {@link Run.end}, it is terminal for
298
+ * this Run instance — the resuming invocation builds a fresh Run. Must be
299
+ * called after {@link Run.start}; a no-op if the run has already ended or
300
+ * suspended.
301
+ */
302
+ suspend(): Promise<void>;
303
+ /**
304
+ * Publish run-end event to the channel and clean up. Terminal.
305
+ * @param params - How the run ended; see {@link RunEndParams}.
306
+ */
307
+ end(params: RunEndParams): Promise<void>;
308
+ }
309
+ /** Server-side session that manages run lifecycles over an Ably channel. */
310
+ export interface AgentSession<TOutput extends CodecOutputEvent, TProjection, TMessage> {
311
+ /**
312
+ * The Ably presence object for this session's channel.
313
+ *
314
+ * Exposed as a convenience so the agent can track and publish presence
315
+ * (`enter`/`leave`/`update`/`get`/`subscribe`) — for example, to detect
316
+ * whether the requesting user is still connected — without obtaining the
317
+ * channel separately. This is the same `Ably.RealtimePresence` instance the
318
+ * underlying channel exposes; the session applies no additional semantics.
319
+ * Presence operations implicitly attach the channel and do not require
320
+ * {@link connect} to have been called first.
321
+ */
322
+ readonly presence: Ably.RealtimePresence;
323
+ /**
324
+ * The Ably LiveObjects entry point for this session's channel.
325
+ *
326
+ * Exposed as a convenience so the agent can read and mutate shared objects
327
+ * (LiveMap / LiveCounter) on the same channel the session uses, without
328
+ * obtaining the channel separately. This is the same `RealtimeObject`
329
+ * instance the underlying channel exposes; the session applies no additional
330
+ * semantics. Operating on it requires (a) the Realtime client to have been
331
+ * constructed with the `LiveObjects` plugin from `ably/liveobjects` and
332
+ * (b) the object channel modes to have been requested via
333
+ * {@link AgentSessionOptions.channelModes}. When either is absent the
334
+ * underlying SDK throws; the session does not suppress the error.
335
+ */
336
+ readonly object: AblyObjects.RealtimeObject;
337
+ /**
338
+ * The session's materialisation tree. Every Ably message received on the channel
339
+ * (live + history) folds into this tree; consumers can introspect hydrated
340
+ * conversation state via {@link Tree.getNodeByCodecMessageId} /
341
+ * {@link Tree.getRunNode} etc. Mirrors `ClientSession.tree` so both
342
+ * sessions share one materialisation engine.
343
+ */
344
+ readonly tree: Tree<TOutput, TProjection>;
345
+ /**
346
+ * Subscribe (unfiltered) to the shared channel and (implicitly) attach. The
347
+ * subscribe is deliberately unfiltered so channel-history-replayed input
348
+ * events reach the materialisation engine, which the input-event lookup
349
+ * queries via the Tree. Idempotent — subsequent calls return the same
350
+ * promise. All run methods (`start`, `pipe`, `loadConversation`,
351
+ * `suspend`, `end`) throw `InvalidArgument` until
352
+ * `connect()` has been *called*; once it has, they await the in-flight
353
+ * connect promise rather than throwing.
354
+ */
355
+ connect(): Promise<void>;
356
+ /**
357
+ * Create a new run from an invocation. Synchronous — no channel activity
358
+ * until start() is called. The run is registered for cancel routing
359
+ * immediately so that early cancels fire the AbortSignal.
360
+ * @param invocation - The {@link Invocation} carrying run identity and
361
+ * conversation messages.
362
+ * @param runtime - Optional runtime hooks and external AbortSignal
363
+ * (e.g. the HTTP request's `req.signal`).
364
+ */
365
+ createRun(invocation: Invocation, runtime?: RunRuntime<TOutput>): Run<TOutput, TProjection, TMessage>;
366
+ /**
367
+ * Unsubscribe from cancel messages, cancel all active runs, detach the
368
+ * channel this session attached, and clean up.
369
+ *
370
+ * Resolves once the detach completes. The detach is best-effort:
371
+ * a failure (e.g. the channel is already FAILED) is swallowed
372
+ * and does not reject. Idempotent.
373
+ */
374
+ close(): Promise<void>;
375
+ }
@@ -0,0 +1,201 @@
1
+ import { Logger } from '../../../logger.js';
2
+ import { Codec, CodecInputEvent, CodecOutputEvent } from '../../codec/types.js';
3
+ import { Invocation } from '../invocation.js';
4
+ import { Tree } from './tree.js';
5
+ import { View } from './view.js';
6
+ /** Client session types: options, send options, the ActiveRun handle, and the ClientSession contract. */
7
+ import type * as Ably from 'ably';
8
+ import type * as AblyObjects from 'ably/liveobjects';
9
+ /** Options for creating a client session. */
10
+ export interface ClientSessionOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
11
+ /**
12
+ * The Ably Realtime client. The caller owns its lifecycle —
13
+ * `session.close()` does not close the client.
14
+ *
15
+ * The session's identity is taken from this client's `auth.clientId` (set
16
+ * via the Ably token or `ClientOptions.clientId`) — it is read at publish
17
+ * time and stamped on the wire as the run/input client id so other clients
18
+ * can attribute messages. A connection without a concrete clientId
19
+ * (anonymous, or a wildcard `*` token) publishes without one.
20
+ */
21
+ client: Ably.Realtime;
22
+ /**
23
+ * The name of the channel to subscribe to and publish cancel signals on.
24
+ * The session owns this channel — do not also resolve it elsewhere with
25
+ * conflicting channel options.
26
+ */
27
+ channelName: string;
28
+ /** The codec to use for encoding/decoding. */
29
+ codec: Codec<TInput, TOutput, TProjection, TMessage>;
30
+ /** Initial messages to seed the conversation tree with. Forms a linear chain. */
31
+ messages?: TMessage[];
32
+ /**
33
+ * Extra Ably channel modes to request on the session's channel, on top of the
34
+ * modes AI Transport always needs. Pass `OBJECT_MODES` (or
35
+ * `['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH']`) to use Ably LiveObjects via
36
+ * {@link ClientSession.object}. Omit to attach with the default mode set.
37
+ *
38
+ * The session requests the union of these modes with the modes it always
39
+ * needs, so passing extra modes never drops the SDK's required modes. The
40
+ * connection's token/key capability must permit the requested operations,
41
+ * otherwise the server grants only the permitted subset.
42
+ */
43
+ channelModes?: readonly Ably.ChannelMode[];
44
+ /** Logger instance for diagnostic output. */
45
+ logger?: Logger;
46
+ }
47
+ /** Per-send options for branching metadata and run identity. */
48
+ export interface SendOptions {
49
+ /**
50
+ * The codec-message-id of the message this send replaces (fork).
51
+ * Set for regeneration (forkOf an assistant message) or
52
+ * edit (forkOf a user message).
53
+ */
54
+ forkOf?: string;
55
+ /**
56
+ * The codec-message-id of the message that precedes this one in the
57
+ * conversation thread. If omitted, auto-computed from the last
58
+ * message in the view.
59
+ */
60
+ parent?: string;
61
+ /**
62
+ * Reuse an existing `runId` (e.g. resume a suspended run). When set,
63
+ * the send is treated as a continuation: the run's existing observer
64
+ * state (router stream, tree run-tracking) is reused; no fresh
65
+ * `crypto.randomUUID()` is minted. Each continuation POST is woken by the
66
+ * agent, which mints a distinct `invocationId` per HTTP request.
67
+ */
68
+ runId?: string;
69
+ }
70
+ /**
71
+ * A handle to an active client-side run, returned by `send()`,
72
+ * `regenerate()`, and `edit()`.
73
+ *
74
+ * The core does not expose a per-run output stream — streaming is a
75
+ * consumer-layer concern (e.g. the Vercel ChatTransport builds a stream from
76
+ * the Tree's `output` events). The handle carries only run identity and
77
+ * control, so it is not parameterized by the codec output type.
78
+ */
79
+ export interface ActiveRun {
80
+ /**
81
+ * The synchronous routing handle for this send: the triggering input's
82
+ * codec-message-id, which the client owns the moment it publishes and the
83
+ * agent echoes back as `input-codec-message-id`. Stream routing and cancel
84
+ * key on this — it is known immediately, unlike {@link runId}, which the
85
+ * agent mints.
86
+ */
87
+ inputCodecMessageId: string;
88
+ /**
89
+ * The run's unique identifier, resolved when the agent's `ai-run-start` for
90
+ * this send is observed on the channel. The agent mints the run-id, so it is
91
+ * not known synchronously: `await run.runId`
92
+ * to learn it (this also tells you the agent has picked up the run). There is
93
+ * no built-in deadline — race it against your own timeout if you need one.
94
+ * Rejects only if the session is closed before run-start arrives. A
95
+ * continuation does not resolve any sooner: its run-id promise resolves when
96
+ * the agent's run-resume (`event.type === 'resume'`) for this send is observed
97
+ * on the channel, not synchronously from the run-id the caller passed in.
98
+ */
99
+ runId: Promise<string>;
100
+ /**
101
+ * The input event's unique identifier. Stamped on the primary input event
102
+ * published to the channel and forwarded in the HTTP POST body so the
103
+ * agent can locate the exact triggering event.
104
+ */
105
+ inputEventId: string;
106
+ /**
107
+ * Cancel this specific run. Publishes a cancel signal synchronously — keyed
108
+ * by the triggering input's codec-message-id ({@link inputCodecMessageId}),
109
+ * which the client owns the moment it publishes, so a cancel issued before
110
+ * the agent mints the run-id is still honoured (the agent buffers it and
111
+ * fires it once its input-event lookup resolves the input to the run). A
112
+ * continuation also carries its known run-id. Resolves once the cancel is
113
+ * published; it does not wait for {@link runId}.
114
+ */
115
+ cancel(): Promise<void>;
116
+ /**
117
+ * The codec-message-id of every input in this send, in input order. Includes
118
+ * wire-only inputs (regenerate, tool resolutions), which are NOT folded into
119
+ * the local projection — so an entry here does not by itself mean an
120
+ * optimistic insert occurred.
121
+ */
122
+ optimisticCodecMessageIds: string[];
123
+ /**
124
+ * Build the {@link Invocation} pointer for this run — only `inputEventId` and
125
+ * the session's channel name as `sessionName`. The body carries no run-id: a
126
+ * fresh run's run-id is minted by the agent, and a continuation's run-id is
127
+ * read off the triggering input event's wire headers, so run identity always
128
+ * lives on the channel rather than the invocation body. The
129
+ * application POSTs `run.toInvocation().toJSON()` to its agent endpoint to
130
+ * wake the agent; the agent rebuilds it via {@link Invocation.fromJSON} and
131
+ * mints the `invocationId` (and a fresh `runId`) itself. The conversation
132
+ * itself is read from the channel, so the pointer carries only identifiers.
133
+ */
134
+ toInvocation(): Invocation;
135
+ }
136
+ /** Client-side session that manages conversation state over an Ably channel. */
137
+ export interface ClientSession<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
138
+ /** The complete conversation tree — all known Run nodes, events for any change. */
139
+ readonly tree: Tree<TOutput, TProjection>;
140
+ /** The default paginated, branch-aware view for rendering — events scoped to visible messages. */
141
+ readonly view: View<TInput, TMessage>;
142
+ /**
143
+ * The Ably presence object for this session's channel.
144
+ *
145
+ * Exposed as a convenience so callers can track and publish presence
146
+ * (`enter`/`leave`/`update`/`get`/`subscribe`) — for example, to detect
147
+ * whether an agent is online — without obtaining the channel separately.
148
+ * This is the same `Ably.RealtimePresence` instance the underlying channel
149
+ * exposes; the session applies no additional semantics. Presence operations
150
+ * implicitly attach the channel and do not require {@link connect} to have
151
+ * been called first.
152
+ */
153
+ readonly presence: Ably.RealtimePresence;
154
+ /**
155
+ * The Ably LiveObjects entry point for this session's channel.
156
+ *
157
+ * Exposed as a convenience so callers can read and mutate shared objects
158
+ * (LiveMap / LiveCounter) on the same channel the session uses, without
159
+ * obtaining the channel separately. This is the same `RealtimeObject`
160
+ * instance the underlying channel exposes; the session applies no additional
161
+ * semantics. Operating on it requires (a) the Realtime client to have been
162
+ * constructed with the `LiveObjects` plugin from `ably/liveobjects` and
163
+ * (b) the object channel modes to have been requested via
164
+ * {@link ClientSessionOptions.channelModes}. When either is absent the
165
+ * underlying SDK throws; the session does not suppress the error.
166
+ */
167
+ readonly object: AblyObjects.RealtimeObject;
168
+ /**
169
+ * Subscribe to the channel and (implicitly) attach. Idempotent —
170
+ * subsequent calls return the same promise. The View's write operations
171
+ * (`send()`, `regenerate()`, `edit()`) and this session's `cancel()` throw
172
+ * `InvalidArgument` until `connect()` resolves.
173
+ */
174
+ connect(): Promise<void>;
175
+ /**
176
+ * Create an additional view over the same conversation tree.
177
+ * Each view has independent branch selections and pagination state.
178
+ * The caller is responsible for calling `close()` on the returned view
179
+ * when it is no longer needed, or it will be closed when the session closes.
180
+ */
181
+ createView(): View<TInput, TMessage>;
182
+ /** Cancel the specified run by publishing an `ai-cancel` signal on the channel. The core does not own a per-run stream; closing any consumer-built stream is the responsibility of the layer that built it (e.g. the Vercel ChatTransport). */
183
+ cancel(runId: string): Promise<void>;
184
+ /**
185
+ * Subscribe to non-fatal session errors. These indicate something went
186
+ * wrong but the session is still operational. Returns an unsubscribe function.
187
+ * Once the session is CLOSED this is a no-op: the handler is not registered and
188
+ * the returned function does nothing.
189
+ */
190
+ on(event: 'error', handler: (error: Ably.ErrorInfo) => void): () => void;
191
+ /**
192
+ * Tear down the session: unsubscribe from the channel, close active views,
193
+ * clear all handlers, and detach the channel this session attached.
194
+ *
195
+ * Detaching stops the session from receiving further channel messages;
196
+ * the server keeps streaming until its runs end on their own. To stop
197
+ * in-progress runs, call {@link cancel} for each before `close()`. The
198
+ * detach is best-effort: a failure is swallowed and does not reject.
199
+ */
200
+ close(): Promise<void>;
201
+ }
@@ -0,0 +1,24 @@
1
+ /** Types shared across the client, agent, tree, and view layers. */
2
+ import type * as Ably from 'ably';
3
+ /**
4
+ * Why a run ended.
5
+ *
6
+ * A run-end is terminal — a run that merely pauses awaiting input publishes
7
+ * `ai-run-suspend` instead (see {@link Run.suspend}).
8
+ *
9
+ * - `complete` — the run finished naturally.
10
+ * - `cancelled` — the run was cancelled by a client.
11
+ * - `error` — the run errored.
12
+ */
13
+ export type RunEndReason = 'complete' | 'cancelled' | 'error';
14
+ /**
15
+ * Passed to a run's `onCancel` hook for authorization decisions.
16
+ * The hook inspects the incoming cancel message and decides whether to
17
+ * allow the targeted run to be cancelled.
18
+ */
19
+ export interface CancelRequest {
20
+ /** The raw Ably message that carried the cancel signal. */
21
+ message: Ably.InboundMessage;
22
+ /** The runId being cancelled. */
23
+ runId: string;
24
+ }