@ably/ai-transport 0.0.1 → 0.1.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.
- package/README.md +54 -47
- package/dist/ably-ai-transport.js +1006 -539
- package/dist/ably-ai-transport.js.map +1 -1
- package/dist/ably-ai-transport.umd.cjs +1 -1
- package/dist/ably-ai-transport.umd.cjs.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/core/codec/types.d.ts +19 -2
- package/dist/core/transport/decode-history.d.ts +8 -6
- package/dist/core/transport/headers.d.ts +4 -2
- package/dist/core/transport/index.d.ts +4 -1
- package/dist/core/transport/pipe-stream.d.ts +3 -2
- package/dist/core/transport/stream-router.d.ts +11 -1
- package/dist/core/transport/tree.d.ts +171 -0
- package/dist/core/transport/turn-manager.d.ts +4 -1
- package/dist/core/transport/types.d.ts +270 -119
- package/dist/core/transport/view.d.ts +166 -0
- package/dist/errors.d.ts +19 -2
- package/dist/index.d.ts +3 -1
- package/dist/react/ably-ai-transport-react.js +1019 -486
- package/dist/react/ably-ai-transport-react.js.map +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
- package/dist/react/contexts/transport-context.d.ts +31 -0
- package/dist/react/contexts/transport-provider.d.ts +49 -0
- package/dist/react/create-transport-hooks.d.ts +124 -0
- package/dist/react/index.d.ts +14 -8
- package/dist/react/use-ably-messages.d.ts +14 -8
- package/dist/react/use-active-turns.d.ts +7 -3
- package/dist/react/use-client-transport.d.ts +78 -5
- package/dist/react/use-create-view.d.ts +22 -0
- package/dist/react/use-tree.d.ts +20 -0
- package/dist/react/use-view.d.ts +79 -0
- package/dist/vercel/ably-ai-transport-vercel.js +1478 -842
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
- package/dist/vercel/codec/tool-transitions.d.ts +50 -0
- package/dist/vercel/index.d.ts +3 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +9099 -852
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +45 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +32 -0
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +84 -0
- package/dist/vercel/react/index.d.ts +5 -0
- package/dist/vercel/react/use-chat-transport.d.ts +61 -20
- package/dist/vercel/react/use-message-sync.d.ts +41 -9
- package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +30 -0
- package/dist/vercel/tool-approvals.d.ts +124 -0
- package/dist/vercel/tool-events.d.ts +26 -0
- package/dist/vercel/transport/chat-transport.d.ts +33 -11
- package/dist/vercel/transport/index.d.ts +5 -2
- package/package.json +23 -17
- package/src/constants.ts +6 -0
- package/src/core/codec/encoder.ts +10 -1
- package/src/core/codec/types.ts +19 -3
- package/src/core/transport/client-transport.ts +382 -364
- package/src/core/transport/decode-history.ts +229 -81
- package/src/core/transport/headers.ts +6 -2
- package/src/core/transport/index.ts +13 -5
- package/src/core/transport/pipe-stream.ts +8 -5
- package/src/core/transport/server-transport.ts +212 -58
- package/src/core/transport/stream-router.ts +21 -3
- package/src/core/transport/{conversation-tree.ts → tree.ts} +192 -77
- package/src/core/transport/turn-manager.ts +28 -10
- package/src/core/transport/types.ts +318 -139
- package/src/core/transport/view.ts +840 -0
- package/src/errors.ts +21 -1
- package/src/index.ts +10 -5
- package/src/react/contexts/transport-context.ts +37 -0
- package/src/react/contexts/transport-provider.tsx +164 -0
- package/src/react/create-transport-hooks.ts +144 -0
- package/src/react/index.ts +15 -8
- package/src/react/use-ably-messages.ts +34 -16
- package/src/react/use-active-turns.ts +28 -17
- package/src/react/use-client-transport.ts +184 -24
- package/src/react/use-create-view.ts +68 -0
- package/src/react/use-tree.ts +53 -0
- package/src/react/use-view.ts +233 -0
- package/src/react/vite.config.ts +4 -1
- package/src/vercel/codec/accumulator.ts +64 -79
- package/src/vercel/codec/decoder.ts +11 -8
- package/src/vercel/codec/encoder.ts +68 -54
- package/src/vercel/codec/index.ts +0 -2
- package/src/vercel/codec/tool-transitions.ts +122 -0
- package/src/vercel/index.ts +17 -0
- package/src/vercel/react/contexts/chat-transport-context.ts +40 -0
- package/src/vercel/react/contexts/chat-transport-provider.tsx +122 -0
- package/src/vercel/react/index.ts +14 -0
- package/src/vercel/react/use-chat-transport.ts +164 -42
- package/src/vercel/react/use-message-sync.ts +77 -19
- package/src/vercel/react/use-staged-add-tool-approval-response.ts +87 -0
- package/src/vercel/react/vite.config.ts +4 -2
- package/src/vercel/tool-approvals.ts +380 -0
- package/src/vercel/tool-events.ts +53 -0
- package/src/vercel/transport/chat-transport.ts +225 -79
- package/src/vercel/transport/index.ts +14 -3
- package/dist/core/transport/conversation-tree.d.ts +0 -9
- package/dist/react/use-conversation-tree.d.ts +0 -20
- package/dist/react/use-edit.d.ts +0 -7
- package/dist/react/use-history.d.ts +0 -19
- package/dist/react/use-messages.d.ts +0 -7
- package/dist/react/use-regenerate.d.ts +0 -7
- package/dist/react/use-send.d.ts +0 -7
- package/src/react/use-conversation-tree.ts +0 -71
- package/src/react/use-edit.ts +0 -24
- package/src/react/use-history.ts +0 -111
- package/src/react/use-messages.ts +0 -32
- package/src/react/use-regenerate.ts +0 -24
- package/src/react/use-send.ts +0 -25
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger } from '../../logger.js';
|
|
2
|
-
import { Codec } from '../codec/types.js';
|
|
2
|
+
import { Codec, WriteOptions } from '../codec/types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Core transport types, parameterized by codec event and message types.
|
|
5
5
|
*
|
|
@@ -35,13 +35,6 @@ export interface CancelRequest {
|
|
|
35
35
|
/** Map of turnId to the ownerClientId for the matched turns. */
|
|
36
36
|
turnOwners: Map<string, string>;
|
|
37
37
|
}
|
|
38
|
-
/** A domain message paired with its Ably transport headers. Used on the read path to snapshot conversation state (e.g. for HTTP POST bodies). */
|
|
39
|
-
export interface MessageWithHeaders<TMessage> {
|
|
40
|
-
/** The domain message. */
|
|
41
|
-
message: TMessage;
|
|
42
|
-
/** Ably headers associated with this message (transport metadata, domain headers). */
|
|
43
|
-
headers?: Record<string, string>;
|
|
44
|
-
}
|
|
45
38
|
/** Options for creating a server transport. */
|
|
46
39
|
export interface ServerTransportOptions<TEvent, TMessage> {
|
|
47
40
|
/** The Ably channel to publish to. Must match the client's channel. */
|
|
@@ -52,35 +45,76 @@ export interface ServerTransportOptions<TEvent, TMessage> {
|
|
|
52
45
|
logger?: Logger;
|
|
53
46
|
/**
|
|
54
47
|
* Called with non-fatal transport-level errors not scoped to any turn.
|
|
55
|
-
* Examples: cancel listener subscription failure, channel attach errors
|
|
48
|
+
* Examples: cancel listener subscription failure, channel attach errors,
|
|
49
|
+
* channel continuity loss (FAILED/SUSPENDED/DETACHED or re-attach with
|
|
50
|
+
* `resumed: false`).
|
|
56
51
|
*/
|
|
57
52
|
onError?: (error: Ably.ErrorInfo) => void;
|
|
58
53
|
}
|
|
59
|
-
/** Options for addMessages — per-operation overrides for
|
|
54
|
+
/** Options for addMessages — per-operation overrides for attribution. */
|
|
60
55
|
export interface AddMessageOptions {
|
|
61
56
|
/** The user's clientId for attribution. */
|
|
62
57
|
clientId?: string;
|
|
63
|
-
/** The msg-id of the immediately preceding message in this branch. */
|
|
64
|
-
parent?: string | null;
|
|
65
|
-
/** The msg-id of the message this one replaces (creates a fork). */
|
|
66
|
-
forkOf?: string;
|
|
67
58
|
}
|
|
68
59
|
/** Result of publishing user messages via addMessages. */
|
|
69
60
|
export interface AddMessagesResult {
|
|
70
61
|
/** The `x-ably-msg-id` of each published message, in order. */
|
|
71
62
|
msgIds: string[];
|
|
72
63
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
64
|
+
/**
|
|
65
|
+
* A batch of events targeting an existing message.
|
|
66
|
+
* Each node specifies the target message and the events to apply to it.
|
|
67
|
+
* Used for cross-turn updates such as tool result delivery.
|
|
68
|
+
*/
|
|
69
|
+
export interface EventsNode<TEvent> {
|
|
70
|
+
/** Discriminator — identifies this as an events node. */
|
|
71
|
+
kind: 'event';
|
|
72
|
+
/** The `x-ably-msg-id` of the existing message to update. */
|
|
73
|
+
msgId: string;
|
|
74
|
+
/** Events to apply to the target message. */
|
|
75
|
+
events: TEvent[];
|
|
76
|
+
}
|
|
77
|
+
/** @deprecated Use {@link EventsNode} instead. */
|
|
78
|
+
export type EventNode<TEvent> = EventsNode<TEvent>;
|
|
79
|
+
/**
|
|
80
|
+
* Options for streamResponse — per-operation overrides for the assistant message.
|
|
81
|
+
* @template TEvent - The codec event type carried by the stream; used by the `resolveWriteOptions` hook.
|
|
82
|
+
*/
|
|
83
|
+
export interface StreamResponseOptions<TEvent> {
|
|
75
84
|
/** The msg-id of the immediately preceding message in this branch. */
|
|
76
|
-
parent?: string
|
|
85
|
+
parent?: string;
|
|
77
86
|
/** The msg-id of the message this response replaces (for regeneration). */
|
|
78
87
|
forkOf?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Optional per-event hook invoked before each event is encoded. The
|
|
90
|
+
* returned {@link WriteOptions} (if any) override the stream's default
|
|
91
|
+
* headers and `msgId` for that one encode call only; return `undefined`
|
|
92
|
+
* to use the stream defaults.
|
|
93
|
+
*
|
|
94
|
+
* Used to carry a subset of events within the stream to a different
|
|
95
|
+
* message (e.g. `tool-output-available` chunks that belong on a prior
|
|
96
|
+
* assistant message, stamped with `x-ably-amend`). Must not be used
|
|
97
|
+
* for events that participate in the encoder's stream-append pipeline
|
|
98
|
+
* — streaming state (stream tracker, append ordering) is anchored to
|
|
99
|
+
* the stream's default identity and is not affected by per-event
|
|
100
|
+
* overrides.
|
|
101
|
+
* @param event - The event about to be encoded.
|
|
102
|
+
* @returns Per-write overrides for this event, or undefined.
|
|
103
|
+
*/
|
|
104
|
+
resolveWriteOptions?: (event: TEvent) => WriteOptions | undefined;
|
|
79
105
|
}
|
|
80
106
|
/** The result of streaming a response through the encoder. */
|
|
81
107
|
export interface StreamResult {
|
|
82
108
|
/** Why the stream ended. */
|
|
83
109
|
reason: TurnEndReason;
|
|
110
|
+
/**
|
|
111
|
+
* The error that caused the stream to fail, present when `reason` is
|
|
112
|
+
* `'error'`. This is the original error (e.g. from the LLM provider)
|
|
113
|
+
* preserved so the caller can inspect provider-specific fields. The
|
|
114
|
+
* turn's `onError` callback also fires with a wrapped `Ably.ErrorInfo`
|
|
115
|
+
* (code `StreamError`) for standardized observability.
|
|
116
|
+
*/
|
|
117
|
+
error?: Error;
|
|
84
118
|
}
|
|
85
119
|
/** Options passed to newTurn for configuring the turn lifecycle. */
|
|
86
120
|
export interface NewTurnOptions<TEvent> {
|
|
@@ -93,7 +127,7 @@ export interface NewTurnOptions<TEvent> {
|
|
|
93
127
|
* Used as the default parent for user messages (via addMessages) and
|
|
94
128
|
* assistant messages (via streamResponse) when not overridden per-operation.
|
|
95
129
|
*/
|
|
96
|
-
parent?: string
|
|
130
|
+
parent?: string;
|
|
97
131
|
/**
|
|
98
132
|
* The msg-id of the message this turn replaces (creates a fork).
|
|
99
133
|
* Stamped on user messages (for edits) or assistant messages
|
|
@@ -118,10 +152,32 @@ export interface NewTurnOptions<TEvent> {
|
|
|
118
152
|
*/
|
|
119
153
|
onCancel?: (request: CancelRequest) => Promise<boolean>;
|
|
120
154
|
/**
|
|
121
|
-
* Called with non-fatal errors
|
|
122
|
-
*
|
|
155
|
+
* Called with non-fatal turn-scoped errors that have no other delivery
|
|
156
|
+
* path. Fires in two scenarios:
|
|
157
|
+
* - Stream failures in `streamResponse` — the underlying error is also
|
|
158
|
+
* returned on `StreamResult.error`, but this callback delivers it
|
|
159
|
+
* wrapped as an `Ably.ErrorInfo` (code `StreamError`) for standardized
|
|
160
|
+
* observability.
|
|
161
|
+
* - Failures in the `onCancel` handler.
|
|
162
|
+
*
|
|
163
|
+
* Publish failures in `start`, `addMessages`, `addEvents`, and `end`
|
|
164
|
+
* are not delivered here — those methods reject their returned promise
|
|
165
|
+
* with an `Ably.ErrorInfo`, and the caller should handle it at the await
|
|
166
|
+
* site. Turn errors never render the transport unusable, but the turn
|
|
167
|
+
* may be in an inconsistent state; the caller should typically `end` it
|
|
168
|
+
* with reason `'error'`.
|
|
169
|
+
*
|
|
170
|
+
* Channel-wide events (e.g. continuity loss) are delivered via the
|
|
171
|
+
* transport-level `onError` on {@link ServerTransportOptions}, not here.
|
|
123
172
|
*/
|
|
124
173
|
onError?: (error: Ably.ErrorInfo) => void;
|
|
174
|
+
/**
|
|
175
|
+
* An external abort signal (typically the HTTP request's `req.signal`) that,
|
|
176
|
+
* when fired, aborts this turn. This allows platform-level cancellation —
|
|
177
|
+
* request cancellation, serverless function timeout — to stop LLM generation
|
|
178
|
+
* and stream piping gracefully.
|
|
179
|
+
*/
|
|
180
|
+
signal?: AbortSignal;
|
|
125
181
|
}
|
|
126
182
|
/** A server-side turn with explicit lifecycle methods. */
|
|
127
183
|
export interface Turn<TEvent, TMessage> {
|
|
@@ -133,18 +189,29 @@ export interface Turn<TEvent, TMessage> {
|
|
|
133
189
|
start(): Promise<void>;
|
|
134
190
|
/**
|
|
135
191
|
* Publish user messages to the channel, scoped to this turn.
|
|
136
|
-
* Each
|
|
137
|
-
*
|
|
138
|
-
*
|
|
192
|
+
* Each node's `msgId`, `parentId`, and `forkOf` are used for message identity
|
|
193
|
+
* and branching. The node's `headers` override transport-generated defaults
|
|
194
|
+
* (e.g. for optimistic reconciliation with the client's inserts).
|
|
139
195
|
* @returns The msg-ids of all published messages, in order.
|
|
140
196
|
*/
|
|
141
|
-
addMessages(messages:
|
|
197
|
+
addMessages(messages: MessageNode<TMessage>[], options?: AddMessageOptions): Promise<AddMessagesResult>;
|
|
142
198
|
/**
|
|
143
199
|
* Pipe a ReadableStream through the encoder to the channel.
|
|
144
200
|
* Returns when the stream completes, is cancelled, or errors.
|
|
145
201
|
* Does NOT call end() — the caller must call end() after streamResponse returns.
|
|
146
202
|
*/
|
|
147
|
-
streamResponse(stream: ReadableStream<TEvent>, options?: StreamResponseOptions): Promise<StreamResult>;
|
|
203
|
+
streamResponse(stream: ReadableStream<TEvent>, options?: StreamResponseOptions<TEvent>): Promise<StreamResult>;
|
|
204
|
+
/**
|
|
205
|
+
* Publish events targeting existing messages in the tree. Each node
|
|
206
|
+
* specifies a target message (by `msgId`) and the events to apply.
|
|
207
|
+
* Events are encoded and published with the target's `x-ably-msg-id`,
|
|
208
|
+
* so receiving clients apply them to the existing node rather than
|
|
209
|
+
* creating a new one.
|
|
210
|
+
*
|
|
211
|
+
* Used for cross-turn updates such as tool result delivery after
|
|
212
|
+
* approval or client-side tool execution.
|
|
213
|
+
*/
|
|
214
|
+
addEvents(nodes: EventsNode<TEvent>[]): Promise<void>;
|
|
148
215
|
/** Publish turn-end event to the channel and clean up. */
|
|
149
216
|
end(reason: TurnEndReason): Promise<void>;
|
|
150
217
|
}
|
|
@@ -167,8 +234,8 @@ export interface ClientTransportOptions<TEvent, TMessage> {
|
|
|
167
234
|
codec: Codec<TEvent, TMessage>;
|
|
168
235
|
/** The client's identity. Sent to the server in the POST body. */
|
|
169
236
|
clientId?: string;
|
|
170
|
-
/** Server endpoint URL for the HTTP POST.
|
|
171
|
-
api
|
|
237
|
+
/** Server endpoint URL for the HTTP POST. */
|
|
238
|
+
api: string;
|
|
172
239
|
/** Headers for the HTTP POST. Function form for dynamic values (e.g. auth tokens). */
|
|
173
240
|
headers?: Record<string, string> | (() => Record<string, string>);
|
|
174
241
|
/** Additional body fields merged into the HTTP POST. Function form for dynamic values. */
|
|
@@ -196,16 +263,20 @@ export interface SendOptions {
|
|
|
196
263
|
forkOf?: string;
|
|
197
264
|
/**
|
|
198
265
|
* The msg-id of the message that precedes this one in the
|
|
199
|
-
* conversation thread.
|
|
200
|
-
*
|
|
266
|
+
* conversation thread. If omitted, auto-computed from the last
|
|
267
|
+
* message in the view.
|
|
201
268
|
*/
|
|
202
|
-
parent?: string
|
|
269
|
+
parent?: string;
|
|
203
270
|
}
|
|
204
271
|
/** A structured event describing a turn starting or ending. */
|
|
205
272
|
export type TurnLifecycleEvent = {
|
|
206
273
|
type: 'x-ably-turn-start';
|
|
207
274
|
turnId: string;
|
|
208
275
|
clientId: string;
|
|
276
|
+
/** The msg-id of the parent message, if known. Omitted for root turns. */
|
|
277
|
+
parent?: string;
|
|
278
|
+
/** The msg-id being forked/replaced, if this is a regeneration or edit. */
|
|
279
|
+
forkOf?: string;
|
|
209
280
|
} | {
|
|
210
281
|
type: 'x-ably-turn-end';
|
|
211
282
|
turnId: string;
|
|
@@ -214,32 +285,43 @@ export type TurnLifecycleEvent = {
|
|
|
214
285
|
};
|
|
215
286
|
/** A handle to an active client-side turn, returned by `send()`, `regenerate()`, and `edit()`. */
|
|
216
287
|
export interface ActiveTurn<TEvent> {
|
|
217
|
-
/** The decoded event stream for this turn. */
|
|
288
|
+
/** The decoded event stream for this turn. May error if the delivery guarantee is broken (e.g. POST failure, channel continuity loss). */
|
|
218
289
|
stream: ReadableStream<TEvent>;
|
|
219
290
|
/** The turn's unique identifier. */
|
|
220
291
|
turnId: string;
|
|
221
292
|
/** Cancel this specific turn. Publishes a cancel message and closes the local stream. */
|
|
222
293
|
cancel(): Promise<void>;
|
|
294
|
+
/**
|
|
295
|
+
* The msg-ids of optimistically inserted user messages, in order.
|
|
296
|
+
* Present when the send included user messages (edit); empty for
|
|
297
|
+
* regeneration (no user messages to insert optimistically).
|
|
298
|
+
*/
|
|
299
|
+
optimisticMsgIds: string[];
|
|
223
300
|
}
|
|
224
301
|
/** Options for closing a client transport. */
|
|
225
302
|
export interface CloseOptions {
|
|
226
303
|
/** Cancel in-progress turns before closing. Publishes a cancel message to the channel. */
|
|
227
304
|
cancel?: CancelFilter;
|
|
228
305
|
}
|
|
229
|
-
/** A
|
|
230
|
-
export interface
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
|
|
235
|
-
/** Ably serial for
|
|
236
|
-
|
|
306
|
+
/** A single decoded history item with its transport metadata. */
|
|
307
|
+
export interface HistoryItem<TMessage> {
|
|
308
|
+
/** The decoded domain message. */
|
|
309
|
+
message: TMessage;
|
|
310
|
+
/** Transport headers for tree identity and ordering. */
|
|
311
|
+
headers: Record<string, string>;
|
|
312
|
+
/** Ably serial for tree ordering. */
|
|
313
|
+
serial: string;
|
|
314
|
+
}
|
|
315
|
+
/** A page of decoded history from the channel. Internal to View/decodeHistory. */
|
|
316
|
+
export interface HistoryPage<TMessage> {
|
|
317
|
+
/** Decoded items in chronological order (oldest first). */
|
|
318
|
+
items: HistoryItem<TMessage>[];
|
|
237
319
|
/** Raw Ably messages that produced this page, in chronological order. */
|
|
238
|
-
rawMessages
|
|
320
|
+
rawMessages: Ably.InboundMessage[];
|
|
239
321
|
/** Whether there are older pages available. */
|
|
240
322
|
hasNext(): boolean;
|
|
241
323
|
/** Fetch the next (older) page. Returns undefined if no more pages. */
|
|
242
|
-
next(): Promise<
|
|
324
|
+
next(): Promise<HistoryPage<TMessage> | undefined>;
|
|
243
325
|
}
|
|
244
326
|
/** Options for loading channel history. */
|
|
245
327
|
export interface LoadHistoryOptions {
|
|
@@ -247,7 +329,9 @@ export interface LoadHistoryOptions {
|
|
|
247
329
|
limit?: number;
|
|
248
330
|
}
|
|
249
331
|
/** A node in the conversation tree, representing a single domain message. */
|
|
250
|
-
export interface
|
|
332
|
+
export interface MessageNode<TMessage> {
|
|
333
|
+
/** Discriminator — identifies this as a message node. */
|
|
334
|
+
kind: 'message';
|
|
251
335
|
/** The domain message. */
|
|
252
336
|
message: TMessage;
|
|
253
337
|
/** The x-ably-msg-id of this node — primary key in the tree. */
|
|
@@ -265,19 +349,16 @@ export interface ConversationNode<TMessage> {
|
|
|
265
349
|
*/
|
|
266
350
|
serial: string | undefined;
|
|
267
351
|
}
|
|
352
|
+
/** @deprecated Use {@link MessageNode} instead. */
|
|
353
|
+
export type TreeNode<TMessage> = MessageNode<TMessage>;
|
|
268
354
|
/**
|
|
269
355
|
* Materializes a branching conversation tree from a flat oplog.
|
|
270
356
|
*
|
|
271
|
-
* Owns the conversation state —
|
|
272
|
-
*
|
|
273
|
-
*
|
|
357
|
+
* Owns the complete conversation state — every node from live messages and
|
|
358
|
+
* history. `flattenNodes()` returns the linear message list for the currently
|
|
359
|
+
* selected branches. Events fire for any change across the full tree.
|
|
274
360
|
*/
|
|
275
|
-
export interface
|
|
276
|
-
/**
|
|
277
|
-
* Flatten the tree along the currently selected branches into
|
|
278
|
-
* a linear message list. This is what getMessages() returns.
|
|
279
|
-
*/
|
|
280
|
-
flatten(): TMessage[];
|
|
361
|
+
export interface Tree<TMessage> {
|
|
281
362
|
/**
|
|
282
363
|
* Get all messages that are siblings (alternatives) at a given
|
|
283
364
|
* fork point. Returns an array ordered chronologically by serial.
|
|
@@ -286,21 +367,8 @@ export interface ConversationTree<TMessage> {
|
|
|
286
367
|
getSiblings(msgId: string): TMessage[];
|
|
287
368
|
/** Whether a message has sibling alternatives (i.e., show navigation arrows). */
|
|
288
369
|
hasSiblings(msgId: string): boolean;
|
|
289
|
-
/** Get the index of the currently selected sibling at a fork point. */
|
|
290
|
-
getSelectedIndex(msgId: string): number;
|
|
291
|
-
/**
|
|
292
|
-
* Select a sibling at a fork point by index. Updates the active branch.
|
|
293
|
-
* Calling flatten() after this returns the new linear thread.
|
|
294
|
-
* Index is clamped to `[0, siblings.length - 1]`.
|
|
295
|
-
*/
|
|
296
|
-
select(msgId: string, index: number): void;
|
|
297
370
|
/** Get a node by msgId, or undefined if not found. */
|
|
298
|
-
getNode(msgId: string):
|
|
299
|
-
/**
|
|
300
|
-
* Get a node by codec message key (e.g. UIMessage.id), or undefined if
|
|
301
|
-
* not found. Uses a secondary index since the tree is keyed by x-ably-msg-id.
|
|
302
|
-
*/
|
|
303
|
-
getNodeByKey(key: string): ConversationNode<TMessage> | undefined;
|
|
371
|
+
getNode(msgId: string): MessageNode<TMessage> | undefined;
|
|
304
372
|
/** Get the stored headers for a node by msgId, or undefined if not found. */
|
|
305
373
|
getHeaders(msgId: string): Record<string, string> | undefined;
|
|
306
374
|
/**
|
|
@@ -312,90 +380,173 @@ export interface ConversationTree<TMessage> {
|
|
|
312
380
|
upsert(msgId: string, message: TMessage, headers: Record<string, string>, serial?: string): void;
|
|
313
381
|
/** Remove a message from the tree. */
|
|
314
382
|
delete(msgId: string): void;
|
|
383
|
+
/** Active turn IDs grouped by clientId (all turns, not just visible). */
|
|
384
|
+
getActiveTurnIds(): Map<string, Set<string>>;
|
|
385
|
+
/** Subscribe to tree structure changes (insert, update, delete). */
|
|
386
|
+
on(event: 'update', handler: () => void): () => void;
|
|
387
|
+
/** Subscribe to raw Ably messages arriving on the channel. */
|
|
388
|
+
on(event: 'ably-message', handler: (msg: Ably.InboundMessage) => void): () => void;
|
|
389
|
+
/** Subscribe to turn lifecycle events (start and end). */
|
|
390
|
+
on(event: 'turn', handler: (event: TurnLifecycleEvent) => void): () => void;
|
|
315
391
|
}
|
|
316
|
-
/**
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
export interface
|
|
392
|
+
/**
|
|
393
|
+
* A paginated, branch-aware projection of the conversation tree.
|
|
394
|
+
*
|
|
395
|
+
* Returns only the visible portion of the selected branch. New live messages
|
|
396
|
+
* appear immediately; older messages are revealed progressively via
|
|
397
|
+
* `loadOlder()`. Events are scoped to the visible window — subscribers
|
|
398
|
+
* are only notified when the visible output changes.
|
|
399
|
+
*/
|
|
400
|
+
export interface View<TEvent, TMessage> {
|
|
401
|
+
/** The visible domain messages along the selected branch. Shorthand for `flattenNodes().map(n => n.message)`. */
|
|
402
|
+
getMessages(): TMessage[];
|
|
403
|
+
/** Visible nodes along the selected branch, filtered by the pagination window. */
|
|
404
|
+
flattenNodes(): MessageNode<TMessage>[];
|
|
405
|
+
/** Whether there are older messages that can be loaded or revealed. */
|
|
406
|
+
hasOlder(): boolean;
|
|
325
407
|
/**
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
408
|
+
* Reveal older messages. Loads from channel history if the tree doesn't
|
|
409
|
+
* have enough, then advances the window to show up to `limit` more messages.
|
|
410
|
+
* Emits 'update' when the visible list changes.
|
|
411
|
+
* @param limit - Maximum number of older messages to reveal. Defaults to 100.
|
|
412
|
+
*/
|
|
413
|
+
loadOlder(limit?: number): Promise<void>;
|
|
414
|
+
/**
|
|
415
|
+
* Select a sibling at a fork point by index. Updates this view's
|
|
416
|
+
* branch selection. Index is clamped to `[0, siblings.length - 1]`.
|
|
417
|
+
* Emits 'update' when the visible output changes.
|
|
418
|
+
*/
|
|
419
|
+
select(msgId: string, index: number): void;
|
|
420
|
+
/** Get the index of the currently selected sibling at a fork point. */
|
|
421
|
+
getSelectedIndex(msgId: string): number;
|
|
422
|
+
/**
|
|
423
|
+
* Get all messages that are siblings (alternatives) at a given
|
|
424
|
+
* fork point. Returns an array ordered chronologically by serial.
|
|
425
|
+
*/
|
|
426
|
+
getSiblings(msgId: string): TMessage[];
|
|
427
|
+
/** Whether a message has sibling alternatives (i.e., show navigation arrows). */
|
|
428
|
+
hasSiblings(msgId: string): boolean;
|
|
429
|
+
/** Get a node by msgId, or undefined if not found. */
|
|
430
|
+
getNode(msgId: string): MessageNode<TMessage> | undefined;
|
|
431
|
+
/**
|
|
432
|
+
* Send one or more messages and start a new turn. The parent is
|
|
433
|
+
* auto-computed from this view's selected branch unless overridden.
|
|
329
434
|
* The HTTP POST is fire-and-forget — the returned stream is available
|
|
330
|
-
* immediately. If the POST fails, the error is surfaced via
|
|
435
|
+
* immediately. If the POST fails, the error is surfaced via the
|
|
436
|
+
* transport's `on("error")` and the stream is errored.
|
|
331
437
|
*/
|
|
332
438
|
send(messages: TMessage | TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>>;
|
|
333
439
|
/**
|
|
334
440
|
* Regenerate an assistant message. Creates a new turn that forks the
|
|
335
441
|
* target message with no new user messages. Automatically computes
|
|
336
|
-
* `forkOf`, `parent`, and truncated `history` from
|
|
337
|
-
*
|
|
338
|
-
* Pass `options.body.history` to override the default truncated history.
|
|
442
|
+
* `forkOf`, `parent`, and truncated `history` from this view's branch.
|
|
339
443
|
*/
|
|
340
444
|
regenerate(messageId: string, options?: SendOptions): Promise<ActiveTurn<TEvent>>;
|
|
341
445
|
/**
|
|
342
446
|
* Edit a user message. Creates a new turn that forks the target message
|
|
343
447
|
* with replacement content. Automatically computes `forkOf`, `parent`,
|
|
344
|
-
* and `history` from
|
|
448
|
+
* and `history` from this view's branch.
|
|
345
449
|
*/
|
|
346
450
|
edit(messageId: string, newMessages: TMessage | TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>>;
|
|
347
451
|
/**
|
|
348
|
-
*
|
|
349
|
-
* The tree is updated
|
|
452
|
+
* Update an existing message and start a continuation turn.
|
|
453
|
+
* The local tree is updated optimistically, then the events are sent
|
|
454
|
+
* to the server in the POST body. The server publishes them to the channel
|
|
455
|
+
* and streams a continuation response.
|
|
456
|
+
* @param msgId - The `x-ably-msg-id` of the existing message to amend.
|
|
457
|
+
* @param events - Events to apply to the target message (e.g. tool output).
|
|
458
|
+
* @param options - Optional send options (body, headers).
|
|
459
|
+
* @returns An active turn with the continuation response stream.
|
|
460
|
+
*/
|
|
461
|
+
update(msgId: string, events: TEvent[], options?: SendOptions): Promise<ActiveTurn<TEvent>>;
|
|
462
|
+
/** Active turn IDs for turns with visible messages, grouped by clientId. */
|
|
463
|
+
getActiveTurnIds(): Map<string, Set<string>>;
|
|
464
|
+
/** The visible message list changed (new visible node, branch switch, window shift). */
|
|
465
|
+
on(event: 'update', handler: () => void): () => void;
|
|
466
|
+
/** A raw Ably message arrived that corresponds to a visible node. */
|
|
467
|
+
on(event: 'ably-message', handler: (msg: Ably.InboundMessage) => void): () => void;
|
|
468
|
+
/** A turn event occurred for a turn with visible messages in the window. */
|
|
469
|
+
on(event: 'turn', handler: (event: TurnLifecycleEvent) => void): () => void;
|
|
470
|
+
/** Tear down the view — unsubscribe from tree events and clear internal state. */
|
|
471
|
+
close(): void;
|
|
472
|
+
}
|
|
473
|
+
/** Entry in the StreamRouter's turn map. Not part of the public API. */
|
|
474
|
+
export interface TurnEntry<TEvent> {
|
|
475
|
+
/** The ReadableStream controller for this turn. */
|
|
476
|
+
controller: ReadableStreamDefaultController<TEvent>;
|
|
477
|
+
/** The turn's unique identifier. */
|
|
478
|
+
turnId: string;
|
|
479
|
+
}
|
|
480
|
+
/** Client-side transport that manages conversation state over an Ably channel. */
|
|
481
|
+
export interface ClientTransport<TEvent, TMessage> {
|
|
482
|
+
/** The complete conversation tree — all known nodes, events for any change. */
|
|
483
|
+
readonly tree: Tree<TMessage>;
|
|
484
|
+
/** The default paginated, branch-aware view for rendering — events scoped to visible messages. */
|
|
485
|
+
readonly view: View<TEvent, TMessage>;
|
|
486
|
+
/**
|
|
487
|
+
* Create an additional view over the same conversation tree.
|
|
488
|
+
* Each view has independent branch selections and pagination state.
|
|
489
|
+
* The caller is responsible for calling `close()` on the returned view
|
|
490
|
+
* when it is no longer needed, or it will be closed when the transport closes.
|
|
350
491
|
*/
|
|
351
|
-
|
|
492
|
+
createView(): View<TEvent, TMessage>;
|
|
352
493
|
/** Cancel turns matching the filter. Defaults to `{ own: true }` (all own turns). */
|
|
353
494
|
cancel(filter?: CancelFilter): Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Apply events to an existing tree message locally and queue them for
|
|
497
|
+
* delivery on the next send.
|
|
498
|
+
*
|
|
499
|
+
* Use for cross-turn updates where the event value is produced on the
|
|
500
|
+
* client (e.g. after `addToolResult` resolves a client-executed tool) and
|
|
501
|
+
* must appear in the tree immediately so downstream observers — such as a
|
|
502
|
+
* destructive `setMessages(...)` mirror — cannot wipe it before it lands
|
|
503
|
+
* on the wire.
|
|
504
|
+
*
|
|
505
|
+
* The events are applied to the tree via the codec's accumulator
|
|
506
|
+
* (tree `update` fires once with the merged message) and queued on the
|
|
507
|
+
* transport. The next send operation flushes the queue into the POST
|
|
508
|
+
* body's `events` field so the server can republish them over the channel.
|
|
509
|
+
*
|
|
510
|
+
* If `msgId` is not present in the tree, the call is a no-op and a
|
|
511
|
+
* warning is logged.
|
|
512
|
+
* @param msgId - The x-ably-msg-id of the existing message to amend.
|
|
513
|
+
* @param events - Events to apply and later ship.
|
|
514
|
+
*/
|
|
515
|
+
stageEvents(msgId: string, events: TEvent[]): void;
|
|
516
|
+
/**
|
|
517
|
+
* Replace the tree's copy of an existing message with a caller-provided
|
|
518
|
+
* version, preserving headers and serial.
|
|
519
|
+
*
|
|
520
|
+
* Use for useChat-style state transitions the codec can't express as
|
|
521
|
+
* chunks — the canonical example is `addToolApprovalResponse`, which
|
|
522
|
+
* sets `state: 'approval-responded'` on a `dynamic-tool` part directly
|
|
523
|
+
* on the UIMessage and has no corresponding chunk variant.
|
|
524
|
+
*
|
|
525
|
+
* Unlike {@link stageEvents}, staged messages are NOT queued for the
|
|
526
|
+
* next send: the tree is authoritative for the POST body's history,
|
|
527
|
+
* so updating it is sufficient.
|
|
528
|
+
*
|
|
529
|
+
* Runs synchronously. Subsequent tree observers (e.g. useMessageSync)
|
|
530
|
+
* see the patched state on the next tick, so an interleaved
|
|
531
|
+
* observer-turn sync can't clobber it back.
|
|
532
|
+
*
|
|
533
|
+
* If `msgId` is not present in the tree, the call is a no-op and a
|
|
534
|
+
* warning is logged.
|
|
535
|
+
* @param msgId - The x-ably-msg-id of the existing message to replace.
|
|
536
|
+
* @param message - The patched message to store.
|
|
537
|
+
*/
|
|
538
|
+
stageMessage(msgId: string, message: TMessage): void;
|
|
354
539
|
/**
|
|
355
540
|
* Returns a promise that resolves when all active turns matching the filter
|
|
356
541
|
* have completed. Resolves immediately if no matching turns are active.
|
|
357
542
|
* Defaults to `{ own: true }`.
|
|
358
543
|
*/
|
|
359
544
|
waitForTurn(filter?: CancelFilter): Promise<void>;
|
|
360
|
-
/**
|
|
361
|
-
* Subscribe to message store changes or raw Ably message additions.
|
|
362
|
-
* The handler is called with no arguments — call `getMessages()` or
|
|
363
|
-
* `getAblyMessages()` for the current state. Returns an unsubscribe function.
|
|
364
|
-
*/
|
|
365
|
-
on(event: 'message' | 'ably-message', handler: () => void): () => void;
|
|
366
|
-
/** Subscribe to turn lifecycle events (start, end). Returns an unsubscribe function. */
|
|
367
|
-
on(event: 'turn', handler: (event: TurnLifecycleEvent) => void): () => void;
|
|
368
545
|
/**
|
|
369
546
|
* Subscribe to non-fatal transport errors. These indicate something went
|
|
370
547
|
* wrong but the transport is still operational. Returns an unsubscribe function.
|
|
371
548
|
*/
|
|
372
549
|
on(event: 'error', handler: (error: Ably.ErrorInfo) => void): () => void;
|
|
373
|
-
/**
|
|
374
|
-
* Get the accumulated raw Ably messages, in chronological order.
|
|
375
|
-
* Includes both live messages and history-loaded messages.
|
|
376
|
-
*/
|
|
377
|
-
getAblyMessages(): Ably.InboundMessage[];
|
|
378
|
-
/** Get all currently active turns, keyed by clientId. */
|
|
379
|
-
getActiveTurnIds(): Map<string, Set<string>>;
|
|
380
|
-
/** Get Ably headers associated with a message via the conversation tree. */
|
|
381
|
-
getMessageHeaders(message: TMessage): Record<string, string> | undefined;
|
|
382
|
-
/** Get the current message list (follows selected branches). Updated by message lifecycle events. */
|
|
383
|
-
getMessages(): TMessage[];
|
|
384
|
-
/**
|
|
385
|
-
* Snapshot the current message list as message + headers pairs.
|
|
386
|
-
* Convenience for building the `history` body field in HTTP POSTs.
|
|
387
|
-
*/
|
|
388
|
-
getMessagesWithHeaders(): MessageWithHeaders<TMessage>[];
|
|
389
|
-
/**
|
|
390
|
-
* Load a page of conversation history from the channel, decoded through
|
|
391
|
-
* the transport's codec. Uses `untilAttach` for gapless continuity with
|
|
392
|
-
* the live subscription.
|
|
393
|
-
*
|
|
394
|
-
* History messages are inserted into the conversation tree and trigger
|
|
395
|
-
* a notification. Returns a PaginatedMessages handle — call `next()`
|
|
396
|
-
* for older pages.
|
|
397
|
-
*/
|
|
398
|
-
history(options?: LoadHistoryOptions): Promise<PaginatedMessages<TMessage>>;
|
|
399
550
|
/**
|
|
400
551
|
* Tear down the transport: unsubscribe from the channel, close active
|
|
401
552
|
* streams, clear all handlers, and prevent further operations.
|