@ably/ai-transport 0.1.0 → 0.3.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 (221) hide show
  1. package/README.md +93 -111
  2. package/dist/ably-ai-transport.js +2401 -1387
  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 +44 -0
  8. package/dist/core/channel-options.d.ts +57 -0
  9. package/dist/core/codec/codec-event.d.ts +9 -0
  10. package/dist/core/codec/decoder.d.ts +24 -24
  11. package/dist/core/codec/define-codec.d.ts +100 -0
  12. package/dist/core/codec/encoder.d.ts +10 -12
  13. package/dist/core/codec/field-bag.d.ts +85 -0
  14. package/dist/core/codec/fields.d.ts +141 -0
  15. package/dist/core/codec/index.d.ts +8 -2
  16. package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
  17. package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
  18. package/dist/core/codec/input-descriptors.d.ts +281 -0
  19. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  20. package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
  21. package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
  22. package/dist/core/codec/output-descriptors.d.ts +237 -0
  23. package/dist/core/codec/types.d.ts +470 -119
  24. package/dist/core/codec/well-known-inputs.d.ts +52 -0
  25. package/dist/core/transport/agent-session.d.ts +10 -0
  26. package/dist/core/transport/agent-view.d.ts +296 -0
  27. package/dist/core/transport/client-session.d.ts +13 -0
  28. package/dist/core/transport/decode-fold.d.ts +55 -0
  29. package/dist/core/transport/headers.d.ts +121 -14
  30. package/dist/core/transport/index.d.ts +5 -6
  31. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  32. package/dist/core/transport/invocation.d.ts +74 -0
  33. package/dist/core/transport/load-history-pages.d.ts +71 -0
  34. package/dist/core/transport/load-history.d.ts +44 -0
  35. package/dist/core/transport/pipe-stream.d.ts +9 -9
  36. package/dist/core/transport/run-manager.d.ts +76 -0
  37. package/dist/core/transport/session-support.d.ts +55 -0
  38. package/dist/core/transport/tree.d.ts +523 -109
  39. package/dist/core/transport/types/agent.d.ts +375 -0
  40. package/dist/core/transport/types/client.d.ts +201 -0
  41. package/dist/core/transport/types/shared.d.ts +24 -0
  42. package/dist/core/transport/types/tree.d.ts +357 -0
  43. package/dist/core/transport/types/view.d.ts +249 -0
  44. package/dist/core/transport/types.d.ts +13 -553
  45. package/dist/core/transport/view.d.ts +390 -84
  46. package/dist/core/transport/wire-log.d.ts +102 -0
  47. package/dist/errors.d.ts +27 -10
  48. package/dist/index.d.ts +8 -9
  49. package/dist/logger.d.ts +12 -0
  50. package/dist/react/ably-ai-transport-react.js +1365 -1010
  51. package/dist/react/ably-ai-transport-react.js.map +1 -1
  52. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  53. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  54. package/dist/react/contexts/client-session-context.d.ts +37 -0
  55. package/dist/react/contexts/client-session-provider.d.ts +56 -0
  56. package/dist/react/create-session-hooks.d.ts +116 -0
  57. package/dist/react/index.d.ts +13 -12
  58. package/dist/react/internal/skipped-session.d.ts +8 -0
  59. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  60. package/dist/react/use-ably-messages.d.ts +17 -14
  61. package/dist/react/use-client-session.d.ts +81 -0
  62. package/dist/react/use-create-view.d.ts +14 -13
  63. package/dist/react/use-tree.d.ts +30 -15
  64. package/dist/react/use-view.d.ts +81 -50
  65. package/dist/utils.d.ts +48 -71
  66. package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
  67. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  68. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  69. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  70. package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
  71. package/dist/vercel/codec/events.d.ts +50 -0
  72. package/dist/vercel/codec/fields.d.ts +44 -0
  73. package/dist/vercel/codec/fold-content.d.ts +16 -0
  74. package/dist/vercel/codec/fold-data.d.ts +16 -0
  75. package/dist/vercel/codec/fold-input.d.ts +67 -0
  76. package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
  77. package/dist/vercel/codec/fold-text.d.ts +16 -0
  78. package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
  79. package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
  80. package/dist/vercel/codec/index.d.ts +7 -20
  81. package/dist/vercel/codec/inputs.d.ts +11 -0
  82. package/dist/vercel/codec/outputs.d.ts +11 -0
  83. package/dist/vercel/codec/reducer-state.d.ts +121 -0
  84. package/dist/vercel/codec/reducer.d.ts +62 -0
  85. package/dist/vercel/codec/tool-transitions.d.ts +2 -8
  86. package/dist/vercel/codec/wire-data.d.ts +34 -0
  87. package/dist/vercel/index.d.ts +5 -5
  88. package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
  89. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  90. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
  91. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  92. package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
  93. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
  94. package/dist/vercel/react/index.d.ts +1 -2
  95. package/dist/vercel/react/use-chat-transport.d.ts +30 -26
  96. package/dist/vercel/react/use-message-sync.d.ts +17 -30
  97. package/dist/vercel/run-end-reason.d.ts +84 -0
  98. package/dist/vercel/tool-part.d.ts +21 -0
  99. package/dist/vercel/transport/chat-transport.d.ts +41 -24
  100. package/dist/vercel/transport/index.d.ts +24 -20
  101. package/dist/vercel/transport/run-output-stream.d.ts +54 -0
  102. package/dist/version.d.ts +2 -0
  103. package/package.json +31 -24
  104. package/src/constants.ts +124 -51
  105. package/src/core/agent.ts +92 -0
  106. package/src/core/channel-options.ts +89 -0
  107. package/src/core/codec/codec-event.ts +27 -0
  108. package/src/core/codec/decoder.ts +202 -105
  109. package/src/core/codec/define-codec.ts +432 -0
  110. package/src/core/codec/encoder.ts +114 -107
  111. package/src/core/codec/field-bag.ts +142 -0
  112. package/src/core/codec/fields.ts +193 -0
  113. package/src/core/codec/index.ts +56 -6
  114. package/src/core/codec/input-descriptor-decoder.ts +97 -0
  115. package/src/core/codec/input-descriptor-encoder.ts +150 -0
  116. package/src/core/codec/input-descriptors.ts +373 -0
  117. package/src/core/codec/lifecycle-tracker.ts +10 -9
  118. package/src/core/codec/output-descriptor-decoder.ts +139 -0
  119. package/src/core/codec/output-descriptor-encoder.ts +101 -0
  120. package/src/core/codec/output-descriptors.ts +307 -0
  121. package/src/core/codec/types.ts +505 -126
  122. package/src/core/codec/well-known-inputs.ts +96 -0
  123. package/src/core/transport/agent-session.ts +1085 -0
  124. package/src/core/transport/agent-view.ts +738 -0
  125. package/src/core/transport/client-session.ts +780 -0
  126. package/src/core/transport/decode-fold.ts +101 -0
  127. package/src/core/transport/headers.ts +234 -22
  128. package/src/core/transport/index.ts +27 -27
  129. package/src/core/transport/internal/bounded-map.ts +27 -0
  130. package/src/core/transport/invocation.ts +98 -0
  131. package/src/core/transport/load-history-pages.ts +220 -0
  132. package/src/core/transport/load-history.ts +271 -0
  133. package/src/core/transport/pipe-stream.ts +63 -39
  134. package/src/core/transport/run-manager.ts +243 -0
  135. package/src/core/transport/session-support.ts +96 -0
  136. package/src/core/transport/tree.ts +1293 -308
  137. package/src/core/transport/types/agent.ts +434 -0
  138. package/src/core/transport/types/client.ts +247 -0
  139. package/src/core/transport/types/shared.ts +27 -0
  140. package/src/core/transport/types/tree.ts +393 -0
  141. package/src/core/transport/types/view.ts +288 -0
  142. package/src/core/transport/types.ts +13 -706
  143. package/src/core/transport/view.ts +1229 -450
  144. package/src/core/transport/wire-log.ts +189 -0
  145. package/src/errors.ts +29 -9
  146. package/src/event-emitter.ts +3 -2
  147. package/src/index.ts +86 -42
  148. package/src/logger.ts +14 -1
  149. package/src/react/contexts/client-session-context.ts +41 -0
  150. package/src/react/contexts/client-session-provider.tsx +222 -0
  151. package/src/react/create-session-hooks.ts +141 -0
  152. package/src/react/index.ts +24 -13
  153. package/src/react/internal/skipped-session.ts +62 -0
  154. package/src/react/internal/use-resolved-session.ts +63 -0
  155. package/src/react/use-ably-messages.ts +32 -22
  156. package/src/react/use-client-session.ts +178 -0
  157. package/src/react/use-create-view.ts +33 -29
  158. package/src/react/use-tree.ts +61 -30
  159. package/src/react/use-view.ts +138 -96
  160. package/src/utils.ts +83 -131
  161. package/src/vercel/codec/decode-lifecycle.ts +70 -0
  162. package/src/vercel/codec/events.ts +85 -0
  163. package/src/vercel/codec/fields.ts +58 -0
  164. package/src/vercel/codec/fold-content.ts +54 -0
  165. package/src/vercel/codec/fold-data.ts +46 -0
  166. package/src/vercel/codec/fold-input.ts +255 -0
  167. package/src/vercel/codec/fold-lifecycle.ts +85 -0
  168. package/src/vercel/codec/fold-text.ts +55 -0
  169. package/src/vercel/codec/fold-tool-input.ts +86 -0
  170. package/src/vercel/codec/fold-tool-output.ts +79 -0
  171. package/src/vercel/codec/index.ts +28 -21
  172. package/src/vercel/codec/inputs.ts +116 -0
  173. package/src/vercel/codec/outputs.ts +207 -0
  174. package/src/vercel/codec/reducer-state.ts +169 -0
  175. package/src/vercel/codec/reducer.ts +191 -0
  176. package/src/vercel/codec/tool-transitions.ts +3 -14
  177. package/src/vercel/codec/wire-data.ts +64 -0
  178. package/src/vercel/index.ts +7 -19
  179. package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
  180. package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
  181. package/src/vercel/react/index.ts +3 -5
  182. package/src/vercel/react/use-chat-transport.ts +44 -66
  183. package/src/vercel/react/use-message-sync.ts +75 -39
  184. package/src/vercel/run-end-reason.ts +157 -0
  185. package/src/vercel/tool-part.ts +25 -0
  186. package/src/vercel/transport/chat-transport.ts +380 -98
  187. package/src/vercel/transport/index.ts +38 -37
  188. package/src/vercel/transport/run-output-stream.ts +169 -0
  189. package/src/version.ts +2 -0
  190. package/dist/core/transport/client-transport.d.ts +0 -10
  191. package/dist/core/transport/decode-history.d.ts +0 -43
  192. package/dist/core/transport/server-transport.d.ts +0 -7
  193. package/dist/core/transport/stream-router.d.ts +0 -29
  194. package/dist/core/transport/turn-manager.d.ts +0 -37
  195. package/dist/react/contexts/transport-context.d.ts +0 -31
  196. package/dist/react/contexts/transport-provider.d.ts +0 -49
  197. package/dist/react/create-transport-hooks.d.ts +0 -124
  198. package/dist/react/use-active-turns.d.ts +0 -12
  199. package/dist/react/use-client-transport.d.ts +0 -80
  200. package/dist/vercel/codec/accumulator.d.ts +0 -21
  201. package/dist/vercel/codec/decoder.d.ts +0 -22
  202. package/dist/vercel/codec/encoder.d.ts +0 -41
  203. package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
  204. package/dist/vercel/tool-approvals.d.ts +0 -124
  205. package/dist/vercel/tool-events.d.ts +0 -26
  206. package/src/core/transport/client-transport.ts +0 -977
  207. package/src/core/transport/decode-history.ts +0 -485
  208. package/src/core/transport/server-transport.ts +0 -612
  209. package/src/core/transport/stream-router.ts +0 -136
  210. package/src/core/transport/turn-manager.ts +0 -165
  211. package/src/react/contexts/transport-context.ts +0 -37
  212. package/src/react/contexts/transport-provider.tsx +0 -164
  213. package/src/react/create-transport-hooks.ts +0 -144
  214. package/src/react/use-active-turns.ts +0 -72
  215. package/src/react/use-client-transport.ts +0 -197
  216. package/src/vercel/codec/accumulator.ts +0 -588
  217. package/src/vercel/codec/decoder.ts +0 -618
  218. package/src/vercel/codec/encoder.ts +0 -410
  219. package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
  220. package/src/vercel/tool-approvals.ts +0 -380
  221. package/src/vercel/tool-events.ts +0 -53
package/README.md CHANGED
@@ -8,23 +8,23 @@ A durable transport layer between AI agents and users. Streams AI responses over
8
8
 
9
9
  Most AI frameworks stream tokens over HTTP response bodies or SSE. That works until it doesn't: connections drop through corporate proxies, responses vanish on page refresh, and sessions are stuck on a single device or tab. Once an agent starts a long-running task, the user has no way to interrupt it, check if it's still running, or continue the conversation from another device. If a human needs to take over from the agent, the session context is lost.
10
10
 
11
- Ably AI Transport replaces the HTTP stream with an Ably channel. The server publishes tokens to the channel as they arrive from the LLM; the response accumulates on the channel and persists, so partial responses survive disconnection. Any client can subscribe to the same channel from any device. Cancel signals, turn lifecycle events, and conversation history all flow through the channel rather than depending on a single HTTP connection.
11
+ Ably AI Transport replaces the HTTP stream with an Ably channel. The server publishes tokens to the channel as they arrive from the LLM; the response accumulates on the channel and persists, so partial responses survive disconnection. Any client can subscribe to the same channel from any device. Cancel signals, run lifecycle events, and conversation history all flow through the channel rather than depending on a single HTTP connection.
12
12
 
13
13
  ```mermaid
14
14
  sequenceDiagram
15
15
  participant U as User
16
- participant CT as Client Transport
16
+ participant CS as Client Session
17
17
  participant AC as Ably Channel
18
- participant ST as Server Transport
18
+ participant AS as Agent Session
19
19
  participant LLM
20
20
 
21
- U->>CT: type message
22
- CT->>ST: HTTP POST (messages)
23
- ST->>LLM: prompt
24
- LLM-->>ST: token stream
25
- ST->>AC: publish chunks
26
- AC->>CT: subscribe (decode)
27
- CT->>U: render tokens
21
+ U->>CS: type message
22
+ CS->>AS: HTTP POST (messages)
23
+ AS->>LLM: prompt
24
+ LLM-->>AS: token stream
25
+ AS->>AC: publish chunks
26
+ AC->>CS: subscribe (decode)
27
+ CS->>U: render tokens
28
28
  ```
29
29
 
30
30
  Ably AI Transport SDK is not an agent framework or orchestration layer - it works alongside whatever agent framework/model provider you choose, through a pluggable codec architecture (Vercel AI SDK supported now, more frameworks and models coming soon). It can be used in a serverless architecture (e.g. Next.js), with a durable execution framework (e.g. Temporal, Vercel Workflow DevKit) or in a traditional client-server architecture.
@@ -34,11 +34,13 @@ Ably AI Transport SDK is not an agent framework or orchestration layer - it work
34
34
  - **Resumable streaming** - If a connection drops mid-response, client reconnects and picks up where it left off. The response persists on the channel, so nothing is lost.
35
35
  - **Session continuity across surfaces** - The session belongs to the channel, not the connection. A user can change tab or device and pick up at the same point.
36
36
  - **Multi-client sync** - Multiple users, agents, or operators subscribe to the same channel. Human-AI handover is a channel operation, not a session migration.
37
- - **Cancellation** - Cancel signals travel over the Ably channel, not the HTTP connection, and the server turn's `abortSignal` fires automatically.
37
+ - **Cancellation** - Cancel signals travel over the Ably channel, not the HTTP connection, and the server run's `abortSignal` fires automatically.
38
38
  - **Interruption** - Users send new messages while the AI is still responding, with composable primitives for cancel-and-resend or queue-until-complete.
39
- - **Concurrent turns** - Multiple request-response cycles run in parallel on the same channel. Each turn has its own stream and abort signal.
39
+ - **Concurrent runs** - Multiple request-response cycles run in parallel on the same channel. Each run has its own stream and abort signal.
40
40
  - **History** - The Ably channel is the conversation record. Clients hydrate from channel history on load - no separate database query needed.
41
41
  - **Branching** - Regenerate or edit messages to fork the conversation. The SDK tracks parent/child relationships and exposes a navigable tree.
42
+ - **Presence** - The session channel carries Ably Presence. `session.presence` exposes it directly, and ably-js's presence hooks work inside the React providers - see which clients are connected to a session.
43
+ - **LiveObjects** - The session channel can carry Ably LiveObjects: synchronized shared state (maps, counters) alongside the conversation. `session.object` exposes it directly - opt in with the LiveObjects plugin and `channelModes`.
42
44
  - **Framework-agnostic** - A codec interface decouples transport from the AI framework. Ships with a Vercel AI SDK codec; bring your own for any other stack.
43
45
 
44
46
  ### When you need this
@@ -68,7 +70,7 @@ npm install @ably/ai-transport ably ai
68
70
 
69
71
  | Platform | Support |
70
72
  | ------------- | -------------------------------------------------- |
71
- | Node.js | 20+ |
73
+ | Node.js | 22+ |
72
74
  | Browsers | All major browsers (Chrome, Firefox, Edge, Safari) |
73
75
  | TypeScript | Written in TypeScript, ships with types |
74
76
  | React | 18+ and 19+ via dedicated hooks |
@@ -88,49 +90,35 @@ import { streamText, convertToModelMessages } from 'ai';
88
90
  import type { UIMessage } from 'ai';
89
91
  import { anthropic } from '@ai-sdk/anthropic';
90
92
  import Ably from 'ably';
91
- import { createServerTransport } from '@ably/ai-transport/vercel';
92
- import type { TreeNode } from '@ably/ai-transport';
93
-
94
- interface ChatRequestBody {
95
- turnId: string;
96
- clientId: string;
97
- messages: TreeNode<UIMessage>[];
98
- history?: TreeNode<UIMessage>[];
99
- chatId: string;
100
- forkOf?: string;
101
- parent?: string | null;
102
- }
93
+ import { createAgentSession } from '@ably/ai-transport/vercel';
94
+ import { Invocation, type InvocationData } from '@ably/ai-transport';
95
+ import type { UIMessageChunk } from 'ai';
103
96
 
104
97
  const ably = new Ably.Realtime({ key: process.env.ABLY_API_KEY });
105
98
 
106
99
  export async function POST(req: Request) {
107
- const { messages, history, chatId, turnId, clientId, forkOf, parent } = (await req.json()) as ChatRequestBody;
108
-
109
- const channel = ably.channels.get(chatId);
110
- const transport = createServerTransport({ channel });
111
- const turn = transport.newTurn({ turnId, clientId, parent, forkOf });
100
+ const data = (await req.json()) as InvocationData<UIMessageChunk, UIMessage>;
101
+ const invocation = Invocation.fromJSON(data);
112
102
 
113
- await turn.start();
103
+ const session = createAgentSession({ client: ably, channelName: invocation.sessionName });
104
+ await session.connect();
105
+ const run = session.createRun(invocation, { signal: req.signal });
114
106
 
115
- if (messages.length > 0) {
116
- await turn.addMessages(messages, { clientId });
117
- }
118
-
119
- const historyMsgs = (history ?? []).map((h) => h.message);
120
- const newMsgs = messages.map((m) => m.message);
107
+ await run.start();
108
+ await run.loadConversation();
121
109
 
122
110
  const result = streamText({
123
111
  model: anthropic('claude-sonnet-4-6'),
124
112
  system: 'You are a helpful assistant.',
125
- messages: await convertToModelMessages([...historyMsgs, ...newMsgs]),
126
- abortSignal: turn.abortSignal,
113
+ messages: await convertToModelMessages(run.messages),
114
+ abortSignal: run.abortSignal,
127
115
  });
128
116
 
129
117
  // Stream the response over Ably in the background
130
118
  after(async () => {
131
- const { reason } = await turn.streamResponse(result.toUIMessageStream());
132
- await turn.end(reason);
133
- transport.close();
119
+ const { reason } = await run.pipe(result.toUIMessageStream());
120
+ await run.end({ reason });
121
+ session.close();
134
122
  });
135
123
 
136
124
  return new Response(null, { status: 200 });
@@ -143,25 +131,19 @@ export async function POST(req: Request) {
143
131
  'use client';
144
132
 
145
133
  import { useChat } from '@ai-sdk/react';
146
- import {
147
- ChatTransportProvider,
148
- useChatTransport,
149
- useMessageSync,
150
- useActiveTurns,
151
- useView,
152
- } from '@ably/ai-transport/vercel/react';
134
+ import { ChatTransportProvider, useChatTransport, useMessageSync, useView } from '@ably/ai-transport/vercel/react';
153
135
 
154
136
  function ChatInner({ chatId }: { chatId: string }) {
155
137
  const { chatTransport } = useChatTransport();
156
138
 
157
- const { messages, setMessages, sendMessage, stop } = useChat({
139
+ const { messages, setMessages, sendMessage, stop, status } = useChat({
158
140
  id: chatId,
159
141
  transport: chatTransport,
160
142
  });
161
143
 
162
144
  useMessageSync({ setMessages });
163
145
 
164
- const activeTurns = useActiveTurns();
146
+ const isStreaming = status === 'submitted' || status === 'streaming';
165
147
  useView({ limit: 30 });
166
148
 
167
149
  return (
@@ -175,7 +157,7 @@ function ChatInner({ chatId }: { chatId: string }) {
175
157
  sendMessage({ text: 'Hello' });
176
158
  }}
177
159
  >
178
- {activeTurns.size > 0 ? (
160
+ {isStreaming ? (
179
161
  <button
180
162
  type="button"
181
163
  onClick={stop}
@@ -190,12 +172,9 @@ function ChatInner({ chatId }: { chatId: string }) {
190
172
  );
191
173
  }
192
174
 
193
- function Chat({ chatId, clientId }: { chatId: string; clientId?: string }) {
175
+ function Chat({ chatId }: { chatId: string }) {
194
176
  return (
195
- <ChatTransportProvider
196
- channelName={chatId}
197
- clientId={clientId}
198
- >
177
+ <ChatTransportProvider channelName={chatId}>
199
178
  <ChatInner chatId={chatId} />
200
179
  </ChatTransportProvider>
201
180
  );
@@ -240,20 +219,20 @@ The core entry point is framework-agnostic. Bring your own `Codec` to map betwee
240
219
  ### Client
241
220
 
242
221
  ```typescript
243
- import { createClientTransport } from '@ably/ai-transport';
222
+ import { createClientSession } from '@ably/ai-transport';
244
223
  import { myCodec } from './my-codec';
245
224
 
246
- const transport = createClientTransport({
247
- channel, // Ably RealtimeChannel
225
+ const session = createClientSession({
226
+ client: ably, // Ably.Realtime
227
+ channelName: 'ai:demo',
248
228
  codec: myCodec,
249
- clientId: 'user-123',
250
- api: '/api/chat',
251
229
  });
230
+ await session.connect();
252
231
 
253
- const turn = await transport.send(messages);
232
+ const run = await session.view.send(messages);
254
233
 
255
234
  // Read the stream
256
- const reader = turn.stream.getReader();
235
+ const reader = run.stream.getReader();
257
236
  while (true) {
258
237
  const { done, value } = await reader.read();
259
238
  if (done) break;
@@ -261,21 +240,22 @@ while (true) {
261
240
  }
262
241
  ```
263
242
 
264
- ### Server
243
+ ### Agent (server-side)
265
244
 
266
245
  ```typescript
267
- import { createServerTransport } from '@ably/ai-transport';
246
+ import { createAgentSession, Invocation } from '@ably/ai-transport';
268
247
  import { myCodec } from './my-codec';
269
248
 
270
- const transport = createServerTransport({ channel, codec: myCodec });
271
- const turn = transport.newTurn({ turnId, clientId, parent, forkOf });
249
+ const session = createAgentSession({ client: ably, channelName: 'ai:demo', codec: myCodec });
250
+ await session.connect();
251
+ const run = session.createRun(invocation);
272
252
 
273
- await turn.start();
274
- await turn.addMessages(messages, { clientId });
253
+ await run.start();
254
+ await run.loadConversation();
275
255
 
276
- const { reason } = await turn.streamResponse(aiStream);
277
- await turn.end(reason);
278
- transport.close();
256
+ const { reason } = await run.pipe(aiStream);
257
+ await run.end({ reason });
258
+ session.close();
279
259
  ```
280
260
 
281
261
  ---
@@ -291,15 +271,14 @@ transport.close();
291
271
 
292
272
  ### React hooks
293
273
 
294
- | Hook | Entry point | Description |
295
- | -------------------- | --------------- | --------------------------------------------------- |
296
- | `useClientTransport` | `/react` | Create and memoize a client transport instance |
297
- | `useView` | `/react` | Subscribe to messages with history loading |
298
- | `useActiveTurns` | `/react` | Track active turns by client ID |
299
- | `useTree` | `/react` | Navigate branches in a forked conversation |
300
- | `useAblyMessages` | `/react` | Access raw Ably messages |
301
- | `useChatTransport` | `/vercel/react` | Wrap transport for Vercel's `useChat` |
302
- | `useMessageSync` | `/vercel/react` | Sync transport state with `useChat`'s `setMessages` |
274
+ | Hook | Entry point | Description |
275
+ | ------------------ | --------------- | ------------------------------------------------- |
276
+ | `useClientSession` | `/react` | Read a client session from the nearest provider |
277
+ | `useView` | `/react` | Subscribe to messages with history loading |
278
+ | `useTree` | `/react` | Navigate branches in a forked conversation |
279
+ | `useAblyMessages` | `/react` | Access raw Ably messages |
280
+ | `useChatTransport` | `/vercel/react` | Wrap session for Vercel's `useChat` |
281
+ | `useMessageSync` | `/vercel/react` | Sync session state with `useChat`'s `setMessages` |
303
282
 
304
283
  ---
305
284
 
@@ -315,17 +294,18 @@ Two mechanisms cover different failure modes:
315
294
  ### Cancellation
316
295
 
317
296
  ```typescript
318
- // Client: cancel your own active turns
319
- await transport.cancel();
297
+ // Client: cancel a specific run by id
298
+ await session.cancel('run-abc');
320
299
 
321
- // Cancel a specific turn
322
- await transport.cancel({ turnId: 'turn-abc' });
300
+ // Or via the ActiveRun returned by send / regenerate / edit
301
+ const run = await view.send(codec.createUserMessage(userMsg));
302
+ await run.cancel();
323
303
 
324
- // Server: the turn's abortSignal fires automatically
304
+ // Agent: the run's abortSignal fires automatically
325
305
  const result = streamText({
326
306
  model: anthropic('claude-sonnet-4-6'),
327
307
  messages,
328
- abortSignal: turn.abortSignal, // Aborted when client cancels
308
+ abortSignal: run.abortSignal, // Aborted when client cancels
329
309
  });
330
310
  ```
331
311
 
@@ -335,15 +315,15 @@ Regenerate or edit messages to create forks in the conversation tree. The SDK tr
335
315
 
336
316
  ```typescript
337
317
  // Regenerate the last assistant message
338
- const turn = await transport.regenerate(assistantMessageId);
318
+ const run = await session.view.regenerate(assistantMessageId);
339
319
 
340
320
  // Edit a user message and regenerate from that point
341
- const turn = await transport.edit(userMessageId, [newMessage]);
321
+ const run = await session.view.edit(userMessageId, [newMessage]);
342
322
 
343
323
  // Navigate branches
344
- const tree = transport.tree;
324
+ const tree = session.tree;
345
325
  const siblings = tree.getSiblings(messageId);
346
- tree.select(messageId, 1); // Switch to second branch
326
+ session.view.select(messageId, 1); // Switch to second branch
347
327
  ```
348
328
 
349
329
  ### History and hydration
@@ -351,9 +331,9 @@ tree.select(messageId, 1); // Switch to second branch
351
331
  Load previous conversation state when a client joins or returns to a session.
352
332
 
353
333
  ```typescript
354
- const view = transport.view;
334
+ const view = session.view;
355
335
  await view.loadOlder(50);
356
- // view.flattenNodes() returns the messages loaded so far
336
+ // view.getMessages() returns the flat message list loaded so far
357
337
 
358
338
  // Load more older messages
359
339
  await view.loadOlder(50);
@@ -362,15 +342,15 @@ await view.loadOlder(50);
362
342
  ### Events
363
343
 
364
344
  ```typescript
365
- transport.view.on('update', () => {
366
- console.log(transport.view.flattenNodes().map((n) => n.message));
345
+ session.view.on('update', () => {
346
+ console.log(session.view.getMessages());
367
347
  });
368
348
 
369
- transport.tree.on('turn', (event) => {
370
- console.log(event.turnId, event.type); // 'x-ably-turn-start' | 'x-ably-turn-end'
349
+ session.tree.on('run', (event) => {
350
+ console.log(event.runId, event.type); // 'ai-run-start' | 'ai-run-end'
371
351
  });
372
352
 
373
- transport.on('error', (error) => {
353
+ session.on('error', (error) => {
374
354
  console.error(error.code, error.message);
375
355
  });
376
356
  ```
@@ -381,10 +361,10 @@ transport.on('error', (error) => {
381
361
 
382
362
  Detailed documentation lives in the [`docs/`](./docs/) directory:
383
363
 
384
- - **[Concepts](./docs/concepts/)** - [Transport architecture](./docs/concepts/transport.md), [Turns](./docs/concepts/turns.md)
385
- - **[Get started](./docs/get-started/)** - [Vercel AI SDK with useChat](./docs/get-started/vercel-use-chat.md), [Vercel AI SDK with useClientTransport](./docs/get-started/vercel-use-client-transport.md)
364
+ - **[Concepts](./docs/concepts/)** - [Sessions](./docs/concepts/sessions.md), [Runs](./docs/concepts/runs.md)
365
+ - **[Get started](./docs/get-started/)** - [Vercel AI SDK with useChat](./docs/get-started/vercel-use-chat.md), [Vercel AI SDK with useClientSession](./docs/get-started/vercel-use-client-session.md)
386
366
  - **[Frameworks](./docs/frameworks/)** - [Vercel AI SDK](./docs/frameworks/vercel-ai-sdk.md)
387
- - **[Features](./docs/features/)** - [Streaming](./docs/features/streaming.md), [Cancellation](./docs/features/cancel.md), [Interruption](./docs/features/interruption.md), [Optimistic updates](./docs/features/optimistic-updates.md), [History](./docs/features/history.md), [Branching](./docs/features/branching.md), [Multi-client sync](./docs/features/multi-client.md), [Concurrent turns](./docs/features/concurrent-turns.md)
367
+ - **[Features](./docs/features/)** - [Streaming](./docs/features/streaming.md), [Cancellation](./docs/features/cancel.md), [Interruption](./docs/features/interruption.md), [Optimistic updates](./docs/features/optimistic-updates.md), [History](./docs/features/history.md), [Branching](./docs/features/branching.md), [Multi-client sync](./docs/features/multi-client.md), [Concurrent runs](./docs/features/concurrent-runs.md), [Presence](./docs/features/presence.md), [LiveObjects](./docs/features/liveobjects.md)
388
368
  - **[Reference](./docs/reference/)** - [React hooks](./docs/reference/react-hooks.md), [Error codes](./docs/reference/error-codes.md)
389
369
  - **[Internals](./docs/internals/)** - Architecture details for contributors
390
370
 
@@ -395,20 +375,22 @@ Detailed documentation lives in the [`docs/`](./docs/) directory:
395
375
  Working demo applications live in the [`demo/`](./demo/) directory:
396
376
 
397
377
  - **[`demo/vercel/react/use-chat/`](./demo/vercel/react/use-chat/)** - Vercel AI SDK with `useChat` integration
398
- - **[`demo/vercel/react/use-client-transport/`](./demo/vercel/react/use-client-transport/)** - Vercel AI SDK with direct `useClientTransport` hooks
378
+ - **[`demo/vercel/react/use-client-session/`](./demo/vercel/react/use-client-session/)** - Vercel AI SDK with direct `useClientSession` hooks
399
379
 
400
380
  ---
401
381
 
402
382
  ## Development
403
383
 
384
+ This repository uses [pnpm](https://pnpm.io/). Enable Corepack once (`corepack enable`) to pick up the pinned version automatically.
385
+
404
386
  ```bash
405
- npm install
406
- npm run build # Build all entry points (ESM + UMD/CJS + .d.ts)
407
- npm run typecheck # Type check
408
- npm run lint # Lint
409
- npm test # Unit tests (mocks only)
410
- npm run test:integration # Integration tests (needs ABLY_API_KEY)
411
- npm run precommit # format:check + lint + typecheck
387
+ pnpm install
388
+ pnpm run build # Build all entry points (ESM + UMD/CJS + .d.ts)
389
+ pnpm run typecheck # Type check
390
+ pnpm run lint # Lint
391
+ pnpm test # Unit tests (mocks only)
392
+ pnpm run test:integration # Integration tests (needs ABLY_API_KEY)
393
+ pnpm run precommit # format:check + lint + typecheck
412
394
  ```
413
395
 
414
396
  ### Project structure
@@ -417,7 +399,7 @@ npm run precommit # format:check + lint + typecheck
417
399
  src/
418
400
  ├── core/ # Generic transport and codec (no framework deps)
419
401
  │ ├── codec/ # Codec interfaces and core encoder/decoder
420
- │ └── transport/ # ClientTransport, ServerTransport, Tree
402
+ │ └── transport/ # ClientSession, AgentSession, Tree
421
403
  ├── react/ # React hooks for any codec
422
404
  ├── vercel/ # Vercel AI SDK codec and transport adapters
423
405
  │ ├── codec/ # UIMessageCodec