@ably/ai-transport 0.2.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 (166) hide show
  1. package/README.md +10 -19
  2. package/dist/ably-ai-transport.js +1790 -1091
  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 +2 -2
  7. package/dist/core/agent.d.ts +20 -5
  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 +4 -1
  11. package/dist/core/codec/define-codec.d.ts +100 -0
  12. package/dist/core/codec/encoder.d.ts +2 -7
  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 -1
  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/output-descriptor-decoder.d.ts +29 -0
  20. package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
  21. package/dist/core/codec/output-descriptors.d.ts +237 -0
  22. package/dist/core/codec/types.d.ts +95 -36
  23. package/dist/core/codec/well-known-inputs.d.ts +52 -0
  24. package/dist/core/transport/agent-view.d.ts +296 -0
  25. package/dist/core/transport/decode-fold.d.ts +40 -32
  26. package/dist/core/transport/headers.d.ts +30 -1
  27. package/dist/core/transport/index.d.ts +1 -1
  28. package/dist/core/transport/invocation.d.ts +1 -1
  29. package/dist/core/transport/load-history-pages.d.ts +71 -0
  30. package/dist/core/transport/load-history.d.ts +21 -16
  31. package/dist/core/transport/run-manager.d.ts +9 -11
  32. package/dist/core/transport/session-support.d.ts +55 -0
  33. package/dist/core/transport/tree.d.ts +165 -15
  34. package/dist/core/transport/types/agent.d.ts +120 -98
  35. package/dist/core/transport/types/client.d.ts +45 -12
  36. package/dist/core/transport/types/tree.d.ts +52 -10
  37. package/dist/core/transport/types/view.d.ts +55 -28
  38. package/dist/core/transport/view.d.ts +176 -58
  39. package/dist/core/transport/wire-log.d.ts +102 -0
  40. package/dist/errors.d.ts +10 -4
  41. package/dist/index.d.ts +6 -5
  42. package/dist/react/ably-ai-transport-react.js +784 -415
  43. package/dist/react/ably-ai-transport-react.js.map +1 -1
  44. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  45. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  46. package/dist/react/contexts/client-session-context.d.ts +2 -1
  47. package/dist/react/contexts/client-session-provider.d.ts +3 -0
  48. package/dist/react/index.d.ts +2 -1
  49. package/dist/react/internal/skipped-session.d.ts +8 -0
  50. package/dist/react/use-view.d.ts +3 -3
  51. package/dist/utils.d.ts +22 -54
  52. package/dist/vercel/ably-ai-transport-vercel.js +2297 -2026
  53. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  54. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  55. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  56. package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
  57. package/dist/vercel/codec/events.d.ts +1 -2
  58. package/dist/vercel/codec/fields.d.ts +44 -0
  59. package/dist/vercel/codec/fold-content.d.ts +16 -0
  60. package/dist/vercel/codec/fold-data.d.ts +16 -0
  61. package/dist/vercel/codec/fold-input.d.ts +67 -0
  62. package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
  63. package/dist/vercel/codec/fold-text.d.ts +16 -0
  64. package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
  65. package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
  66. package/dist/vercel/codec/index.d.ts +5 -30
  67. package/dist/vercel/codec/inputs.d.ts +11 -0
  68. package/dist/vercel/codec/outputs.d.ts +11 -0
  69. package/dist/vercel/codec/reducer-state.d.ts +121 -0
  70. package/dist/vercel/codec/reducer.d.ts +20 -102
  71. package/dist/vercel/codec/tool-transitions.d.ts +0 -6
  72. package/dist/vercel/codec/wire-data.d.ts +34 -0
  73. package/dist/vercel/index.d.ts +1 -0
  74. package/dist/vercel/react/ably-ai-transport-vercel-react.js +2013 -9500
  75. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  76. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -70
  77. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  78. package/dist/vercel/react/contexts/chat-transport-context.d.ts +2 -1
  79. package/dist/vercel/run-end-reason.d.ts +66 -11
  80. package/dist/vercel/tool-part.d.ts +21 -0
  81. package/dist/vercel/transport/chat-transport.d.ts +0 -2
  82. package/dist/vercel/transport/index.d.ts +1 -1
  83. package/dist/vercel/transport/run-output-stream.d.ts +6 -8
  84. package/dist/version.d.ts +1 -1
  85. package/package.json +2 -2
  86. package/src/constants.ts +2 -2
  87. package/src/core/agent.ts +43 -19
  88. package/src/core/channel-options.ts +89 -0
  89. package/src/core/codec/codec-event.ts +27 -0
  90. package/src/core/codec/decoder.ts +145 -21
  91. package/src/core/codec/define-codec.ts +432 -0
  92. package/src/core/codec/encoder.ts +13 -54
  93. package/src/core/codec/field-bag.ts +142 -0
  94. package/src/core/codec/fields.ts +193 -0
  95. package/src/core/codec/index.ts +43 -0
  96. package/src/core/codec/input-descriptor-decoder.ts +97 -0
  97. package/src/core/codec/input-descriptor-encoder.ts +150 -0
  98. package/src/core/codec/input-descriptors.ts +373 -0
  99. package/src/core/codec/output-descriptor-decoder.ts +139 -0
  100. package/src/core/codec/output-descriptor-encoder.ts +101 -0
  101. package/src/core/codec/output-descriptors.ts +307 -0
  102. package/src/core/codec/types.ts +99 -36
  103. package/src/core/codec/well-known-inputs.ts +96 -0
  104. package/src/core/transport/agent-session.ts +330 -589
  105. package/src/core/transport/agent-view.ts +738 -0
  106. package/src/core/transport/client-session.ts +74 -69
  107. package/src/core/transport/decode-fold.ts +57 -47
  108. package/src/core/transport/headers.ts +57 -4
  109. package/src/core/transport/index.ts +2 -1
  110. package/src/core/transport/invocation.ts +1 -1
  111. package/src/core/transport/load-history-pages.ts +220 -0
  112. package/src/core/transport/load-history.ts +63 -61
  113. package/src/core/transport/pipe-stream.ts +10 -1
  114. package/src/core/transport/run-manager.ts +25 -31
  115. package/src/core/transport/session-support.ts +96 -0
  116. package/src/core/transport/tree.ts +414 -47
  117. package/src/core/transport/types/agent.ts +129 -102
  118. package/src/core/transport/types/client.ts +49 -13
  119. package/src/core/transport/types/tree.ts +61 -12
  120. package/src/core/transport/types/view.ts +57 -28
  121. package/src/core/transport/view.ts +520 -172
  122. package/src/core/transport/wire-log.ts +189 -0
  123. package/src/errors.ts +10 -3
  124. package/src/index.ts +44 -11
  125. package/src/react/contexts/client-session-context.ts +1 -1
  126. package/src/react/contexts/client-session-provider.tsx +38 -2
  127. package/src/react/index.ts +2 -1
  128. package/src/react/internal/skipped-session.ts +62 -0
  129. package/src/react/use-client-session.ts +7 -30
  130. package/src/react/use-view.ts +3 -3
  131. package/src/utils.ts +31 -97
  132. package/src/vercel/codec/decode-lifecycle.ts +70 -0
  133. package/src/vercel/codec/events.ts +1 -3
  134. package/src/vercel/codec/fields.ts +58 -0
  135. package/src/vercel/codec/fold-content.ts +54 -0
  136. package/src/vercel/codec/fold-data.ts +46 -0
  137. package/src/vercel/codec/fold-input.ts +255 -0
  138. package/src/vercel/codec/fold-lifecycle.ts +85 -0
  139. package/src/vercel/codec/fold-text.ts +55 -0
  140. package/src/vercel/codec/fold-tool-input.ts +86 -0
  141. package/src/vercel/codec/fold-tool-output.ts +79 -0
  142. package/src/vercel/codec/index.ts +23 -63
  143. package/src/vercel/codec/inputs.ts +116 -0
  144. package/src/vercel/codec/outputs.ts +207 -0
  145. package/src/vercel/codec/reducer-state.ts +169 -0
  146. package/src/vercel/codec/reducer.ts +52 -838
  147. package/src/vercel/codec/tool-transitions.ts +1 -12
  148. package/src/vercel/codec/wire-data.ts +64 -0
  149. package/src/vercel/index.ts +1 -0
  150. package/src/vercel/react/contexts/chat-transport-context.ts +1 -1
  151. package/src/vercel/react/use-chat-transport.ts +8 -28
  152. package/src/vercel/react/use-message-sync.ts +5 -10
  153. package/src/vercel/run-end-reason.ts +95 -16
  154. package/src/vercel/tool-part.ts +25 -0
  155. package/src/vercel/transport/chat-transport.ts +10 -22
  156. package/src/vercel/transport/index.ts +1 -1
  157. package/src/vercel/transport/run-output-stream.ts +7 -8
  158. package/src/version.ts +1 -1
  159. package/dist/core/transport/branch-chain.d.ts +0 -43
  160. package/dist/core/transport/load-conversation.d.ts +0 -128
  161. package/dist/vercel/codec/decoder.d.ts +0 -9
  162. package/dist/vercel/codec/encoder.d.ts +0 -11
  163. package/src/core/transport/branch-chain.ts +0 -58
  164. package/src/core/transport/load-conversation.ts +0 -355
  165. package/src/vercel/codec/decoder.ts +0 -696
  166. package/src/vercel/codec/encoder.ts +0 -548
@@ -1,5 +1,5 @@
1
1
  import { Logger } from '../../logger.js';
2
- import { CodecInputEvent, CodecOutputEvent, Reducer } from '../codec/types.js';
2
+ import { CodecEvent, CodecInputEvent, CodecOutputEvent, Reducer } from '../codec/types.js';
3
3
  import { ConversationNode, OutputEvent, RunLifecycleEvent, RunNode, Tree } from './types.js';
4
4
  /**
5
5
  * Tree — materializes a branching conversation as a forest of nodes. Each turn
@@ -24,6 +24,14 @@ import { ConversationNode, OutputEvent, RunLifecycleEvent, RunNode, Tree } from
24
24
  * sharing the same input-node parent (no fork-of).
25
25
  */
26
26
  import type * as Ably from 'ably';
27
+ /**
28
+ * How long (in ms, on the Ably message-timestamp timeline) a structurally
29
+ * complete run's event log is retained after the node's last observed
30
+ * activity. Bounds cross-publisher live delivery reorder: a wire can be
31
+ * delivered after a higher-serial wire by at most this window. Conservative
32
+ * placeholder pending confirmation of the actual cross-region bound.
33
+ */
34
+ export declare const REORDER_WINDOW_MS = 120000;
27
35
  /**
28
36
  * The primary key a node is indexed under: a reply run's `runId`, or an input
29
37
  * node's `codecMessageId` (the client owns it before the agent mints a runId).
@@ -70,24 +78,34 @@ export interface TreeInternal<TInput extends CodecInputEvent, TOutput extends Co
70
78
  * @param events.outputs - Agent-published events (`ai-output` wire).
71
79
  * @param headers - Transport headers from the inbound Ably message.
72
80
  * @param serial - Ably channel serial; undefined for optimistic inserts.
81
+ * @param timestamp - Ably server timestamp (epoch ms) of the message —
82
+ * top-level `Message.timestamp`, the message's create time on every
83
+ * delivery (an append's own receive time lives in `version.timestamp`) —
84
+ * or undefined for optimistic inserts. Advances the Tree's event-log
85
+ * retention clock and the owning node's last-activity time.
86
+ * @param version - The delivery's `Message.version.serial`, or undefined
87
+ * when the delivery carried none (optimistic inserts, never-mutated
88
+ * deliveries from sources that omit it). Guards the node's event log
89
+ * against whole-wire replays: a delivery at or below the version already
90
+ * decoded into its log entry is dropped.
73
91
  */
74
92
  applyMessage(events: {
75
93
  inputs: TInput[];
76
94
  outputs: TOutput[];
77
- }, headers: Record<string, string>, serial?: string): void;
95
+ }, headers: Record<string, string>, serial?: string, timestamp?: number, version?: string): void;
78
96
  /**
79
97
  * Apply a run-lifecycle event.
80
98
  *
81
99
  * - `start`: creates the reply run (if missing) or, for an existing run,
82
- * sets RunNode.status to 'active', promotes startSerial, and backfills
100
+ * sets RunNode.state to 'active', promotes startSerial, and backfills
83
101
  * structural metadata (parent / forkOf / regenerates / invocationId).
84
- * - `suspend`: sets RunNode.status to 'suspended' and records `endSerial`.
102
+ * - `suspend`: sets RunNode.state to 'suspended' and records `endSerial`.
85
103
  * The run stays live so a resume under the same `runId` picks up where it
86
104
  * left off.
87
- * - `resume`: re-activates an existing suspended Run (status back to
105
+ * - `resume`: re-activates an existing suspended Run (state back to
88
106
  * 'active') without touching its structure or serials — a pure re-entry
89
107
  * signal. A no-op if the Run is not yet known.
90
- * - `end`: sets RunNode.status to the terminal reason and records
108
+ * - `end`: sets RunNode.state to the terminal reason and records
91
109
  * `endSerial`.
92
110
  *
93
111
  * Always emits a 'run' event to subscribers.
@@ -169,7 +187,31 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
169
187
  */
170
188
  private _siblingCache;
171
189
  private _siblingCacheVersion;
172
- constructor(codec: Reducer<TInput | TOutput, TProjection>, logger: Logger);
190
+ /**
191
+ * Index from `event-id` header to the raw Ably message that carried it.
192
+ * Populated incrementally as messages arrive via {@link emitAblyMessage};
193
+ * reads back the raw message for the agent's input-event lookup
194
+ * ({@link findAblyMessageByEventId}). Bounded by the Tree's lifetime — cleared
195
+ * when the Tree is replaced on continuity loss / session close.
196
+ */
197
+ private readonly _eventIdIndex;
198
+ /**
199
+ * Event-log retention logical clock: the max Ably message timestamp (epoch
200
+ * ms) observed across every apply, 0 until the first timestamped one. Only
201
+ * ever advances — older-page history application carries smaller timestamps
202
+ * and leaves it (and therefore the sweep) untouched.
203
+ */
204
+ private _clock;
205
+ /**
206
+ * Keys of structurally complete run nodes (run-start and run-end both
207
+ * observed) whose event logs await the retention window, in completion
208
+ * order. Drained from the front whenever {@link _clock} advances; sweeping
209
+ * only at clock advances keeps a history page's batch atomic — applying an
210
+ * older page can never advance the clock, so a node cannot be swept between
211
+ * its run-start and the rest of its wires in the same page.
212
+ */
213
+ private readonly _sweepQueue;
214
+ constructor(codec: Reducer<CodecEvent<TInput, TOutput>, TProjection>, logger: Logger);
173
215
  /**
174
216
  * Compare two nodes (Run or input) for sorted list ordering.
175
217
  * Serial-bearing nodes sort by their sort serial (`startSerial` for runs,
@@ -224,6 +266,88 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
224
266
  * @param messageId - The reducer routing key (codec-message-id), or undefined.
225
267
  */
226
268
  private _foldInto;
269
+ /**
270
+ * Record a serial-bearing wire in the node's event log and fold it. Events
271
+ * extending the log tail (the common case — in-order live delivery) fold
272
+ * incrementally onto the existing projection, identical to a bare
273
+ * {@link _foldInto}. Events that land earlier in the log (an earlier-serial
274
+ * wire delivered late — cross-publisher reorder, or a history page applying
275
+ * an older message after a newer one) cannot be folded incrementally without
276
+ * corrupting serial order, so the node is refolded from the whole log via
277
+ * {@link _refold}.
278
+ *
279
+ * Optimistic (serial-less) applies and empty event batches are not logged;
280
+ * an optimistic seed folds into the projection but never into the log, and
281
+ * marks the node `optimistic`. The first serial-bearing wire (the echo of
282
+ * the optimistic input, which re-delivers the seeded content) refolds the
283
+ * node from the log alone — rebuilding the projection without the seed
284
+ * rather than folding the echo on top of it. The codec therefore never sees
285
+ * the seed and its echo in one projection, and needs no seed-replacement
286
+ * logic. The seed must be a faithful preview of the echo, since the echo's
287
+ * content is what survives.
288
+ *
289
+ * Whole-wire replays are dropped at the log: each entry records the highest
290
+ * `Message.version.serial` decoded into it (`decodedThrough`), so a
291
+ * version-bearing delivery the entry has already incorporated — a second
292
+ * hydration over a populated Tree, a remounted View's re-fetch, an agent
293
+ * re-walk — records nothing and folds nothing. A newer version of a
294
+ * discrete wire (an edited discrete) is likewise dropped; propagating edits
295
+ * into projections is deliberately out of scope.
296
+ * @param entry - The internal node whose log and projection are updated.
297
+ * @param events - The decoded events to fold, in wire order.
298
+ * @param serial - Ably channel serial; undefined for an optimistic insert.
299
+ * @param messageId - The reducer routing key (codec-message-id), or undefined.
300
+ * @param version - The delivery's `Message.version.serial`, or undefined.
301
+ * @param streamed - Whether the delivery is part of a streamed wire.
302
+ */
303
+ private _recordAndFold;
304
+ /**
305
+ * Rebuild a node's projection from its event log in canonical serial order:
306
+ * a fresh {@link Reducer.init} folded through every logged event, each with
307
+ * its own wire's serial and messageId. Used when a late, earlier-serial wire
308
+ * makes incremental folding unsound. Reducer purity (a fold is a function of
309
+ * its inputs alone) is what makes the rebuild faithful; the per-fold
310
+ * try/catch mirrors {@link _foldInto} so one throwing event can't abort the
311
+ * rebuild.
312
+ *
313
+ * Rebuilds the projection only; the surrounding apply emits its usual
314
+ * `output` event carrying just the triggering wire's events. Consumers read
315
+ * the rebuilt state from `node.projection` (the View recomputes its message
316
+ * list from it), so on the refold path the event's `events` payload is not a
317
+ * delta of the full projection change.
318
+ * @param entry - The internal node whose projection is rebuilt in place.
319
+ */
320
+ private _refold;
321
+ /**
322
+ * Record activity on a node and advance the retention clock. Updates the
323
+ * node's `lastActivityTs` and the Tree-wide `_clock` to the given timestamp
324
+ * when it is newer; a clock advance drains the sweep queue. `undefined`
325
+ * (an optimistic local apply) advances nothing.
326
+ * @param entry - The node the activity belongs to.
327
+ * @param timestamp - Ably message timestamp (epoch ms), or undefined.
328
+ */
329
+ private _recordActivity;
330
+ /**
331
+ * Queue a run node's event log for retention sweeping once the node is
332
+ * structurally complete: its run-start (serial floor — no older history page
333
+ * can add to it) and its run-end (no further agent output) have both been
334
+ * observed. The actual drop happens in {@link _drainSweepQueue} once the
335
+ * reorder window has also lapsed. No-op for input nodes (never swept — no
336
+ * floor marker, and their logs are bounded by one user message), for nodes
337
+ * already queued or swept, and while either marker is missing.
338
+ * @param entry - The node to consider for sweeping.
339
+ */
340
+ private _maybeQueueSweep;
341
+ /**
342
+ * Drop the event logs of queued nodes whose retention window has lapsed:
343
+ * `lastActivityTs + REORDER_WINDOW_MS < _clock`. Drains from the front and
344
+ * stops at the first node still inside the window — completion order is
345
+ * time-ordered for live traffic, so this is amortised O(1) per apply, and
346
+ * stopping early only ever over-retains (memory, never correctness). Called
347
+ * only when the clock advances, so applying an older history page (smaller
348
+ * timestamps) can never sweep mid-batch. Deleted nodes are skipped.
349
+ */
350
+ private _drainSweepQueue;
227
351
  private _addToParentIndex;
228
352
  private _removeFromParentIndex;
229
353
  /**
@@ -297,7 +421,7 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
297
421
  applyMessage(events: {
298
422
  inputs: TInput[];
299
423
  outputs: TOutput[];
300
- }, headers: Record<string, string>, serial?: string): void;
424
+ }, headers: Record<string, string>, serial?: string, timestamp?: number, version?: string): void;
301
425
  /**
302
426
  * Apply a run-less user input wire: create (or promote the serial of) the
303
427
  * input node keyed by its codec-message-id, fold the input events into its
@@ -306,7 +430,9 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
306
430
  * @param codecMessageId - The input node's codec-message-id (its primary key).
307
431
  * @param headers - Transport headers from the inbound Ably message.
308
432
  * @param serial - Ably channel serial; undefined for an optimistic insert.
309
- * @param all - The decoded input events to fold, in wire order.
433
+ * @param timestamp - Ably server timestamp (epoch ms); undefined for an optimistic insert.
434
+ * @param version - The delivery's `Message.version.serial`, or undefined.
435
+ * @param all - The direction-tagged input events to fold, in wire order.
310
436
  */
311
437
  private _applyInputMessage;
312
438
  /**
@@ -322,6 +448,8 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
322
448
  * @param events.outputs - Agent-published events (`ai-output` wire).
323
449
  * @param headers - Transport headers from the inbound Ably message.
324
450
  * @param serial - Ably channel serial; undefined for an optimistic insert.
451
+ * @param timestamp - Ably server timestamp (epoch ms); undefined for an optimistic insert.
452
+ * @param version - The delivery's `Message.version.serial`, or undefined.
325
453
  */
326
454
  private _applyRunMessage;
327
455
  /**
@@ -365,10 +493,17 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
365
493
  */
366
494
  private _applyRunResume;
367
495
  /**
368
- * Apply a run-end lifecycle event: record the terminal reason as the node's
369
- * status and the serial it ended at. Status/endSerial are content, not
370
- * structure, so this never mutates `_structuralVersion`; the caller owns the
371
- * emits.
496
+ * Apply a run-end lifecycle event: record the terminal reason (and, for an
497
+ * error end, the error) as the node's state, plus the serial it ended at.
498
+ * State/endSerial are content, not structure, so this never mutates
499
+ * `_structuralVersion`; the caller owns the emits.
500
+ *
501
+ * A run-end for an unknown runId is a no-op: nothing else is known about the
502
+ * run yet, so there is no node to mark. When that happens during history
503
+ * replay (a page boundary falling just before the run-end, so the run's
504
+ * other wires arrive in later pages), the run is never marked terminal and
505
+ * its event log is retained for the Tree's lifetime — over-retention, never
506
+ * corruption.
372
507
  * @param event - The run-end lifecycle event.
373
508
  */
374
509
  private _applyRunEnd;
@@ -382,6 +517,17 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
382
517
  * @returns A newly-allocated internal run node ready for insertion.
383
518
  */
384
519
  private _createRunFromHeaders;
520
+ /**
521
+ * Wrap a freshly-built conversation node in its internal envelope — sort
522
+ * sequence, event log, and retention/promotion state. The single home for
523
+ * those per-node fields, so a new field is added in one place rather than at
524
+ * every node-construction site.
525
+ * @param node - The conversation node to wrap.
526
+ * @param runStartSeen - Whether the run's ai-run-start has been observed
527
+ * (run nodes only; always false for input nodes).
528
+ * @returns A newly-allocated internal node ready for insertion.
529
+ */
530
+ private _wrapNode;
385
531
  /**
386
532
  * Allocate a RunNode from already-resolved fields. Shared by the
387
533
  * header-driven and lifecycle-driven run creators: both build the identical
@@ -394,6 +540,7 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
394
540
  * @param params.clientId - The publishing client's id.
395
541
  * @param params.invocationId - The agent invocation id.
396
542
  * @param params.startSerial - Ably channel serial; undefined for optimistic inserts.
543
+ * @param params.runStartSeen - Whether the run's ai-run-start has been observed (true only for lifecycle-created runs).
397
544
  * @returns A newly-allocated internal run node ready for insertion.
398
545
  */
399
546
  private _buildRunNode;
@@ -418,10 +565,13 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
418
565
  on(event: 'run', handler: (event: RunLifecycleEvent) => void): () => void;
419
566
  on(event: 'output', handler: (event: OutputEvent<TOutput>) => void): () => void;
420
567
  /**
421
- * Forward a raw Ably message event to tree subscribers.
568
+ * Forward a raw Ably message event to tree subscribers. Also indexes the
569
+ * Ably message by `event-id` header (if present) for
570
+ * {@link findAblyMessageByEventId} lookups.
422
571
  * @param msg - The raw Ably message to emit.
423
572
  */
424
573
  emitAblyMessage(msg: Ably.InboundMessage): void;
574
+ findAblyMessageByEventId(eventId: string): Ably.InboundMessage | undefined;
425
575
  }
426
576
  /**
427
577
  * Create a Tree that materializes branching conversation history from a flat
@@ -432,4 +582,4 @@ export declare class DefaultTree<TInput extends CodecInputEvent, TOutput extends
432
582
  * directly for internal methods (applyMessage, applyRunLifecycle,
433
583
  * emitAblyMessage). Public consumers see the narrower {@link Tree} interface.
434
584
  */
435
- export declare const createTree: <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection>(codec: Reducer<TInput | TOutput, TProjection>, logger: Logger) => DefaultTree<TInput, TOutput, TProjection>;
585
+ export declare const createTree: <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection>(codec: Reducer<CodecEvent<TInput, TOutput>, TProjection>, logger: Logger) => DefaultTree<TInput, TOutput, TProjection>;
@@ -2,9 +2,10 @@ import { Logger } from '../../../logger.js';
2
2
  import { Codec, CodecInputEvent, CodecOutputEvent, WriteOptions } from '../../codec/types.js';
3
3
  import { Invocation } from '../invocation.js';
4
4
  import { CancelRequest, RunEndReason } from './shared.js';
5
- import { MessageNode } from './tree.js';
5
+ import { MessageNode, Tree } from './tree.js';
6
6
  /** Agent (server-side) session types: options, run runtime, and the Run / AgentSession contracts. */
7
7
  import type * as Ably from 'ably';
8
+ import type * as AblyObjects from 'ably/liveobjects';
8
9
  /** Options for creating an agent session. */
9
10
  export interface AgentSessionOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
10
11
  /**
@@ -30,60 +31,43 @@ export interface AgentSessionOptions<TInput extends CodecInputEvent, TOutput ext
30
31
  onError?: (error: Ably.ErrorInfo) => void;
31
32
  /**
32
33
  * How long `Run.start()` will wait for the input event(s) tagged with
33
- * the run's `invocationId` to arrive on the channel (rewind + live wait)
34
- * before rejecting with `InputEventNotFound`. The rejection bubbles up to the
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
35
37
  * developer's HTTP handler, which should surface it as a non-2xx response
36
38
  * so the client's pending send fails.
37
39
  * Default: 30000 (30 seconds).
38
40
  */
39
41
  inputEventLookupTimeoutMs?: number;
40
42
  /**
41
- * Maximum number of distinct invocation-ids whose input events
42
- * may be buffered while waiting for `Run.start()` to register a lookup
43
- * listener. Channel rewind on attach can replay input events before any
44
- * run has been created for them; this buffer holds those events so
45
- * that subsequent `start()` calls can drain them on registration.
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.
46
47
  *
47
- * Each entry corresponds to one invocation-id regardless of how many
48
- * events that invocation buffered. When the limit is exceeded the
49
- * oldest invocation entry (and all its buffered events) is FIFO-evicted
50
- * — the client whose input was dropped will fail their lookup with
51
- * `InputEventNotFound`. The eviction is logged at warn level so operators
52
- * can correlate capacity pressure with `InputEventNotFound` errors.
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.
53
51
  *
54
- * Default: 200.
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).
55
57
  */
56
- inputEventBufferLimit?: number;
58
+ inputEventLookbackMs?: number;
57
59
  /**
58
- * The channel rewind applied when the agent attaches. Replays the whole
59
- * channel subscription on attach (not just input events) so the lookup
60
- * can catch input events published before the session attached. Passed
61
- * through verbatim to Ably's `params.rewind` channel parameter accepts
62
- * duration strings (`"2m"`, `"30s"`) or a count of messages as a string
63
- * (e.g. `"50"`). Malformed values surface as a channel attach error from
64
- * Ably; the SDK does not pre-validate.
65
- *
66
- * A longer window improves the chances of catching an input event for an
67
- * agent that takes a while to come up after the client published, but
68
- * also increases the buffer pressure on `inputEventBufferLimit` because
69
- * more events may be replayed on attach.
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.
70
64
  *
71
- * Default: `"2m"`.
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.
72
69
  */
73
- rewindWindow?: string;
74
- }
75
- /**
76
- * A batch of events targeting an existing message.
77
- * Each node specifies the target message and the events to apply to it.
78
- * Used for cross-run updates such as tool result delivery.
79
- */
80
- export interface EventsNode<TOutput extends CodecOutputEvent> {
81
- /** Discriminator — identifies this as an events node. */
82
- kind: 'event';
83
- /** The `codec-message-id` of the existing message to update. */
84
- codecMessageId: string;
85
- /** Outputs to apply to the target message. */
86
- events: TOutput[];
70
+ channelModes?: readonly Ably.ChannelMode[];
87
71
  }
88
72
  /**
89
73
  * Options for `Run.pipe` — per-operation overrides for the assistant message.
@@ -177,7 +161,7 @@ export interface RunRuntime<TOutput extends CodecOutputEvent> {
177
161
  * `Ably.ErrorInfo` (code `StreamError`) for standardized observability.
178
162
  * - Failures in the `onCancel` handler.
179
163
  *
180
- * Publish failures in `start`, `addEvents`, and `end`
164
+ * Publish failures in `start` and `end`
181
165
  * are not delivered here — those methods reject their returned promise
182
166
  * with an `Ably.ErrorInfo`, and the caller should handle it at the await
183
167
  * site. Run errors never render the session unusable, but the run may
@@ -205,21 +189,41 @@ export interface RunView<TMessage> {
205
189
  /** Options for {@link Run.loadConversation}. */
206
190
  export interface LoadConversationOptions {
207
191
  /**
208
- * Number of wire messages to request per history page.
209
- * Default: 200.
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.
210
202
  */
211
- pageLimit?: number;
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';
212
215
  /**
213
- * Maximum total wire messages to collect across all pages before
214
- * stopping pagination. A safety bound so a long-lived channel
215
- * doesn't exhaust memory.
216
- * Default: 2000.
216
+ * Optional terminal error to surface to clients. Omit to end in error
217
+ * without detail.
217
218
  */
218
- maxMessages?: number;
219
- }
219
+ error?: Ably.ErrorInfo;
220
+ };
220
221
  /**
221
222
  * A server-side run with explicit lifecycle methods. Generic over the codec's
222
- * output, projection, and message types.
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.
223
227
  */
224
228
  export interface Run<TOutput extends CodecOutputEvent, TProjection, TMessage> {
225
229
  /** The run's unique identifier. */
@@ -254,7 +258,7 @@ export interface Run<TOutput extends CodecOutputEvent, TProjection, TMessage> {
254
258
  /**
255
259
  * Publish the run's opening lifecycle event to the channel (run-start, or
256
260
  * run-resume for a continuation). Must be called before any other run method
257
- * (pipe, addEvents, suspend, end).
261
+ * (pipe, suspend, end).
258
262
  */
259
263
  start(): Promise<void>;
260
264
  /**
@@ -263,45 +267,26 @@ export interface Run<TOutput extends CodecOutputEvent, TProjection, TMessage> {
263
267
  * Does NOT call end() — the caller must call end() after pipe returns.
264
268
  */
265
269
  pipe(stream: ReadableStream<TOutput>, options?: PipeOptions<TOutput>): Promise<StreamResult>;
266
- /**
267
- * Publish events targeting existing messages in the tree. Each node
268
- * specifies a target message (by `codecMessageId`) and the events to apply.
269
- * Events are encoded and published with the target's `codec-message-id`,
270
- * so receiving clients apply them to the existing node rather than
271
- * creating a new one.
272
- *
273
- * Used for cross-run updates such as tool result delivery after
274
- * approval or client-side tool execution.
275
- */
276
- addEvents(nodes: EventsNode<TOutput>[]): Promise<void>;
277
- /**
278
- * Fetch every channel message bound to this run and fold them through
279
- * the codec into a single projection. Used by the agent to reconstruct
280
- * the run's full state — including client-published tool-output amends
281
- * the agent didn't observe live — when resuming a suspended run.
282
- *
283
- * Uses `channel.history()` (no `untilAttach`) so messages published
284
- * after the channel was originally attached are still included. Each
285
- * call paginates until either there are no more pages or an internal
286
- * safety bound is reached.
287
- * @returns The TProjection produced by folding every event for this run
288
- * in serial order. The caller extracts what they need via
289
- * {@link Codec.getMessages}.
290
- */
291
- loadProjection(): Promise<TProjection>;
292
270
  /**
293
271
  * Reconstruct the full multi-turn conversation by walking the ancestor
294
- * run chain and concatenating each run's messages, oldest turn first.
272
+ * run chain over the session's Tree, concatenating each ancestor's
273
+ * projection (oldest turn first) plus the current run's projection.
295
274
  *
296
- * Performs a single `channel.history()` scan and builds projections for
297
- * all ancestor runs plus the current run. After this call:
298
- * - {@link Run.messages} returns the complete conversation (all ancestor
299
- * turns followed by the current run's messages), making it ready to
300
- * pass directly to the LLM.
301
- * - The current run's projection is cached so {@link Run.pipe} works
302
- * correctly without a separate {@link Run.loadProjection} call.
303
- * @param options - Optional tuning for history pagination.
304
- * @returns The same message list now accessible via {@link Run.messages}.
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.
305
290
  */
306
291
  loadConversation(options?: LoadConversationOptions): Promise<TMessage[]>;
307
292
  /**
@@ -315,18 +300,55 @@ export interface Run<TOutput extends CodecOutputEvent, TProjection, TMessage> {
315
300
  * suspended.
316
301
  */
317
302
  suspend(): Promise<void>;
318
- /** Publish run-end event to the channel and clean up. Terminal. */
319
- end(reason: RunEndReason): 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>;
320
308
  }
321
309
  /** Server-side session that manages run lifecycles over an Ably channel. */
322
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>;
323
345
  /**
324
346
  * Subscribe (unfiltered) to the shared channel and (implicitly) attach. The
325
- * subscribe is deliberately unfiltered so channel-rewind-replayed input
326
- * events also reach the dispatcher, which routes by name (cancel vs. input
327
- * event). Idempotent — subsequent calls return the same promise. All run
328
- * methods (`start`, `addEvents`, `pipe`, `loadProjection`,
329
- * `loadConversation`, `suspend`, `end`) throw `InvalidArgument` until
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
330
352
  * `connect()` has been *called*; once it has, they await the in-flight
331
353
  * connect promise rather than throwing.
332
354
  */