@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.
- package/README.md +91 -100
- package/dist/ably-ai-transport.js +1553 -1238
- 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 +116 -42
- package/dist/core/agent.d.ts +29 -0
- package/dist/core/codec/decoder.d.ts +20 -23
- package/dist/core/codec/encoder.d.ts +11 -8
- package/dist/core/codec/index.d.ts +1 -2
- package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
- package/dist/core/codec/types.d.ts +407 -115
- package/dist/core/transport/agent-session.d.ts +10 -0
- package/dist/core/transport/branch-chain.d.ts +43 -0
- package/dist/core/transport/client-session.d.ts +13 -0
- package/dist/core/transport/decode-fold.d.ts +47 -0
- package/dist/core/transport/headers.d.ts +96 -18
- package/dist/core/transport/index.d.ts +5 -6
- package/dist/core/transport/internal/bounded-map.d.ts +20 -0
- package/dist/core/transport/invocation.d.ts +74 -0
- package/dist/core/transport/load-conversation.d.ts +128 -0
- package/dist/core/transport/load-history.d.ts +39 -0
- package/dist/core/transport/pipe-stream.d.ts +9 -9
- package/dist/core/transport/run-manager.d.ts +78 -0
- package/dist/core/transport/tree.d.ts +373 -109
- package/dist/core/transport/types/agent.d.ts +353 -0
- package/dist/core/transport/types/client.d.ts +168 -0
- package/dist/core/transport/types/shared.d.ts +24 -0
- package/dist/core/transport/types/tree.d.ts +315 -0
- package/dist/core/transport/types/view.d.ts +222 -0
- package/dist/core/transport/types.d.ts +13 -553
- package/dist/core/transport/view.d.ts +272 -84
- package/dist/errors.d.ts +21 -10
- package/dist/index.d.ts +6 -8
- package/dist/logger.d.ts +12 -0
- package/dist/react/ably-ai-transport-react.js +976 -990
- 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/client-session-context.d.ts +36 -0
- package/dist/react/contexts/client-session-provider.d.ts +53 -0
- package/dist/react/create-session-hooks.d.ts +116 -0
- package/dist/react/index.d.ts +12 -12
- package/dist/react/internal/use-resolved-session.d.ts +36 -0
- package/dist/react/use-ably-messages.d.ts +17 -14
- package/dist/react/use-client-session.d.ts +81 -0
- package/dist/react/use-create-view.d.ts +14 -13
- package/dist/react/use-tree.d.ts +30 -15
- package/dist/react/use-view.d.ts +82 -51
- package/dist/utils.d.ts +32 -23
- package/dist/vercel/ably-ai-transport-vercel.js +2573 -2086
- 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/decoder.d.ts +5 -18
- package/dist/vercel/codec/encoder.d.ts +6 -36
- package/dist/vercel/codec/events.d.ts +51 -0
- package/dist/vercel/codec/index.d.ts +24 -12
- package/dist/vercel/codec/reducer.d.ts +144 -0
- package/dist/vercel/codec/tool-transitions.d.ts +2 -2
- package/dist/vercel/index.d.ts +4 -5
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +3907 -3266
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +33 -8
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +7 -6
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
- package/dist/vercel/react/index.d.ts +1 -2
- package/dist/vercel/react/use-chat-transport.d.ts +30 -26
- package/dist/vercel/react/use-message-sync.d.ts +17 -30
- package/dist/vercel/run-end-reason.d.ts +29 -0
- package/dist/vercel/transport/chat-transport.d.ts +43 -24
- package/dist/vercel/transport/index.d.ts +25 -21
- package/dist/vercel/transport/run-output-stream.d.ts +56 -0
- package/dist/version.d.ts +2 -0
- package/package.json +30 -23
- package/src/constants.ts +124 -51
- package/src/core/agent.ts +68 -0
- package/src/core/codec/decoder.ts +71 -98
- package/src/core/codec/encoder.ts +113 -65
- package/src/core/codec/index.ts +13 -6
- package/src/core/codec/lifecycle-tracker.ts +10 -9
- package/src/core/codec/types.ts +436 -120
- package/src/core/transport/agent-session.ts +1344 -0
- package/src/core/transport/branch-chain.ts +58 -0
- package/src/core/transport/client-session.ts +775 -0
- package/src/core/transport/decode-fold.ts +91 -0
- package/src/core/transport/headers.ts +181 -22
- package/src/core/transport/index.ts +25 -26
- package/src/core/transport/internal/bounded-map.ts +27 -0
- package/src/core/transport/invocation.ts +98 -0
- package/src/core/transport/load-conversation.ts +355 -0
- package/src/core/transport/load-history.ts +269 -0
- package/src/core/transport/pipe-stream.ts +54 -39
- package/src/core/transport/run-manager.ts +249 -0
- package/src/core/transport/tree.ts +926 -308
- package/src/core/transport/types/agent.ts +407 -0
- package/src/core/transport/types/client.ts +211 -0
- package/src/core/transport/types/shared.ts +27 -0
- package/src/core/transport/types/tree.ts +344 -0
- package/src/core/transport/types/view.ts +259 -0
- package/src/core/transport/types.ts +13 -706
- package/src/core/transport/view.ts +864 -433
- package/src/errors.ts +22 -9
- package/src/event-emitter.ts +3 -2
- package/src/index.ts +52 -41
- package/src/logger.ts +14 -1
- package/src/react/contexts/client-session-context.ts +41 -0
- package/src/react/contexts/client-session-provider.tsx +186 -0
- package/src/react/create-session-hooks.ts +141 -0
- package/src/react/index.ts +23 -13
- package/src/react/internal/use-resolved-session.ts +63 -0
- package/src/react/use-ably-messages.ts +32 -22
- package/src/react/use-client-session.ts +201 -0
- package/src/react/use-create-view.ts +33 -29
- package/src/react/use-tree.ts +61 -30
- package/src/react/use-view.ts +139 -97
- package/src/utils.ts +63 -45
- package/src/vercel/codec/decoder.ts +336 -258
- package/src/vercel/codec/encoder.ts +343 -205
- package/src/vercel/codec/events.ts +87 -0
- package/src/vercel/codec/index.ts +60 -13
- package/src/vercel/codec/reducer.ts +977 -0
- package/src/vercel/codec/tool-transitions.ts +2 -2
- package/src/vercel/index.ts +6 -19
- package/src/vercel/react/contexts/chat-transport-context.ts +7 -6
- package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
- package/src/vercel/react/index.ts +3 -5
- package/src/vercel/react/use-chat-transport.ts +47 -49
- package/src/vercel/react/use-message-sync.ts +80 -39
- package/src/vercel/run-end-reason.ts +78 -0
- package/src/vercel/transport/chat-transport.ts +392 -98
- package/src/vercel/transport/index.ts +39 -38
- package/src/vercel/transport/run-output-stream.ts +170 -0
- package/src/version.ts +2 -0
- package/dist/core/transport/client-transport.d.ts +0 -10
- package/dist/core/transport/decode-history.d.ts +0 -43
- package/dist/core/transport/server-transport.d.ts +0 -7
- package/dist/core/transport/stream-router.d.ts +0 -29
- package/dist/core/transport/turn-manager.d.ts +0 -37
- package/dist/react/contexts/transport-context.d.ts +0 -31
- package/dist/react/contexts/transport-provider.d.ts +0 -49
- package/dist/react/create-transport-hooks.d.ts +0 -124
- package/dist/react/use-active-turns.d.ts +0 -12
- package/dist/react/use-client-transport.d.ts +0 -80
- package/dist/vercel/codec/accumulator.d.ts +0 -21
- package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
- package/dist/vercel/tool-approvals.d.ts +0 -124
- package/dist/vercel/tool-events.d.ts +0 -26
- package/src/core/transport/client-transport.ts +0 -977
- package/src/core/transport/decode-history.ts +0 -485
- package/src/core/transport/server-transport.ts +0 -612
- package/src/core/transport/stream-router.ts +0 -136
- package/src/core/transport/turn-manager.ts +0 -165
- package/src/react/contexts/transport-context.ts +0 -37
- package/src/react/contexts/transport-provider.tsx +0 -164
- package/src/react/create-transport-hooks.ts +0 -144
- package/src/react/use-active-turns.ts +0 -72
- package/src/react/use-client-transport.ts +0 -197
- package/src/vercel/codec/accumulator.ts +0 -588
- package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
- package/src/vercel/tool-approvals.ts +0 -380
- package/src/vercel/tool-events.ts +0 -53
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { Logger } from '../../../logger.js';
|
|
2
|
+
import { Codec, CodecInputEvent, CodecOutputEvent, WriteOptions } from '../../codec/types.js';
|
|
3
|
+
import { Invocation } from '../invocation.js';
|
|
4
|
+
import { CancelRequest, RunEndReason } from './shared.js';
|
|
5
|
+
import { MessageNode } from './tree.js';
|
|
6
|
+
/** Agent (server-side) session types: options, run runtime, and the Run / AgentSession contracts. */
|
|
7
|
+
import type * as Ably from 'ably';
|
|
8
|
+
/** Options for creating an agent session. */
|
|
9
|
+
export interface AgentSessionOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
|
|
10
|
+
/**
|
|
11
|
+
* The Ably Realtime client. The caller owns its lifecycle —
|
|
12
|
+
* `session.close()` does not close the client.
|
|
13
|
+
*/
|
|
14
|
+
client: Ably.Realtime;
|
|
15
|
+
/**
|
|
16
|
+
* The name of the channel to publish to. The session owns this channel —
|
|
17
|
+
* do not also resolve it elsewhere with conflicting channel options.
|
|
18
|
+
*/
|
|
19
|
+
channelName: string;
|
|
20
|
+
/** The codec to use for encoding events and messages. */
|
|
21
|
+
codec: Codec<TInput, TOutput, TProjection, TMessage>;
|
|
22
|
+
/** Logger instance for diagnostic output. */
|
|
23
|
+
logger?: Logger;
|
|
24
|
+
/**
|
|
25
|
+
* Called with non-fatal session-level errors not scoped to any run.
|
|
26
|
+
* Examples: cancel listener subscription failure, channel attach errors,
|
|
27
|
+
* channel continuity loss (FAILED/SUSPENDED/DETACHED or re-attach with
|
|
28
|
+
* `resumed: false`).
|
|
29
|
+
*/
|
|
30
|
+
onError?: (error: Ably.ErrorInfo) => void;
|
|
31
|
+
/**
|
|
32
|
+
* How long `Run.start()` will wait for the input event(s) tagged with
|
|
33
|
+
* the run's `invocationId` to arrive on the channel (rewind + live wait)
|
|
34
|
+
* before rejecting with `InputEventNotFound`. The rejection bubbles up to the
|
|
35
|
+
* developer's HTTP handler, which should surface it as a non-2xx response
|
|
36
|
+
* so the client's pending send fails.
|
|
37
|
+
* Default: 30000 (30 seconds).
|
|
38
|
+
*/
|
|
39
|
+
inputEventLookupTimeoutMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Maximum number of distinct invocation-ids whose input events
|
|
42
|
+
* may be buffered while waiting for `Run.start()` to register a lookup
|
|
43
|
+
* listener. Channel rewind on attach can replay input events before any
|
|
44
|
+
* run has been created for them; this buffer holds those events so
|
|
45
|
+
* that subsequent `start()` calls can drain them on registration.
|
|
46
|
+
*
|
|
47
|
+
* Each entry corresponds to one invocation-id regardless of how many
|
|
48
|
+
* events that invocation buffered. When the limit is exceeded the
|
|
49
|
+
* oldest invocation entry (and all its buffered events) is FIFO-evicted
|
|
50
|
+
* — the client whose input was dropped will fail their lookup with
|
|
51
|
+
* `InputEventNotFound`. The eviction is logged at warn level so operators
|
|
52
|
+
* can correlate capacity pressure with `InputEventNotFound` errors.
|
|
53
|
+
*
|
|
54
|
+
* Default: 200.
|
|
55
|
+
*/
|
|
56
|
+
inputEventBufferLimit?: number;
|
|
57
|
+
/**
|
|
58
|
+
* The channel rewind applied when the agent attaches. Replays the whole
|
|
59
|
+
* channel subscription on attach (not just input events) so the lookup
|
|
60
|
+
* can catch input events published before the session attached. Passed
|
|
61
|
+
* through verbatim to Ably's `params.rewind` channel parameter — accepts
|
|
62
|
+
* duration strings (`"2m"`, `"30s"`) or a count of messages as a string
|
|
63
|
+
* (e.g. `"50"`). Malformed values surface as a channel attach error from
|
|
64
|
+
* Ably; the SDK does not pre-validate.
|
|
65
|
+
*
|
|
66
|
+
* A longer window improves the chances of catching an input event for an
|
|
67
|
+
* agent that takes a while to come up after the client published, but
|
|
68
|
+
* also increases the buffer pressure on `inputEventBufferLimit` because
|
|
69
|
+
* more events may be replayed on attach.
|
|
70
|
+
*
|
|
71
|
+
* Default: `"2m"`.
|
|
72
|
+
*/
|
|
73
|
+
rewindWindow?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A batch of events targeting an existing message.
|
|
77
|
+
* Each node specifies the target message and the events to apply to it.
|
|
78
|
+
* Used for cross-run updates such as tool result delivery.
|
|
79
|
+
*/
|
|
80
|
+
export interface EventsNode<TOutput extends CodecOutputEvent> {
|
|
81
|
+
/** Discriminator — identifies this as an events node. */
|
|
82
|
+
kind: 'event';
|
|
83
|
+
/** The `codec-message-id` of the existing message to update. */
|
|
84
|
+
codecMessageId: string;
|
|
85
|
+
/** Outputs to apply to the target message. */
|
|
86
|
+
events: TOutput[];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Options for `Run.pipe` — per-operation overrides for the assistant message.
|
|
90
|
+
* @template TOutput - The codec output type carried by the stream; used by the `resolveWriteOptions` hook.
|
|
91
|
+
*/
|
|
92
|
+
export interface PipeOptions<TOutput extends CodecOutputEvent> {
|
|
93
|
+
/** The codec-message-id of the immediately preceding message in this branch. */
|
|
94
|
+
parent?: string;
|
|
95
|
+
/** The codec-message-id of the message this response replaces (for regeneration). */
|
|
96
|
+
forkOf?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Optional per-output hook invoked before each output is encoded. The
|
|
99
|
+
* returned {@link WriteOptions} (if any) override the stream's default
|
|
100
|
+
* headers and `codecMessageId` for that one encode call only; return `undefined`
|
|
101
|
+
* to use the stream defaults.
|
|
102
|
+
*
|
|
103
|
+
* Used to carry a subset of outputs within the stream to a different
|
|
104
|
+
* message (e.g. `tool-output-available` chunks that belong on a prior
|
|
105
|
+
* assistant message, stamped with `amend`). Must not be used
|
|
106
|
+
* for outputs that participate in the encoder's stream-append pipeline
|
|
107
|
+
* — streaming state (stream tracker, append ordering) is anchored to
|
|
108
|
+
* the stream's default identity and is not affected by per-output
|
|
109
|
+
* overrides.
|
|
110
|
+
* @param output - The output about to be encoded.
|
|
111
|
+
* @returns Per-write overrides for this output, or undefined.
|
|
112
|
+
*/
|
|
113
|
+
resolveWriteOptions?: (output: TOutput) => WriteOptions | undefined;
|
|
114
|
+
}
|
|
115
|
+
/** The result of streaming a response through the encoder. */
|
|
116
|
+
export interface StreamResult {
|
|
117
|
+
/** Why the stream ended. */
|
|
118
|
+
reason: RunEndReason;
|
|
119
|
+
/**
|
|
120
|
+
* The error that caused the stream to fail, present when `reason` is
|
|
121
|
+
* `'error'`. This is the original error (e.g. from the LLM provider)
|
|
122
|
+
* preserved so the caller can inspect provider-specific fields. The
|
|
123
|
+
* run's `onError` callback also fires with a wrapped `Ably.ErrorInfo`
|
|
124
|
+
* (code `StreamError`) for standardized observability.
|
|
125
|
+
*/
|
|
126
|
+
error?: Error;
|
|
127
|
+
}
|
|
128
|
+
/** Per-run runtime hooks, signal, and overrides supplied at `createRun()` time. */
|
|
129
|
+
export interface RunRuntime<TOutput extends CodecOutputEvent> {
|
|
130
|
+
/**
|
|
131
|
+
* Override the invocation id for this run. When omitted, the agent mints a
|
|
132
|
+
* fresh `crypto.randomUUID()` — the normal path (one per HTTP request).
|
|
133
|
+
* Supply a non-empty fixed value for deterministic ids in tests or
|
|
134
|
+
* in-process drivers; the empty string is not a valid override (it is the
|
|
135
|
+
* "unset" sentinel and does not fall through to minting).
|
|
136
|
+
*/
|
|
137
|
+
invocationId?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Override the run id for a FRESH run. When omitted, the agent mints a fresh
|
|
140
|
+
* `crypto.randomUUID()` — the normal path. A continuation IGNORES this: its
|
|
141
|
+
* run id is read from the triggering input event's wire headers, since a
|
|
142
|
+
* continuation re-enters a run that already exists. Supply a non-empty fixed
|
|
143
|
+
* value for deterministic ids in tests or in-process drivers; the empty
|
|
144
|
+
* string is not a valid override (it is the "unset" sentinel and does not
|
|
145
|
+
* fall through to minting).
|
|
146
|
+
*/
|
|
147
|
+
runId?: string;
|
|
148
|
+
/**
|
|
149
|
+
* An external AbortSignal (typically the HTTP request's `req.signal`) that,
|
|
150
|
+
* when fired, cancels this run. This allows platform-level cancellation —
|
|
151
|
+
* request cancellation, serverless function timeout — to stop LLM generation
|
|
152
|
+
* and stream piping gracefully.
|
|
153
|
+
*/
|
|
154
|
+
signal?: AbortSignal;
|
|
155
|
+
/**
|
|
156
|
+
* Called before each Ably message is published in this run.
|
|
157
|
+
* Mutate the Ably message in place to add custom headers under extras.ai.
|
|
158
|
+
*/
|
|
159
|
+
onMessage?: (message: Ably.Message) => void;
|
|
160
|
+
/**
|
|
161
|
+
* Called when the run's stream is cancelled (by client cancel or server).
|
|
162
|
+
* Receives a write function to publish final outputs before the cancellation finalises.
|
|
163
|
+
*/
|
|
164
|
+
onCancelled?: (write: (output: TOutput) => Promise<void>) => void | Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Called when a cancel message arrives matching this run.
|
|
167
|
+
* Return true to allow cancellation (fires `abortSignal`, stream cancels).
|
|
168
|
+
* Return false to reject (cancel ignored, stream continues).
|
|
169
|
+
* If not provided, all cancels are accepted.
|
|
170
|
+
*/
|
|
171
|
+
onCancel?: (request: CancelRequest) => Promise<boolean>;
|
|
172
|
+
/**
|
|
173
|
+
* Called with non-fatal run-scoped errors that have no other delivery
|
|
174
|
+
* path. Fires in two scenarios:
|
|
175
|
+
* - Stream failures in `pipe` — the underlying error is also returned on
|
|
176
|
+
* `StreamResult.error`, but this callback delivers it wrapped as an
|
|
177
|
+
* `Ably.ErrorInfo` (code `StreamError`) for standardized observability.
|
|
178
|
+
* - Failures in the `onCancel` handler.
|
|
179
|
+
*
|
|
180
|
+
* Publish failures in `start`, `addEvents`, and `end`
|
|
181
|
+
* are not delivered here — those methods reject their returned promise
|
|
182
|
+
* with an `Ably.ErrorInfo`, and the caller should handle it at the await
|
|
183
|
+
* site. Run errors never render the session unusable, but the run may
|
|
184
|
+
* be in an inconsistent state; the caller should typically `end` it
|
|
185
|
+
* with reason `'error'`.
|
|
186
|
+
*
|
|
187
|
+
* Channel-wide events (e.g. continuity loss) are delivered via the
|
|
188
|
+
* session-level `onError` on {@link AgentSessionOptions}, not here.
|
|
189
|
+
*/
|
|
190
|
+
onError?: (error: Ably.ErrorInfo) => void;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Read-only view exposed on a {@link Run} of the conversation messages
|
|
194
|
+
* this run was created with.
|
|
195
|
+
*
|
|
196
|
+
* TODO(AIT-771): when the agent rebuilds full conversation history from
|
|
197
|
+
* the channel, this should expose `RunNode[]`-shaped data to match the
|
|
198
|
+
* client side. Today it carries the flat input messages handed to the
|
|
199
|
+
* invocation.
|
|
200
|
+
*/
|
|
201
|
+
export interface RunView<TMessage> {
|
|
202
|
+
/** Invocation input messages handed to this run; no branch awareness today. */
|
|
203
|
+
readonly messages: MessageNode<TMessage>[];
|
|
204
|
+
}
|
|
205
|
+
/** Options for {@link Run.loadConversation}. */
|
|
206
|
+
export interface LoadConversationOptions {
|
|
207
|
+
/**
|
|
208
|
+
* Number of wire messages to request per history page.
|
|
209
|
+
* Default: 200.
|
|
210
|
+
*/
|
|
211
|
+
pageLimit?: number;
|
|
212
|
+
/**
|
|
213
|
+
* Maximum total wire messages to collect across all pages before
|
|
214
|
+
* stopping pagination. A safety bound so a long-lived channel
|
|
215
|
+
* doesn't exhaust memory.
|
|
216
|
+
* Default: 2000.
|
|
217
|
+
*/
|
|
218
|
+
maxMessages?: number;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* A server-side run with explicit lifecycle methods. Generic over the codec's
|
|
222
|
+
* output, projection, and message types.
|
|
223
|
+
*/
|
|
224
|
+
export interface Run<TOutput extends CodecOutputEvent, TProjection, TMessage> {
|
|
225
|
+
/** The run's unique identifier. */
|
|
226
|
+
readonly runId: string;
|
|
227
|
+
/**
|
|
228
|
+
* The invocation's unique identifier, minted by the agent when the run is
|
|
229
|
+
* created (one per HTTP request that invokes the agent). Readable
|
|
230
|
+
* synchronously — the application returns it on the HTTP response so the
|
|
231
|
+
* caller can observe it. The agent stamps it on every event it publishes
|
|
232
|
+
* for this invocation (run lifecycle + outputs).
|
|
233
|
+
*/
|
|
234
|
+
readonly invocationId: string;
|
|
235
|
+
/** AbortSignal scoped to this run. Fires when a cancel event arrives for this runId. */
|
|
236
|
+
readonly abortSignal: AbortSignal;
|
|
237
|
+
/** Read-only view of the conversation messages associated with this run. */
|
|
238
|
+
readonly view: RunView<TMessage>;
|
|
239
|
+
/**
|
|
240
|
+
* The conversation messages this run should feed to the model.
|
|
241
|
+
*
|
|
242
|
+
* - Before {@link start} resolves: empty (no view contribution yet).
|
|
243
|
+
* - After {@link start}: the user-prompt messages looked up on the
|
|
244
|
+
* channel for this invocation.
|
|
245
|
+
* - After {@link loadConversation}: the full multi-turn conversation —
|
|
246
|
+
* all ancestor run messages followed by the current run's messages,
|
|
247
|
+
* oldest turn first. This is the value to pass to the LLM when the
|
|
248
|
+
* agent handles a reply in an ongoing conversation.
|
|
249
|
+
*
|
|
250
|
+
* Each access returns a fresh array — safe to mutate without affecting
|
|
251
|
+
* internal Run state.
|
|
252
|
+
*/
|
|
253
|
+
readonly messages: TMessage[];
|
|
254
|
+
/**
|
|
255
|
+
* Publish the run's opening lifecycle event to the channel (run-start, or
|
|
256
|
+
* run-resume for a continuation). Must be called before any other run method
|
|
257
|
+
* (pipe, addEvents, suspend, end).
|
|
258
|
+
*/
|
|
259
|
+
start(): Promise<void>;
|
|
260
|
+
/**
|
|
261
|
+
* Pipe a ReadableStream through the encoder to the channel.
|
|
262
|
+
* Returns when the stream completes, is cancelled, or errors.
|
|
263
|
+
* Does NOT call end() — the caller must call end() after pipe returns.
|
|
264
|
+
*/
|
|
265
|
+
pipe(stream: ReadableStream<TOutput>, options?: PipeOptions<TOutput>): Promise<StreamResult>;
|
|
266
|
+
/**
|
|
267
|
+
* Publish events targeting existing messages in the tree. Each node
|
|
268
|
+
* specifies a target message (by `codecMessageId`) and the events to apply.
|
|
269
|
+
* Events are encoded and published with the target's `codec-message-id`,
|
|
270
|
+
* so receiving clients apply them to the existing node rather than
|
|
271
|
+
* creating a new one.
|
|
272
|
+
*
|
|
273
|
+
* Used for cross-run updates such as tool result delivery after
|
|
274
|
+
* approval or client-side tool execution.
|
|
275
|
+
*/
|
|
276
|
+
addEvents(nodes: EventsNode<TOutput>[]): Promise<void>;
|
|
277
|
+
/**
|
|
278
|
+
* Fetch every channel message bound to this run and fold them through
|
|
279
|
+
* the codec into a single projection. Used by the agent to reconstruct
|
|
280
|
+
* the run's full state — including client-published tool-output amends
|
|
281
|
+
* the agent didn't observe live — when resuming a suspended run.
|
|
282
|
+
*
|
|
283
|
+
* Uses `channel.history()` (no `untilAttach`) so messages published
|
|
284
|
+
* after the channel was originally attached are still included. Each
|
|
285
|
+
* call paginates until either there are no more pages or an internal
|
|
286
|
+
* safety bound is reached.
|
|
287
|
+
* @returns The TProjection produced by folding every event for this run
|
|
288
|
+
* in serial order. The caller extracts what they need via
|
|
289
|
+
* {@link Codec.getMessages}.
|
|
290
|
+
*/
|
|
291
|
+
loadProjection(): Promise<TProjection>;
|
|
292
|
+
/**
|
|
293
|
+
* Reconstruct the full multi-turn conversation by walking the ancestor
|
|
294
|
+
* run chain and concatenating each run's messages, oldest turn first.
|
|
295
|
+
*
|
|
296
|
+
* Performs a single `channel.history()` scan and builds projections for
|
|
297
|
+
* all ancestor runs plus the current run. After this call:
|
|
298
|
+
* - {@link Run.messages} returns the complete conversation (all ancestor
|
|
299
|
+
* turns followed by the current run's messages), making it ready to
|
|
300
|
+
* pass directly to the LLM.
|
|
301
|
+
* - The current run's projection is cached so {@link Run.pipe} works
|
|
302
|
+
* correctly without a separate {@link Run.loadProjection} call.
|
|
303
|
+
* @param options - Optional tuning for history pagination.
|
|
304
|
+
* @returns The same message list now accessible via {@link Run.messages}.
|
|
305
|
+
*/
|
|
306
|
+
loadConversation(options?: LoadConversationOptions): Promise<TMessage[]>;
|
|
307
|
+
/**
|
|
308
|
+
* Publish a run-suspend event to the channel and clean up, pausing the run
|
|
309
|
+
* without ending it. Call this instead of {@link Run.end} when the run is
|
|
310
|
+
* waiting on participant input (e.g. a client-side tool execution or a
|
|
311
|
+
* server-side tool approval): the run stays live and a later invocation can
|
|
312
|
+
* resume it under the same `runId`. Like {@link Run.end}, it is terminal for
|
|
313
|
+
* this Run instance — the resuming invocation builds a fresh Run. Must be
|
|
314
|
+
* called after {@link Run.start}; a no-op if the run has already ended or
|
|
315
|
+
* suspended.
|
|
316
|
+
*/
|
|
317
|
+
suspend(): Promise<void>;
|
|
318
|
+
/** Publish run-end event to the channel and clean up. Terminal. */
|
|
319
|
+
end(reason: RunEndReason): Promise<void>;
|
|
320
|
+
}
|
|
321
|
+
/** Server-side session that manages run lifecycles over an Ably channel. */
|
|
322
|
+
export interface AgentSession<TOutput extends CodecOutputEvent, TProjection, TMessage> {
|
|
323
|
+
/**
|
|
324
|
+
* Subscribe (unfiltered) to the shared channel and (implicitly) attach. The
|
|
325
|
+
* subscribe is deliberately unfiltered so channel-rewind-replayed input
|
|
326
|
+
* events also reach the dispatcher, which routes by name (cancel vs. input
|
|
327
|
+
* event). Idempotent — subsequent calls return the same promise. All run
|
|
328
|
+
* methods (`start`, `addEvents`, `pipe`, `loadProjection`,
|
|
329
|
+
* `loadConversation`, `suspend`, `end`) throw `InvalidArgument` until
|
|
330
|
+
* `connect()` has been *called*; once it has, they await the in-flight
|
|
331
|
+
* connect promise rather than throwing.
|
|
332
|
+
*/
|
|
333
|
+
connect(): Promise<void>;
|
|
334
|
+
/**
|
|
335
|
+
* Create a new run from an invocation. Synchronous — no channel activity
|
|
336
|
+
* until start() is called. The run is registered for cancel routing
|
|
337
|
+
* immediately so that early cancels fire the AbortSignal.
|
|
338
|
+
* @param invocation - The {@link Invocation} carrying run identity and
|
|
339
|
+
* conversation messages.
|
|
340
|
+
* @param runtime - Optional runtime hooks and external AbortSignal
|
|
341
|
+
* (e.g. the HTTP request's `req.signal`).
|
|
342
|
+
*/
|
|
343
|
+
createRun(invocation: Invocation, runtime?: RunRuntime<TOutput>): Run<TOutput, TProjection, TMessage>;
|
|
344
|
+
/**
|
|
345
|
+
* Unsubscribe from cancel messages, cancel all active runs, detach the
|
|
346
|
+
* channel this session attached, and clean up.
|
|
347
|
+
*
|
|
348
|
+
* Resolves once the detach completes. The detach is best-effort:
|
|
349
|
+
* a failure (e.g. the channel is already FAILED) is swallowed
|
|
350
|
+
* and does not reject. Idempotent.
|
|
351
|
+
*/
|
|
352
|
+
close(): Promise<void>;
|
|
353
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Logger } from '../../../logger.js';
|
|
2
|
+
import { Codec, CodecInputEvent, CodecOutputEvent } from '../../codec/types.js';
|
|
3
|
+
import { Invocation } from '../invocation.js';
|
|
4
|
+
import { Tree } from './tree.js';
|
|
5
|
+
import { View } from './view.js';
|
|
6
|
+
/** Client session types: options, send options, the ActiveRun handle, and the ClientSession contract. */
|
|
7
|
+
import type * as Ably from 'ably';
|
|
8
|
+
/** Options for creating a client session. */
|
|
9
|
+
export interface ClientSessionOptions<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
|
|
10
|
+
/**
|
|
11
|
+
* The Ably Realtime client. The caller owns its lifecycle —
|
|
12
|
+
* `session.close()` does not close the client.
|
|
13
|
+
*/
|
|
14
|
+
client: Ably.Realtime;
|
|
15
|
+
/**
|
|
16
|
+
* The name of the channel to subscribe to and publish cancel signals on.
|
|
17
|
+
* The session owns this channel — do not also resolve it elsewhere with
|
|
18
|
+
* conflicting channel options.
|
|
19
|
+
*/
|
|
20
|
+
channelName: string;
|
|
21
|
+
/** The codec to use for encoding/decoding. */
|
|
22
|
+
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
|
+
/** Initial messages to seed the conversation tree with. Forms a linear chain. */
|
|
30
|
+
messages?: TMessage[];
|
|
31
|
+
/** Logger instance for diagnostic output. */
|
|
32
|
+
logger?: Logger;
|
|
33
|
+
}
|
|
34
|
+
/** Per-send options for branching metadata and run identity. */
|
|
35
|
+
export interface SendOptions {
|
|
36
|
+
/**
|
|
37
|
+
* The codec-message-id of the message this send replaces (fork).
|
|
38
|
+
* Set for regeneration (forkOf an assistant message) or
|
|
39
|
+
* edit (forkOf a user message).
|
|
40
|
+
*/
|
|
41
|
+
forkOf?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The codec-message-id of the message that precedes this one in the
|
|
44
|
+
* conversation thread. If omitted, auto-computed from the last
|
|
45
|
+
* message in the view.
|
|
46
|
+
*/
|
|
47
|
+
parent?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Reuse an existing `runId` (e.g. resume a suspended run). When set,
|
|
50
|
+
* the send is treated as a continuation: the run's existing observer
|
|
51
|
+
* state (router stream, tree run-tracking) is reused; no fresh
|
|
52
|
+
* `crypto.randomUUID()` is minted. Each continuation POST is woken by the
|
|
53
|
+
* agent, which mints a distinct `invocationId` per HTTP request.
|
|
54
|
+
*/
|
|
55
|
+
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
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A handle to an active client-side run, returned by `send()`,
|
|
65
|
+
* `regenerate()`, and `edit()`.
|
|
66
|
+
*
|
|
67
|
+
* The core does not expose a per-run output stream — streaming is a
|
|
68
|
+
* consumer-layer concern (e.g. the Vercel ChatTransport builds a stream from
|
|
69
|
+
* the Tree's `output` events). The handle carries only run identity and
|
|
70
|
+
* control, so it is not parameterized by the codec output type.
|
|
71
|
+
*/
|
|
72
|
+
export interface ActiveRun {
|
|
73
|
+
/**
|
|
74
|
+
* The synchronous routing handle for this send: the triggering input's
|
|
75
|
+
* codec-message-id, which the client owns the moment it publishes and the
|
|
76
|
+
* agent echoes back as `input-codec-message-id`. Stream routing and cancel
|
|
77
|
+
* key on this — it is known immediately, unlike {@link runId}, which the
|
|
78
|
+
* agent mints.
|
|
79
|
+
*/
|
|
80
|
+
inputCodecMessageId: string;
|
|
81
|
+
/**
|
|
82
|
+
* The run's unique identifier, resolved when the agent's `ai-run-start` for
|
|
83
|
+
* this send is observed on the channel. The agent mints the run-id, so it is
|
|
84
|
+
* not known synchronously: `await run.runId`
|
|
85
|
+
* to learn it (this also tells you the agent has picked up the run). There is
|
|
86
|
+
* no built-in deadline — race it against your own timeout if you need one.
|
|
87
|
+
* Rejects only if the session is closed before run-start arrives. A
|
|
88
|
+
* continuation does not resolve any sooner: its run-id promise resolves when
|
|
89
|
+
* the agent's run-resume (`event.type === 'resume'`) for this send is observed
|
|
90
|
+
* on the channel, not synchronously from the run-id the caller passed in.
|
|
91
|
+
*/
|
|
92
|
+
runId: Promise<string>;
|
|
93
|
+
/**
|
|
94
|
+
* The input event's unique identifier. Stamped on the primary input event
|
|
95
|
+
* published to the channel and forwarded in the HTTP POST body so the
|
|
96
|
+
* agent can locate the exact triggering event.
|
|
97
|
+
*/
|
|
98
|
+
inputEventId: string;
|
|
99
|
+
/**
|
|
100
|
+
* Cancel this specific run. Publishes a cancel signal synchronously — keyed
|
|
101
|
+
* by the triggering input's codec-message-id ({@link inputCodecMessageId}),
|
|
102
|
+
* which the client owns the moment it publishes, so a cancel issued before
|
|
103
|
+
* the agent mints the run-id is still honoured (the agent buffers it and
|
|
104
|
+
* fires it once its input-event lookup resolves the input to the run). A
|
|
105
|
+
* continuation also carries its known run-id. Resolves once the cancel is
|
|
106
|
+
* published; it does not wait for {@link runId}.
|
|
107
|
+
*/
|
|
108
|
+
cancel(): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* The codec-message-id of every input in this send, in input order. Includes
|
|
111
|
+
* wire-only inputs (regenerate, tool resolutions), which are NOT folded into
|
|
112
|
+
* the local projection — so an entry here does not by itself mean an
|
|
113
|
+
* optimistic insert occurred.
|
|
114
|
+
*/
|
|
115
|
+
optimisticCodecMessageIds: string[];
|
|
116
|
+
/**
|
|
117
|
+
* Build the {@link Invocation} pointer for this run — only `inputEventId` and
|
|
118
|
+
* the session's channel name as `sessionName`. The body carries no run-id: a
|
|
119
|
+
* fresh run's run-id is minted by the agent, and a continuation's run-id is
|
|
120
|
+
* read off the triggering input event's wire headers, so run identity always
|
|
121
|
+
* lives on the channel rather than the invocation body. The
|
|
122
|
+
* application POSTs `run.toInvocation().toJSON()` to its agent endpoint to
|
|
123
|
+
* wake the agent; the agent rebuilds it via {@link Invocation.fromJSON} and
|
|
124
|
+
* mints the `invocationId` (and a fresh `runId`) itself. The conversation
|
|
125
|
+
* itself is read from the channel, so the pointer carries only identifiers.
|
|
126
|
+
*/
|
|
127
|
+
toInvocation(): Invocation;
|
|
128
|
+
}
|
|
129
|
+
/** Client-side session that manages conversation state over an Ably channel. */
|
|
130
|
+
export interface ClientSession<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
|
|
131
|
+
/** The complete conversation tree — all known Run nodes, events for any change. */
|
|
132
|
+
readonly tree: Tree<TOutput, TProjection>;
|
|
133
|
+
/** The default paginated, branch-aware view for rendering — events scoped to visible messages. */
|
|
134
|
+
readonly view: View<TInput, TMessage>;
|
|
135
|
+
/**
|
|
136
|
+
* Subscribe to the channel and (implicitly) attach. Idempotent —
|
|
137
|
+
* subsequent calls return the same promise. The View's write operations
|
|
138
|
+
* (`send()`, `regenerate()`, `edit()`) and this session's `cancel()` throw
|
|
139
|
+
* `InvalidArgument` until `connect()` resolves.
|
|
140
|
+
*/
|
|
141
|
+
connect(): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Create an additional view over the same conversation tree.
|
|
144
|
+
* Each view has independent branch selections and pagination state.
|
|
145
|
+
* The caller is responsible for calling `close()` on the returned view
|
|
146
|
+
* when it is no longer needed, or it will be closed when the session closes.
|
|
147
|
+
*/
|
|
148
|
+
createView(): View<TInput, TMessage>;
|
|
149
|
+
/** Cancel the specified run by publishing an `ai-cancel` signal on the channel. The core does not own a per-run stream; closing any consumer-built stream is the responsibility of the layer that built it (e.g. the Vercel ChatTransport). */
|
|
150
|
+
cancel(runId: string): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Subscribe to non-fatal session errors. These indicate something went
|
|
153
|
+
* wrong but the session is still operational. Returns an unsubscribe function.
|
|
154
|
+
* Once the session is CLOSED this is a no-op: the handler is not registered and
|
|
155
|
+
* the returned function does nothing.
|
|
156
|
+
*/
|
|
157
|
+
on(event: 'error', handler: (error: Ably.ErrorInfo) => void): () => void;
|
|
158
|
+
/**
|
|
159
|
+
* Tear down the session: unsubscribe from the channel, close active views,
|
|
160
|
+
* clear all handlers, and detach the channel this session attached.
|
|
161
|
+
*
|
|
162
|
+
* Detaching stops the session from receiving further channel messages;
|
|
163
|
+
* the server keeps streaming until its runs end on their own. To stop
|
|
164
|
+
* in-progress runs, call {@link cancel} for each before `close()`. The
|
|
165
|
+
* detach is best-effort: a failure is swallowed and does not reject.
|
|
166
|
+
*/
|
|
167
|
+
close(): Promise<void>;
|
|
168
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Types shared across the client, agent, tree, and view layers. */
|
|
2
|
+
import type * as Ably from 'ably';
|
|
3
|
+
/**
|
|
4
|
+
* Why a run ended.
|
|
5
|
+
*
|
|
6
|
+
* A run-end is terminal — a run that merely pauses awaiting input publishes
|
|
7
|
+
* `ai-run-suspend` instead (see {@link Run.suspend}).
|
|
8
|
+
*
|
|
9
|
+
* - `complete` — the run finished naturally.
|
|
10
|
+
* - `cancelled` — the run was cancelled by a client.
|
|
11
|
+
* - `error` — the run errored.
|
|
12
|
+
*/
|
|
13
|
+
export type RunEndReason = 'complete' | 'cancelled' | 'error';
|
|
14
|
+
/**
|
|
15
|
+
* Passed to a run's `onCancel` hook for authorization decisions.
|
|
16
|
+
* The hook inspects the incoming cancel message and decides whether to
|
|
17
|
+
* allow the targeted run to be cancelled.
|
|
18
|
+
*/
|
|
19
|
+
export interface CancelRequest {
|
|
20
|
+
/** The raw Ably message that carried the cancel signal. */
|
|
21
|
+
message: Ably.InboundMessage;
|
|
22
|
+
/** The runId being cancelled. */
|
|
23
|
+
runId: string;
|
|
24
|
+
}
|