@ably/ai-transport 0.0.1 → 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 (167) hide show
  1. package/README.md +114 -116
  2. package/dist/ably-ai-transport.js +1743 -961
  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 +117 -39
  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 +410 -101
  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 +97 -17
  18. package/dist/core/transport/index.d.ts +5 -3
  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 -8
  24. package/dist/core/transport/run-manager.d.ts +78 -0
  25. package/dist/core/transport/tree.d.ts +435 -0
  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 -402
  32. package/dist/core/transport/view.d.ts +354 -0
  33. package/dist/errors.d.ts +37 -9
  34. package/dist/index.d.ts +6 -6
  35. package/dist/logger.d.ts +12 -0
  36. package/dist/react/ably-ai-transport-react.js +1164 -645
  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 +16 -10
  44. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  45. package/dist/react/use-ably-messages.d.ts +20 -11
  46. package/dist/react/use-client-session.d.ts +81 -0
  47. package/dist/react/use-create-view.d.ts +23 -0
  48. package/dist/react/use-tree.d.ts +35 -0
  49. package/dist/react/use-view.d.ts +110 -0
  50. package/dist/utils.d.ts +32 -23
  51. package/dist/vercel/ably-ai-transport-vercel.js +2748 -1625
  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 +50 -0
  61. package/dist/vercel/index.d.ts +4 -2
  62. package/dist/vercel/react/ably-ai-transport-vercel-react.js +10298 -1410
  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 +70 -1
  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 +33 -0
  67. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +96 -0
  68. package/dist/vercel/react/index.d.ts +4 -0
  69. package/dist/vercel/react/use-chat-transport.d.ts +66 -21
  70. package/dist/vercel/react/use-message-sync.d.ts +31 -12
  71. package/dist/vercel/run-end-reason.d.ts +29 -0
  72. package/dist/vercel/transport/chat-transport.d.ts +71 -30
  73. package/dist/vercel/transport/index.d.ts +25 -18
  74. package/dist/vercel/transport/run-output-stream.d.ts +56 -0
  75. package/dist/version.d.ts +2 -0
  76. package/package.json +47 -34
  77. package/src/constants.ts +126 -47
  78. package/src/core/agent.ts +68 -0
  79. package/src/core/codec/decoder.ts +71 -98
  80. package/src/core/codec/encoder.ts +115 -58
  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 +438 -106
  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 +182 -19
  89. package/src/core/transport/index.ts +29 -22
  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 +58 -40
  95. package/src/core/transport/run-manager.ts +249 -0
  96. package/src/core/transport/tree.ts +1167 -0
  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 -527
  103. package/src/core/transport/view.ts +1271 -0
  104. package/src/errors.ts +42 -9
  105. package/src/event-emitter.ts +3 -2
  106. package/src/index.ts +55 -39
  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 +27 -10
  112. package/src/react/internal/use-resolved-session.ts +63 -0
  113. package/src/react/use-ably-messages.ts +47 -19
  114. package/src/react/use-client-session.ts +201 -0
  115. package/src/react/use-create-view.ts +72 -0
  116. package/src/react/use-tree.ts +84 -0
  117. package/src/react/use-view.ts +275 -0
  118. package/src/react/vite.config.ts +4 -1
  119. package/src/utils.ts +63 -45
  120. package/src/vercel/codec/decoder.ts +336 -255
  121. package/src/vercel/codec/encoder.ts +348 -196
  122. package/src/vercel/codec/events.ts +87 -0
  123. package/src/vercel/codec/index.ts +59 -14
  124. package/src/vercel/codec/reducer.ts +977 -0
  125. package/src/vercel/codec/tool-transitions.ts +122 -0
  126. package/src/vercel/index.ts +7 -3
  127. package/src/vercel/react/contexts/chat-transport-context.ts +41 -0
  128. package/src/vercel/react/contexts/chat-transport-provider.tsx +150 -0
  129. package/src/vercel/react/index.ts +13 -1
  130. package/src/vercel/react/use-chat-transport.ts +162 -42
  131. package/src/vercel/react/use-message-sync.ts +121 -22
  132. package/src/vercel/react/vite.config.ts +4 -2
  133. package/src/vercel/run-end-reason.ts +78 -0
  134. package/src/vercel/transport/chat-transport.ts +553 -113
  135. package/src/vercel/transport/index.ts +40 -28
  136. package/src/vercel/transport/run-output-stream.ts +170 -0
  137. package/src/version.ts +2 -0
  138. package/dist/core/transport/client-transport.d.ts +0 -10
  139. package/dist/core/transport/conversation-tree.d.ts +0 -9
  140. package/dist/core/transport/decode-history.d.ts +0 -41
  141. package/dist/core/transport/server-transport.d.ts +0 -7
  142. package/dist/core/transport/stream-router.d.ts +0 -19
  143. package/dist/core/transport/turn-manager.d.ts +0 -34
  144. package/dist/react/use-active-turns.d.ts +0 -8
  145. package/dist/react/use-client-transport.d.ts +0 -7
  146. package/dist/react/use-conversation-tree.d.ts +0 -20
  147. package/dist/react/use-edit.d.ts +0 -7
  148. package/dist/react/use-history.d.ts +0 -19
  149. package/dist/react/use-messages.d.ts +0 -7
  150. package/dist/react/use-regenerate.d.ts +0 -7
  151. package/dist/react/use-send.d.ts +0 -7
  152. package/dist/vercel/codec/accumulator.d.ts +0 -21
  153. package/src/core/transport/client-transport.ts +0 -959
  154. package/src/core/transport/conversation-tree.ts +0 -434
  155. package/src/core/transport/decode-history.ts +0 -337
  156. package/src/core/transport/server-transport.ts +0 -458
  157. package/src/core/transport/stream-router.ts +0 -118
  158. package/src/core/transport/turn-manager.ts +0 -147
  159. package/src/react/use-active-turns.ts +0 -61
  160. package/src/react/use-client-transport.ts +0 -37
  161. package/src/react/use-conversation-tree.ts +0 -71
  162. package/src/react/use-edit.ts +0 -24
  163. package/src/react/use-history.ts +0 -111
  164. package/src/react/use-messages.ts +0 -32
  165. package/src/react/use-regenerate.ts +0 -24
  166. package/src/react/use-send.ts +0 -25
  167. package/src/vercel/codec/accumulator.ts +0 -603
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Shared tool part transition logic for the Vercel AI SDK codec.
3
+ *
4
+ * Keeps the tool output state transition logic in one place, reusable by the
5
+ * Vercel codec reducer and any other callers.
6
+ */
7
+
8
+ import type * as AI from 'ai';
9
+
10
+ import { stripUndefined } from '../../utils.js';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Tool output chunk type guard
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /** The set of UIMessageChunk types that represent tool output transitions. */
17
+ export type ToolOutputChunk = Extract<
18
+ AI.UIMessageChunk,
19
+ { type: 'tool-output-available' | 'tool-output-error' | 'tool-output-denied' | 'tool-approval-request' }
20
+ >;
21
+
22
+ /**
23
+ * Whether a UIMessageChunk is a tool output transition event.
24
+ * @param chunk - The chunk to test.
25
+ * @returns True if the chunk is a tool output transition type.
26
+ */
27
+ export const isToolOutputChunk = (chunk: AI.UIMessageChunk): chunk is ToolOutputChunk =>
28
+ chunk.type === 'tool-output-available' ||
29
+ chunk.type === 'tool-output-error' ||
30
+ chunk.type === 'tool-output-denied' ||
31
+ chunk.type === 'tool-approval-request';
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Tool base helper
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /** Fields shared by all DynamicToolUIPart state variants. */
38
+ interface ToolBaseFields {
39
+ type: 'dynamic-tool';
40
+ toolName: string;
41
+ toolCallId: string;
42
+ title?: string;
43
+ providerExecuted?: boolean;
44
+ }
45
+
46
+ /**
47
+ * Extract the state-independent base fields for a DynamicToolUIPart.
48
+ * Works with both chunks (tool-input-start, etc.) and existing parts.
49
+ * @param source - Any object containing the required tool identity fields.
50
+ * @param source.toolCallId - The tool call identifier.
51
+ * @param source.toolName - The tool name.
52
+ * @param source.title - Optional display title.
53
+ * @param source.providerExecuted - Whether the provider executed the tool.
54
+ * @returns Base fields shared across all DynamicToolUIPart state variants.
55
+ */
56
+ export const toolBase = (source: {
57
+ toolCallId: string;
58
+ toolName: string;
59
+ title?: string;
60
+ providerExecuted?: boolean;
61
+ }): ToolBaseFields =>
62
+ stripUndefined({
63
+ type: 'dynamic-tool' as const,
64
+ toolCallId: source.toolCallId,
65
+ toolName: source.toolName,
66
+ title: source.title,
67
+ providerExecuted: source.providerExecuted,
68
+ });
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Tool part transition
72
+ // ---------------------------------------------------------------------------
73
+
74
+ /**
75
+ * Transition a DynamicToolUIPart to a new state based on a tool output chunk.
76
+ * Pure function — does not mutate the input part.
77
+ * @param part - The existing tool part to transition.
78
+ * @param chunk - The tool output chunk describing the transition.
79
+ * @returns A new DynamicToolUIPart in the target state.
80
+ */
81
+ export const transitionToolPart = (part: AI.DynamicToolUIPart, chunk: ToolOutputChunk): AI.DynamicToolUIPart => {
82
+ const base = toolBase(part);
83
+
84
+ switch (chunk.type) {
85
+ case 'tool-output-available': {
86
+ return stripUndefined({
87
+ ...base,
88
+ state: 'output-available' as const,
89
+ input: part.input,
90
+ output: chunk.output,
91
+ preliminary: chunk.preliminary,
92
+ });
93
+ }
94
+
95
+ case 'tool-output-error': {
96
+ return {
97
+ ...base,
98
+ state: 'output-error',
99
+ input: part.input,
100
+ errorText: chunk.errorText,
101
+ };
102
+ }
103
+
104
+ case 'tool-output-denied': {
105
+ return {
106
+ ...base,
107
+ state: 'output-denied',
108
+ input: part.input,
109
+ approval: { id: '', approved: false },
110
+ };
111
+ }
112
+
113
+ case 'tool-approval-request': {
114
+ return {
115
+ ...base,
116
+ state: 'approval-requested',
117
+ input: part.input,
118
+ approval: { id: chunk.approvalId },
119
+ };
120
+ }
121
+ }
122
+ };
@@ -1,4 +1,5 @@
1
1
  // Vercel AI SDK codec
2
+ export type { VercelInput, VercelOutput, VercelProjection } from './codec/index.js';
2
3
  export { UIMessageCodec } from './codec/index.js';
3
4
 
4
5
  // Vercel AI SDK transport wrappers (pre-bound to UIMessageCodec)
@@ -6,7 +7,10 @@ export type {
6
7
  ChatTransport,
7
8
  ChatTransportOptions,
8
9
  SendMessagesRequestContext,
9
- VercelClientTransportOptions,
10
- VercelServerTransportOptions,
10
+ VercelAgentSessionOptions,
11
+ VercelClientSessionOptions,
11
12
  } from './transport/index.js';
12
- export { createChatTransport, createClientTransport, createServerTransport } from './transport/index.js';
13
+ export { createAgentSession, createChatTransport, createClientSession } from './transport/index.js';
14
+
15
+ // Vercel-shaped helpers
16
+ export { vercelRunOutcome } from './run-end-reason.js';
@@ -0,0 +1,41 @@
1
+ import type * as Ably from 'ably';
2
+ import type * as AI from 'ai';
3
+ import { createContext } from 'react';
4
+
5
+ import type { ClientSession } from '../../../core/transport/types.js';
6
+ import type { VercelInput, VercelOutput, VercelProjection } from '../../codec/index.js';
7
+ import type { ChatTransport } from '../../transport/chat-transport.js';
8
+
9
+ /**
10
+ * A single entry in the chat transport registry, holding both the
11
+ * underlying {@link ClientSession} and the {@link ChatTransport} wrapping it.
12
+ */
13
+ export interface ChatTransportSlot {
14
+ /** The underlying client session used to create the chat transport. */
15
+ readonly session: ClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>;
16
+ /** Construction error from the underlying {@link ClientSession}, or `undefined` on success. */
17
+ readonly sessionError?: Ably.ErrorInfo | undefined;
18
+ /** The chat transport adapter for use with Vercel's useChat hook. */
19
+ readonly chatTransport: ChatTransport;
20
+ }
21
+
22
+ /**
23
+ * The shape of the single {@link ChatTransportContext} value.
24
+ * Combines the nearest slot with the full registry in one context object.
25
+ */
26
+ export interface ChatTransportContextValue {
27
+ /** The slot from the nearest {@link ChatTransportProvider} in the tree. */
28
+ readonly nearest: ChatTransportSlot | undefined;
29
+ /** All registered slots, keyed by channelName. */
30
+ readonly providers: Readonly<Record<string, ChatTransportSlot>>;
31
+ }
32
+
33
+ /**
34
+ * Context that carries both the nearest {@link ChatTransportSlot} and the full registry of
35
+ * registered slots keyed by channelName. Populated by {@link ChatTransportProvider};
36
+ * read by {@link useChatTransport}.
37
+ */
38
+ export const ChatTransportContext = createContext<ChatTransportContextValue>({
39
+ nearest: undefined,
40
+ providers: {},
41
+ });
@@ -0,0 +1,150 @@
1
+ /**
2
+ * ChatTransportProvider: creates a ChatTransport from a ClientSession and makes it
3
+ * available to descendants via ChatTransportContext.
4
+ *
5
+ * Wraps children with ClientSessionProvider (using UIMessageCodec). The
6
+ * surrounding `<AblyProvider>` supplies the Realtime client; the session
7
+ * resolves the channel from `channelName` itself. An inner component reads
8
+ * the ClientSession via useClientSession() and creates the ChatTransport
9
+ * via useMemo, keyed on the session and transport options.
10
+ *
11
+ * The ChatTransport is NOT closed on unmount — the underlying ClientSession
12
+ * lifecycle is managed by the wrapping ClientSessionProvider. Auto-closing would break
13
+ * React Strict Mode, and ChatTransport.close() delegates to ClientSession.close()
14
+ * which ClientSessionProvider already calls.
15
+ *
16
+ * Multiple ChatTransportProviders can be nested using distinct channelNames.
17
+ * Each provider merges its session into the parent registry, so descendants
18
+ * can access all registered sessions via useChatTransport({ channelName }).
19
+ */
20
+
21
+ import type * as AI from 'ai';
22
+ import { type PropsWithChildren, type ReactNode, useContext, useMemo } from 'react';
23
+
24
+ import { type ClientSessionProviderProps, createSessionHooks } from '../../../react/index.js';
25
+ import { UIMessageCodec, type VercelInput, type VercelOutput, type VercelProjection } from '../../codec/index.js';
26
+ import type { ChatTransportOptions } from '../../transport/index.js';
27
+ import { createChatTransport } from '../../transport/index.js';
28
+ import type { ChatTransportSlot } from './chat-transport-context.js';
29
+ import { ChatTransportContext } from './chat-transport-context.js';
30
+
31
+ export const { ClientSessionProvider, useAblyMessages, useClientSession, useCreateView, useTree, useView } =
32
+ createSessionHooks<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>();
33
+
34
+ type CoreClientSessionProviderProps = Omit<
35
+ ClientSessionProviderProps<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>,
36
+ 'codec'
37
+ >;
38
+
39
+ /**
40
+ * Props for {@link ChatTransportProvider}.
41
+ *
42
+ * All {@link ClientSessionProviderProps} for Vercel types except `codec` (baked as UIMessageCodec),
43
+ * plus the transport-owned invocation POST options (`api` / `credentials` / `fetch`) and
44
+ * `chatOptions` for customizing chat request construction.
45
+ */
46
+ export interface ChatTransportProviderProps extends CoreClientSessionProviderProps {
47
+ /** Endpoint the chat transport POSTs the invocation to, to wake the agent. Default `/api/chat`. */
48
+ api?: string;
49
+ /** Fetch credentials mode for the invocation POST. */
50
+ credentials?: RequestCredentials;
51
+ /** Custom fetch implementation for the invocation POST. Defaults to `globalThis.fetch`. */
52
+ fetch?: typeof globalThis.fetch;
53
+ /**
54
+ * Optional transport options for customizing chat request construction (e.g. the `prepareSendMessagesRequest` hook).
55
+ * Must be stable across renders — wrap in `useMemo` or define outside the component.
56
+ * A new object reference triggers ChatTransport recreation.
57
+ * If this object also sets `api`/`credentials`/`fetch`, the dedicated top-level props of the same name take precedence.
58
+ */
59
+ chatOptions?: ChatTransportOptions;
60
+ }
61
+
62
+ const ChatTransportProviderInner = ({
63
+ channelName,
64
+ chatTransportOptions,
65
+ children,
66
+ }: {
67
+ channelName: string;
68
+ chatTransportOptions: ChatTransportOptions;
69
+ children: ReactNode;
70
+ }) => {
71
+ const { session, sessionError } = useClientSession();
72
+ const { providers: parentProviders } = useContext(ChatTransportContext);
73
+ const chatTransport = useMemo(
74
+ () => createChatTransport(session, chatTransportOptions),
75
+ [session, chatTransportOptions],
76
+ );
77
+ const contextValue = useMemo(() => {
78
+ const slot: ChatTransportSlot = { session, sessionError, chatTransport };
79
+ return {
80
+ nearest: slot,
81
+ providers: { ...parentProviders, [channelName]: slot },
82
+ };
83
+ }, [channelName, parentProviders, chatTransport, session, sessionError]);
84
+
85
+ return <ChatTransportContext.Provider value={contextValue}>{children}</ChatTransportContext.Provider>;
86
+ };
87
+
88
+ /**
89
+ * Provide a {@link ChatTransport} and its underlying {@link ClientSession} to descendant components.
90
+ *
91
+ * Wraps children with `ClientSessionProvider` using `channelName` (the Realtime
92
+ * client is read from the surrounding `<AblyProvider>`), creates a
93
+ * {@link ClientSession} with UIMessageCodec, wraps it in a {@link ChatTransport},
94
+ * and registers the full slot in `ChatTransportContext` under `channelName`. Descendants call
95
+ * {@link useChatTransport} with the same `channelName` to access both.
96
+ *
97
+ * `useClientSession` is also available inside this provider's subtree.
98
+ *
99
+ * ```tsx
100
+ * <ChatTransportProvider channelName="ai:demo">
101
+ * <Chat />
102
+ * </ChatTransportProvider>
103
+ *
104
+ * // Inside Chat:
105
+ * const { chatTransport, session } = useChatTransport();
106
+ * const { session } = useClientSession(); // also available
107
+ * ```
108
+ * @param props - Provider configuration including `channelName`, the invocation POST options (`api` / `credentials` / `fetch`), optional `chatOptions`, and all other session options.
109
+ * @param props.api - Endpoint the chat transport POSTs the invocation to. Default `/api/chat`.
110
+ * @param props.credentials - Fetch credentials mode for the invocation POST.
111
+ * @param props.fetch - Custom fetch implementation for the invocation POST.
112
+ * @param props.chatOptions - Optional hooks for customizing chat request construction. Must be stable (memoized) — a new reference recreates the ChatTransport.
113
+ * @param props.children - Descendant components that consume the chat transport via hooks.
114
+ * @returns A React element wrapping children with ClientSessionContext and ChatTransportContext.
115
+ */
116
+ export const ChatTransportProvider = ({
117
+ api,
118
+ credentials,
119
+ fetch,
120
+ chatOptions,
121
+ children,
122
+ ...sessionProps
123
+ }: ChatTransportProviderProps & PropsWithChildren): ReactNode => {
124
+ // Fold the transport-owned POST options into a single ChatTransportOptions.
125
+ // Memoized so the ChatTransport isn't recreated each render — createChatTransport
126
+ // is keyed on this object's identity.
127
+ const chatTransportOptions = useMemo<ChatTransportOptions>(
128
+ () => ({
129
+ ...chatOptions,
130
+ ...(api !== undefined && { api }),
131
+ ...(credentials !== undefined && { credentials }),
132
+ ...(fetch !== undefined && { fetch }),
133
+ }),
134
+ [api, credentials, fetch, chatOptions],
135
+ );
136
+
137
+ return (
138
+ <ClientSessionProvider
139
+ {...sessionProps}
140
+ codec={UIMessageCodec}
141
+ >
142
+ <ChatTransportProviderInner
143
+ channelName={sessionProps.channelName}
144
+ chatTransportOptions={chatTransportOptions}
145
+ >
146
+ {children}
147
+ </ChatTransportProviderInner>
148
+ </ClientSessionProvider>
149
+ );
150
+ };
@@ -1,4 +1,16 @@
1
- // Vercel-specific React hooks
1
+ // Vercel-specific React entry point: providers, hooks, and their types
2
2
  export type { ChatTransport } from '../transport/chat-transport.js';
3
+ export type { ChatTransportProviderProps } from './contexts/chat-transport-provider.js';
4
+ export {
5
+ ChatTransportProvider,
6
+ ClientSessionProvider,
7
+ useAblyMessages,
8
+ useClientSession,
9
+ useCreateView,
10
+ useTree,
11
+ useView,
12
+ } from './contexts/chat-transport-provider.js';
13
+ export type { ChatTransportHandle, UseChatTransportOptions } from './use-chat-transport.js';
3
14
  export { useChatTransport } from './use-chat-transport.js';
15
+ export type { UseMessageSyncOptions } from './use-message-sync.js';
4
16
  export { useMessageSync } from './use-message-sync.js';
@@ -1,60 +1,180 @@
1
1
  /**
2
- * useChatTransport: wraps a core ClientTransport into the ChatTransport
3
- * shape that Vercel's useChat expects.
2
+ * useChatTransport: reads a ChatTransport and its underlying ClientSession from
3
+ * the nearest ChatTransportProvider.
4
4
  *
5
- * Accepts either an existing ClientTransport or options to create one:
6
- * - From an existing ClientTransport wraps it directly
7
- * - From options creates a ClientTransport with UIMessageCodec and wraps it
5
+ * The chat transport is created by ChatTransportProvider, which wraps the subtree
6
+ * with ClientSessionProvider. The Ably Realtime client is read from the
7
+ * surrounding `<AblyProvider>`. This hook is a thin context reader it does
8
+ * not create or manage any session/transport state.
8
9
  *
9
- * Both forms accept an optional second argument for ChatTransportOptions
10
- * (e.g. prepareSendMessagesRequest for the persistence pattern).
11
- *
12
- * The hook does NOT auto-close the transport on unmount. Channel lifecycle is
13
- * managed by the Ably provider (useChannel). Auto-closing would break React
14
- * Strict Mode. Call chatTransport.close() explicitly if needed.
10
+ * Pass `channelName` to look up a specific provider by name. Omit to use the nearest
11
+ * provider in the tree. Pass `skip: true` to defer (e.g. when auth is not yet resolved)
12
+ * — returns stubs whose properties throw with a descriptive error.
15
13
  */
16
14
 
15
+ import * as Ably from 'ably';
17
16
  import type * as AI from 'ai';
18
- import { useRef } from 'react';
17
+ import { useContext } from 'react';
18
+
19
+ import type { ClientSession, Tree, View } from '../../core/transport/types.js';
20
+ import { ErrorCode } from '../../errors.js';
21
+ import type { VercelInput, VercelOutput, VercelProjection } from '../codec/index.js';
22
+ import type { ChatTransport } from '../transport/index.js';
23
+ import { ChatTransportContext } from './contexts/chat-transport-context.js';
19
24
 
20
- import type { ClientTransport } from '../../core/transport/types.js';
21
- import type { ChatTransport, ChatTransportOptions } from '../transport/chat-transport.js';
22
- import { createChatTransport } from '../transport/chat-transport.js';
23
- import type { VercelClientTransportOptions } from '../transport/index.js';
24
- import { createClientTransport as createCoreClientTransport } from '../transport/index.js';
25
+ const SKIPPED_CLIENT_SESSION: ClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage> = {
26
+ get tree(): Tree<VercelOutput, VercelProjection> {
27
+ throw new Ably.ErrorInfo('unable to access tree; hook is skipped', ErrorCode.InvalidArgument, 400);
28
+ },
29
+ get view(): View<VercelInput, AI.UIMessage> {
30
+ throw new Ably.ErrorInfo('unable to access view; hook is skipped', ErrorCode.InvalidArgument, 400);
31
+ },
32
+ connect: () => {
33
+ throw new Ably.ErrorInfo('unable to connect; hook is skipped', ErrorCode.InvalidArgument, 400);
34
+ },
35
+ createView: (): View<VercelInput, AI.UIMessage> => {
36
+ throw new Ably.ErrorInfo('unable to create view; hook is skipped', ErrorCode.InvalidArgument, 400);
37
+ },
38
+ cancel: () => {
39
+ throw new Ably.ErrorInfo('unable to cancel; hook is skipped', ErrorCode.InvalidArgument, 400);
40
+ },
41
+ on: () => {
42
+ throw new Ably.ErrorInfo('unable to subscribe; hook is skipped', ErrorCode.InvalidArgument, 400);
43
+ },
44
+ close: () => {
45
+ throw new Ably.ErrorInfo('unable to close; hook is skipped', ErrorCode.InvalidArgument, 400);
46
+ },
47
+ };
48
+
49
+ const SKIPPED_CHAT_TRANSPORT: ChatTransport = {
50
+ sendMessages: (): never => {
51
+ throw new Ably.ErrorInfo('unable to send messages; hook is skipped', ErrorCode.InvalidArgument, 400);
52
+ },
53
+ reconnectToStream: (): never => {
54
+ throw new Ably.ErrorInfo('unable to reconnect to stream; hook is skipped', ErrorCode.InvalidArgument, 400);
55
+ },
56
+ close: (): never => {
57
+ throw new Ably.ErrorInfo('unable to close; hook is skipped', ErrorCode.InvalidArgument, 400);
58
+ },
59
+ get streaming(): never {
60
+ throw new Ably.ErrorInfo('unable to access streaming; hook is skipped', ErrorCode.InvalidArgument, 400);
61
+ },
62
+ onStreamingChange: (): never => {
63
+ throw new Ably.ErrorInfo(
64
+ 'unable to subscribe to streaming changes; hook is skipped',
65
+ ErrorCode.InvalidArgument,
66
+ 400,
67
+ );
68
+ },
69
+ };
70
+
71
+ /** Options for {@link useChatTransport}. */
72
+ export interface UseChatTransportOptions {
73
+ /** Channel name to look up; omit to use the nearest {@link ChatTransportProvider}. */
74
+ channelName?: string;
75
+ /** When `true`, return stubs that throw on any access. */
76
+ skip?: boolean;
77
+ }
25
78
 
26
79
  /**
27
- * Type guard: distinguish an existing ClientTransport from options.
28
- * @param x - Either a transport instance or options object.
29
- * @returns True if the argument is a ClientTransport instance.
80
+ * The value returned by {@link useChatTransport}.
81
+ * Provides both the underlying {@link ClientSession} and the {@link ChatTransport}
82
+ * adapter for Vercel's useChat hook.
30
83
  */
31
- const isClientTransport = (
32
- x: ClientTransport<AI.UIMessageChunk, AI.UIMessage> | VercelClientTransportOptions,
33
- ): x is ClientTransport<AI.UIMessageChunk, AI.UIMessage> => 'send' in x && typeof x.send === 'function';
84
+ export interface ChatTransportHandle {
85
+ /**
86
+ * The underlying client session, also available via {@link useClientSession}.
87
+ * A throwing stub when `skip` is `true`, when no matching {@link ClientSessionProvider}
88
+ * was found in the tree, or when session construction failed. Check `sessionError` before use.
89
+ */
90
+ session: ClientSession<VercelInput, VercelOutput, VercelProjection, AI.UIMessage>;
91
+
92
+ /**
93
+ * The chat transport adapter for use with Vercel's `useChat` hook.
94
+ *
95
+ * A throwing stub when `skip` is `true` or when no matching
96
+ * {@link ChatTransportProvider} was found in the tree. When a provider is found
97
+ * but the underlying {@link ClientSession} failed to construct, this is the real
98
+ * transport and `sessionError` is set instead. Check `chatTransportError` and
99
+ * `sessionError` before use.
100
+ */
101
+ chatTransport: ChatTransport;
102
+
103
+ /**
104
+ * Set when no matching {@link ClientSessionProvider} was found, when session
105
+ * construction failed, and `skip` is `false`.
106
+ * `undefined` when the session resolved successfully or when `skip` is `true`.
107
+ */
108
+ sessionError?: Ably.ErrorInfo | undefined;
109
+ /**
110
+ * Set only when no matching {@link ChatTransportProvider} was found and `skip` is
111
+ * `false`.
112
+ * `undefined` when the chat transport resolved successfully (even if session
113
+ * construction failed — see `sessionError`) or when `skip` is `true`.
114
+ */
115
+ chatTransportError?: Ably.ErrorInfo | undefined;
116
+ }
34
117
 
35
118
  /**
36
- * Create and memoize a {@link ChatTransport} for Vercel's useChat hook.
119
+ * Access a {@link ChatTransport} and {@link ClientSession} from the nearest {@link ChatTransportProvider}.
37
120
  *
38
- * Pass an existing `ClientTransport` to wrap it, or pass
39
- * `VercelClientTransportOptions` to create one internally with UIMessageCodec.
40
- * @param transportOrOptions - An existing ClientTransport, or options to create one.
41
- * @param chatOptions - Optional hooks for customizing request construction.
42
- * @returns A {@link ChatTransport} compatible with Vercel's useChat hook.
121
+ * When `channelName` is omitted, the innermost `ChatTransportProvider` in the tree is used.
122
+ * When `skip` is `true`, returns stubs whose every property and method throws
123
+ * an {@link Ably.ErrorInfo} safe to hold in state before conditions are ready.
124
+ * When no provider is found, returns stubs with `chatTransportError` set instead of throwing.
125
+ * @param props - Options for selecting the chat transport.
126
+ * @param props.channelName - The channel name passed to the enclosing `ChatTransportProvider`. Omit to use the nearest.
127
+ * @param props.skip - When `true`, return stubs that throw on any access instead of reading from context.
128
+ * @returns The `ChatTransportHandle` containing both the chat transport adapter and the underlying client session.
43
129
  */
44
- export const useChatTransport = (
45
- transportOrOptions: ClientTransport<AI.UIMessageChunk, AI.UIMessage> | VercelClientTransportOptions,
46
- chatOptions?: ChatTransportOptions,
47
- ): ChatTransport => {
48
- const chatTransportRef = useRef<ChatTransport | null>(null);
49
-
50
- if (chatTransportRef.current === null) {
51
- if (isClientTransport(transportOrOptions)) {
52
- chatTransportRef.current = createChatTransport(transportOrOptions, chatOptions);
53
- } else {
54
- const transport = createCoreClientTransport(transportOrOptions);
55
- chatTransportRef.current = createChatTransport(transport, chatOptions);
130
+ export const useChatTransport = ({ channelName, skip }: UseChatTransportOptions = {}): ChatTransportHandle => {
131
+ const { nearest, providers } = useContext(ChatTransportContext);
132
+
133
+ if (skip) {
134
+ return { session: SKIPPED_CLIENT_SESSION, chatTransport: SKIPPED_CHAT_TRANSPORT };
135
+ }
136
+
137
+ if (channelName !== undefined) {
138
+ const slot = providers[channelName];
139
+ if (slot) {
140
+ return { session: slot.session, chatTransport: slot.chatTransport, sessionError: slot.sessionError };
56
141
  }
142
+ return {
143
+ session: SKIPPED_CLIENT_SESSION,
144
+ chatTransport: SKIPPED_CHAT_TRANSPORT,
145
+ sessionError: new Ably.ErrorInfo(
146
+ `unable to use client session; no ClientSessionProvider found for channelName "${channelName}"`,
147
+ ErrorCode.BadRequest,
148
+ 400,
149
+ ),
150
+ chatTransportError: new Ably.ErrorInfo(
151
+ `unable to use chat transport; no ChatTransportProvider found for channelName "${channelName}"`,
152
+ ErrorCode.BadRequest,
153
+ 400,
154
+ ),
155
+ };
156
+ }
157
+
158
+ if (nearest) {
159
+ return {
160
+ session: nearest.session,
161
+ chatTransport: nearest.chatTransport,
162
+ sessionError: nearest.sessionError,
163
+ };
57
164
  }
58
165
 
59
- return chatTransportRef.current;
166
+ return {
167
+ session: SKIPPED_CLIENT_SESSION,
168
+ chatTransport: SKIPPED_CHAT_TRANSPORT,
169
+ sessionError: new Ably.ErrorInfo(
170
+ 'unable to use session; no ClientSessionProvider found in the tree',
171
+ ErrorCode.BadRequest,
172
+ 400,
173
+ ),
174
+ chatTransportError: new Ably.ErrorInfo(
175
+ 'unable to use chat transport; no ChatTransportProvider found in the tree',
176
+ ErrorCode.BadRequest,
177
+ 400,
178
+ ),
179
+ };
60
180
  };