@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
@@ -5,11 +5,18 @@ import { Tree } from './tree.js';
5
5
  import { View } from './view.js';
6
6
  /** Client session types: options, send options, the ActiveRun handle, and the ClientSession contract. */
7
7
  import type * as Ably from 'ably';
8
+ import type * as AblyObjects from 'ably/liveobjects';
8
9
  /** Options for creating a client session. */
9
10
  export interface ClientSessionOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
10
11
  /**
11
12
  * The Ably Realtime client. The caller owns its lifecycle —
12
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.
13
20
  */
14
21
  client: Ably.Realtime;
15
22
  /**
@@ -20,14 +27,20 @@ export interface ClientSessionOptions<TInput extends CodecInputEvent, TOutput ex
20
27
  channelName: string;
21
28
  /** The codec to use for encoding/decoding. */
22
29
  codec: Codec<TInput, TOutput, TProjection, TMessage>;
23
- /**
24
- * The client's identity, used as the Ably publisher `clientId` on
25
- * everything this session publishes. Surfaces on the wire as the
26
- * run/input client id so other clients can attribute messages.
27
- */
28
- clientId?: string;
29
30
  /** Initial messages to seed the conversation tree with. Forms a linear chain. */
30
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[];
31
44
  /** Logger instance for diagnostic output. */
32
45
  logger?: Logger;
33
46
  }
@@ -53,12 +66,6 @@ export interface SendOptions {
53
66
  * agent, which mints a distinct `invocationId` per HTTP request.
54
67
  */
55
68
  runId?: string;
56
- /**
57
- * Currently non-functional: the send path always mints each input's
58
- * `inputEventId` with `crypto.randomUUID()` (no override is read), so a
59
- * value supplied here has no effect.
60
- */
61
- inputEventId?: string;
62
69
  }
63
70
  /**
64
71
  * A handle to an active client-side run, returned by `send()`,
@@ -132,6 +139,32 @@ export interface ClientSession<TInput extends CodecInputEvent, TOutput extends C
132
139
  readonly tree: Tree<TOutput, TProjection>;
133
140
  /** The default paginated, branch-aware view for rendering — events scoped to visible messages. */
134
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;
135
168
  /**
136
169
  * Subscribe to the channel and (implicitly) attach. Idempotent —
137
170
  * subsequent calls return the same promise. The View's write operations
@@ -18,6 +18,12 @@ interface RunLifecycleBase {
18
18
  * Empty string if the wire didn't carry an invocation-id.
19
19
  */
20
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;
21
27
  }
22
28
  /**
23
29
  * A structured event describing a run starting, suspending, resuming, or
@@ -72,12 +78,20 @@ export type RunLifecycleEvent = (RunLifecycleBase & {
72
78
  * optimistic local event. The Tree reads it to set the Run's endSerial.
73
79
  */
74
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';
75
87
  /**
76
- * Why the run ended the terminal reason the Tree records as the
77
- * RunNode's status: `complete`, `cancelled`, or `error`.
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`.
78
92
  */
79
- reason: RunEndReason;
80
- });
93
+ error: Ably.ErrorInfo;
94
+ }));
81
95
  /** A node in the conversation tree, representing a single domain message. */
82
96
  export interface MessageNode<TMessage> {
83
97
  /** Discriminator — identifies this as a message node. */
@@ -99,6 +113,24 @@ export interface MessageNode<TMessage> {
99
113
  */
100
114
  serial: string | undefined;
101
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
+ };
102
134
  /**
103
135
  * A node in the conversation tree, representing a single Run.
104
136
  *
@@ -155,13 +187,12 @@ export interface RunNode<TProjection> {
155
187
  */
156
188
  clientId: string;
157
189
  /**
158
- * Run lifecycle status.
159
- * - `'active'` run-start observed, no terminal event yet.
160
- * - `'suspended'` run-suspend observed; the run is paused awaiting input
161
- * and stays live (a continuation re-activates it). Not terminal.
162
- * - {@link RunEndReason} — terminal state reflecting the run-end reason.
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'`.
163
194
  */
164
- status: 'active' | 'suspended' | RunEndReason;
195
+ state: RunNodeState;
165
196
  /** Per-Run codec projection. Folded by the Tree from every event published under this run-id. */
166
197
  projection: TProjection;
167
198
  /**
@@ -292,6 +323,17 @@ export interface Tree<TOutput extends CodecOutputEvent, TProjection> {
292
323
  * @returns The ordered sibling nodes.
293
324
  */
294
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;
295
337
  /**
296
338
  * Subscribe to tree structural changes (Run insert, delete, sort-reorder,
297
339
  * startSerial promotion, run-start metadata backfill). Does NOT fire on
@@ -18,16 +18,8 @@ export interface LoadHistoryOptions {
18
18
  /** Max messages per page. Default: 100. */
19
19
  limit?: number;
20
20
  }
21
- /**
22
- * Projection-free, View-facing snapshot of a Run.
23
- *
24
- * Exposes the Run facts a UI consumer needs (`runId`, owner `clientId`,
25
- * lifecycle `status`, `invocationId`) without leaking the codec's
26
- * opaque per-Run projection or the Tree's structural fields. Callers
27
- * that need the full Run record (parent / fork relationships, serials,
28
- * projection) reach `session.tree.getRunNode(runId)` directly.
29
- */
30
- export interface RunInfo {
21
+ /** Fields common to every {@link RunInfo} arm. */
22
+ interface RunInfoBase {
31
23
  /** The Run's unique identifier. */
32
24
  runId: string;
33
25
  /**
@@ -35,14 +27,6 @@ export interface RunInfo {
35
27
  * when the wire didn't carry an owner client id.
36
28
  */
37
29
  clientId: string;
38
- /**
39
- * Run lifecycle status. `'active'` while the Run is streaming;
40
- * `'suspended'` while it is paused awaiting input (still live, a
41
- * continuation re-activates it); otherwise the {@link RunEndReason} the Run
42
- * terminated with. Literal lifecycle vocabulary — UIs that want `'streaming'`
43
- * rendering language translate at the component boundary.
44
- */
45
- status: 'active' | 'suspended' | RunEndReason;
46
30
  /**
47
31
  * The agent-minted `invocationId` observed for this Run, adopted from the
48
32
  * wire `ai-run-start`. Stable across the Run's lifecycle once observed.
@@ -52,6 +36,45 @@ export interface RunInfo {
52
36
  */
53
37
  invocationId: string;
54
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
+ });
55
78
  /**
56
79
  * Bundle returned by {@link View.branchSelection} describing the
57
80
  * sibling group anchored at a given codec-message-id.
@@ -121,19 +144,22 @@ export interface View<TInput extends CodecInputEvent, TMessage> {
121
144
  * the Tree.
122
145
  */
123
146
  runs(): RunInfo[];
124
- /** Whether there are older Runs that can be loaded or revealed. */
147
+ /** Whether there are older messages that can be loaded or revealed. */
125
148
  hasOlder(): boolean;
126
149
  /**
127
- * Reveal older Runs. Loads from channel history if the tree doesn't have
128
- * enough, then advances the pagination window by up to `limit` Runs.
129
- * Emits 'update' when the visible list changes.
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.
130
154
  *
131
- * The pagination unit is the **Run**, not the message. A single Run
132
- * typically contributes more than one message to the flat list returned
133
- * by {@link View.getMessages} (e.g. a user prompt + assistant reply
134
- * pair). Revealing `limit` Runs may add 1..N messages each to the
135
- * visible window.
136
- * @param limit - Maximum number of older Runs to reveal. Defaults to 100.
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.
137
163
  */
138
164
  loadOlder(limit?: number): Promise<void>;
139
165
  /**
@@ -220,3 +246,4 @@ export interface View<TInput extends CodecInputEvent, TMessage> {
220
246
  /** Tear down the view — unsubscribe from tree events and clear internal state. */
221
247
  close(): void;
222
248
  }
249
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { Logger } from '../../logger.js';
2
2
  import { Codec, CodecInputEvent, CodecMessage, CodecOutputEvent } from '../codec/types.js';
3
+ import { WireApplier } from './decode-fold.js';
3
4
  import { TreeInternal } from './tree.js';
4
5
  import { ActiveRun, BranchSelection, RunInfo, RunLifecycleEvent, SendOptions, View } from './types.js';
5
6
  /**
@@ -39,13 +40,19 @@ import * as Ably from 'ably';
39
40
  */
40
41
  export type SendDelegate<TInput extends CodecInputEvent> = (input: TInput[], options: SendOptions | undefined, parentCodecMessageId: string | undefined) => Promise<ActiveRun>;
41
42
  /** Options for creating a View. */
42
- export interface ViewOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
43
+ interface ViewOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
43
44
  /** The tree to project. */
44
45
  tree: TreeInternal<TInput, TOutput, TProjection>;
45
46
  /** The Ably channel to load history from. */
46
47
  channel: Ably.RealtimeChannel;
47
- /** The codec used to project messages, mint regenerate inputs, and decode history. */
48
+ /** The codec used to project messages and mint regenerate inputs. */
48
49
  codec: Codec<TInput, TOutput, TProjection, TMessage>;
50
+ /**
51
+ * The Tree's single decode-and-apply engine, owned by the session and
52
+ * shared with the live decode loop. History replay feeds pages through it
53
+ * so the one decoder instance sees every route into the Tree.
54
+ */
55
+ applier: WireApplier;
49
56
  /** Delegate for executing sends through the session. */
50
57
  sendDelegate: SendDelegate<TInput>;
51
58
  /** Logger for diagnostic output. */
@@ -57,6 +64,7 @@ export declare class DefaultView<TInput extends CodecInputEvent, TOutput extends
57
64
  private readonly _tree;
58
65
  private readonly _channel;
59
66
  private readonly _codec;
67
+ private readonly _applier;
60
68
  private readonly _sendDelegate;
61
69
  private readonly _logger;
62
70
  private readonly _emitter;
@@ -76,6 +84,16 @@ export declare class DefaultView<TInput extends CodecInputEvent, TOutput extends
76
84
  * landed).
77
85
  */
78
86
  private readonly _regenSelections;
87
+ /**
88
+ * Non-head regenerate selections, keyed by the regenerate target's
89
+ * codec-message-id. Separate from {@link _regenSelections} because a non-head
90
+ * regenerator parents inside the owner run rather than as a same-parent
91
+ * sibling, so it lives outside the Tree's `visibleNodes` selection space and
92
+ * is resolved at extraction (see `_extractMessages`). Value is the selected
93
+ * member's nodeKey (the owner run id, or a regenerator run id); absent groups
94
+ * default to the newest regenerator.
95
+ */
96
+ private readonly _nonHeadRegenSelections;
79
97
  /** Spec: AIT-CT11c — runIds loaded from history but not yet revealed to the UI. */
80
98
  private readonly _withheldRunIds;
81
99
  /** Snapshot of visible node keys — used to detect structural changes and for selection pinning. */
@@ -99,6 +117,16 @@ export declare class DefaultView<TInput extends CodecInputEvent, TOutput extends
99
117
  private _lastHistoryPage;
100
118
  /** Buffer of withheld nodes (input + reply), drained newest-first by successive loadOlder() calls. */
101
119
  private readonly _withheldBuffer;
120
+ /**
121
+ * Message-level trim on top of the run-level pagination window. Runs are
122
+ * revealed whole (via `_withheldRunIds`/`_withheldBuffer`), so a `loadOlder`
123
+ * may surface more messages than asked; this is the count of OLDEST messages
124
+ * of the visible node chain to hide from `getMessages()` so a page lands on
125
+ * exactly `limit` messages. The boundary run still appears in `runs()` (it's
126
+ * a revealed node); only its oldest messages are trimmed from the flat list.
127
+ * Live messages append at the newest end and are never trimmed.
128
+ */
129
+ private _hiddenMessageCount;
102
130
  /** Unsubscribe functions for tree event subscriptions. */
103
131
  private readonly _unsubs;
104
132
  /**
@@ -152,36 +180,91 @@ export declare class DefaultView<TInput extends CodecInputEvent, TOutput extends
152
180
  */
153
181
  private _runByCodecMessageId;
154
182
  /**
155
- * Extract the flat TMessage[] from a visible node chain.
156
- *
157
- * In the two-node model the Tree's `visibleNodes` has already selected one
158
- * member per sibling group (the chosen edit version, the chosen regenerate
159
- * run), so a regenerate is just a sibling reply run that appears in place of
160
- * the original. Each visible node contributes its own messages in projection
161
- * order; the flat list is their concatenation.
162
- *
163
- * Deferred caveat: a mid-reply regenerate that replaces a non-head message
164
- * inside a multi-message reply run is not expressible as a sibling run in
165
- * this model and is not handled here (see the `regenerate-of-multi-message`
166
- * golden test).
167
- * @param nodes - The visible nodes (inputs + reply runs) in chronological order.
168
- * @returns The flat message list, each message paired with its codec-message-id.
183
+ * The regenerator runs that replaced a non-head message of a reply run. They
184
+ * file under the target's predecessor (not the owner run's input node), so the
185
+ * Tree's `visibleNodes` cannot collapse them into the owner's slot; this
186
+ * surfaces them for the View to resolve and render. Head-message (index 0)
187
+ * regenerates are excluded - those are whole-reply sibling runs the Tree
188
+ * already groups.
189
+ * @param targetCodecMessageId - The regenerate target's (non-head) message id.
190
+ * @param predecessorCodecMessageId - The codec-message-id immediately before it in the owner run.
191
+ * @returns The regenerator runs in startSerial order (oldest first).
192
+ */
193
+ private _nonHeadRegenerators;
194
+ /**
195
+ * Resolve the selected member of a non-head regenerate group anchored at
196
+ * `targetCodecMessageId`. Members are the owner run `O` (memberNodeKey =
197
+ * `ownerRunId`, the regenerate target in place) followed by each regenerator
198
+ * run. Honours an explicit {@link _nonHeadRegenSelections} entry, else
199
+ * defaults to the latest member (newest regenerator), mirroring the
200
+ * whole-reply regenerate default.
201
+ * @param targetCodecMessageId - The regenerate target's message id (the group anchor).
202
+ * @param ownerRunId - The runId of the run that owns the regenerate target.
203
+ * @param regenerators - The regenerator runs (oldest first) from `_nonHeadRegenerators`.
204
+ * @returns The selected member's node key (`ownerRunId` or a regenerator runId).
205
+ */
206
+ private _selectedNonHeadMember;
207
+ /**
208
+ * Flatten visible nodes to messages, collapsing a non-head regenerate into the
209
+ * slot it replaces: while emitting a reply run, at each non-head message that
210
+ * has a selected regenerator, drop that message and the run's tail and emit the
211
+ * selected regenerator instead (recursive for regen-of-regen). Whole-reply
212
+ * regenerates need nothing here - visibleNodes already picks the sibling.
213
+ * @param nodes - Visible nodes (inputs + reply runs), chronological.
214
+ * @returns The flat message list, each paired with its codec-message-id.
169
215
  */
170
216
  private _extractMessages;
217
+ /**
218
+ * Emit one visible node's messages into `out`, applying non-head regenerate
219
+ * substitution for a reply run (see `_extractMessages`). Input nodes and runs
220
+ * with no non-head regenerators emit their projection verbatim.
221
+ * @param node - The node to emit.
222
+ * @param out - The accumulating flat message list (mutated in place).
223
+ * @param consumedRunIds - Set of regenerator runIds already emitted via substitution (mutated in place).
224
+ */
225
+ private _emitNodeMessages;
171
226
  hasOlder(): boolean;
172
227
  /**
173
- * Reveal up to `limit` older Runs in this view.
228
+ * Reveal `limit` more older codecMessages in this view — fewer only when
229
+ * channel history is exhausted.
174
230
  *
175
- * The pagination unit is the **Run**, not the message. A single Run
176
- * typically materialises into multiple messages (e.g. user + assistant
177
- * pair) so revealing `limit` Runs may add several messages to the flat
178
- * list returned by {@link getMessages}. Channel pages don't align to
179
- * Run boundaries, so {@link _loadUntilVisible} keeps fetching channel
180
- * pages until at least `limit` Runs are buffered (or the channel is
181
- * exhausted).
182
- * @param limit - Maximum number of older Runs to reveal. Defaults to 100.
231
+ * Internally runs are revealed WHOLE (run-granular withholding), counting
232
+ * codecMessages to decide how many runs to bring in, then the flat list
233
+ * returned by {@link getMessages} is trimmed to exactly `limit` more
234
+ * messages. So a run straddling the boundary still appears in {@link runs}
235
+ * (it's a revealed node) while only its newest messages show in
236
+ * `getMessages`. Live messages append at the newest end and are never
237
+ * trimmed.
238
+ * @param limit - Number of older codecMessages to reveal. Defaults to 10.
183
239
  */
184
240
  loadOlder(limit?: number): Promise<void>;
241
+ /**
242
+ * Fetch older channel history covering at least `target` more codecMessages,
243
+ * buffering the older nodes and revealing whole runs. The withheld buffer is
244
+ * assumed already drained by the caller. Loads the first page when no history
245
+ * has been fetched yet, otherwise advances to the next older page; the
246
+ * page-walk inside {@link _revealFromPage} loops until `target` messages are
247
+ * covered or history runs out. No-op (leaving `_hasMoreHistory` false) once
248
+ * channel history is exhausted.
249
+ * @param target - Minimum additional codecMessages this fetch aims to cover.
250
+ */
251
+ private _fetchOlder;
252
+ /**
253
+ * Find the index in `nodes` (chronological, oldest-first) at which the newest
254
+ * whole runs covering at least `target` codecMessages begin. Walks newest-first
255
+ * summing each node's `codec.getMessages(projection)` count; once the running
256
+ * total reaches `target`, the current node (and everything newer) is the
257
+ * revealed batch — so whole runs are revealed and the batch may overshoot
258
+ * `target` (the caller trims). Returns `0` when the nodes hold fewer than
259
+ * `target` messages — reveal everything.
260
+ *
261
+ * Shared by the buffer-drain and history-fetch reveal paths so they agree on
262
+ * "covering `target` messages".
263
+ * @param nodes - Candidate nodes, oldest-first.
264
+ * @param target - Minimum codecMessages the revealed batch must cover.
265
+ * @returns The split index; `nodes[splitIdx..]` is the revealed batch.
266
+ */
267
+ private _messageTailSplitIndex;
185
268
  runOf(codecMessageId: string): RunInfo | undefined;
186
269
  /**
187
270
  * Resolve the reply run currently selected for an input node, honouring the
@@ -203,27 +286,45 @@ export declare class DefaultView<TInput extends CodecInputEvent, TOutput extends
203
286
  */
204
287
  private _resolveSelectedIndex;
205
288
  /**
206
- * Resolve the branch point anchored at `codecMessageId`, if any.
207
- *
208
- * Returns the resolved group `kind` along with the sibling list so the
209
- * caller can update the correct selection map without re-entering the
210
- * runId-based `select()` dispatch (which biases to fork-of first and
211
- * would mis-route a regen-anchor codec-message-id when the owning Run is in
212
- * BOTH groups — e.g. R1 owns both a user prompt that got edited and
213
- * an assistant that got regenerated).
214
- *
215
- * Two anchor cases:
216
- * - **fork-of** — `codecMessageId` is the first message of a Run in a fork-of
217
- * sibling group (edit-style branch point anchored at the user prompt).
218
- * - **regen** — `codecMessageId` is the regen-anchor itself (in the owner Run)
219
- * or content of a regenerator Run (regen-style branch point anchored
220
- * at the assistant slot).
289
+ * Resolve the branch point anchored at `codecMessageId`, if any, returning the
290
+ * group `kind` + members + groupRoot so the caller routes to the correct
291
+ * selection map directly (not via a runId dispatch that would mis-route when
292
+ * the owning Run is in both a fork-of and a regen group).
221
293
  * @param codecMessageId - The codec-message-id to look up.
222
- * @returns The kind + sibling list + group key (runId for fork-of,
223
- * anchor codec-message-id for regen), or undefined when `codecMessageId` is not an
224
- * anchor in either group type.
294
+ * @returns The resolved branch point, or undefined when `codecMessageId`
295
+ * anchors no group.
225
296
  */
226
297
  private _resolveMessageBranchPoint;
298
+ /**
299
+ * Resolve a non-head regenerate branch point from a reply-run message, if any.
300
+ * `codecMessageId` is either (a) a non-head message `M` of its owner run with
301
+ * regenerators, or (b) a regenerator run's head; both resolve to the same group
302
+ * anchored at `M` (key matching {@link _nonHeadRegenSelections}).
303
+ * @param node - The reply run owning `codecMessageId`.
304
+ * @param ownMessages - That run's projected messages (already extracted).
305
+ * @param codecMessageId - The slot's codec-message-id (an `M`, or a regenerator head).
306
+ * @returns The non-head branch point, or undefined when `codecMessageId` anchors none.
307
+ */
308
+ private _resolveNonHeadBranchPoint;
309
+ /**
310
+ * Build the {@link MessageBranchPoint} for a non-head regenerate group, or
311
+ * undefined when the anchor has no regenerators. The owner member's
312
+ * representative is the anchor message (the regenerate target); each
313
+ * regenerator's is its head message.
314
+ * @param anchorCodecMessageId - The regenerate target's (non-head) message id.
315
+ * @param ownerRunId - The runId owning the regenerate target.
316
+ * @param predecessorCodecMessageId - The codec-message-id immediately before the anchor in the owner run.
317
+ * @returns The non-head branch point, or undefined when there are no regenerators.
318
+ */
319
+ private _buildNonHeadGroup;
320
+ /**
321
+ * Project nodes to {@link BranchMember}s for fork-of / whole-reply regen
322
+ * groups, where each member's branch-arrow representative is its own head
323
+ * message and its memberNodeKey is its node key.
324
+ * @param nodes - The sibling nodes.
325
+ * @returns One member per node that has a head message.
326
+ */
327
+ private _nodeHeadMembers;
227
328
  send(input: TInput | TInput[], options?: SendOptions): Promise<ActiveRun>;
228
329
  /**
229
330
  * Auto-select / pin branch selections after a forking send.
@@ -269,28 +370,33 @@ export declare class DefaultView<TInput extends CodecInputEvent, TOutput extends
269
370
  close(): void;
270
371
  private _loadFirstPage;
271
372
  /**
272
- * Walk channel history from `page` until at least `limit` new Runs are
273
- * observed (or the channel is exhausted), then reveal the newest batch and
274
- * withhold the rest. Snapshots the already-visible nodes up front so only
275
- * newly-observed Runs count toward `limit`. No-op if the view closed during
276
- * the page walk.
373
+ * Walk channel history from `page` until the newly-observed nodes hold at
374
+ * least `target` codecMessages (or the channel is exhausted), then reveal the
375
+ * newest whole runs covering `target` and withhold the rest. Snapshots the
376
+ * already-visible nodes up front so only newly-observed nodes count toward
377
+ * `target`. No-op if the view closed during the page walk.
277
378
  * @param page - The decoded history page to start from.
278
- * @param limit - Max Runs to reveal in this batch.
379
+ * @param target - Minimum codecMessages to reveal in this batch.
279
380
  */
280
381
  private _revealFromPage;
281
382
  /**
282
- * Reveal the newest `limit` Runs from `newVisible` and withhold the rest
283
- * so subsequent `loadOlder` calls can drain them. Called by
284
- * {@link _revealFromPage} to enforce the Run-unit pagination contract.
285
- * @param newVisible - Newly observed Runs from the history fetch.
286
- * @param limit - Max Runs to reveal in this batch.
383
+ * Reveal the newest whole runs covering `target` codecMessages from
384
+ * `newVisible` and withhold the rest so subsequent `loadOlder` calls can
385
+ * drain them. Reveal granularity is the whole run; the caller trims the flat
386
+ * message list (via `_hiddenMessageCount`) to make the visible message count
387
+ * exact. Called by {@link _revealFromPage}.
388
+ * @param newVisible - Newly observed nodes (inputs + reply runs) from the history fetch, chronological.
389
+ * @param target - Minimum codecMessages the revealed batch must cover.
287
390
  */
288
391
  private _splitReveal;
289
392
  /**
290
- * Replay a history page's raw messages into the Tree. Dispatches by Ably
291
- * message name to run-lifecycle vs. regular wire messages, mirroring the
292
- * live `client-session._handleMessage` decode loop. Uses a fresh decoder
293
- * since the session's live decoder maintains its own stream-tracker state.
393
+ * Replay a history page's raw messages into the Tree through the Tree's
394
+ * single decode-and-apply engine the same applier (and decoder instance)
395
+ * the client's live loop uses, so history replay can't drift from it. The
396
+ * shared decoder's version-guarded trackers make the overlap between the
397
+ * two routes safe: an in-flight stream that spans the attach boundary is
398
+ * continued rather than re-started, and content the live route already
399
+ * incorporated decodes to nothing.
294
400
  * @param page - The history page returned by `loadHistory`.
295
401
  */
296
402
  private _processHistoryPage;
@@ -335,6 +441,17 @@ export declare class DefaultView<TInput extends CodecInputEvent, TOutput extends
335
441
  * newest as the selected member.
336
442
  */
337
443
  private _resolvePendingRegenSelections;
444
+ /**
445
+ * Roll `pending` and `auto` non-head regenerate selections forward to the
446
+ * newest regenerator of their anchor message. Mirrors
447
+ * {@link _resolvePendingRegenSelections} for the non-head group, which lives in
448
+ * a separate selection map (anchored by the regenerate target rather than a
449
+ * sibling-group root): a `user` selection pins and is left untouched; a
450
+ * `pending`/`auto` slot adopts the newest regenerator once one lands. The
451
+ * anchor's predecessor — the key the regenerators file under — is recovered
452
+ * from the owning run's projection.
453
+ */
454
+ private _resolvePendingNonHeadRegenSelections;
338
455
  private _onTreeAblyMessage;
339
456
  private _onTreeRun;
340
457
  /**
@@ -352,3 +469,4 @@ export declare class DefaultView<TInput extends CodecInputEvent, TOutput extends
352
469
  * @returns A new {@link DefaultView} instance.
353
470
  */
354
471
  export declare const createView: <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage>(options: ViewOptions<TInput, TOutput, TProjection, TMessage>) => DefaultView<TInput, TOutput, TProjection, TMessage>;
472
+ export {};