@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
|
@@ -5,63 +5,64 @@
|
|
|
5
5
|
* explicitly when using the Vercel AI SDK integration.
|
|
6
6
|
*
|
|
7
7
|
* ```ts
|
|
8
|
-
* import {
|
|
8
|
+
* import { createClientSession } from '@ably/ai-transport/vercel';
|
|
9
9
|
*
|
|
10
|
-
* const
|
|
10
|
+
* const session = createClientSession({ client, channelName: 'ai:demo' });
|
|
11
|
+
* await session.connect();
|
|
11
12
|
* ```
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
// Chat transport adapter
|
|
15
16
|
export type { ChatTransport, ChatTransportOptions, SendMessagesRequestContext } from './chat-transport.js';
|
|
16
|
-
export { createChatTransport } from './chat-transport.js';
|
|
17
|
+
export { createChatTransport, DEFAULT_VERCEL_API } from './chat-transport.js';
|
|
17
18
|
|
|
18
19
|
import type * as AI from 'ai';
|
|
19
20
|
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
21
|
+
import { createAgentSession as createCoreAgentSession } from '../../core/transport/agent-session.js';
|
|
22
|
+
import { createClientSession as createCoreClientSession } from '../../core/transport/client-session.js';
|
|
22
23
|
import type {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
AgentSession,
|
|
25
|
+
AgentSessionOptions,
|
|
26
|
+
ClientSession,
|
|
27
|
+
ClientSessionOptions,
|
|
27
28
|
} from '../../core/transport/types.js';
|
|
28
|
-
import { UIMessageCodec } from '../codec/index.js';
|
|
29
|
+
import { UIMessageCodec, type VercelInput, type VercelOutput, type VercelProjection } from '../codec/index.js';
|
|
29
30
|
|
|
30
|
-
/** Core client
|
|
31
|
-
type CoreClientOpts =
|
|
31
|
+
/** Core client session options with Vercel AI SDK types pre-applied. */
|
|
32
|
+
type CoreClientOpts = ClientSessionOptions<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>;
|
|
32
33
|
|
|
33
|
-
/** Options for creating a Vercel client
|
|
34
|
-
export type
|
|
34
|
+
/** Options for creating a Vercel client session. Same as core options but without the codec field. */
|
|
35
|
+
export type VercelClientSessionOptions = Omit<CoreClientOpts, 'codec'>;
|
|
35
36
|
|
|
36
|
-
/** Options for creating a Vercel
|
|
37
|
-
export type
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
/** Options for creating a Vercel agent session. Same as core options but without the codec field. */
|
|
38
|
+
export type VercelAgentSessionOptions = Omit<
|
|
39
|
+
AgentSessionOptions<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>,
|
|
40
|
+
'codec'
|
|
41
|
+
>;
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
|
-
* Create a client-side
|
|
44
|
+
* Create a client-side session pre-configured with the Vercel AI SDK codec.
|
|
43
45
|
*
|
|
44
|
-
* Equivalent to calling the core `
|
|
45
|
-
*
|
|
46
|
-
*
|
|
46
|
+
* Equivalent to calling the core `createClientSession` with `codec: UIMessageCodec`.
|
|
47
|
+
* The core session is a pure Ably-channel transport — it never sends HTTP.
|
|
48
|
+
* To wake a serverless agent over HTTP, POST `run.toInvocation().toJSON()`
|
|
49
|
+
* yourself, or use `createChatTransport` (which does it for useChat parity).
|
|
50
|
+
* @param options - Configuration for the client session (codec is provided automatically).
|
|
51
|
+
* @returns A new {@link ClientSession} for Vercel AI SDK UIMessage/UIMessageChunk types.
|
|
47
52
|
*/
|
|
48
|
-
export const
|
|
49
|
-
options:
|
|
50
|
-
):
|
|
51
|
-
|
|
52
|
-
...options,
|
|
53
|
-
codec: UIMessageCodec,
|
|
54
|
-
// Mirrors the Vercel AI SDK's DefaultChatTransport default.
|
|
55
|
-
api: options.api ?? DEFAULT_VERCEL_API,
|
|
56
|
-
});
|
|
53
|
+
export const createClientSession = (
|
|
54
|
+
options: VercelClientSessionOptions,
|
|
55
|
+
): ClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage> =>
|
|
56
|
+
createCoreClientSession({ ...options, codec: UIMessageCodec });
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Create
|
|
59
|
+
* Create an agent (server-side) session pre-configured with the Vercel AI SDK codec.
|
|
60
60
|
*
|
|
61
|
-
* Equivalent to calling the core `
|
|
62
|
-
* @param options - Configuration for the
|
|
63
|
-
* @returns A new {@link
|
|
61
|
+
* Equivalent to calling the core `createAgentSession` with `codec: UIMessageCodec`.
|
|
62
|
+
* @param options - Configuration for the agent session (codec is provided automatically).
|
|
63
|
+
* @returns A new {@link AgentSession} for Vercel AI SDK UIMessage/UIMessageChunk types.
|
|
64
64
|
*/
|
|
65
|
-
export const
|
|
66
|
-
options:
|
|
67
|
-
):
|
|
65
|
+
export const createAgentSession = (
|
|
66
|
+
options: VercelAgentSessionOptions,
|
|
67
|
+
): AgentSession<VercelOutput, VercelProjection, AI.UIMessage> =>
|
|
68
|
+
createCoreAgentSession({ ...options, codec: UIMessageCodec });
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel-owned per-run output stream.
|
|
3
|
+
*
|
|
4
|
+
* Builds the `ReadableStream<UIMessageChunk>` that `useChat` consumes by
|
|
5
|
+
* subscribing to the session Tree's `output` and `run` events for a single
|
|
6
|
+
* run. Streaming is a useChat-integration concern, so it lives in the Vercel
|
|
7
|
+
* layer rather than the generic core: the core Tree is the fan-out point, and
|
|
8
|
+
* this projects its events into the shape `useChat` expects.
|
|
9
|
+
*
|
|
10
|
+
* Close semantics — the stream the consumer reads ends when:
|
|
11
|
+
* - a **terminal chunk** (`finish` / `error` / `abort`) is folded for the run.
|
|
12
|
+
* This is the signal `useChat`'s `sendAutomaticallyWhen` waits for, and it
|
|
13
|
+
* fires even when the run merely *suspends* for a tool call (a tool-calls
|
|
14
|
+
* `finish` ends the consumer stream while the core run stays alive in the
|
|
15
|
+
* Tree for the continuation); or
|
|
16
|
+
* - the run reaches `run-end`, which is always terminal (safety net for a run
|
|
17
|
+
* that ends without emitting a terminal chunk). A `run-suspend` keeps the
|
|
18
|
+
* core run alive and does not close the consumer stream.
|
|
19
|
+
*
|
|
20
|
+
* It errors when the session emits a non-fatal `error` (e.g. channel
|
|
21
|
+
* continuity loss, or an agent-reported mid-run error), so the consumer's
|
|
22
|
+
* reader rejects rather than hanging.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import * as Ably from 'ably';
|
|
26
|
+
import type * as AI from 'ai';
|
|
27
|
+
|
|
28
|
+
import type { ClientSession } from '../../core/transport/types.js';
|
|
29
|
+
import { ErrorCode } from '../../errors.js';
|
|
30
|
+
import type { VercelInput, VercelOutput, VercelProjection } from '../codec/index.js';
|
|
31
|
+
|
|
32
|
+
type VercelSession = ClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether a Vercel output chunk ends the consumer-facing stream. The terminal
|
|
36
|
+
* variants are `finish` (end of an LLM turn, including tool-calls), `error`,
|
|
37
|
+
* and `abort`.
|
|
38
|
+
* @param output - The decoded output chunk.
|
|
39
|
+
* @returns True when the chunk should close the consumer stream.
|
|
40
|
+
*/
|
|
41
|
+
const isTerminalChunk = (output: VercelOutput): boolean =>
|
|
42
|
+
output.type === 'finish' || output.type === 'error' || output.type === 'abort';
|
|
43
|
+
|
|
44
|
+
/** A consumer-facing run output stream plus the handles to settle it externally. */
|
|
45
|
+
export interface RunOutputStream {
|
|
46
|
+
/** The stream of decoded outputs for the run, as `useChat` consumes it. */
|
|
47
|
+
stream: ReadableStream<VercelOutput>;
|
|
48
|
+
/** Close the stream now (e.g. on local cancel). Idempotent. */
|
|
49
|
+
close: () => void;
|
|
50
|
+
/** Error the stream now (e.g. on a failed agent-invocation POST). Idempotent. */
|
|
51
|
+
error: (reason: Ably.ErrorInfo) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a consumer-facing output stream for a send, sourced from the session
|
|
56
|
+
* Tree's events. See the module docs for close/error semantics. The returned
|
|
57
|
+
* `close`/`error` let the caller settle the stream for conditions the Tree
|
|
58
|
+
* doesn't surface (local cancel, POST failure).
|
|
59
|
+
*
|
|
60
|
+
* Outputs route PURELY by the triggering input's codec-message-id — the key the
|
|
61
|
+
* client owns from send time, before the agent mints the runId. The agent's
|
|
62
|
+
* minted runId is supplied as a promise so the run-end safety-net can still
|
|
63
|
+
* close the stream once it resolves.
|
|
64
|
+
* @param session - The Vercel client session whose Tree to observe.
|
|
65
|
+
* @param runId - The agent-minted runId, resolved when run-start is observed.
|
|
66
|
+
* Used only by the run-end safety-net; routing keys on `inputCodecMessageId`.
|
|
67
|
+
* @param inputCodecMessageId - The triggering input's codec-message-id. An
|
|
68
|
+
* output routes to this stream when it carries this id.
|
|
69
|
+
* @returns The stream and its external settle handles.
|
|
70
|
+
*/
|
|
71
|
+
export const createRunOutputStream = (
|
|
72
|
+
session: VercelSession,
|
|
73
|
+
runId: Promise<string>,
|
|
74
|
+
inputCodecMessageId: string,
|
|
75
|
+
): RunOutputStream => {
|
|
76
|
+
const holder: { controller?: ReadableStreamDefaultController<VercelOutput> } = {};
|
|
77
|
+
// ReadableStream's start() runs synchronously, so the controller is captured
|
|
78
|
+
// before the constructor returns.
|
|
79
|
+
const unsubscribe: (() => void)[] = [];
|
|
80
|
+
const stream = new ReadableStream<VercelOutput>({
|
|
81
|
+
start: (controller) => {
|
|
82
|
+
holder.controller = controller;
|
|
83
|
+
},
|
|
84
|
+
cancel: () => {
|
|
85
|
+
teardown();
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const { controller } = holder;
|
|
89
|
+
if (!controller) {
|
|
90
|
+
throw new Ably.ErrorInfo(
|
|
91
|
+
'unable to create run stream; ReadableStream start() was not called synchronously',
|
|
92
|
+
ErrorCode.SessionSubscriptionError,
|
|
93
|
+
500,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The agent mints the runId; learn it (for the run-end safety-net) when the
|
|
98
|
+
// promise resolves. Fire-and-forget: the stream opens on the input key, so a
|
|
99
|
+
// never-resolving runId only forgoes the safety-net, not normal close.
|
|
100
|
+
let resolvedRunId: string | undefined;
|
|
101
|
+
// Best-effort: failure only disables the run-end safety-net; normal close is
|
|
102
|
+
// the terminal chunk. `void` discards the promise (no await needed here).
|
|
103
|
+
void runId.then(
|
|
104
|
+
(id) => {
|
|
105
|
+
resolvedRunId = id;
|
|
106
|
+
},
|
|
107
|
+
() => {
|
|
108
|
+
/* session closed before run-start; safety-net stays disarmed */
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
let settled = false;
|
|
113
|
+
const teardown = (): void => {
|
|
114
|
+
for (const unsub of unsubscribe) unsub();
|
|
115
|
+
unsubscribe.length = 0;
|
|
116
|
+
};
|
|
117
|
+
// Settle the stream at most once: run the controller action (close/error),
|
|
118
|
+
// swallow the throw if the consumer already cancelled, then tear down.
|
|
119
|
+
const settle = (action: () => void): void => {
|
|
120
|
+
if (settled) return;
|
|
121
|
+
settled = true;
|
|
122
|
+
try {
|
|
123
|
+
action();
|
|
124
|
+
} catch {
|
|
125
|
+
/* consumer already cancelled the stream */
|
|
126
|
+
}
|
|
127
|
+
teardown();
|
|
128
|
+
};
|
|
129
|
+
const close = (): void => {
|
|
130
|
+
settle(() => {
|
|
131
|
+
controller.close();
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
const error = (reason: Ably.ErrorInfo): void => {
|
|
135
|
+
settle(() => {
|
|
136
|
+
controller.error(reason);
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
unsubscribe.push(
|
|
141
|
+
session.tree.on('output', (event) => {
|
|
142
|
+
if (event.inputCodecMessageId !== inputCodecMessageId) return;
|
|
143
|
+
for (const output of event.events) {
|
|
144
|
+
try {
|
|
145
|
+
controller.enqueue(output);
|
|
146
|
+
} catch {
|
|
147
|
+
close();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (isTerminalChunk(output)) {
|
|
151
|
+
close();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}),
|
|
156
|
+
session.tree.on('run', (event) => {
|
|
157
|
+
// run-end is always terminal; a run-suspend (event.type === 'suspend')
|
|
158
|
+
// keeps the core run alive and must not close the consumer stream. Match
|
|
159
|
+
// against the resolved runId once the agent has minted it.
|
|
160
|
+
if (event.type === 'end' && resolvedRunId !== undefined && event.runId === resolvedRunId) {
|
|
161
|
+
close();
|
|
162
|
+
}
|
|
163
|
+
}),
|
|
164
|
+
session.on('error', (reason) => {
|
|
165
|
+
error(reason);
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return { stream, close, error };
|
|
170
|
+
};
|
package/src/version.ts
ADDED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { ClientTransport, ClientTransportOptions } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Create a client-side transport that manages conversation state over an Ably channel.
|
|
4
|
-
*
|
|
5
|
-
* Subscribes to the channel immediately (before attach per RTL7g). The caller should
|
|
6
|
-
* ensure the channel is attached or will be attached shortly after creation.
|
|
7
|
-
* @param options - Configuration for the client transport.
|
|
8
|
-
* @returns A new {@link ClientTransport} instance.
|
|
9
|
-
*/
|
|
10
|
-
export declare const createClientTransport: <TEvent, TMessage>(options: ClientTransportOptions<TEvent, TMessage>) => ClientTransport<TEvent, TMessage>;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Logger } from '../../logger.js';
|
|
2
|
-
import { Codec } from '../codec/types.js';
|
|
3
|
-
import { HistoryPage, LoadHistoryOptions } from './types.js';
|
|
4
|
-
/**
|
|
5
|
-
* decodeHistory — load conversation history from an Ably channel and
|
|
6
|
-
* return decoded messages as a paginated HistoryPage result.
|
|
7
|
-
*
|
|
8
|
-
* Uses a fresh decoder (not shared with the live subscription) to avoid
|
|
9
|
-
* state conflicts. Per-turn accumulators handle interleaved turns correctly.
|
|
10
|
-
*
|
|
11
|
-
* The `limit` option controls the number of **messages** returned,
|
|
12
|
-
* not the number of Ably wire messages fetched. The implementation pages
|
|
13
|
-
* back through Ably history until `limit` complete messages have
|
|
14
|
-
* been assembled. Partial turns (incomplete at the page boundary) are
|
|
15
|
-
* buffered internally and completed when `next()` fetches more pages.
|
|
16
|
-
*
|
|
17
|
-
* Only completed messages appear in `items`. A message is complete when
|
|
18
|
-
* its terminal event (finish/abort/error) has been received.
|
|
19
|
-
*
|
|
20
|
-
* Because Ably history returns newest-first while the decoder requires
|
|
21
|
-
* chronological order, all collected Ably messages are re-decoded from
|
|
22
|
-
* oldest to newest at the point a result is built. This handles turns
|
|
23
|
-
* that span page boundaries correctly. The fetch loop uses a cheap
|
|
24
|
-
* header-based completion counter to decide when to stop paging, so the
|
|
25
|
-
* full decode runs exactly once per traversal regardless of page count.
|
|
26
|
-
*/
|
|
27
|
-
import type * as Ably from 'ably';
|
|
28
|
-
/**
|
|
29
|
-
* Load conversation history from a channel and return decoded messages.
|
|
30
|
-
*
|
|
31
|
-
* Attaches the channel if not already attached, then calls
|
|
32
|
-
* `channel.history({ untilAttach: true })` to guarantee no gap between
|
|
33
|
-
* historical and live messages. The attach is idempotent.
|
|
34
|
-
*
|
|
35
|
-
* The `limit` option controls the number of complete messages
|
|
36
|
-
* returned per page, not the number of Ably wire messages fetched.
|
|
37
|
-
* @param channel - The Ably channel to load history from.
|
|
38
|
-
* @param codec - The codec for decoding wire messages into domain messages.
|
|
39
|
-
* @param options - Pagination options.
|
|
40
|
-
* @param logger - Logger for diagnostic output.
|
|
41
|
-
* @returns The first page of decoded history.
|
|
42
|
-
*/
|
|
43
|
-
export declare const decodeHistory: <TEvent, TMessage>(channel: Ably.RealtimeChannel, codec: Codec<TEvent, TMessage>, options: LoadHistoryOptions | undefined, logger: Logger) => Promise<HistoryPage<TMessage>>;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { ServerTransport, ServerTransportOptions } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Create a server transport bound to the given channel and codec.
|
|
4
|
-
* @param options - Transport configuration.
|
|
5
|
-
* @returns A new {@link ServerTransport} instance.
|
|
6
|
-
*/
|
|
7
|
-
export declare const createServerTransport: <TEvent, TMessage>(options: ServerTransportOptions<TEvent, TMessage>) => ServerTransport<TEvent, TMessage>;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Logger } from '../../logger.js';
|
|
2
|
-
/**
|
|
3
|
-
* Client-side stream routing.
|
|
4
|
-
*
|
|
5
|
-
* Maintains a map of turnId to ReadableStreamController. Routes decoded events
|
|
6
|
-
* to the correct stream. Closes streams on terminal events, explicit close, or
|
|
7
|
-
* error.
|
|
8
|
-
*/
|
|
9
|
-
import * as Ably from 'ably';
|
|
10
|
-
/** Routes decoded events to the correct turn's ReadableStream. */
|
|
11
|
-
export interface StreamRouter<TEvent> {
|
|
12
|
-
/** Register a new stream for a turnId. Returns the ReadableStream the consumer reads from. */
|
|
13
|
-
createStream(turnId: string): ReadableStream<TEvent>;
|
|
14
|
-
/** Close the stream for a turnId. Returns true if a stream existed. */
|
|
15
|
-
closeStream(turnId: string): boolean;
|
|
16
|
-
/** Error the stream for a turnId. The consumer's reader will reject with the given error. Returns true if a stream existed. */
|
|
17
|
-
errorStream(turnId: string, error: Ably.ErrorInfo): boolean;
|
|
18
|
-
/** Enqueue an event to the correct stream. Returns true if routed successfully. */
|
|
19
|
-
route(turnId: string, event: TEvent): boolean;
|
|
20
|
-
/** Whether a specific turnId has an active stream. */
|
|
21
|
-
has(turnId: string): boolean;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Create a StreamRouter that routes decoded events to per-turn ReadableStreams.
|
|
25
|
-
* @param isTerminal - Predicate that returns true for events that close the stream.
|
|
26
|
-
* @param logger - Logger for diagnostic output.
|
|
27
|
-
* @returns A new {@link StreamRouter} instance.
|
|
28
|
-
*/
|
|
29
|
-
export declare const createStreamRouter: <TEvent>(isTerminal: (event: TEvent) => boolean, logger: Logger) => StreamRouter<TEvent>;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Logger } from '../../logger.js';
|
|
2
|
-
import { TurnEndReason } from './types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Server-side turn state management and lifecycle event publishing.
|
|
5
|
-
*
|
|
6
|
-
* Owns the authoritative turn lifecycle. Tracks active turns with their
|
|
7
|
-
* AbortControllers and clientIds. Publishes turn-start and turn-end events
|
|
8
|
-
* on the Ably channel so all clients can react to turn state changes.
|
|
9
|
-
*/
|
|
10
|
-
import type * as Ably from 'ably';
|
|
11
|
-
/** Manages active turns and publishes turn lifecycle events on the channel. */
|
|
12
|
-
export interface TurnManager {
|
|
13
|
-
/** Register a new turn. Publishes turn-start on the channel. Returns AbortSignal. */
|
|
14
|
-
startTurn(turnId: string, clientId?: string, controller?: AbortController, metadata?: {
|
|
15
|
-
parent?: string;
|
|
16
|
-
forkOf?: string;
|
|
17
|
-
}): Promise<AbortSignal>;
|
|
18
|
-
/** End a turn. Publishes turn-end on the channel. Cleans up internal state. */
|
|
19
|
-
endTurn(turnId: string, reason: TurnEndReason): Promise<void>;
|
|
20
|
-
/** Get the AbortSignal for a turn. */
|
|
21
|
-
getSignal(turnId: string): AbortSignal | undefined;
|
|
22
|
-
/** Get the clientId that owns a turn. */
|
|
23
|
-
getClientId(turnId: string): string | undefined;
|
|
24
|
-
/** Abort the signal for a turn. */
|
|
25
|
-
abort(turnId: string): void;
|
|
26
|
-
/** Get all active turn IDs. */
|
|
27
|
-
getActiveTurnIds(): string[];
|
|
28
|
-
/** Abort all active turns and clear state. */
|
|
29
|
-
close(): void;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Create a turn manager bound to the given channel.
|
|
33
|
-
* @param channel - The Ably channel to publish lifecycle events on.
|
|
34
|
-
* @param logger - Optional logger for diagnostic output.
|
|
35
|
-
* @returns A new {@link TurnManager} instance.
|
|
36
|
-
*/
|
|
37
|
-
export declare const createTurnManager: (channel: Ably.RealtimeChannel, logger?: Logger) => TurnManager;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { ClientTransport } from '../../core/transport/types.js';
|
|
2
|
-
import type * as Ably from 'ably';
|
|
3
|
-
/**
|
|
4
|
-
* A single entry in the transport registry, holding the transport and any
|
|
5
|
-
* error that occurred during its construction.
|
|
6
|
-
*
|
|
7
|
-
* `transport` is `undefined` when construction failed.
|
|
8
|
-
* `error` is set when `createClientTransport` threw during provider render.
|
|
9
|
-
*/
|
|
10
|
-
export interface TransportSlot {
|
|
11
|
-
/** The constructed transport, or `undefined` if construction failed. */
|
|
12
|
-
transport: ClientTransport<unknown, unknown> | undefined;
|
|
13
|
-
/** Construction error from `createClientTransport`, or `undefined` on success. */
|
|
14
|
-
error: Ably.ErrorInfo | undefined;
|
|
15
|
-
}
|
|
16
|
-
/** The shape of the TransportContext value — a record of channelName → slot. */
|
|
17
|
-
export type TransportContextValue = Readonly<Record<string, TransportSlot>>;
|
|
18
|
-
/**
|
|
19
|
-
* Context that holds the registered {@link ClientTransport} slots, keyed by channelName.
|
|
20
|
-
* Each slot contains the transport (or `undefined` on construction failure) and any error.
|
|
21
|
-
* Populated by {@link TransportProvider}; read by {@link useClientTransport}.
|
|
22
|
-
*/
|
|
23
|
-
export declare const TransportContext: import('react').Context<Readonly<Record<string, TransportSlot>>>;
|
|
24
|
-
/**
|
|
25
|
-
* Context that holds the nearest (innermost) transport slot.
|
|
26
|
-
* Each {@link TransportProvider} sets this to its own slot, so descendants
|
|
27
|
-
* can access the nearest transport without knowing its channel name.
|
|
28
|
-
* `undefined` when no provider is present.
|
|
29
|
-
* Read by hooks whose `transport` argument is omitted.
|
|
30
|
-
*/
|
|
31
|
-
export declare const NearestTransportContext: import('react').Context<TransportSlot | undefined>;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { PropsWithChildren, ReactNode } from 'react';
|
|
2
|
-
import { ClientTransportOptions } from '../../core/transport/types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Props for {@link TransportProvider}.
|
|
5
|
-
*
|
|
6
|
-
* All {@link ClientTransportOptions} except `channel` (managed internally) plus `channelName`.
|
|
7
|
-
*/
|
|
8
|
-
export interface TransportProviderProps<TEvent, TMessage> extends Omit<ClientTransportOptions<TEvent, TMessage>, 'channel'>, PropsWithChildren {
|
|
9
|
-
/** The Ably channel name to subscribe to. Also used as the context registry key. */
|
|
10
|
-
channelName: string;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Provide a {@link ClientTransport} to descendant components.
|
|
14
|
-
*
|
|
15
|
-
* Wraps children with Ably's `ChannelProvider` using `channelName`, creates a
|
|
16
|
-
* transport from the resolved channel and the remaining options, and registers it
|
|
17
|
-
* in `TransportContext` under `channelName`. Descendants call
|
|
18
|
-
* {@link useClientTransport} with the same `channelName` to access the transport.
|
|
19
|
-
*
|
|
20
|
-
* If `createClientTransport` throws during construction, the error is surfaced
|
|
21
|
-
* through `useClientTransport` as `transportError` — the component tree does not
|
|
22
|
-
* crash and children are still rendered.
|
|
23
|
-
*
|
|
24
|
-
* ```tsx
|
|
25
|
-
* <TransportProvider channelName="ai:demo" codec={UIMessageCodec}>
|
|
26
|
-
* <Chat />
|
|
27
|
-
* </TransportProvider>
|
|
28
|
-
*
|
|
29
|
-
* // Inside Chat:
|
|
30
|
-
* const { transport, transportError } = useClientTransport({ channelName: 'ai:demo' });
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* For multiple transports, nest providers with distinct channelNames:
|
|
34
|
-
*
|
|
35
|
-
* ```tsx
|
|
36
|
-
* <TransportProvider channelName="ai:main" codec={UIMessageCodec}>
|
|
37
|
-
* <TransportProvider channelName="ai:aux" codec={UIMessageCodec}>
|
|
38
|
-
* <App />
|
|
39
|
-
* </TransportProvider>
|
|
40
|
-
* </TransportProvider>
|
|
41
|
-
*
|
|
42
|
-
* // Inside App:
|
|
43
|
-
* const { transport: main } = useClientTransport({ channelName: 'ai:main' });
|
|
44
|
-
* const { transport: aux } = useClientTransport({ channelName: 'ai:aux' });
|
|
45
|
-
* ```
|
|
46
|
-
* @param props - Provider configuration including `channelName`, `codec`, and all other {@link ClientTransportOptions}.
|
|
47
|
-
* @returns A React element wrapping children with ChannelProvider and TransportContext.
|
|
48
|
-
*/
|
|
49
|
-
export declare const TransportProvider: <TEvent, TMessage>(props: TransportProviderProps<TEvent, TMessage>) => ReactNode;
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { ComponentType } from 'react';
|
|
2
|
-
import { ClientTransport, View } from '../core/transport/types.js';
|
|
3
|
-
import { TransportProviderProps } from './contexts/transport-provider.js';
|
|
4
|
-
import { ClientTransportHandle } from './use-client-transport.js';
|
|
5
|
-
import { TreeHandle } from './use-tree.js';
|
|
6
|
-
import { ViewHandle } from './use-view.js';
|
|
7
|
-
/**
|
|
8
|
-
* createTransportHooks: factory that captures TEvent and TMessage once and returns
|
|
9
|
-
* a bundle of type-safe hooks + TransportProvider. Hook call sites need no type
|
|
10
|
-
* parameters at every use — just call the hooks directly.
|
|
11
|
-
* @example
|
|
12
|
-
* // Once per app (e.g. in a shared transport.ts):
|
|
13
|
-
* export const {
|
|
14
|
-
* TransportProvider,
|
|
15
|
-
* useClientTransport,
|
|
16
|
-
* useView,
|
|
17
|
-
* useActiveTurns,
|
|
18
|
-
* } = createTransportHooks<UIMessageChunk, UIMessage>();
|
|
19
|
-
*
|
|
20
|
-
* // In page:
|
|
21
|
-
* <TransportProvider channelName="ai:demo" codec={UIMessageCodec}>
|
|
22
|
-
* <Chat />
|
|
23
|
-
* </TransportProvider>
|
|
24
|
-
*
|
|
25
|
-
* // In Chat — no type params needed, transport is implicit from nearest provider:
|
|
26
|
-
* const { nodes } = useView({ limit: 30 });
|
|
27
|
-
* const turns = useActiveTurns();
|
|
28
|
-
*/
|
|
29
|
-
import type * as Ably from 'ably';
|
|
30
|
-
/**
|
|
31
|
-
* Bundle of type-safe hooks and provider returned by {@link createTransportHooks}.
|
|
32
|
-
*
|
|
33
|
-
* `TEvent` and `TMessage` are baked in at factory creation time so no type params
|
|
34
|
-
* are needed at hook call sites.
|
|
35
|
-
*/
|
|
36
|
-
export interface TransportHooks<TEvent, TMessage> {
|
|
37
|
-
/**
|
|
38
|
-
* `TransportProvider` narrowed to `TEvent`/`TMessage`. No JSX type params needed.
|
|
39
|
-
*/
|
|
40
|
-
TransportProvider: ComponentType<TransportProviderProps<TEvent, TMessage>>;
|
|
41
|
-
/**
|
|
42
|
-
* Read the transport from context. No type params needed.
|
|
43
|
-
*
|
|
44
|
-
* Returns `{ transport, transportError }`. When no provider is found,
|
|
45
|
-
* `transportError` is set and `transport` is a stub that throws on access —
|
|
46
|
-
* the hook never throws during render.
|
|
47
|
-
*
|
|
48
|
-
* Pass `onError` to subscribe to post-construction transport errors
|
|
49
|
-
* (e.g. send failures, channel continuity loss) without wiring
|
|
50
|
-
* `transport.on('error', …)` manually.
|
|
51
|
-
*/
|
|
52
|
-
useClientTransport: (props?: {
|
|
53
|
-
/** Channel name to look up; omit to use the nearest {@link TransportProvider}. */
|
|
54
|
-
channelName?: string;
|
|
55
|
-
/** When `true`, return a stub transport that throws on any access. */
|
|
56
|
-
skip?: boolean;
|
|
57
|
-
/** Called whenever the resolved transport emits an error event. */
|
|
58
|
-
onError?: (error: Ably.ErrorInfo) => void;
|
|
59
|
-
}) => ClientTransportHandle<TEvent, TMessage>;
|
|
60
|
-
/**
|
|
61
|
-
* Subscribe to the nearest transport's view and return the visible node list with pagination.
|
|
62
|
-
* Pass `transport` to use a transport's default view, `view` to subscribe to a specific view
|
|
63
|
-
* directly. Pass `limit` to auto-load on mount. Pass `skip: true` for an empty handle.
|
|
64
|
-
*/
|
|
65
|
-
useView: (props?: {
|
|
66
|
-
/** Client transport whose default view to subscribe to; defaults to the nearest {@link TransportProvider}. */
|
|
67
|
-
transport?: ClientTransport<TEvent, TMessage> | null;
|
|
68
|
-
/** A specific {@link View} to subscribe to directly. Takes priority over `transport`. */
|
|
69
|
-
view?: View<TEvent, TMessage> | null;
|
|
70
|
-
/** When provided, auto-loads the first page on mount. */
|
|
71
|
-
limit?: number;
|
|
72
|
-
/** When `true`, skip all subscriptions and return an empty handle. */
|
|
73
|
-
skip?: boolean;
|
|
74
|
-
}) => ViewHandle<TEvent, TMessage>;
|
|
75
|
-
/**
|
|
76
|
-
* Track active turns across all clients on the channel.
|
|
77
|
-
* Pass `transport` to override; defaults to the nearest {@link TransportProvider}.
|
|
78
|
-
*/
|
|
79
|
-
useActiveTurns: (props?: {
|
|
80
|
-
/** Override transport; defaults to the nearest {@link TransportProvider}. */
|
|
81
|
-
transport?: ClientTransport<TEvent, TMessage> | null;
|
|
82
|
-
}) => Map<string, Set<string>>;
|
|
83
|
-
/**
|
|
84
|
-
* Navigate conversation branches in the transport tree.
|
|
85
|
-
* Pass `transport` to override; defaults to the nearest {@link TransportProvider}.
|
|
86
|
-
*/
|
|
87
|
-
useTree: (props?: {
|
|
88
|
-
/** Override transport; defaults to the nearest {@link TransportProvider}. */
|
|
89
|
-
transport?: ClientTransport<TEvent, TMessage>;
|
|
90
|
-
}) => TreeHandle<TMessage>;
|
|
91
|
-
/**
|
|
92
|
-
* Subscribe to raw Ably messages on the transport channel.
|
|
93
|
-
* Pass `transport` to override; defaults to the nearest {@link TransportProvider}.
|
|
94
|
-
* Pass `skip: true` to return an empty array without subscribing.
|
|
95
|
-
*/
|
|
96
|
-
useAblyMessages: (props?: {
|
|
97
|
-
/** Override transport; defaults to the nearest {@link TransportProvider}. */
|
|
98
|
-
transport?: ClientTransport<TEvent, TMessage>;
|
|
99
|
-
/** When `true`, skip all subscriptions and return an empty array. */
|
|
100
|
-
skip?: boolean;
|
|
101
|
-
}) => Ably.InboundMessage[];
|
|
102
|
-
/**
|
|
103
|
-
* Create an independent view over the same tree.
|
|
104
|
-
* Pass `transport` to override; defaults to the nearest {@link TransportProvider}.
|
|
105
|
-
* Pass `skip: true` to return an empty handle without creating a view.
|
|
106
|
-
*/
|
|
107
|
-
useCreateView: (props?: {
|
|
108
|
-
/** Override transport; defaults to the nearest {@link TransportProvider}. */
|
|
109
|
-
transport?: ClientTransport<TEvent, TMessage> | null;
|
|
110
|
-
/** When provided, auto-loads the first page on mount. */
|
|
111
|
-
limit?: number;
|
|
112
|
-
/** When `true`, skip view creation and return an empty handle. */
|
|
113
|
-
skip?: boolean;
|
|
114
|
-
}) => ViewHandle<TEvent, TMessage>;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Create a bundle of type-safe hooks and provider for a given `TEvent`/`TMessage` pair.
|
|
118
|
-
*
|
|
119
|
-
* `TEvent` and `TMessage` are captured at factory creation time; hook call sites need
|
|
120
|
-
* no type parameters. The returned hooks are thin wrappers around the standalone hooks
|
|
121
|
-
* with the types resolved.
|
|
122
|
-
* @returns A {@link TransportHooks} bundle.
|
|
123
|
-
*/
|
|
124
|
-
export declare const createTransportHooks: <TEvent, TMessage>() => TransportHooks<TEvent, TMessage>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { ClientTransport } from '../core/transport/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Returns a reactive Map of all active turns on the channel, keyed by clientId.
|
|
4
|
-
* Updates when turns start or end. When `transport` is omitted, uses the nearest
|
|
5
|
-
* {@link TransportProvider}'s transport via context.
|
|
6
|
-
* @param props - Options including optional `transport`.
|
|
7
|
-
* @param props.transport - Transport to track turns for; defaults to the nearest provider.
|
|
8
|
-
* @returns A Map where keys are clientIds and values are Sets of active turnIds.
|
|
9
|
-
*/
|
|
10
|
-
export declare const useActiveTurns: <TEvent, TMessage>({ transport, }?: {
|
|
11
|
-
transport?: ClientTransport<TEvent, TMessage> | null;
|
|
12
|
-
}) => Map<string, Set<string>>;
|