@ably/ai-transport 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +91 -100
  2. package/dist/ably-ai-transport.js +1553 -1238
  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 +29 -0
  8. package/dist/core/codec/decoder.d.ts +20 -23
  9. package/dist/core/codec/encoder.d.ts +11 -8
  10. package/dist/core/codec/index.d.ts +1 -2
  11. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  12. package/dist/core/codec/types.d.ts +407 -115
  13. package/dist/core/transport/agent-session.d.ts +10 -0
  14. package/dist/core/transport/branch-chain.d.ts +43 -0
  15. package/dist/core/transport/client-session.d.ts +13 -0
  16. package/dist/core/transport/decode-fold.d.ts +47 -0
  17. package/dist/core/transport/headers.d.ts +96 -18
  18. package/dist/core/transport/index.d.ts +5 -6
  19. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  20. package/dist/core/transport/invocation.d.ts +74 -0
  21. package/dist/core/transport/load-conversation.d.ts +128 -0
  22. package/dist/core/transport/load-history.d.ts +39 -0
  23. package/dist/core/transport/pipe-stream.d.ts +9 -9
  24. package/dist/core/transport/run-manager.d.ts +78 -0
  25. package/dist/core/transport/tree.d.ts +373 -109
  26. package/dist/core/transport/types/agent.d.ts +353 -0
  27. package/dist/core/transport/types/client.d.ts +168 -0
  28. package/dist/core/transport/types/shared.d.ts +24 -0
  29. package/dist/core/transport/types/tree.d.ts +315 -0
  30. package/dist/core/transport/types/view.d.ts +222 -0
  31. package/dist/core/transport/types.d.ts +13 -553
  32. package/dist/core/transport/view.d.ts +272 -84
  33. package/dist/errors.d.ts +21 -10
  34. package/dist/index.d.ts +6 -8
  35. package/dist/logger.d.ts +12 -0
  36. package/dist/react/ably-ai-transport-react.js +976 -990
  37. package/dist/react/ably-ai-transport-react.js.map +1 -1
  38. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  39. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  40. package/dist/react/contexts/client-session-context.d.ts +36 -0
  41. package/dist/react/contexts/client-session-provider.d.ts +53 -0
  42. package/dist/react/create-session-hooks.d.ts +116 -0
  43. package/dist/react/index.d.ts +12 -12
  44. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  45. package/dist/react/use-ably-messages.d.ts +17 -14
  46. package/dist/react/use-client-session.d.ts +81 -0
  47. package/dist/react/use-create-view.d.ts +14 -13
  48. package/dist/react/use-tree.d.ts +30 -15
  49. package/dist/react/use-view.d.ts +82 -51
  50. package/dist/utils.d.ts +32 -23
  51. package/dist/vercel/ably-ai-transport-vercel.js +2573 -2086
  52. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  53. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  54. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  55. package/dist/vercel/codec/decoder.d.ts +5 -18
  56. package/dist/vercel/codec/encoder.d.ts +6 -36
  57. package/dist/vercel/codec/events.d.ts +51 -0
  58. package/dist/vercel/codec/index.d.ts +24 -12
  59. package/dist/vercel/codec/reducer.d.ts +144 -0
  60. package/dist/vercel/codec/tool-transitions.d.ts +2 -2
  61. package/dist/vercel/index.d.ts +4 -5
  62. package/dist/vercel/react/ably-ai-transport-vercel-react.js +3907 -3266
  63. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  64. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +33 -8
  65. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  66. package/dist/vercel/react/contexts/chat-transport-context.d.ts +7 -6
  67. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
  68. package/dist/vercel/react/index.d.ts +1 -2
  69. package/dist/vercel/react/use-chat-transport.d.ts +30 -26
  70. package/dist/vercel/react/use-message-sync.d.ts +17 -30
  71. package/dist/vercel/run-end-reason.d.ts +29 -0
  72. package/dist/vercel/transport/chat-transport.d.ts +43 -24
  73. package/dist/vercel/transport/index.d.ts +25 -21
  74. package/dist/vercel/transport/run-output-stream.d.ts +56 -0
  75. package/dist/version.d.ts +2 -0
  76. package/package.json +30 -23
  77. package/src/constants.ts +124 -51
  78. package/src/core/agent.ts +68 -0
  79. package/src/core/codec/decoder.ts +71 -98
  80. package/src/core/codec/encoder.ts +113 -65
  81. package/src/core/codec/index.ts +13 -6
  82. package/src/core/codec/lifecycle-tracker.ts +10 -9
  83. package/src/core/codec/types.ts +436 -120
  84. package/src/core/transport/agent-session.ts +1344 -0
  85. package/src/core/transport/branch-chain.ts +58 -0
  86. package/src/core/transport/client-session.ts +775 -0
  87. package/src/core/transport/decode-fold.ts +91 -0
  88. package/src/core/transport/headers.ts +181 -22
  89. package/src/core/transport/index.ts +25 -26
  90. package/src/core/transport/internal/bounded-map.ts +27 -0
  91. package/src/core/transport/invocation.ts +98 -0
  92. package/src/core/transport/load-conversation.ts +355 -0
  93. package/src/core/transport/load-history.ts +269 -0
  94. package/src/core/transport/pipe-stream.ts +54 -39
  95. package/src/core/transport/run-manager.ts +249 -0
  96. package/src/core/transport/tree.ts +926 -308
  97. package/src/core/transport/types/agent.ts +407 -0
  98. package/src/core/transport/types/client.ts +211 -0
  99. package/src/core/transport/types/shared.ts +27 -0
  100. package/src/core/transport/types/tree.ts +344 -0
  101. package/src/core/transport/types/view.ts +259 -0
  102. package/src/core/transport/types.ts +13 -706
  103. package/src/core/transport/view.ts +864 -433
  104. package/src/errors.ts +22 -9
  105. package/src/event-emitter.ts +3 -2
  106. package/src/index.ts +52 -41
  107. package/src/logger.ts +14 -1
  108. package/src/react/contexts/client-session-context.ts +41 -0
  109. package/src/react/contexts/client-session-provider.tsx +186 -0
  110. package/src/react/create-session-hooks.ts +141 -0
  111. package/src/react/index.ts +23 -13
  112. package/src/react/internal/use-resolved-session.ts +63 -0
  113. package/src/react/use-ably-messages.ts +32 -22
  114. package/src/react/use-client-session.ts +201 -0
  115. package/src/react/use-create-view.ts +33 -29
  116. package/src/react/use-tree.ts +61 -30
  117. package/src/react/use-view.ts +139 -97
  118. package/src/utils.ts +63 -45
  119. package/src/vercel/codec/decoder.ts +336 -258
  120. package/src/vercel/codec/encoder.ts +343 -205
  121. package/src/vercel/codec/events.ts +87 -0
  122. package/src/vercel/codec/index.ts +60 -13
  123. package/src/vercel/codec/reducer.ts +977 -0
  124. package/src/vercel/codec/tool-transitions.ts +2 -2
  125. package/src/vercel/index.ts +6 -19
  126. package/src/vercel/react/contexts/chat-transport-context.ts +7 -6
  127. package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
  128. package/src/vercel/react/index.ts +3 -5
  129. package/src/vercel/react/use-chat-transport.ts +47 -49
  130. package/src/vercel/react/use-message-sync.ts +80 -39
  131. package/src/vercel/run-end-reason.ts +78 -0
  132. package/src/vercel/transport/chat-transport.ts +392 -98
  133. package/src/vercel/transport/index.ts +39 -38
  134. package/src/vercel/transport/run-output-stream.ts +170 -0
  135. package/src/version.ts +2 -0
  136. package/dist/core/transport/client-transport.d.ts +0 -10
  137. package/dist/core/transport/decode-history.d.ts +0 -43
  138. package/dist/core/transport/server-transport.d.ts +0 -7
  139. package/dist/core/transport/stream-router.d.ts +0 -29
  140. package/dist/core/transport/turn-manager.d.ts +0 -37
  141. package/dist/react/contexts/transport-context.d.ts +0 -31
  142. package/dist/react/contexts/transport-provider.d.ts +0 -49
  143. package/dist/react/create-transport-hooks.d.ts +0 -124
  144. package/dist/react/use-active-turns.d.ts +0 -12
  145. package/dist/react/use-client-transport.d.ts +0 -80
  146. package/dist/vercel/codec/accumulator.d.ts +0 -21
  147. package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
  148. package/dist/vercel/tool-approvals.d.ts +0 -124
  149. package/dist/vercel/tool-events.d.ts +0 -26
  150. package/src/core/transport/client-transport.ts +0 -977
  151. package/src/core/transport/decode-history.ts +0 -485
  152. package/src/core/transport/server-transport.ts +0 -612
  153. package/src/core/transport/stream-router.ts +0 -136
  154. package/src/core/transport/turn-manager.ts +0 -165
  155. package/src/react/contexts/transport-context.ts +0 -37
  156. package/src/react/contexts/transport-provider.tsx +0 -164
  157. package/src/react/create-transport-hooks.ts +0 -144
  158. package/src/react/use-active-turns.ts +0 -72
  159. package/src/react/use-client-transport.ts +0 -197
  160. package/src/vercel/codec/accumulator.ts +0 -588
  161. package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
  162. package/src/vercel/tool-approvals.ts +0 -380
  163. package/src/vercel/tool-events.ts +0 -53
@@ -1,8 +1,12 @@
1
1
  /**
2
- * Core codec interfaces as defined in the general codec specification.
2
+ * Core codec interfaces for the event-sourced model.
3
3
  *
4
- * These types define the contract between domain event streams and Ably's
5
- * native message primitives (publish, append, update, delete).
4
+ * The codec describes the wire as a flat stream of TEvent values. A reducer
5
+ * folds events into an opaque TProjection. The SDK extracts TMessage[] from
6
+ * the projection to populate the conversation Tree.
7
+ *
8
+ * All types are framework-agnostic. Domain codecs (e.g. the Vercel codec)
9
+ * choose concrete shapes for TEvent / TProjection / TMessage.
6
10
  */
7
11
  import type * as Ably from 'ably';
8
12
  /**
@@ -18,9 +22,9 @@ export interface ChannelWriter {
18
22
  /** Replace the data of an existing message identified by its serial. */
19
23
  updateMessage(message: Ably.Message, operation?: Ably.MessageOperation, options?: Ably.PublishOptions): Promise<Ably.UpdateDeleteResult>;
20
24
  }
21
- /** Shape of the extras object passed through WriteOptions and EncoderOptions. */
25
+ /** Shape of the extras config passed through WriteOptions and EncoderOptions. */
22
26
  export interface Extras {
23
- /** Headers to attach to the Ably message extras. */
27
+ /** Transport-tier headers to attach to the message's `extras.ai.transport` namespace. */
24
28
  headers?: Record<string, string>;
25
29
  }
26
30
  /** Per-write overrides for encoder operations. */
@@ -29,15 +33,16 @@ export interface WriteOptions {
29
33
  clientId?: string;
30
34
  /** Override the default extras for this write. */
31
35
  extras?: Extras;
32
- /** Message identity for accumulator correlation. Stamped as `x-ably-msg-id`. */
36
+ /** Message identity for projection routing. Stamped as `codec-message-id`. */
33
37
  messageId?: string;
34
38
  }
35
39
  /**
36
40
  * A codec-agnostic description of a discrete Ably message. Used on both sides:
37
41
  * - **Encode:** the domain encoder describes what to publish; the encoder core
38
42
  * handles header merging, clientId resolution, and the actual publish.
39
- * - **Decode:** the decoder core extracts these fields from an `Ably.InboundMessage`
40
- * before calling domain hooks, keeping hooks free of Ably SDK types.
43
+ * - **Decode:** the decoder core extracts these fields from an
44
+ * `Ably.InboundMessage` before calling domain hooks, keeping hooks free of
45
+ * Ably SDK types.
41
46
  *
42
47
  * Data is `unknown` because discrete messages can carry arbitrary payloads
43
48
  * (strings, objects, etc.) — Ably handles serialization natively.
@@ -47,23 +52,34 @@ export interface MessagePayload {
47
52
  name: string;
48
53
  /** Message data. Ably handles serialization — strings, objects, and arrays are all valid. */
49
54
  data: unknown;
50
- /** Headers from the Ably message extras. */
51
- headers?: Record<string, string>;
55
+ /** Codec-tier headers the codec's own fields, carried under `extras.ai.codec`. */
56
+ codecHeaders?: Record<string, string>;
57
+ /**
58
+ * Transport-tier headers a codec needs to stamp directly (e.g. `role`,
59
+ * `status`), carried under `extras.ai.transport`. Most codec payloads leave
60
+ * this unset and let the transport layer supply transport headers via config.
61
+ */
62
+ transportHeaders?: Record<string, string>;
52
63
  /** Mark this message as ephemeral (not persisted in channel history). Only meaningful on encode. */
53
64
  ephemeral?: boolean;
54
65
  }
55
66
  /**
56
- * Payload for streamed messages. Data must be a string because
57
- * the message append lifecycle uses text append/accumulate semantics —
58
- * deltas are concatenated for recovery and prefix-matching on the decoder.
67
+ * Payload for streamed messages. Data must be a string because the message
68
+ * append lifecycle uses text append/accumulate semantics — deltas are
69
+ * concatenated for recovery and prefix-matching on the decoder.
59
70
  */
60
71
  export interface StreamPayload {
61
72
  /** Ably message name (e.g. "text", "reasoning", "tool-input"). */
62
73
  name: string;
63
74
  /** Initial or closing data for the stream. Must be a string for append/accumulate semantics. */
64
75
  data: string;
65
- /** Headers from the Ably message extras. */
66
- headers?: Record<string, string>;
76
+ /** Codec-tier headers the codec's own fields, carried under `extras.ai.codec`. */
77
+ codecHeaders?: Record<string, string>;
78
+ /**
79
+ * Transport-tier headers a codec needs to stamp directly (e.g. `role`,
80
+ * `status`), carried under `extras.ai.transport`.
81
+ */
82
+ transportHeaders?: Record<string, string>;
67
83
  }
68
84
  /**
69
85
  * Running state of a streamed message tracked by the decoder core.
@@ -76,130 +92,406 @@ export interface StreamTrackerState {
76
92
  streamId: string;
77
93
  /** Full accumulated text so far. */
78
94
  accumulated: string;
79
- /** Current headers for this stream. Initially set from the first publish, but may be replaced on update. */
80
- headers: Record<string, string>;
81
- /** Whether this stream has been closed (finished or aborted). */
95
+ /**
96
+ * Current codec-tier headers (`extras.ai.codec`) for this stream. Initially
97
+ * set from the first publish, but may be replaced on update.
98
+ */
99
+ codecHeaders: Record<string, string>;
100
+ /**
101
+ * Current transport-tier headers (`extras.ai.transport`) for this stream.
102
+ * Initially set from the first publish, but may be replaced on update.
103
+ */
104
+ transportHeaders: Record<string, string>;
105
+ /** Whether this stream has been closed (complete or cancelled). */
82
106
  closed: boolean;
83
107
  }
84
108
  /**
85
- * The subset of encoder operations that are stateless — safe for long-lived
86
- * reuse across turns. Publishes complete messages and discrete events without
87
- * any streaming lifecycle (no trackers, no pending appends, no close).
88
- *
89
- * The server transport calls `writeMessages` to publish user messages to the
90
- * channel. All messages in a single call are published atomically and share
91
- * a single `x-ably-msg-id`, forming one node in the conversation tree.
92
- * `writeEvent` is a public API for consumers to publish standalone discrete
93
- * events outside the streaming flow — it is not called by the transport internally.
109
+ * Transport-derived metadata passed alongside each TEvent into `fold`. Read
110
+ * by the SDK from the inbound Ably message and stamped before each fold call.
94
111
  */
95
- export interface DiscreteEncoder<TEvent, TMessage> {
112
+ export interface ReducerMeta {
96
113
  /**
97
- * Encode and publish one or more domain messages atomically in a single
98
- * channel publish. All messages share the encoder's transport headers
99
- * (including `x-ably-msg-id`), so they form one logical unit in the
100
- * conversation tree.
114
+ * Ably channel serial of the message that produced this event. The reducer
115
+ * uses this for idempotency / dedup: events at or below the projection's
116
+ * high-water-mark serial must be skipped (no-op return).
101
117
  */
102
- writeMessages(messages: TMessage[], options?: WriteOptions): Promise<Ably.PublishResult>;
118
+ serial: string;
103
119
  /**
104
- * Encode and publish a single domain event as a standalone discrete message.
105
- * Available for consumers to publish events outside the streaming flow.
106
- * Implementations should throw for event types that are only meaningful
107
- * within a stream (e.g. text deltas).
120
+ * Optional `codec-message-id` from the inbound Ably message. Reducers use this
121
+ * to route an event to a target message within the projection (e.g. to
122
+ * amend an existing assistant message addressed by its codec-message-id).
108
123
  */
109
- writeEvent(event: TEvent, options?: WriteOptions): Promise<Ably.PublishResult>;
124
+ messageId?: string;
110
125
  }
111
126
  /**
112
- * Full streaming encoder with single-turn lifecycle. Extends
113
- * `DiscreteEncoder` with stateful streaming operations (`appendEvent` for
114
- * content streams, `close` to flush). Used by the server transport.
127
+ * Pure, stateless reducer contract. A reducer folds TEvents into an opaque
128
+ * TProjection. The same `(state, event, meta)` triple must produce the same
129
+ * result every time — `fold` is a pure function and the reducer holds no
130
+ * instance state.
131
+ *
132
+ * Idempotency: re-folding an event whose serial has already been incorporated
133
+ * must be a no-op. The reducer is free to store a high-water-mark inside the
134
+ * projection.
135
+ *
136
+ * Mutation: `fold` is allowed to mutate the projection passed in and return
137
+ * it. The caller treats the projection as single-owner and never retains a
138
+ * reference to an old state.
115
139
  */
116
- export interface StreamEncoder<TEvent, TMessage> extends DiscreteEncoder<TEvent, TMessage> {
117
- /** Encode and append a streaming domain event to an in-progress stream (delta semantics). */
118
- appendEvent(event: TEvent, options?: WriteOptions): Promise<void>;
140
+ export interface Reducer<TEvent, TProjection> {
119
141
  /**
120
- * Abort all in-progress streams and publish a codec-specific abort signal.
121
- * Called by the transport when a turn is cancelled. Idempotent calling
122
- * abort after all streams are already aborted is a no-op.
123
- * @param reason - Optional reason string for the abort (e.g. 'cancelled').
142
+ * Build an empty initial projection. Called once per conversation node — a
143
+ * Run node or a run-less input node before any of that node's events are
144
+ * folded.
124
145
  */
125
- abort(reason?: string): Promise<void>;
126
- /** Flush all pending appends and close the encoder. */
127
- close(): Promise<void>;
146
+ init(): TProjection;
147
+ /**
148
+ * Fold one TEvent into the projection and return the updated projection.
149
+ * The reducer may mutate `state` in place.
150
+ */
151
+ fold(state: TProjection, event: TEvent, meta: ReducerMeta): TProjection;
128
152
  }
129
- /**
130
- * A single output from the decoder: either a domain event or a complete domain message.
131
- * Event outputs may carry a `messageId` read from the `x-ably-msg-id` header, which the
132
- * accumulator uses to route events to the correct in-progress message.
133
- */
134
- export type DecoderOutput<TEvent, TMessage> = {
135
- kind: 'event';
136
- event: TEvent;
137
- messageId?: string;
138
- } | {
139
- kind: 'message';
140
- message: TMessage;
141
- };
142
- /** Decodes Ably messages into domain events and messages. */
143
- export interface StreamDecoder<TEvent, TMessage> {
144
- /** Decode a single Ably message into zero or more domain outputs. */
145
- decode(message: Ably.InboundMessage): DecoderOutput<TEvent, TMessage>[];
146
- }
147
- /** Accumulates decoder outputs into a list of domain messages, tracking active streams. */
148
- export interface MessageAccumulator<TEvent, TMessage> {
149
- /** Process a batch of decoder outputs, updating internal message state. */
150
- processOutputs(outputs: DecoderOutput<TEvent, TMessage>[]): void;
151
- /** Apply an external update to a message (e.g. from an update callback). */
152
- updateMessage(message: TMessage): void;
153
- /**
154
- * Ensure the accumulator is ready to process events for the given message.
155
- * If not already active, creates internal tracking state from the message.
156
- * If already active, syncs internal state with the provided message
157
- * (picking up external changes like cross-turn amendments).
158
- * Idempotent — safe to call before every processOutputs.
159
- */
160
- initMessage(messageId: string, message: TMessage): void;
161
- /**
162
- * Mark a message as completed. Removes it from active tracking so it
163
- * appears in {@link completedMessages}. No-op if not active.
164
- */
165
- completeMessage(messageId: string): void;
166
- /** All messages accumulated so far (in-progress and completed). */
167
- readonly messages: TMessage[];
168
- /** Only messages whose streams have finished. */
169
- readonly completedMessages: TMessage[];
170
- /** Whether any stream is still actively receiving data. */
171
- readonly hasActiveStream: boolean;
172
- }
173
- /** Options passed to a codec's `createEncoder` factory to configure default identity and message hooks. */
153
+ /** Options passed to a codec's `createEncoder` factory. */
174
154
  export interface EncoderOptions {
175
155
  /** Default clientId for all writes. */
176
156
  clientId?: string;
177
157
  /** Default extras (e.g. headers) merged into every Ably message. */
178
158
  extras?: Extras;
179
- /** Hook called before each Ably message is published. Mutate the message in place to add transport-level headers. */
159
+ /** Hook called before each Ably message is published. Mutate the message in place to add transport-level headers under `extras.ai`. */
180
160
  onMessage?: (message: Ably.Message) => void;
181
161
  /**
182
- * Domain-level message identity. Domain encoders use this as a fallback
183
- * messageId when a lifecycle chunk (e.g. `start`) does not provide one,
184
- * ensuring useChat and the transport accumulator assign the same ID.
162
+ * Default `codec-message-id` for messages where the event payload doesn't
163
+ * supply one. Overridden by `WriteOptions.messageId` per-publish.
185
164
  */
186
165
  messageId?: string;
187
166
  }
188
167
  /**
189
- * The complete codec contract that a core transport needs.
168
+ * Stateful encoder for a single channel. Two publish methods enforce
169
+ * direction at the call site — `publishInput` for client-published events
170
+ * (`ai-input` wire) and `publishOutput` for agent-published events
171
+ * (`ai-output` wire). Stream-tracker state lives inside the encoder and
172
+ * is shared across both directions.
173
+ */
174
+ export interface Encoder<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent> {
175
+ /**
176
+ * Encode and publish a single client input on the `ai-input` wire.
177
+ * Throws synchronously if the codec cannot encode the given input
178
+ * variant.
179
+ */
180
+ publishInput(input: TInput, options?: WriteOptions): Promise<void>;
181
+ /**
182
+ * Encode and publish a single agent output on the `ai-output` wire.
183
+ * Throws synchronously if the codec cannot encode the given output
184
+ * variant.
185
+ */
186
+ publishOutput(output: TOutput, options?: WriteOptions): Promise<void>;
187
+ /**
188
+ * Cancel any in-progress streams and emit a codec-specific cancel signal.
189
+ * Idempotent across repeated calls — a second `cancel` is a no-op. Must not
190
+ * be called after `close`; doing so throws because the encoder is already
191
+ * closed.
192
+ * @param reason - Optional reason string for the cancellation (e.g. 'cancelled').
193
+ */
194
+ cancel(reason?: string): Promise<void>;
195
+ /** Flush pending appends and release encoder resources. */
196
+ close(): Promise<void>;
197
+ }
198
+ /**
199
+ * Tagged result of decoding one inbound Ably message — the codec routes
200
+ * by the wire `name` and returns inputs and outputs separately so the
201
+ * SDK never has to introspect direction.
202
+ */
203
+ export interface DecodedMessage<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent> {
204
+ /** Inputs decoded from the inbound message (only populated when the wire `name` is `ai-input`). */
205
+ inputs: TInput[];
206
+ /** Outputs decoded from the inbound message (only populated when the wire `name` is `ai-output`). */
207
+ outputs: TOutput[];
208
+ }
209
+ /**
210
+ * Stateful decoder for a single channel subscription. Maintains internal
211
+ * stream-tracker state across messages so that mid-stream join (history
212
+ * compaction, partial-history page boundary, rewind miss) synthesizes any
213
+ * missing start events before deltas reach the SDK — the reducer always
214
+ * sees a clean `(start, delta*, end)` sequence.
215
+ */
216
+ export interface Decoder<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent> {
217
+ /** Decode one Ably inbound message into the input/output halves. */
218
+ decode(message: Ably.InboundMessage): DecodedMessage<TInput, TOutput>;
219
+ }
220
+ /**
221
+ * Structural base every codec input variant must satisfy. Codec authors
222
+ * compose their `TInput` union from variants extending this type; the
223
+ * transport reads the routing fields directly off the variant to stamp
224
+ * wire headers, so no runtime classification step is needed.
225
+ *
226
+ * The `kind` discriminator is required on every variant so the codec's
227
+ * reducer can switch on it. Codec authors pick the literal value per
228
+ * variant; the SDK ships well-known literals (`'user-message'`,
229
+ * `'regenerate'`) — see {@link UserMessage}, {@link Regenerate}.
230
+ */
231
+ export interface CodecInputEvent {
232
+ /**
233
+ * Discriminator. Codec authors pick the literal value per variant. The
234
+ * SDK reserves the literals used by well-known variants
235
+ * ({@link UserMessage}, {@link Regenerate}); codec-specific variants pick
236
+ * any other literal.
237
+ */
238
+ kind: string;
239
+ /**
240
+ * Sets the wire `parent` header — the codec-message-id of the
241
+ * preceding codec-message on this branch. When omitted, the SDK
242
+ * auto-computes the parent from the visible branch tail at send time.
243
+ */
244
+ parent?: string;
245
+ /**
246
+ * Pointer to another codec-message this input references. The semantic
247
+ * depends on `kind` — for `regenerate`, the assistant codec-message to
248
+ * regenerate; codec-specific `kind`s may give it other meanings. The
249
+ * input event itself does not create a fork — it requests one. The fork
250
+ * relationship is established on the agent's response (and on
251
+ * `ai-run-start`), which the codec encoder maps `target` to via the
252
+ * wire's `fork-of` header.
253
+ */
254
+ target?: string;
255
+ /**
256
+ * Targets an existing codec-message-id instead of minting a fresh one.
257
+ * Used by continuation inputs (tool results, approval responses) that
258
+ * amend an existing assistant message rather than creating a new one;
259
+ * the wire's `codec-message-id` is stamped with this value so
260
+ * the reducer's direct-fold path matches by codec-message-id.
261
+ */
262
+ codecMessageId?: string;
263
+ }
264
+ /**
265
+ * Well-known input variant: a new user message in the codec's domain
266
+ * representation. Pinned `kind: 'user-message'`. Produced by the SDK's
267
+ * `Codec.createUserMessage` factory and published via `View.send`
268
+ * (e.g. `view.send(codec.createUserMessage(message))`).
269
+ * @template TMessage - The codec's per-message domain type.
270
+ */
271
+ export interface UserMessage<TMessage> extends CodecInputEvent {
272
+ /** Discriminator. */
273
+ kind: 'user-message';
274
+ /** The user's message in the codec's domain representation. */
275
+ message: TMessage;
276
+ }
277
+ /**
278
+ * Well-known input variant: request that an existing assistant
279
+ * codec-message be regenerated. Pinned `kind: 'regenerate'`. This event
280
+ * is a signal, not itself a fork — `target` names the assistant message
281
+ * to regenerate, and `parent` names the user message the new assistant
282
+ * threads under. Both are required; the codec cannot regenerate without
283
+ * both. Produced by the SDK's `Codec.createRegenerate` factory and
284
+ * surfaced via `View.regenerate`.
285
+ */
286
+ export interface Regenerate extends CodecInputEvent {
287
+ /** Discriminator. */
288
+ kind: 'regenerate';
289
+ /** The codec-message-id of the assistant to regenerate. Required. */
290
+ target: string;
291
+ /** The codec-message-id of the parent user message the new assistant threads under. Required. */
292
+ parent: string;
293
+ }
294
+ /**
295
+ * Well-known input variant: client-published tool result (success). The
296
+ * tool ran and produced output. Mutates the assistant codec-message
297
+ * addressed by `codecMessageId` — the codec's reducer applies the result
298
+ * onto the existing tool-call state of the referenced assistant.
299
+ *
300
+ * The core is domain-independent: it knows only that this input amends the
301
+ * assistant at `codecMessageId` and carries a codec-defined `payload`. The
302
+ * shape of `payload` (e.g. the tool-call id and output value) is supplied
303
+ * by the codec via `TPayload` — see the Vercel layer's payload type.
190
304
  *
191
- * Combines factory methods (createEncoder, createDecoder, createAccumulator)
192
- * with protocol knowledge (isTerminal). Transport-level concerns like turn
193
- * correlation, optimistic reconciliation, and cancel signals
194
- * are handled by the transport layer using standard `x-ably-*` headers.
195
- */
196
- export interface Codec<TEvent, TMessage> {
197
- /** Create a streaming encoder bound to the given channel. */
198
- createEncoder(channel: ChannelWriter, options?: EncoderOptions): StreamEncoder<TEvent, TMessage>;
199
- /** Create a decoder for converting Ably messages back into domain outputs. */
200
- createDecoder(): StreamDecoder<TEvent, TMessage>;
201
- /** Create an accumulator for building domain messages from decoder outputs. */
202
- createAccumulator(): MessageAccumulator<TEvent, TMessage>;
203
- /** Whether an event signals stream completion (finish, error, abort). */
204
- isTerminal(event: TEvent): boolean;
305
+ * Codecs opt in to client-side tool resolution by including this variant
306
+ * in their `TInput` union. Codecs whose domain model doesn't natively
307
+ * distinguish client-side tool results as a top-level event type (e.g.
308
+ * Anthropic Messages, where tool results are normally embedded in a
309
+ * user-role message) can still use this variant — the codec's reducer
310
+ * translates the wire-level update into the codec's domain representation.
311
+ * @template TPayload - The codec's domain payload for a tool result.
312
+ */
313
+ export interface ToolResult<TPayload> extends CodecInputEvent {
314
+ /** Discriminator. */
315
+ kind: 'tool-result';
316
+ /** The assistant codec-message containing the tool call. Required. */
317
+ codecMessageId: string;
318
+ /** The codec's domain payload describing the tool result. */
319
+ payload: TPayload;
320
+ }
321
+ /**
322
+ * Well-known input variant: client-published tool result (failure). The
323
+ * tool ran and failed. Mutates the assistant codec-message addressed by
324
+ * `codecMessageId`. The failure detail (e.g. tool-call id and error text)
325
+ * is the codec's domain `payload` — see the Vercel layer's payload type.
326
+ * @template TPayload - The codec's domain payload for a tool-result failure.
327
+ */
328
+ export interface ToolResultError<TPayload> extends CodecInputEvent {
329
+ /** Discriminator. */
330
+ kind: 'tool-result-error';
331
+ /** The assistant codec-message containing the tool call. Required. */
332
+ codecMessageId: string;
333
+ /** The codec's domain payload describing the failure. */
334
+ payload: TPayload;
335
+ }
336
+ /**
337
+ * Well-known input variant: client-published response to an
338
+ * agent-emitted tool-approval request. Mutates the assistant
339
+ * codec-message addressed by `codecMessageId` — flipping the targeted
340
+ * tool call from pending-approval to approved or denied. The decision
341
+ * detail (e.g. tool-call id, approved flag, reason) is the codec's domain
342
+ * `payload` — see the Vercel layer's payload type.
343
+ *
344
+ * Codecs may layer approval semantics on top of domain models that don't
345
+ * natively support gating tool execution behind an approval — the codec
346
+ * is responsible for mapping the decision into its own representation.
347
+ * @template TPayload - The codec's domain payload for an approval response.
348
+ */
349
+ export interface ToolApprovalResponse<TPayload> extends CodecInputEvent {
350
+ /** Discriminator. */
351
+ kind: 'tool-approval-response';
352
+ /** The assistant codec-message containing the tool call. Required. */
353
+ codecMessageId: string;
354
+ /** The codec's domain payload describing the approval decision. */
355
+ payload: TPayload;
356
+ }
357
+ /**
358
+ * Extract the domain `payload` type of a codec's {@link ToolResult} member
359
+ * from its `TInput` union, or `never` if the codec has no tool-result
360
+ * variant. Used to type {@link Codec.createToolResult} without adding a
361
+ * standalone type parameter to the codec.
362
+ * @template TInput - The codec's input union.
363
+ */
364
+ export type ToolResultPayloadOf<TInput> = TInput extends ToolResult<infer P> ? P : never;
365
+ /**
366
+ * Extract the domain `payload` type of a codec's {@link ToolResultError}
367
+ * member from its `TInput` union, or `never` if absent.
368
+ * @template TInput - The codec's input union.
369
+ */
370
+ export type ToolResultErrorPayloadOf<TInput> = TInput extends ToolResultError<infer P> ? P : never;
371
+ /**
372
+ * Extract the domain `payload` type of a codec's {@link ToolApprovalResponse}
373
+ * member from its `TInput` union, or `never` if absent.
374
+ * @template TInput - The codec's input union.
375
+ */
376
+ export type ToolApprovalResponsePayloadOf<TInput> = TInput extends ToolApprovalResponse<infer P> ? P : never;
377
+ /**
378
+ * Structural base every codec output variant must satisfy. The output
379
+ * counterpart of {@link CodecInputEvent}: pins a `type` discriminator so
380
+ * the SDK can reliably narrow `TInput | TOutput` (inputs carry `kind`,
381
+ * outputs carry `type`) and reserves a contract for future routing
382
+ * fields on outputs without a breaking generic-arity change.
383
+ *
384
+ * The `type` discriminator is required on every variant so the codec's
385
+ * reducer can switch on it. `AI.UIMessageChunk` already satisfies this
386
+ * constraint structurally — its `type` literal is assignable to
387
+ * `string` — so the Vercel codec needs no implementation changes.
388
+ *
389
+ * No routing fields today: outputs carry no per-event `codecMessageId` /
390
+ * `parent` / `forkOf` overrides. Those move onto this base when a concrete
391
+ * output needs to carry them.
392
+ */
393
+ export interface CodecOutputEvent {
394
+ /** Discriminator. Codec authors pick the literal value per variant. */
395
+ type: string;
396
+ }
397
+ /**
398
+ * A single domain message paired with the codec-message-id that identifies
399
+ * it on the wire. Returned from {@link Codec.getMessages}.
400
+ *
401
+ * The two fields are deliberately separate: `message` is reconstructed to
402
+ * faithfully reproduce the values the source produced (e.g. the id the AI
403
+ * SDK stream assigned) and is surfaced to the application as-is; the SDK
404
+ * never inspects `message` for identity. All internal correlation —
405
+ * Tree indexing, parent/fork/regenerate routing, branch grouping — keys on
406
+ * `codecMessageId`, the SDK's own client-minted identifier. The two need
407
+ * not be equal.
408
+ * @template TMessage - The codec's per-message domain type.
409
+ */
410
+ export interface CodecMessage<TMessage> {
411
+ /** The SDK's codec-message-id for this message — the correlation key. */
412
+ codecMessageId: string;
413
+ /** The domain message, reconstructed verbatim from the source values. */
414
+ message: TMessage;
415
+ }
416
+ /**
417
+ * The codec describes the wire and folds events into a per-node projection.
418
+ *
419
+ * Type parameters:
420
+ * - `TInput` — the union of input variants the client publishes on the
421
+ * `ai-input` wire. Every variant extends {@link CodecInputEvent}.
422
+ * - `TOutput` — the union of output variants the agent publishes on the
423
+ * `ai-output` wire. Every variant extends {@link CodecOutputEvent}.
424
+ * - `TProjection` — the opaque per-node state the reducer folds events into
425
+ * (one projection per node, whether a RunNode or a run-less input node).
426
+ * The SDK never inspects it directly; use {@link Codec.getMessages} to
427
+ * extract messages for the conversation Tree.
428
+ * - `TMessage` — the per-message shape consumed by the Tree. Returned from
429
+ * {@link Codec.getMessages}.
430
+ */
431
+ export interface Codec<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> extends Reducer<TInput | TOutput, TProjection> {
432
+ /** Create a stateful encoder bound to the given channel. */
433
+ createEncoder(channel: ChannelWriter, options?: EncoderOptions): Encoder<TInput, TOutput>;
434
+ /** Create a stateful decoder for converting Ably inbound messages into typed inputs and outputs. */
435
+ createDecoder(): Decoder<TInput, TOutput>;
436
+ /**
437
+ * Extract the per-message list from a projection, each message paired
438
+ * with its codec-message-id (see {@link CodecMessage}). The SDK uses the
439
+ * `codecMessageId` to correlate messages — it never reads identity from
440
+ * the message itself — and surfaces `message` to the application
441
+ * unchanged.
442
+ */
443
+ getMessages(projection: TProjection): CodecMessage<TMessage>[];
444
+ /**
445
+ * Wrap a TMessage as the codec's well-known {@link UserMessage} variant,
446
+ * returned as a `TInput` ready to publish on the `ai-input` wire. This is
447
+ * the public way to turn a caller-provided TMessage into an input for
448
+ * `View.send`; the client session's seed-message path uses it too. The
449
+ * returned value is a `UserMessage<TMessage>` member of the codec's
450
+ * `TInput` union — typed as `TInput` so callers need no cast.
451
+ * @param message - The user's message in the codec's domain representation.
452
+ * @returns A `TInput` (the codec's {@link UserMessage} variant).
453
+ */
454
+ createUserMessage(message: TMessage): TInput;
455
+ /**
456
+ * Build a {@link Regenerate} for the codec, returned as a `TInput`. The
457
+ * View calls this from `regenerate(messageId)`; the input event is a
458
+ * signal (not itself a fork) that targets the assistant codec-message to
459
+ * be regenerated.
460
+ *
461
+ * The new Run is a CONTINUATION of the regenerated message's Run, not a
462
+ * fork at the Run level. The message-level replacement (the new
463
+ * assistant supersedes the original) happens at the View's
464
+ * projection-extraction step (Spec: AIT-CT13d).
465
+ * @param target - The codec-message-id of the assistant being regenerated.
466
+ * @param parent - The codec-message-id of the parent user message the new assistant threads under.
467
+ * @returns A `TInput` (the codec's {@link Regenerate} variant).
468
+ */
469
+ createRegenerate(target: string, parent: string): TInput;
470
+ /**
471
+ * Build a {@link ToolResult} for the codec, returned as a `TInput`.
472
+ * Amends the assistant at `codecMessageId` with the codec's domain
473
+ * `payload`. Optional — only codecs whose `TInput` includes the
474
+ * {@link ToolResult} variant implement it.
475
+ * @param codecMessageId - The assistant codec-message the result amends.
476
+ * @param payload - The codec's domain tool-result payload.
477
+ * @returns A `TInput` (the codec's {@link ToolResult} variant).
478
+ */
479
+ createToolResult?(codecMessageId: string, payload: ToolResultPayloadOf<TInput>): TInput;
480
+ /**
481
+ * Build a {@link ToolResultError} for the codec, returned as a `TInput`.
482
+ * Optional — only codecs whose `TInput` includes the variant implement it.
483
+ * @param codecMessageId - The assistant codec-message the error amends.
484
+ * @param payload - The codec's domain tool-result-failure payload.
485
+ * @returns A `TInput` (the codec's {@link ToolResultError} variant).
486
+ */
487
+ createToolResultError?(codecMessageId: string, payload: ToolResultErrorPayloadOf<TInput>): TInput;
488
+ /**
489
+ * Build a {@link ToolApprovalResponse} for the codec, returned as a
490
+ * `TInput`. Optional — only codecs whose `TInput` includes the variant
491
+ * implement it.
492
+ * @param codecMessageId - The assistant codec-message the response amends.
493
+ * @param payload - The codec's domain approval-decision payload.
494
+ * @returns A `TInput` (the codec's {@link ToolApprovalResponse} variant).
495
+ */
496
+ createToolApprovalResponse?(codecMessageId: string, payload: ToolApprovalResponsePayloadOf<TInput>): TInput;
205
497
  }
@@ -0,0 +1,10 @@
1
+ import { CodecInputEvent, CodecOutputEvent } from '../codec/types.js';
2
+ import { AgentSession, AgentSessionOptions } from './types.js';
3
+ /**
4
+ * Create an agent (server-side) session bound to the given Realtime client
5
+ * and channel name. The caller owns the client's lifecycle; the session
6
+ * owns its channel.
7
+ * @param options - Session configuration.
8
+ * @returns A new {@link AgentSession} instance.
9
+ */
10
+ export declare const createAgentSession: <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage>(options: AgentSessionOptions<TInput, TOutput, TProjection, TMessage>) => AgentSession<TOutput, TProjection, TMessage>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * buildBranchChain — order a single conversation branch by walking
3
+ * codec-message-id parent links upward from an anchor node to the root.
4
+ *
5
+ * This is the shared ordering spine of the agent's conversation
6
+ * reconstruction and of history decode: both need the same root→anchor
7
+ * sequence of nodes before folding each node's projection. Keeping the walk
8
+ * here — pure, with no codec, no I/O, no logger — lets it be proven in
9
+ * isolation and reused by both engines without drift.
10
+ *
11
+ * Branch selection is implicit: a node reaches only its own ancestors via
12
+ * `parentCodecMessageId`, so sibling branches (edits / regenerates that the
13
+ * anchor did not descend from) are never visited. There is no separate
14
+ * fork/regenerate filtering step — the un-taken sibling is simply unreachable.
15
+ */
16
+ /**
17
+ * The single field {@link buildBranchChain} reads from a node. Richer node-meta
18
+ * shapes (carrying run-id, fork-of, regenerates, …) satisfy this structurally,
19
+ * so callers can pass their full index map directly.
20
+ */
21
+ export interface BranchChainNode {
22
+ /**
23
+ * Codec-message-id of this node's structural parent — the node it hangs off
24
+ * — or `undefined` for a root node. This is the only edge the walk follows.
25
+ */
26
+ parentCodecMessageId: string | undefined;
27
+ }
28
+ /**
29
+ * Walk `parentCodecMessageId` links upward from `anchorCodecMessageId` and
30
+ * return the branch it sits on, ordered root-first (oldest) to anchor (newest,
31
+ * last). The anchor is always the final element.
32
+ *
33
+ * The walk stops at the root (a node with no parent), at a dangling parent
34
+ * (a parent id absent from `nodeMeta` is still included as the chain head,
35
+ * then the walk ends), or on revisiting a node (a cycle in malformed data is
36
+ * broken best-effort rather than looping forever).
37
+ * @param nodeMeta - Lookup from codec-message-id to its node meta. Need not
38
+ * contain the anchor or every ancestor; missing entries simply end the walk.
39
+ * @param anchorCodecMessageId - The codec-message-id to start the walk from
40
+ * (the newest node on the branch; included in the result).
41
+ * @returns The branch's codec-message-ids ordered root-first to anchor-last.
42
+ */
43
+ export declare const buildBranchChain: (nodeMeta: ReadonlyMap<string, BranchChainNode>, anchorCodecMessageId: string) => string[];