@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
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Shared reducer state: the projection shape, its internal tracker types,
3
+ * `init`, and the message/tracker lookup helpers the per-concern fold modules
4
+ * build on. This module is the base of the reducer's import DAG — the fold
5
+ * modules depend on it; it depends on none of them.
6
+ */
7
+
8
+ import type * as AI from 'ai';
9
+
10
+ import type { CodecMessage } from '../../core/codec/index.js';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Internal tracker state
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /**
17
+ * Tracks an in-progress tool part within a UIMessage. Text and reasoning
18
+ * parts don't need this — we write to them directly via partIndex. Tool
19
+ * parts need an extra `inputText` buffer because deltas arrive as raw
20
+ * JSON fragments that must be accumulated before parsing.
21
+ */
22
+ export interface ToolPartTracker {
23
+ /** Index in the message's parts array. */
24
+ partIndex: number;
25
+ /** Accumulated streaming input text (for JSON parsing on completion). */
26
+ inputText: string;
27
+ }
28
+
29
+ /** Per-codecMessageId tracking state for in-progress streams within a UIMessage. */
30
+ export interface MessageTrackers {
31
+ /** Text stream id → partIndex. */
32
+ text: Map<string, number>;
33
+ /** Reasoning stream id → partIndex. */
34
+ reasoning: Map<string, number>;
35
+ /** Tool call id → tracker. */
36
+ tools: Map<string, ToolPartTracker>;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Projection
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /**
44
+ * The per-Run state produced by the Vercel codec's reducer.
45
+ *
46
+ * The SDK reads only `messages` (via `Codec.getMessages`). The remaining
47
+ * fields are internal to the reducer; they happen to live on the
48
+ * projection because the projection is the only thing the reducer can
49
+ * carry from fold to fold (it has no instance state).
50
+ */
51
+ export interface VercelProjection {
52
+ /**
53
+ * UIMessages produced or modified in this Run, in publication order,
54
+ * each paired with its codec-message-id. The reducer correlates strictly
55
+ * on `codecMessageId`; `message.id` is preserved verbatim from the source
56
+ * (the AI SDK stream's `start.messageId` for assistants, the caller's id
57
+ * for user messages) and is never used as an identity key.
58
+ */
59
+ messages: CodecMessage<AI.UIMessage>[];
60
+ /** Per-codecMessageId tracker state for streamed parts. Internal — do not access. */
61
+ trackers: Map<string, MessageTrackers>;
62
+ /**
63
+ * Tool-resolution events that arrived before any assistant in this
64
+ * projection had a matching `toolCallId`. Re-evaluated on every
65
+ * subsequent fold so that an out-of-order tool output is folded as
66
+ * soon as the corresponding assistant lands.
67
+ */
68
+ pendingToolResolutions: PendingToolResolution[];
69
+ }
70
+
71
+ /**
72
+ * A buffered tool resolution waiting for its assistant message to arrive.
73
+ * The reducer scans pending entries after every successful fold so an
74
+ * out-of-order tool output is promoted as soon as the matching assistant
75
+ * is added to the projection.
76
+ */
77
+ export interface PendingToolResolution {
78
+ /** The codec-message-id of the assistant the resolution targets. */
79
+ targetCodecMessageId: string;
80
+ /** Tool call this resolution targets. */
81
+ toolCallId: string;
82
+ /** Variant of the tool-resolution used to transition the assistant's tool part. */
83
+ resolution:
84
+ | { kind: 'tool-result'; output: unknown }
85
+ | { kind: 'tool-result-error'; message: string }
86
+ | { kind: 'tool-approval-response'; approved: boolean; reason?: string };
87
+ }
88
+
89
+ /** A located `dynamic-tool` part with its owning message and tracker. */
90
+ export interface OwnerLookup {
91
+ /** The message owning the tool part. */
92
+ message: AI.UIMessage;
93
+ /** The tracker pointing at the part's index. */
94
+ tracker: ToolPartTracker;
95
+ /** The resolved `dynamic-tool` part itself. */
96
+ part: AI.DynamicToolUIPart;
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // init
101
+ // ---------------------------------------------------------------------------
102
+
103
+ /**
104
+ * Build an empty initial projection.
105
+ * @returns A fresh VercelProjection with no messages and no tracker state.
106
+ */
107
+ export const init = (): VercelProjection => ({
108
+ messages: [],
109
+ trackers: new Map(),
110
+ pendingToolResolutions: [],
111
+ });
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Message + tracker helpers
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /**
118
+ * Resolve the assistant message for a codec-message-id, creating an empty
119
+ * placeholder when none exists yet.
120
+ * @param state - Projection to read or extend.
121
+ * @param codecMessageId - The codec-message-id to resolve.
122
+ * @returns The existing or newly-seeded UIMessage for that id.
123
+ */
124
+ export const ensureMessage = (state: VercelProjection, codecMessageId: string): AI.UIMessage => {
125
+ let entry = state.messages.find((e) => e.codecMessageId === codecMessageId);
126
+ if (!entry) {
127
+ // No source id seen yet — seed the domain `message.id` with the
128
+ // codec-message-id as a fallback. The `start` chunk overwrites it with
129
+ // the stream's `messageId` when the stream provides one.
130
+ entry = { codecMessageId, message: { id: codecMessageId, role: 'assistant', parts: [] } };
131
+ state.messages.push(entry);
132
+ }
133
+ return entry.message;
134
+ };
135
+
136
+ /**
137
+ * Resolve the stream trackers for a codec-message-id, creating empty maps
138
+ * when none exist yet.
139
+ * @param state - Projection to read or extend.
140
+ * @param messageId - The codec-message-id whose trackers to resolve.
141
+ * @returns The existing or newly-created tracker maps for that id.
142
+ */
143
+ export const ensureTrackers = (state: VercelProjection, messageId: string): MessageTrackers => {
144
+ let trackers = state.trackers.get(messageId);
145
+ if (!trackers) {
146
+ trackers = { text: new Map(), reasoning: new Map(), tools: new Map() };
147
+ state.trackers.set(messageId, trackers);
148
+ }
149
+ return trackers;
150
+ };
151
+
152
+ /**
153
+ * Resolve the `dynamic-tool` part tracked for a toolCallId within a message.
154
+ * @param message - The message whose parts to read.
155
+ * @param trackers - The message's tracker maps.
156
+ * @param toolCallId - The tool call to resolve.
157
+ * @returns The tracker and part, or `undefined` if untracked or the part is not a dynamic-tool.
158
+ */
159
+ export const getToolPart = (
160
+ message: AI.UIMessage,
161
+ trackers: MessageTrackers,
162
+ toolCallId: string,
163
+ ): { tracker: ToolPartTracker; part: AI.DynamicToolUIPart } | undefined => {
164
+ const tracker = trackers.tools.get(toolCallId);
165
+ if (!tracker) return undefined;
166
+ const part = message.parts[tracker.partIndex];
167
+ if (part?.type !== 'dynamic-tool') return undefined;
168
+ return { tracker, part };
169
+ };
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Vercel AI SDK reducer.
3
+ *
4
+ * Pure `(init, fold)` over the `VercelInput | VercelOutput` union. Folds
5
+ * input variants (user-message, tool-result, tool-result-error,
6
+ * tool-approval-response) and `UIMessageChunk` outputs into a
7
+ * VercelProjection holding `UIMessage[]` plus internal stream-tracker
8
+ * state.
9
+ *
10
+ * The reducer is stateless: every fold is `(state, event, meta) → state'`,
11
+ * with no instance state. Mutation in place is allowed — the projection
12
+ * is single-owner.
13
+ *
14
+ * The reducer does not dedup or reorder. The transport sequences events
15
+ * canonically — ascending by wire serial across messages, in decode order
16
+ * within a wire — and delivers each exactly once, so the reducer folds
17
+ * unconditionally. Last-writer-wins for events competing over the same
18
+ * logical state (e.g. two `tool-output-available` for one `toolCallId`)
19
+ * falls out of fold order: the highest-serial event folds last.
20
+ *
21
+ * Client-published tool resolutions (`ToolResult`, `ToolResultError`,
22
+ * `ToolApprovalResponse`) carry `codecMessageId` targeting the assistant
23
+ * they amend; the reducer applies the resolution onto that assistant's
24
+ * `dynamic-tool` part directly. If the assistant has not yet arrived in
25
+ * the projection (out-of-order delivery), the resolution is buffered in
26
+ * `pendingToolResolutions` and re-evaluated on each subsequent fold.
27
+ *
28
+ * This file is the reducer's public facade and dispatch: `init`,
29
+ * `getMessages`, `fold`, and the output-chunk router. The per-concern fold
30
+ * logic lives in the sibling `fold-*` modules over a shared `reducer-state`
31
+ * base; the import graph is an acyclic DAG rooted here.
32
+ */
33
+
34
+ import type * as AI from 'ai';
35
+
36
+ import type { CodecEvent, CodecMessage, ReducerMeta } from '../../core/codec/index.js';
37
+ import type { VercelInput, VercelOutput } from './events.js';
38
+ import { foldContentPart } from './fold-content.js';
39
+ import { foldDataPart } from './fold-data.js';
40
+ import {
41
+ foldClientToolResult,
42
+ foldClientToolResultError,
43
+ foldToolApprovalResponse,
44
+ foldUserMessage,
45
+ retryPendingResolutions,
46
+ } from './fold-input.js';
47
+ import { foldLifecycle } from './fold-lifecycle.js';
48
+ import { foldTextOrReasoning } from './fold-text.js';
49
+ import { foldToolInput } from './fold-tool-input.js';
50
+ import { foldToolOutput } from './fold-tool-output.js';
51
+ import type { VercelProjection } from './reducer-state.js';
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // fold
55
+ // ---------------------------------------------------------------------------
56
+
57
+ /**
58
+ * Fold one input or output event into the projection. Mutates and returns
59
+ * `state`.
60
+ *
61
+ * The transport invokes `fold` exactly once per event, in canonical order,
62
+ * so the reducer folds unconditionally — no dedup or high-water-mark here.
63
+ * Competing events resolve by order (the highest-serial event folds last
64
+ * and wins). Orphan events (e.g. tool-output for an unknown toolCallId) are
65
+ * dropped silently inside the per-variant fold helpers.
66
+ * @param state - Projection to fold into (may be mutated in place).
67
+ * @param event - Input or output event to fold.
68
+ * @param meta - Transport-derived metadata (serial, optional messageId).
69
+ * @returns The same projection reference, possibly mutated.
70
+ */
71
+ export const fold = (
72
+ state: VercelProjection,
73
+ event: CodecEvent<VercelInput, VercelOutput>,
74
+ meta: ReducerMeta,
75
+ ): VercelProjection => {
76
+ if (event.direction === 'input') {
77
+ const input = event.event;
78
+ switch (input.kind) {
79
+ case 'user-message': {
80
+ foldUserMessage(state, input.message, meta);
81
+ break;
82
+ }
83
+ case 'regenerate': {
84
+ // Regenerate input — wire-only signal. Carries no projection state;
85
+ // the agent reads `target` / `parent` from the wire headers via
86
+ // the input-event lookup path. No fold work to do here.
87
+ break;
88
+ }
89
+ case 'tool-result': {
90
+ foldClientToolResult(state, input);
91
+ break;
92
+ }
93
+ case 'tool-result-error': {
94
+ foldClientToolResultError(state, input);
95
+ break;
96
+ }
97
+ case 'tool-approval-response': {
98
+ foldToolApprovalResponse(state, input);
99
+ break;
100
+ }
101
+ }
102
+ } else {
103
+ foldChunk(state, event.event, meta);
104
+ }
105
+
106
+ // Re-evaluate pending tool resolutions in case the just-folded event
107
+ // produced the assistant they were waiting on. Cheap when the list is
108
+ // empty (the common case).
109
+ if (state.pendingToolResolutions.length > 0) {
110
+ retryPendingResolutions(state);
111
+ }
112
+
113
+ return state;
114
+ };
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // UIMessageChunk dispatch
118
+ // ---------------------------------------------------------------------------
119
+
120
+ const foldChunk = (state: VercelProjection, chunk: VercelOutput, meta: ReducerMeta): VercelProjection => {
121
+ const messageId = meta.messageId;
122
+ if (messageId === undefined) {
123
+ // Without a target codec-message-id, a chunk has nowhere to land. Drop.
124
+ return state;
125
+ }
126
+
127
+ switch (chunk.type) {
128
+ case 'start':
129
+ case 'start-step':
130
+ case 'finish-step':
131
+ case 'finish':
132
+ case 'abort':
133
+ case 'error':
134
+ case 'message-metadata': {
135
+ return foldLifecycle(state, chunk, messageId);
136
+ }
137
+
138
+ case 'text-start':
139
+ case 'text-delta':
140
+ case 'text-end':
141
+ case 'reasoning-start':
142
+ case 'reasoning-delta':
143
+ case 'reasoning-end': {
144
+ return foldTextOrReasoning(state, chunk, messageId);
145
+ }
146
+
147
+ case 'tool-input-start':
148
+ case 'tool-input-delta':
149
+ case 'tool-input-available':
150
+ case 'tool-input-error': {
151
+ return foldToolInput(state, chunk, messageId);
152
+ }
153
+
154
+ case 'tool-output-available':
155
+ case 'tool-output-error':
156
+ case 'tool-output-denied':
157
+ case 'tool-approval-request': {
158
+ return foldToolOutput(state, chunk, messageId);
159
+ }
160
+
161
+ case 'file':
162
+ case 'source-url':
163
+ case 'source-document': {
164
+ return foldContentPart(state, chunk, messageId);
165
+ }
166
+
167
+ default: {
168
+ if (chunk.type.startsWith('data-')) {
169
+ return foldDataPart(state, chunk, messageId);
170
+ }
171
+ return state;
172
+ }
173
+ }
174
+ };
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // getMessages
178
+ // ---------------------------------------------------------------------------
179
+
180
+ /**
181
+ * Extract the UIMessage list from a projection, each paired with its
182
+ * codec-message-id. Client-published tool resolutions amend existing
183
+ * assistants in place via `kind: 'tool-result'` etc. — they never
184
+ * materialise as their own UIMessage in the projection, so no filtering is
185
+ * needed here.
186
+ * @param projection - Projection produced by `init` + repeated `fold` calls.
187
+ * @returns The visible messages with their codec-message-ids, in publication order.
188
+ */
189
+ export const getMessages = (projection: VercelProjection): CodecMessage<AI.UIMessage>[] => projection.messages;
190
+
191
+ export { init, type VercelProjection } from './reducer-state.js';
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Shared tool part transition logic for the Vercel AI SDK codec.
3
3
  *
4
- * Extracted from the accumulator so the tool output state transition logic
5
- * lives in one place, reusable by the accumulator and any other callers.
4
+ * Keeps the tool output state transition logic in one place, reusable by the
5
+ * Vercel codec reducer and any other callers.
6
6
  */
7
7
 
8
8
  import type * as AI from 'ai';
@@ -10,7 +10,7 @@ import type * as AI from 'ai';
10
10
  import { stripUndefined } from '../../utils.js';
11
11
 
12
12
  // ---------------------------------------------------------------------------
13
- // Tool output chunk type guard
13
+ // Tool output chunk type
14
14
  // ---------------------------------------------------------------------------
15
15
 
16
16
  /** The set of UIMessageChunk types that represent tool output transitions. */
@@ -19,17 +19,6 @@ export type ToolOutputChunk = Extract<
19
19
  { type: 'tool-output-available' | 'tool-output-error' | 'tool-output-denied' | 'tool-approval-request' }
20
20
  >;
21
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
22
  // ---------------------------------------------------------------------------
34
23
  // Tool base helper
35
24
  // ---------------------------------------------------------------------------
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Wire-data shapes and runtime guards for the tool payloads whose `data`
3
+ * envelope is JSON-parsed from the network (a trust boundary). The guards
4
+ * validate the typed envelope fields; tool-defined `output`/`input` stay
5
+ * unconstrained. Shared by the output and input descriptor tables.
6
+ */
7
+
8
+ /** Wire format for the agent-side `tool-input-error` chunk data payload. */
9
+ export interface ToolInputErrorWireData {
10
+ errorText?: string;
11
+ input?: unknown;
12
+ }
13
+
14
+ /** Wire format for the `tool-output-available` (agent) / `tool-result` (client) data payload. */
15
+ export interface ToolOutputAvailableWireData {
16
+ output?: unknown;
17
+ }
18
+
19
+ /** Wire format for the agent-side `tool-output-error` chunk data payload. */
20
+ export interface AgentToolOutputErrorWireData {
21
+ errorText?: string;
22
+ }
23
+
24
+ /** Wire format for the client-side `tool-result-error` input data payload. */
25
+ export interface ClientToolResultErrorWireData {
26
+ message?: string;
27
+ }
28
+
29
+ // Narrow JSON-parsed wire data to a record. The encoder is expected to publish
30
+ // an object for these payloads, but a malformed publish could carry a primitive
31
+ // or null — callers fall back to field defaults when these guards reject.
32
+ const isRecord = (data: unknown): data is Record<string, unknown> => typeof data === 'object' && data !== null;
33
+
34
+ // Validate that `data` is a record whose named field is absent or a string. The
35
+ // optional-string check for the typed error fields below lives here once so the
36
+ // guards can't drift. No `as` needed: `isRecord` narrows `data` to a record, so
37
+ // string-key indexing is well-typed.
38
+ const isRecordWithOptionalString = (data: unknown, key: string): boolean =>
39
+ isRecord(data) && (data[key] === undefined || typeof data[key] === 'string');
40
+
41
+ // Validates the typed `errorText` field; `input` is tool-defined and
42
+ // intentionally left unconstrained.
43
+ /**
44
+ * Coerce wire `data` to a string, falling back to `''` for any non-string
45
+ * payload — the defensive read for descriptors whose data is plain text.
46
+ * @param data - The inbound wire data.
47
+ * @returns The string payload, or `''` when the data is not a string.
48
+ */
49
+ export const asString = (data: unknown): string => (typeof data === 'string' ? data : '');
50
+
51
+ export const isToolInputErrorWireData = (data: unknown): data is ToolInputErrorWireData =>
52
+ isRecordWithOptionalString(data, 'errorText');
53
+
54
+ // The sole field `output` is tool-defined and intentionally unconstrained, so
55
+ // this asserts only that the payload is an object envelope.
56
+ export const isToolOutputAvailableWireData = (data: unknown): data is ToolOutputAvailableWireData => isRecord(data);
57
+
58
+ // Validates the typed `errorText` field.
59
+ export const isAgentToolOutputErrorWireData = (data: unknown): data is AgentToolOutputErrorWireData =>
60
+ isRecordWithOptionalString(data, 'errorText');
61
+
62
+ // Validates the typed `message` field.
63
+ export const isClientToolResultErrorWireData = (data: unknown): data is ClientToolResultErrorWireData =>
64
+ isRecordWithOptionalString(data, 'message');
@@ -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,24 +7,11 @@ 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';
13
14
 
14
- // Server-side tool result merge helper
15
- export { applyToolEventsToHistory } from './tool-events.js';
16
-
17
- // Server-side tool approval helpers
18
- export type {
19
- PrepareApprovalTurnOptions,
20
- PrepareApprovalTurnResult,
21
- StreamResponseWithApprovalRedirectOptions,
22
- ToolApprovalDecision,
23
- } from './tool-approvals.js';
24
- export {
25
- applyToolApprovalsToHistory,
26
- extractApprovalDecisionsFromHistory,
27
- prepareApprovalTurn,
28
- streamResponseWithApprovalRedirect,
29
- } from './tool-approvals.js';
15
+ // Vercel-shaped helpers
16
+ export type { VercelRunOutcome } from './run-end-reason.js';
17
+ export { vercelRunOutcome } from './run-end-reason.js';
@@ -2,18 +2,19 @@ import type * as Ably from 'ably';
2
2
  import type * as AI from 'ai';
3
3
  import { createContext } from 'react';
4
4
 
5
- import type { ClientTransport } from '../../../core/transport/types.js';
5
+ import type { ClientSession } from '../../../core/transport/types.js';
6
+ import type { VercelInput, VercelOutput, VercelProjection } from '../../codec/index.js';
6
7
  import type { ChatTransport } from '../../transport/chat-transport.js';
7
8
 
8
9
  /**
9
10
  * A single entry in the chat transport registry, holding both the
10
- * underlying {@link ClientTransport} and the {@link ChatTransport} wrapping it.
11
+ * underlying {@link ClientSession} and the {@link ChatTransport} wrapping it.
11
12
  */
12
13
  export interface ChatTransportSlot {
13
- /** The underlying client transport used to create the chat transport. */
14
- readonly transport: ClientTransport<AI.UIMessageChunk, AI.UIMessage>;
15
- /** Construction error from the underlying {@link ClientTransport}, or `undefined` on success. */
16
- readonly transportError: Ably.ErrorInfo | undefined;
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;
17
18
  /** The chat transport adapter for use with Vercel's useChat hook. */
18
19
  readonly chatTransport: ChatTransport;
19
20
  }
@@ -22,7 +23,7 @@ export interface ChatTransportSlot {
22
23
  * The shape of the single {@link ChatTransportContext} value.
23
24
  * Combines the nearest slot with the full registry in one context object.
24
25
  */
25
- export interface ChatTransportContextValue {
26
+ interface ChatTransportContextValue {
26
27
  /** The slot from the nearest {@link ChatTransportProvider} in the tree. */
27
28
  readonly nearest: ChatTransportSlot | undefined;
28
29
  /** All registered slots, keyed by channelName. */