@ably/ai-transport 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +91 -100
  2. package/dist/ably-ai-transport.js +1553 -1238
  3. package/dist/ably-ai-transport.js.map +1 -1
  4. package/dist/ably-ai-transport.umd.cjs +1 -1
  5. package/dist/ably-ai-transport.umd.cjs.map +1 -1
  6. package/dist/constants.d.ts +116 -42
  7. package/dist/core/agent.d.ts +29 -0
  8. package/dist/core/codec/decoder.d.ts +20 -23
  9. package/dist/core/codec/encoder.d.ts +11 -8
  10. package/dist/core/codec/index.d.ts +1 -2
  11. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  12. package/dist/core/codec/types.d.ts +407 -115
  13. package/dist/core/transport/agent-session.d.ts +10 -0
  14. package/dist/core/transport/branch-chain.d.ts +43 -0
  15. package/dist/core/transport/client-session.d.ts +13 -0
  16. package/dist/core/transport/decode-fold.d.ts +47 -0
  17. package/dist/core/transport/headers.d.ts +96 -18
  18. package/dist/core/transport/index.d.ts +5 -6
  19. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  20. package/dist/core/transport/invocation.d.ts +74 -0
  21. package/dist/core/transport/load-conversation.d.ts +128 -0
  22. package/dist/core/transport/load-history.d.ts +39 -0
  23. package/dist/core/transport/pipe-stream.d.ts +9 -9
  24. package/dist/core/transport/run-manager.d.ts +78 -0
  25. package/dist/core/transport/tree.d.ts +373 -109
  26. package/dist/core/transport/types/agent.d.ts +353 -0
  27. package/dist/core/transport/types/client.d.ts +168 -0
  28. package/dist/core/transport/types/shared.d.ts +24 -0
  29. package/dist/core/transport/types/tree.d.ts +315 -0
  30. package/dist/core/transport/types/view.d.ts +222 -0
  31. package/dist/core/transport/types.d.ts +13 -553
  32. package/dist/core/transport/view.d.ts +272 -84
  33. package/dist/errors.d.ts +21 -10
  34. package/dist/index.d.ts +6 -8
  35. package/dist/logger.d.ts +12 -0
  36. package/dist/react/ably-ai-transport-react.js +976 -990
  37. package/dist/react/ably-ai-transport-react.js.map +1 -1
  38. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  39. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  40. package/dist/react/contexts/client-session-context.d.ts +36 -0
  41. package/dist/react/contexts/client-session-provider.d.ts +53 -0
  42. package/dist/react/create-session-hooks.d.ts +116 -0
  43. package/dist/react/index.d.ts +12 -12
  44. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  45. package/dist/react/use-ably-messages.d.ts +17 -14
  46. package/dist/react/use-client-session.d.ts +81 -0
  47. package/dist/react/use-create-view.d.ts +14 -13
  48. package/dist/react/use-tree.d.ts +30 -15
  49. package/dist/react/use-view.d.ts +82 -51
  50. package/dist/utils.d.ts +32 -23
  51. package/dist/vercel/ably-ai-transport-vercel.js +2573 -2086
  52. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  53. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  54. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  55. package/dist/vercel/codec/decoder.d.ts +5 -18
  56. package/dist/vercel/codec/encoder.d.ts +6 -36
  57. package/dist/vercel/codec/events.d.ts +51 -0
  58. package/dist/vercel/codec/index.d.ts +24 -12
  59. package/dist/vercel/codec/reducer.d.ts +144 -0
  60. package/dist/vercel/codec/tool-transitions.d.ts +2 -2
  61. package/dist/vercel/index.d.ts +4 -5
  62. package/dist/vercel/react/ably-ai-transport-vercel-react.js +3907 -3266
  63. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  64. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +33 -8
  65. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  66. package/dist/vercel/react/contexts/chat-transport-context.d.ts +7 -6
  67. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
  68. package/dist/vercel/react/index.d.ts +1 -2
  69. package/dist/vercel/react/use-chat-transport.d.ts +30 -26
  70. package/dist/vercel/react/use-message-sync.d.ts +17 -30
  71. package/dist/vercel/run-end-reason.d.ts +29 -0
  72. package/dist/vercel/transport/chat-transport.d.ts +43 -24
  73. package/dist/vercel/transport/index.d.ts +25 -21
  74. package/dist/vercel/transport/run-output-stream.d.ts +56 -0
  75. package/dist/version.d.ts +2 -0
  76. package/package.json +30 -23
  77. package/src/constants.ts +124 -51
  78. package/src/core/agent.ts +68 -0
  79. package/src/core/codec/decoder.ts +71 -98
  80. package/src/core/codec/encoder.ts +113 -65
  81. package/src/core/codec/index.ts +13 -6
  82. package/src/core/codec/lifecycle-tracker.ts +10 -9
  83. package/src/core/codec/types.ts +436 -120
  84. package/src/core/transport/agent-session.ts +1344 -0
  85. package/src/core/transport/branch-chain.ts +58 -0
  86. package/src/core/transport/client-session.ts +775 -0
  87. package/src/core/transport/decode-fold.ts +91 -0
  88. package/src/core/transport/headers.ts +181 -22
  89. package/src/core/transport/index.ts +25 -26
  90. package/src/core/transport/internal/bounded-map.ts +27 -0
  91. package/src/core/transport/invocation.ts +98 -0
  92. package/src/core/transport/load-conversation.ts +355 -0
  93. package/src/core/transport/load-history.ts +269 -0
  94. package/src/core/transport/pipe-stream.ts +54 -39
  95. package/src/core/transport/run-manager.ts +249 -0
  96. package/src/core/transport/tree.ts +926 -308
  97. package/src/core/transport/types/agent.ts +407 -0
  98. package/src/core/transport/types/client.ts +211 -0
  99. package/src/core/transport/types/shared.ts +27 -0
  100. package/src/core/transport/types/tree.ts +344 -0
  101. package/src/core/transport/types/view.ts +259 -0
  102. package/src/core/transport/types.ts +13 -706
  103. package/src/core/transport/view.ts +864 -433
  104. package/src/errors.ts +22 -9
  105. package/src/event-emitter.ts +3 -2
  106. package/src/index.ts +52 -41
  107. package/src/logger.ts +14 -1
  108. package/src/react/contexts/client-session-context.ts +41 -0
  109. package/src/react/contexts/client-session-provider.tsx +186 -0
  110. package/src/react/create-session-hooks.ts +141 -0
  111. package/src/react/index.ts +23 -13
  112. package/src/react/internal/use-resolved-session.ts +63 -0
  113. package/src/react/use-ably-messages.ts +32 -22
  114. package/src/react/use-client-session.ts +201 -0
  115. package/src/react/use-create-view.ts +33 -29
  116. package/src/react/use-tree.ts +61 -30
  117. package/src/react/use-view.ts +139 -97
  118. package/src/utils.ts +63 -45
  119. package/src/vercel/codec/decoder.ts +336 -258
  120. package/src/vercel/codec/encoder.ts +343 -205
  121. package/src/vercel/codec/events.ts +87 -0
  122. package/src/vercel/codec/index.ts +60 -13
  123. package/src/vercel/codec/reducer.ts +977 -0
  124. package/src/vercel/codec/tool-transitions.ts +2 -2
  125. package/src/vercel/index.ts +6 -19
  126. package/src/vercel/react/contexts/chat-transport-context.ts +7 -6
  127. package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
  128. package/src/vercel/react/index.ts +3 -5
  129. package/src/vercel/react/use-chat-transport.ts +47 -49
  130. package/src/vercel/react/use-message-sync.ts +80 -39
  131. package/src/vercel/run-end-reason.ts +78 -0
  132. package/src/vercel/transport/chat-transport.ts +392 -98
  133. package/src/vercel/transport/index.ts +39 -38
  134. package/src/vercel/transport/run-output-stream.ts +170 -0
  135. package/src/version.ts +2 -0
  136. package/dist/core/transport/client-transport.d.ts +0 -10
  137. package/dist/core/transport/decode-history.d.ts +0 -43
  138. package/dist/core/transport/server-transport.d.ts +0 -7
  139. package/dist/core/transport/stream-router.d.ts +0 -29
  140. package/dist/core/transport/turn-manager.d.ts +0 -37
  141. package/dist/react/contexts/transport-context.d.ts +0 -31
  142. package/dist/react/contexts/transport-provider.d.ts +0 -49
  143. package/dist/react/create-transport-hooks.d.ts +0 -124
  144. package/dist/react/use-active-turns.d.ts +0 -12
  145. package/dist/react/use-client-transport.d.ts +0 -80
  146. package/dist/vercel/codec/accumulator.d.ts +0 -21
  147. package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
  148. package/dist/vercel/tool-approvals.d.ts +0 -124
  149. package/dist/vercel/tool-events.d.ts +0 -26
  150. package/src/core/transport/client-transport.ts +0 -977
  151. package/src/core/transport/decode-history.ts +0 -485
  152. package/src/core/transport/server-transport.ts +0 -612
  153. package/src/core/transport/stream-router.ts +0 -136
  154. package/src/core/transport/turn-manager.ts +0 -165
  155. package/src/react/contexts/transport-context.ts +0 -37
  156. package/src/react/contexts/transport-provider.tsx +0 -164
  157. package/src/react/create-transport-hooks.ts +0 -144
  158. package/src/react/use-active-turns.ts +0 -72
  159. package/src/react/use-client-transport.ts +0 -197
  160. package/src/vercel/codec/accumulator.ts +0 -588
  161. package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
  162. package/src/vercel/tool-approvals.ts +0 -380
  163. package/src/vercel/tool-events.ts +0 -53
@@ -5,63 +5,64 @@
5
5
  * explicitly when using the Vercel AI SDK integration.
6
6
  *
7
7
  * ```ts
8
- * import { createClientTransport } from '@ably/ai-transport/vercel';
8
+ * import { createClientSession } from '@ably/ai-transport/vercel';
9
9
  *
10
- * const transport = createClientTransport({ channel });
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 { createClientTransport as createCoreClientTransport } from '../../core/transport/client-transport.js';
21
- import { createServerTransport as createCoreServerTransport } from '../../core/transport/server-transport.js';
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
- ClientTransport,
24
- ClientTransportOptions,
25
- ServerTransport,
26
- ServerTransportOptions,
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 transport options with Vercel AI SDK types pre-applied. */
31
- type CoreClientOpts = ClientTransportOptions<AI.UIMessageChunk, AI.UIMessage>;
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 transport. Same as core options but without the codec field, and with `api` optional (defaults to `"/api/chat"`). */
34
- export type VercelClientTransportOptions = Omit<CoreClientOpts, 'codec' | 'api'> & Partial<Pick<CoreClientOpts, 'api'>>;
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 server transport. Same as core options but without the codec field. */
37
- export type VercelServerTransportOptions = Omit<ServerTransportOptions<AI.UIMessageChunk, AI.UIMessage>, 'codec'>;
38
-
39
- export const DEFAULT_VERCEL_API = '/api/chat';
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 transport pre-configured with the Vercel AI SDK codec.
44
+ * Create a client-side session pre-configured with the Vercel AI SDK codec.
43
45
  *
44
- * Equivalent to calling the core `createClientTransport` with `codec: UIMessageCodec`.
45
- * @param options - Configuration for the client transport (codec is provided automatically).
46
- * @returns A new {@link ClientTransport} for Vercel AI SDK UIMessage/UIMessageChunk types.
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 createClientTransport = (
49
- options: VercelClientTransportOptions,
50
- ): ClientTransport<AI.UIMessageChunk, AI.UIMessage> =>
51
- createCoreClientTransport({
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 a server-side transport pre-configured with the Vercel AI SDK codec.
59
+ * Create an agent (server-side) session pre-configured with the Vercel AI SDK codec.
60
60
  *
61
- * Equivalent to calling the core `createServerTransport` with `codec: UIMessageCodec`.
62
- * @param options - Configuration for the server transport (codec is provided automatically).
63
- * @returns A new {@link ServerTransport} for Vercel AI SDK UIMessage/UIMessageChunk types.
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 createServerTransport = (
66
- options: VercelServerTransportOptions,
67
- ): ServerTransport<AI.UIMessageChunk, AI.UIMessage> => createCoreServerTransport({ ...options, codec: UIMessageCodec });
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
@@ -0,0 +1,2 @@
1
+ /** SDK version. Kept in sync with `package.json` by the `/release` workflow. */
2
+ export const VERSION = '0.2.0';
@@ -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>>;