@ably/ai-transport 0.2.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 (166) hide show
  1. package/README.md +10 -19
  2. package/dist/ably-ai-transport.js +1790 -1091
  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 +2 -2
  7. package/dist/core/agent.d.ts +20 -5
  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 +4 -1
  11. package/dist/core/codec/define-codec.d.ts +100 -0
  12. package/dist/core/codec/encoder.d.ts +2 -7
  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 -1
  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/output-descriptor-decoder.d.ts +29 -0
  20. package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
  21. package/dist/core/codec/output-descriptors.d.ts +237 -0
  22. package/dist/core/codec/types.d.ts +95 -36
  23. package/dist/core/codec/well-known-inputs.d.ts +52 -0
  24. package/dist/core/transport/agent-view.d.ts +296 -0
  25. package/dist/core/transport/decode-fold.d.ts +40 -32
  26. package/dist/core/transport/headers.d.ts +30 -1
  27. package/dist/core/transport/index.d.ts +1 -1
  28. package/dist/core/transport/invocation.d.ts +1 -1
  29. package/dist/core/transport/load-history-pages.d.ts +71 -0
  30. package/dist/core/transport/load-history.d.ts +21 -16
  31. package/dist/core/transport/run-manager.d.ts +9 -11
  32. package/dist/core/transport/session-support.d.ts +55 -0
  33. package/dist/core/transport/tree.d.ts +165 -15
  34. package/dist/core/transport/types/agent.d.ts +120 -98
  35. package/dist/core/transport/types/client.d.ts +45 -12
  36. package/dist/core/transport/types/tree.d.ts +52 -10
  37. package/dist/core/transport/types/view.d.ts +55 -28
  38. package/dist/core/transport/view.d.ts +176 -58
  39. package/dist/core/transport/wire-log.d.ts +102 -0
  40. package/dist/errors.d.ts +10 -4
  41. package/dist/index.d.ts +6 -5
  42. package/dist/react/ably-ai-transport-react.js +784 -415
  43. package/dist/react/ably-ai-transport-react.js.map +1 -1
  44. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  45. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  46. package/dist/react/contexts/client-session-context.d.ts +2 -1
  47. package/dist/react/contexts/client-session-provider.d.ts +3 -0
  48. package/dist/react/index.d.ts +2 -1
  49. package/dist/react/internal/skipped-session.d.ts +8 -0
  50. package/dist/react/use-view.d.ts +3 -3
  51. package/dist/utils.d.ts +22 -54
  52. package/dist/vercel/ably-ai-transport-vercel.js +2297 -2026
  53. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  54. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  55. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  56. package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
  57. package/dist/vercel/codec/events.d.ts +1 -2
  58. package/dist/vercel/codec/fields.d.ts +44 -0
  59. package/dist/vercel/codec/fold-content.d.ts +16 -0
  60. package/dist/vercel/codec/fold-data.d.ts +16 -0
  61. package/dist/vercel/codec/fold-input.d.ts +67 -0
  62. package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
  63. package/dist/vercel/codec/fold-text.d.ts +16 -0
  64. package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
  65. package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
  66. package/dist/vercel/codec/index.d.ts +5 -30
  67. package/dist/vercel/codec/inputs.d.ts +11 -0
  68. package/dist/vercel/codec/outputs.d.ts +11 -0
  69. package/dist/vercel/codec/reducer-state.d.ts +121 -0
  70. package/dist/vercel/codec/reducer.d.ts +20 -102
  71. package/dist/vercel/codec/tool-transitions.d.ts +0 -6
  72. package/dist/vercel/codec/wire-data.d.ts +34 -0
  73. package/dist/vercel/index.d.ts +1 -0
  74. package/dist/vercel/react/ably-ai-transport-vercel-react.js +2013 -9500
  75. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  76. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -70
  77. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  78. package/dist/vercel/react/contexts/chat-transport-context.d.ts +2 -1
  79. package/dist/vercel/run-end-reason.d.ts +66 -11
  80. package/dist/vercel/tool-part.d.ts +21 -0
  81. package/dist/vercel/transport/chat-transport.d.ts +0 -2
  82. package/dist/vercel/transport/index.d.ts +1 -1
  83. package/dist/vercel/transport/run-output-stream.d.ts +6 -8
  84. package/dist/version.d.ts +1 -1
  85. package/package.json +2 -2
  86. package/src/constants.ts +2 -2
  87. package/src/core/agent.ts +43 -19
  88. package/src/core/channel-options.ts +89 -0
  89. package/src/core/codec/codec-event.ts +27 -0
  90. package/src/core/codec/decoder.ts +145 -21
  91. package/src/core/codec/define-codec.ts +432 -0
  92. package/src/core/codec/encoder.ts +13 -54
  93. package/src/core/codec/field-bag.ts +142 -0
  94. package/src/core/codec/fields.ts +193 -0
  95. package/src/core/codec/index.ts +43 -0
  96. package/src/core/codec/input-descriptor-decoder.ts +97 -0
  97. package/src/core/codec/input-descriptor-encoder.ts +150 -0
  98. package/src/core/codec/input-descriptors.ts +373 -0
  99. package/src/core/codec/output-descriptor-decoder.ts +139 -0
  100. package/src/core/codec/output-descriptor-encoder.ts +101 -0
  101. package/src/core/codec/output-descriptors.ts +307 -0
  102. package/src/core/codec/types.ts +99 -36
  103. package/src/core/codec/well-known-inputs.ts +96 -0
  104. package/src/core/transport/agent-session.ts +330 -589
  105. package/src/core/transport/agent-view.ts +738 -0
  106. package/src/core/transport/client-session.ts +74 -69
  107. package/src/core/transport/decode-fold.ts +57 -47
  108. package/src/core/transport/headers.ts +57 -4
  109. package/src/core/transport/index.ts +2 -1
  110. package/src/core/transport/invocation.ts +1 -1
  111. package/src/core/transport/load-history-pages.ts +220 -0
  112. package/src/core/transport/load-history.ts +63 -61
  113. package/src/core/transport/pipe-stream.ts +10 -1
  114. package/src/core/transport/run-manager.ts +25 -31
  115. package/src/core/transport/session-support.ts +96 -0
  116. package/src/core/transport/tree.ts +414 -47
  117. package/src/core/transport/types/agent.ts +129 -102
  118. package/src/core/transport/types/client.ts +49 -13
  119. package/src/core/transport/types/tree.ts +61 -12
  120. package/src/core/transport/types/view.ts +57 -28
  121. package/src/core/transport/view.ts +520 -172
  122. package/src/core/transport/wire-log.ts +189 -0
  123. package/src/errors.ts +10 -3
  124. package/src/index.ts +44 -11
  125. package/src/react/contexts/client-session-context.ts +1 -1
  126. package/src/react/contexts/client-session-provider.tsx +38 -2
  127. package/src/react/index.ts +2 -1
  128. package/src/react/internal/skipped-session.ts +62 -0
  129. package/src/react/use-client-session.ts +7 -30
  130. package/src/react/use-view.ts +3 -3
  131. package/src/utils.ts +31 -97
  132. package/src/vercel/codec/decode-lifecycle.ts +70 -0
  133. package/src/vercel/codec/events.ts +1 -3
  134. package/src/vercel/codec/fields.ts +58 -0
  135. package/src/vercel/codec/fold-content.ts +54 -0
  136. package/src/vercel/codec/fold-data.ts +46 -0
  137. package/src/vercel/codec/fold-input.ts +255 -0
  138. package/src/vercel/codec/fold-lifecycle.ts +85 -0
  139. package/src/vercel/codec/fold-text.ts +55 -0
  140. package/src/vercel/codec/fold-tool-input.ts +86 -0
  141. package/src/vercel/codec/fold-tool-output.ts +79 -0
  142. package/src/vercel/codec/index.ts +23 -63
  143. package/src/vercel/codec/inputs.ts +116 -0
  144. package/src/vercel/codec/outputs.ts +207 -0
  145. package/src/vercel/codec/reducer-state.ts +169 -0
  146. package/src/vercel/codec/reducer.ts +52 -838
  147. package/src/vercel/codec/tool-transitions.ts +1 -12
  148. package/src/vercel/codec/wire-data.ts +64 -0
  149. package/src/vercel/index.ts +1 -0
  150. package/src/vercel/react/contexts/chat-transport-context.ts +1 -1
  151. package/src/vercel/react/use-chat-transport.ts +8 -28
  152. package/src/vercel/react/use-message-sync.ts +5 -10
  153. package/src/vercel/run-end-reason.ts +95 -16
  154. package/src/vercel/tool-part.ts +25 -0
  155. package/src/vercel/transport/chat-transport.ts +10 -22
  156. package/src/vercel/transport/index.ts +1 -1
  157. package/src/vercel/transport/run-output-stream.ts +7 -8
  158. package/src/version.ts +1 -1
  159. package/dist/core/transport/branch-chain.d.ts +0 -43
  160. package/dist/core/transport/load-conversation.d.ts +0 -128
  161. package/dist/vercel/codec/decoder.d.ts +0 -9
  162. package/dist/vercel/codec/encoder.d.ts +0 -11
  163. package/src/core/transport/branch-chain.ts +0 -58
  164. package/src/core/transport/load-conversation.ts +0 -355
  165. package/src/vercel/codec/decoder.ts +0 -696
  166. package/src/vercel/codec/encoder.ts +0 -548
@@ -0,0 +1,9 @@
1
+ import { LifecyclePolicy } from '../../core/codec/index.js';
2
+ import { VercelOutput } from './events.js';
3
+ /**
4
+ * Build a fresh Vercel decode lifecycle policy (with its own tracker). Passed
5
+ * to `defineCodec` as the `decodeLifecycle` factory so each decoder instance
6
+ * gets independent per-run phase state.
7
+ * @returns A {@link LifecyclePolicy} for the Vercel output union.
8
+ */
9
+ export declare const createVercelDecodeLifecycle: () => LifecyclePolicy<VercelOutput>;
@@ -1,4 +1,4 @@
1
- import { Regenerate, ToolApprovalResponse, ToolResult, ToolResultError, UserMessage } from '../../core/codec/types.js';
1
+ import { Regenerate, ToolApprovalResponse, ToolResult, ToolResultError, UserMessage } from '../../core/codec/index.js';
2
2
  /**
3
3
  * Vercel codec input/output unions.
4
4
  *
@@ -48,4 +48,3 @@ export type VercelInput = UserMessage<AI.UIMessage> | Regenerate | ToolResult<Ve
48
48
  * `UIMessageChunk` through unchanged.
49
49
  */
50
50
  export type VercelOutput = AI.UIMessageChunk;
51
- export type { VercelProjection } from './reducer.js';
@@ -0,0 +1,44 @@
1
+ import { HeaderField } from '../../core/codec/index.js';
2
+ /**
3
+ * Shared Vercel codec header-field bindings.
4
+ *
5
+ * Each field binds a codec header key to its value type once (see
6
+ * {@link HeaderField}); the output/input descriptors and escape hatches all
7
+ * read and write through these bindings, so a header key cannot drift between
8
+ * the encode and decode side. Domain field names live in the Vercel layer, not
9
+ * core, per the header-discipline rule.
10
+ */
11
+ import type * as AI from 'ai';
12
+ /** Stream / message id (text & reasoning streams). */
13
+ export declare const fId: HeaderField<string | undefined, "id">;
14
+ /**
15
+ * Provider metadata envelope, typed to the AI SDK shape. Annotated explicitly:
16
+ * the inferred type resolves to the AI SDK's internal `SharedV3ProviderMetadata`
17
+ * alias, which isn't portably nameable across the package boundary.
18
+ */
19
+ export declare const fMeta: HeaderField<AI.ProviderMetadata | undefined, 'providerMetadata'>;
20
+ /** Tool call id — defaulted to total: an absent header reads as `''`. */
21
+ export declare const fToolCallId: HeaderField<string, "toolCallId">;
22
+ /** Tool name — defaulted to total. */
23
+ export declare const fToolName: HeaderField<string, "toolName">;
24
+ /** Whether the tool is a dynamic tool. */
25
+ export declare const fDynamic: HeaderField<boolean | undefined, "dynamic">;
26
+ /** Optional human-readable title. */
27
+ export declare const fTitle: HeaderField<string | undefined, "title">;
28
+ /** Whether the provider executed the tool. */
29
+ export declare const fProviderExecuted: HeaderField<boolean | undefined, "providerExecuted">;
30
+ /** Media type for file / source-document parts — defaulted to total. */
31
+ export declare const fMediaType: HeaderField<string, "mediaType">;
32
+ /** Source id for source-url / source-document parts — defaulted to total. */
33
+ export declare const fSourceId: HeaderField<string, "sourceId">;
34
+ /** Domain message id (`message.id`) stamped on every user-message part — distinct from the wire codec-message-id transport header. */
35
+ export declare const fMessageId: HeaderField<string | undefined, "messageId">;
36
+ /** Whether the user approved a tool execution — defaulted to total so an absent header reads `false`. */
37
+ export declare const fApproved: HeaderField<boolean, "approved">;
38
+ /** Optional human-readable reason on a tool-approval response. */
39
+ export declare const fReason: HeaderField<string | undefined, "reason">;
40
+ /**
41
+ * Validated finish reason. Mirrors the AI SDK's `FinishReason` literals and
42
+ * falls back to `'stop'` for an absent or unrecognized value.
43
+ */
44
+ export declare const fFinishReason: HeaderField<"error" | "length" | "stop" | "content-filter" | "tool-calls" | "other", "finishReason">;
@@ -0,0 +1,16 @@
1
+ import { VercelProjection } from './reducer-state.js';
2
+ /**
3
+ * File and source content-part folds: file / source-url / source-document.
4
+ * These are independent attachments — each appends a part, never dedups.
5
+ */
6
+ import type * as AI from 'ai';
7
+ /**
8
+ * Fold a file or source content chunk into the projection.
9
+ * @param state - Projection to fold into.
10
+ * @param chunk - The file, source-url, or source-document chunk.
11
+ * @param messageId - The target codec-message-id.
12
+ * @returns The same projection reference.
13
+ */
14
+ export declare const foldContentPart: (state: VercelProjection, chunk: Extract<AI.UIMessageChunk, {
15
+ type: "file" | "source-url" | "source-document";
16
+ }>, messageId: string) => VercelProjection;
@@ -0,0 +1,16 @@
1
+ import { VercelProjection } from './reducer-state.js';
2
+ /**
3
+ * data-* part folds. Transient data parts are dropped; persistent ones are
4
+ * appended, or replaced in place when a matching `id` is already present.
5
+ */
6
+ import type * as AI from 'ai';
7
+ /**
8
+ * Fold a `data-*` chunk into the projection.
9
+ * @param state - Projection to fold into.
10
+ * @param chunk - The data-* chunk.
11
+ * @param messageId - The target codec-message-id.
12
+ * @returns The same projection reference.
13
+ */
14
+ export declare const foldDataPart: (state: VercelProjection, chunk: Extract<AI.UIMessageChunk, {
15
+ type: `data-${string}`;
16
+ }>, messageId: string) => VercelProjection;
@@ -0,0 +1,67 @@
1
+ import { ReducerMeta, ToolApprovalResponse, ToolResult, ToolResultError } from '../../core/codec/index.js';
2
+ import { VercelToolApprovalResponsePayload, VercelToolResultErrorPayload, VercelToolResultPayload } from './events.js';
3
+ import { VercelProjection } from './reducer-state.js';
4
+ /**
5
+ * Client-published input folds and the pending-resolution buffering.
6
+ *
7
+ * Tool resolutions (`ToolResult`, `ToolResultError`, `ToolApprovalResponse`)
8
+ * carry a `codecMessageId` targeting the assistant they amend. When that
9
+ * assistant (or its tool part) has not yet arrived, the resolution is buffered
10
+ * in `pendingToolResolutions` and {@link retryPendingResolutions} re-evaluates
11
+ * it after every subsequent fold.
12
+ */
13
+ import type * as AI from 'ai';
14
+ /**
15
+ * Fold a user message into the projection, correlating on the wire
16
+ * codec-message-id (the caller's `message.id` is preserved verbatim). A
17
+ * multi-part user message fans out into one wire event per part, all sharing
18
+ * the codec-message-id — folding appends the incoming parts to the existing
19
+ * entry, reassembling the message part by part. The transport delivers each
20
+ * wire exactly once (its per-message version high-water-mark drops replays),
21
+ * so the merge sees every part once and stays consistent.
22
+ *
23
+ * Optimistic (serial-less) seeds need no special handling here: the transport
24
+ * refolds the node from its log when the echo's serial arrives, rebuilding the
25
+ * projection from a fresh `init` so the seed never coexists with its echo.
26
+ * @param state - Projection to fold into.
27
+ * @param message - The user message (or one decoded part of it) to add or merge.
28
+ * @param meta - Transport-derived metadata carrying the codec-message-id.
29
+ * @returns The same projection reference.
30
+ */
31
+ export declare const foldUserMessage: (state: VercelProjection, message: AI.UIMessage, meta: ReducerMeta) => VercelProjection;
32
+ /**
33
+ * Fold a client-published `ToolResult`. The input carries
34
+ * `codecMessageId` pointing at the assistant whose `dynamic-tool` part
35
+ * holds the matching `toolCallId`. If the assistant and its matching
36
+ * `dynamic-tool` part are both present, fold directly; otherwise pend
37
+ * until that tool part arrives.
38
+ * @param state - Projection to fold into.
39
+ * @param event - The tool-result input (codecMessageId + domain payload).
40
+ * @returns The same projection reference.
41
+ */
42
+ export declare const foldClientToolResult: (state: VercelProjection, event: ToolResult<VercelToolResultPayload>) => VercelProjection;
43
+ /**
44
+ * Fold a client-published `ToolResultError`. Mirrors
45
+ * {@link foldClientToolResult} but with the error transition.
46
+ * @param state - Projection to fold into.
47
+ * @param event - The tool-result-error input (codecMessageId + domain payload).
48
+ * @returns The same projection reference.
49
+ */
50
+ export declare const foldClientToolResultError: (state: VercelProjection, event: ToolResultError<VercelToolResultErrorPayload>) => VercelProjection;
51
+ /**
52
+ * Fold a client-published `ToolApprovalResponse`. The input carries
53
+ * `codecMessageId` pointing at the assistant whose `dynamic-tool` part
54
+ * holds the matching `toolCallId`. Approval → `approval-responded`;
55
+ * denial → `output-denied` via {@link transitionToolPart}.
56
+ * @param state - Projection to fold into.
57
+ * @param event - The approval-response input.
58
+ * @returns The same projection reference.
59
+ */
60
+ export declare const foldToolApprovalResponse: (state: VercelProjection, event: ToolApprovalResponse<VercelToolApprovalResponsePayload>) => VercelProjection;
61
+ /**
62
+ * Re-attempt every pending tool resolution against the current projection.
63
+ * Successfully promoted entries are removed from the pending list. Cheap:
64
+ * bounded by the number of pending entries.
65
+ * @param state - Projection to walk and mutate.
66
+ */
67
+ export declare const retryPendingResolutions: (state: VercelProjection) => void;
@@ -0,0 +1,16 @@
1
+ import { VercelProjection } from './reducer-state.js';
2
+ /**
3
+ * Lifecycle chunk folds: start, start-step, finish-step, finish, abort,
4
+ * error, message-metadata.
5
+ */
6
+ import type * as AI from 'ai';
7
+ /**
8
+ * Fold a message-lifecycle chunk into the projection.
9
+ * @param state - Projection to fold into.
10
+ * @param chunk - The lifecycle chunk.
11
+ * @param messageId - The target codec-message-id.
12
+ * @returns The same projection reference.
13
+ */
14
+ export declare const foldLifecycle: (state: VercelProjection, chunk: Extract<AI.UIMessageChunk, {
15
+ type: "start" | "start-step" | "finish-step" | "finish" | "abort" | "error" | "message-metadata";
16
+ }>, messageId: string) => VercelProjection;
@@ -0,0 +1,16 @@
1
+ import { VercelProjection } from './reducer-state.js';
2
+ /**
3
+ * Text and reasoning streaming folds: the {start, delta, end} lifecycle for
4
+ * both `text-*` and `reasoning-*` chunks, which share the same shape.
5
+ */
6
+ import type * as AI from 'ai';
7
+ /**
8
+ * Fold a text or reasoning streaming chunk into the projection.
9
+ * @param state - Projection to fold into.
10
+ * @param chunk - The text/reasoning start, delta, or end chunk.
11
+ * @param messageId - The target codec-message-id.
12
+ * @returns The same projection reference.
13
+ */
14
+ export declare const foldTextOrReasoning: (state: VercelProjection, chunk: Extract<AI.UIMessageChunk, {
15
+ type: "text-start" | "text-delta" | "text-end" | "reasoning-start" | "reasoning-delta" | "reasoning-end";
16
+ }>, messageId: string) => VercelProjection;
@@ -0,0 +1,17 @@
1
+ import { VercelProjection } from './reducer-state.js';
2
+ /**
3
+ * Tool-input streaming folds: tool-input-start / -delta / -available / -error.
4
+ * Tool deltas arrive as raw JSON fragments accumulated in the tracker's
5
+ * `inputText` buffer and parsed on each delta.
6
+ */
7
+ import type * as AI from 'ai';
8
+ /**
9
+ * Fold a tool-input streaming chunk into the projection.
10
+ * @param state - Projection to fold into.
11
+ * @param chunk - The tool-input start, delta, available, or error chunk.
12
+ * @param messageId - The target codec-message-id.
13
+ * @returns The same projection reference.
14
+ */
15
+ export declare const foldToolInput: (state: VercelProjection, chunk: Extract<AI.UIMessageChunk, {
16
+ type: "tool-input-start" | "tool-input-delta" | "tool-input-available" | "tool-input-error";
17
+ }>, messageId: string) => VercelProjection;
@@ -0,0 +1,16 @@
1
+ import { VercelProjection } from './reducer-state.js';
2
+ /**
3
+ * Agent-published tool-output transitions: tool-output-available /
4
+ * tool-output-error / tool-output-denied / tool-approval-request.
5
+ */
6
+ import type * as AI from 'ai';
7
+ /**
8
+ * Fold an agent-published tool-output chunk into the projection.
9
+ * @param state - Projection to fold into.
10
+ * @param chunk - The tool-output-available/-error/-denied or tool-approval-request chunk.
11
+ * @param messageId - The target codec-message-id (used for the approval-request / denied paths).
12
+ * @returns The same projection reference.
13
+ */
14
+ export declare const foldToolOutput: (state: VercelProjection, chunk: Extract<AI.UIMessageChunk, {
15
+ type: "tool-output-available" | "tool-output-error" | "tool-output-denied" | "tool-approval-request";
16
+ }>, messageId: string) => VercelProjection;
@@ -1,34 +1,9 @@
1
- import { VercelInput, VercelOutput, VercelToolApprovalResponsePayload, VercelToolResultErrorPayload, VercelToolResultPayload } from './events.js';
2
- import { VercelProjection } from './reducer.js';
1
+ import { VercelInput, VercelOutput } from './events.js';
3
2
  /**
4
- * Vercel AI SDK codec — implements
5
- * `Codec<VercelInput, VercelOutput, VercelProjection, UIMessage>`.
6
- *
7
- * The codec is the reducer (extends `Reducer<VercelInput | VercelOutput,
8
- * VercelProjection>`) plus encoder/decoder factories and `getMessages`
9
- * for Tree population.
10
- *
11
- * ```ts
12
- * import { UIMessageCodec } from '@ably/ai-transport/vercel';
13
- *
14
- * const encoder = UIMessageCodec.createEncoder(writer, options);
15
- * const decoder = UIMessageCodec.createDecoder();
16
- * const projection = UIMessageCodec.init();
17
- * ```
3
+ * Vercel AI SDK codec implementing
4
+ * `Codec<VercelInput, VercelOutput, VercelProjection, UIMessage>`. `VercelProjection`
5
+ * and `UIMessage` are inferred from the reducer.
18
6
  */
19
- import type * as AI from 'ai';
20
- export declare const UIMessageCodec: {
21
- adapterTag: "vercel-ai-sdk-ui-message";
22
- init: () => VercelProjection;
23
- fold: (state: VercelProjection, event: VercelInput | VercelOutput, meta: import('../../core/codec/types.js').ReducerMeta) => VercelProjection;
24
- createEncoder: (writer: import('../../core/codec/types.js').ChannelWriter, options?: import('../../index.js').EncoderCoreOptions) => import('../../core/codec/types.js').Encoder<VercelInput, VercelOutput>;
25
- createDecoder: (options?: import('../../index.js').DecoderCoreOptions) => import('../../core/codec/types.js').Decoder<VercelInput, VercelOutput>;
26
- getMessages: (projection: VercelProjection) => import('../../core/codec/types.js').CodecMessage<AI.UIMessage>[];
27
- createUserMessage: (message: AI.UIMessage) => VercelInput;
28
- createRegenerate: (target: string, parent: string) => VercelInput;
29
- createToolResult: (codecMessageId: string, payload: VercelToolResultPayload) => VercelInput;
30
- createToolResultError: (codecMessageId: string, payload: VercelToolResultErrorPayload) => VercelInput;
31
- createToolApprovalResponse: (codecMessageId: string, payload: VercelToolApprovalResponsePayload) => VercelInput;
32
- };
7
+ export declare const UIMessageCodec: import('../../core/codec/define-codec.js').DefinedCodec<VercelInput, VercelOutput, import('./reducer-state.js').VercelProjection, import('ai').UIMessage<unknown, import('ai').UIDataTypes, import('ai').UITools>>;
33
8
  export type { VercelInput, VercelOutput } from './events.js';
34
9
  export { type VercelProjection } from './reducer.js';
@@ -0,0 +1,11 @@
1
+ import { InputBuilder, InputDescriptor } from '../../core/codec/index.js';
2
+ import { VercelInput } from './events.js';
3
+ /**
4
+ * The Vercel codec's `ai-input` descriptors, built from the injected
5
+ * direction-scoped builder.
6
+ * @param builder - The `{ event, batch }` builder curried on `VercelInput`.
7
+ * @param builder.event - Define a single-event input (payload-nested, or `wireOnly`).
8
+ * @param builder.batch - Define a multi-part (batch) input that fans out into one wire event per part.
9
+ * @returns The input descriptor table the generic input drivers consume.
10
+ */
11
+ export declare const inputs: ({ event, batch }: InputBuilder<VercelInput>) => readonly InputDescriptor<VercelInput>[];
@@ -0,0 +1,11 @@
1
+ import { OutputBuilder, OutputDescriptor } from '../../core/codec/index.js';
2
+ import { VercelOutput } from './events.js';
3
+ /**
4
+ * The Vercel codec's `ai-output` descriptors, built from the injected
5
+ * direction-scoped builder.
6
+ * @param builder - The `{ event, stream }` builder curried on `VercelOutput`.
7
+ * @param builder.event - Define a single discrete output event.
8
+ * @param builder.stream - Define a streamed output family (start / delta / end).
9
+ * @returns The output descriptor table the generic output drivers consume.
10
+ */
11
+ export declare const outputs: ({ event, stream }: OutputBuilder<VercelOutput>) => readonly OutputDescriptor<VercelOutput>[];
@@ -0,0 +1,121 @@
1
+ import { CodecMessage } from '../../core/codec/index.js';
2
+ /**
3
+ * Shared reducer state: the projection shape, its internal tracker types,
4
+ * `init`, and the message/tracker lookup helpers the per-concern fold modules
5
+ * build on. This module is the base of the reducer's import DAG — the fold
6
+ * modules depend on it; it depends on none of them.
7
+ */
8
+ import type * as AI from 'ai';
9
+ /**
10
+ * Tracks an in-progress tool part within a UIMessage. Text and reasoning
11
+ * parts don't need this — we write to them directly via partIndex. Tool
12
+ * parts need an extra `inputText` buffer because deltas arrive as raw
13
+ * JSON fragments that must be accumulated before parsing.
14
+ */
15
+ export interface ToolPartTracker {
16
+ /** Index in the message's parts array. */
17
+ partIndex: number;
18
+ /** Accumulated streaming input text (for JSON parsing on completion). */
19
+ inputText: string;
20
+ }
21
+ /** Per-codecMessageId tracking state for in-progress streams within a UIMessage. */
22
+ export interface MessageTrackers {
23
+ /** Text stream id → partIndex. */
24
+ text: Map<string, number>;
25
+ /** Reasoning stream id → partIndex. */
26
+ reasoning: Map<string, number>;
27
+ /** Tool call id → tracker. */
28
+ tools: Map<string, ToolPartTracker>;
29
+ }
30
+ /**
31
+ * The per-Run state produced by the Vercel codec's reducer.
32
+ *
33
+ * The SDK reads only `messages` (via `Codec.getMessages`). The remaining
34
+ * fields are internal to the reducer; they happen to live on the
35
+ * projection because the projection is the only thing the reducer can
36
+ * carry from fold to fold (it has no instance state).
37
+ */
38
+ export interface VercelProjection {
39
+ /**
40
+ * UIMessages produced or modified in this Run, in publication order,
41
+ * each paired with its codec-message-id. The reducer correlates strictly
42
+ * on `codecMessageId`; `message.id` is preserved verbatim from the source
43
+ * (the AI SDK stream's `start.messageId` for assistants, the caller's id
44
+ * for user messages) and is never used as an identity key.
45
+ */
46
+ messages: CodecMessage<AI.UIMessage>[];
47
+ /** Per-codecMessageId tracker state for streamed parts. Internal — do not access. */
48
+ trackers: Map<string, MessageTrackers>;
49
+ /**
50
+ * Tool-resolution events that arrived before any assistant in this
51
+ * projection had a matching `toolCallId`. Re-evaluated on every
52
+ * subsequent fold so that an out-of-order tool output is folded as
53
+ * soon as the corresponding assistant lands.
54
+ */
55
+ pendingToolResolutions: PendingToolResolution[];
56
+ }
57
+ /**
58
+ * A buffered tool resolution waiting for its assistant message to arrive.
59
+ * The reducer scans pending entries after every successful fold so an
60
+ * out-of-order tool output is promoted as soon as the matching assistant
61
+ * is added to the projection.
62
+ */
63
+ export interface PendingToolResolution {
64
+ /** The codec-message-id of the assistant the resolution targets. */
65
+ targetCodecMessageId: string;
66
+ /** Tool call this resolution targets. */
67
+ toolCallId: string;
68
+ /** Variant of the tool-resolution used to transition the assistant's tool part. */
69
+ resolution: {
70
+ kind: 'tool-result';
71
+ output: unknown;
72
+ } | {
73
+ kind: 'tool-result-error';
74
+ message: string;
75
+ } | {
76
+ kind: 'tool-approval-response';
77
+ approved: boolean;
78
+ reason?: string;
79
+ };
80
+ }
81
+ /** A located `dynamic-tool` part with its owning message and tracker. */
82
+ export interface OwnerLookup {
83
+ /** The message owning the tool part. */
84
+ message: AI.UIMessage;
85
+ /** The tracker pointing at the part's index. */
86
+ tracker: ToolPartTracker;
87
+ /** The resolved `dynamic-tool` part itself. */
88
+ part: AI.DynamicToolUIPart;
89
+ }
90
+ /**
91
+ * Build an empty initial projection.
92
+ * @returns A fresh VercelProjection with no messages and no tracker state.
93
+ */
94
+ export declare const init: () => VercelProjection;
95
+ /**
96
+ * Resolve the assistant message for a codec-message-id, creating an empty
97
+ * placeholder when none exists yet.
98
+ * @param state - Projection to read or extend.
99
+ * @param codecMessageId - The codec-message-id to resolve.
100
+ * @returns The existing or newly-seeded UIMessage for that id.
101
+ */
102
+ export declare const ensureMessage: (state: VercelProjection, codecMessageId: string) => AI.UIMessage;
103
+ /**
104
+ * Resolve the stream trackers for a codec-message-id, creating empty maps
105
+ * when none exist yet.
106
+ * @param state - Projection to read or extend.
107
+ * @param messageId - The codec-message-id whose trackers to resolve.
108
+ * @returns The existing or newly-created tracker maps for that id.
109
+ */
110
+ export declare const ensureTrackers: (state: VercelProjection, messageId: string) => MessageTrackers;
111
+ /**
112
+ * Resolve the `dynamic-tool` part tracked for a toolCallId within a message.
113
+ * @param message - The message whose parts to read.
114
+ * @param trackers - The message's tracker maps.
115
+ * @param toolCallId - The tool call to resolve.
116
+ * @returns The tracker and part, or `undefined` if untracked or the part is not a dynamic-tool.
117
+ */
118
+ export declare const getToolPart: (message: AI.UIMessage, trackers: MessageTrackers, toolCallId: string) => {
119
+ tracker: ToolPartTracker;
120
+ part: AI.DynamicToolUIPart;
121
+ } | undefined;
@@ -1,5 +1,6 @@
1
- import { CodecMessage, ReducerMeta } from '../../core/codec/types.js';
1
+ import { CodecEvent, CodecMessage, ReducerMeta } from '../../core/codec/index.js';
2
2
  import { VercelInput, VercelOutput } from './events.js';
3
+ import { VercelProjection } from './reducer-state.js';
3
4
  /**
4
5
  * Vercel AI SDK reducer.
5
6
  *
@@ -13,11 +14,12 @@ import { VercelInput, VercelOutput } from './events.js';
13
14
  * with no instance state. Mutation in place is allowed — the projection
14
15
  * is single-owner.
15
16
  *
16
- * Idempotency is **per conflict key**, not stream-wide: when two events
17
- * compete for the same logical state (e.g. two `tool-output-available`
18
- * for the same `toolCallId`), the higher-serial one wins and the other
19
- * is dropped. Unrelated events arrive freely in any order. See
20
- * `_conflictKeyOf` for the per-variant key derivation.
17
+ * The reducer does not dedup or reorder. The transport sequences events
18
+ * canonically ascending by wire serial across messages, in decode order
19
+ * within a wire and delivers each exactly once, so the reducer folds
20
+ * unconditionally. Last-writer-wins for events competing over the same
21
+ * logical state (e.g. two `tool-output-available` for one `toolCallId`)
22
+ * falls out of fold order: the highest-serial event folds last.
21
23
  *
22
24
  * Client-published tool resolutions (`ToolResult`, `ToolResultError`,
23
25
  * `ToolApprovalResponse`) carry `codecMessageId` targeting the assistant
@@ -25,112 +27,28 @@ import { VercelInput, VercelOutput } from './events.js';
25
27
  * `dynamic-tool` part directly. If the assistant has not yet arrived in
26
28
  * the projection (out-of-order delivery), the resolution is buffered in
27
29
  * `pendingToolResolutions` and re-evaluated on each subsequent fold.
28
- */
29
- import type * as AI from 'ai';
30
- /**
31
- * Tracks an in-progress tool part within a UIMessage. Text and reasoning
32
- * parts don't need this — we write to them directly via partIndex. Tool
33
- * parts need an extra `inputText` buffer because deltas arrive as raw
34
- * JSON fragments that must be accumulated before parsing.
35
- */
36
- interface ToolPartTracker {
37
- /** Index in the message's parts array. */
38
- partIndex: number;
39
- /** Accumulated streaming input text (for JSON parsing on completion). */
40
- inputText: string;
41
- }
42
- /** Per-codecMessageId tracking state for in-progress streams within a UIMessage. */
43
- interface MessageTrackers {
44
- /** Text stream id → partIndex. */
45
- text: Map<string, number>;
46
- /** Reasoning stream id → partIndex. */
47
- reasoning: Map<string, number>;
48
- /** Tool call id → tracker. */
49
- tools: Map<string, ToolPartTracker>;
50
- }
51
- /**
52
- * The per-Run state produced by the Vercel codec's reducer.
53
30
  *
54
- * The SDK reads only `messages` (via `Codec.getMessages`). The remaining
55
- * fields are internal to the reducer; they happen to live on the
56
- * projection because the projection is the only thing the reducer can
57
- * carry from fold to fold (it has no instance state).
31
+ * This file is the reducer's public facade and dispatch: `init`,
32
+ * `getMessages`, `fold`, and the output-chunk router. The per-concern fold
33
+ * logic lives in the sibling `fold-*` modules over a shared `reducer-state`
34
+ * base; the import graph is an acyclic DAG rooted here.
58
35
  */
59
- export interface VercelProjection {
60
- /**
61
- * UIMessages produced or modified in this Run, in publication order,
62
- * each paired with its codec-message-id. The reducer correlates strictly
63
- * on `codecMessageId`; `message.id` is preserved verbatim from the source
64
- * (the AI SDK stream's `start.messageId` for assistants, the caller's id
65
- * for user messages) and is never used as an identity key.
66
- */
67
- messages: CodecMessage<AI.UIMessage>[];
68
- /**
69
- * Per-conflict-key high-water-marks. Maps a codec-derived conflict key
70
- * (see `_conflictKeyOf`) to the highest `meta.serial` already folded for
71
- * that key. Events whose serial is `<=` the stored value are dropped as
72
- * duplicates of an already-incorporated operation. Events that have no
73
- * conflict key (additive content, lifecycle markers) are folded
74
- * unconditionally.
75
- */
76
- conflictSerials: Map<string, string>;
77
- /** Per-codecMessageId tracker state for streamed parts. Internal — do not access. */
78
- trackers: Map<string, MessageTrackers>;
79
- /**
80
- * Tool-resolution events that arrived before any assistant in this
81
- * projection had a matching `toolCallId`. Re-evaluated on every
82
- * subsequent fold so that an out-of-order tool output is folded as
83
- * soon as the corresponding assistant lands.
84
- */
85
- pendingToolResolutions: PendingToolResolution[];
86
- }
87
- /**
88
- * A buffered tool resolution waiting for its assistant message to arrive.
89
- * The reducer scans pending entries after every successful fold so an
90
- * out-of-order tool output is promoted as soon as the matching assistant
91
- * is added to the projection.
92
- */
93
- interface PendingToolResolution {
94
- /** The codec-message-id of the assistant the resolution targets. */
95
- targetCodecMessageId: string;
96
- /** Tool call this resolution targets. */
97
- toolCallId: string;
98
- /** Serial of the wire message — used by the conflict-key check on promotion. */
99
- serial: string;
100
- /** Variant of the tool-resolution used to transition the assistant's tool part. */
101
- resolution: {
102
- kind: 'tool-result';
103
- output: unknown;
104
- } | {
105
- kind: 'tool-result-error';
106
- message: string;
107
- } | {
108
- kind: 'tool-approval-response';
109
- approved: boolean;
110
- reason?: string;
111
- };
112
- }
113
- /**
114
- * Build an empty initial projection.
115
- * @returns A fresh VercelProjection with no messages and no tracker state.
116
- */
117
- export declare const init: () => VercelProjection;
36
+ import type * as AI from 'ai';
118
37
  /**
119
38
  * Fold one input or output event into the projection. Mutates and returns
120
39
  * `state`.
121
40
  *
122
- * Idempotency is per conflict key (see `_conflictKeyOf`): if the event has
123
- * a conflict key and the projection has already folded an event for that
124
- * key at a higher-or-equal serial, this call is a no-op. Events without a
125
- * conflict key (additive content, lifecycle markers) are folded
126
- * unconditionally. Orphan events (e.g. tool-output for an unknown
127
- * toolCallId) are dropped silently inside the per-variant fold helpers.
41
+ * The transport invokes `fold` exactly once per event, in canonical order,
42
+ * so the reducer folds unconditionally no dedup or high-water-mark here.
43
+ * Competing events resolve by order (the highest-serial event folds last
44
+ * and wins). Orphan events (e.g. tool-output for an unknown toolCallId) are
45
+ * dropped silently inside the per-variant fold helpers.
128
46
  * @param state - Projection to fold into (may be mutated in place).
129
47
  * @param event - Input or output event to fold.
130
48
  * @param meta - Transport-derived metadata (serial, optional messageId).
131
49
  * @returns The same projection reference, possibly mutated.
132
50
  */
133
- export declare const fold: (state: VercelProjection, event: VercelInput | VercelOutput, meta: ReducerMeta) => VercelProjection;
51
+ export declare const fold: (state: VercelProjection, event: CodecEvent<VercelInput, VercelOutput>, meta: ReducerMeta) => VercelProjection;
134
52
  /**
135
53
  * Extract the UIMessage list from a projection, each paired with its
136
54
  * codec-message-id. Client-published tool resolutions amend existing
@@ -141,4 +59,4 @@ export declare const fold: (state: VercelProjection, event: VercelInput | Vercel
141
59
  * @returns The visible messages with their codec-message-ids, in publication order.
142
60
  */
143
61
  export declare const getMessages: (projection: VercelProjection) => CodecMessage<AI.UIMessage>[];
144
- export {};
62
+ export { init, type VercelProjection } from './reducer-state.js';
@@ -9,12 +9,6 @@ import type * as AI from 'ai';
9
9
  export type ToolOutputChunk = Extract<AI.UIMessageChunk, {
10
10
  type: 'tool-output-available' | 'tool-output-error' | 'tool-output-denied' | 'tool-approval-request';
11
11
  }>;
12
- /**
13
- * Whether a UIMessageChunk is a tool output transition event.
14
- * @param chunk - The chunk to test.
15
- * @returns True if the chunk is a tool output transition type.
16
- */
17
- export declare const isToolOutputChunk: (chunk: AI.UIMessageChunk) => chunk is ToolOutputChunk;
18
12
  /** Fields shared by all DynamicToolUIPart state variants. */
19
13
  interface ToolBaseFields {
20
14
  type: 'dynamic-tool';
@@ -0,0 +1,34 @@
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
+ /** Wire format for the agent-side `tool-input-error` chunk data payload. */
8
+ export interface ToolInputErrorWireData {
9
+ errorText?: string;
10
+ input?: unknown;
11
+ }
12
+ /** Wire format for the `tool-output-available` (agent) / `tool-result` (client) data payload. */
13
+ export interface ToolOutputAvailableWireData {
14
+ output?: unknown;
15
+ }
16
+ /** Wire format for the agent-side `tool-output-error` chunk data payload. */
17
+ export interface AgentToolOutputErrorWireData {
18
+ errorText?: string;
19
+ }
20
+ /** Wire format for the client-side `tool-result-error` input data payload. */
21
+ export interface ClientToolResultErrorWireData {
22
+ message?: string;
23
+ }
24
+ /**
25
+ * Coerce wire `data` to a string, falling back to `''` for any non-string
26
+ * payload — the defensive read for descriptors whose data is plain text.
27
+ * @param data - The inbound wire data.
28
+ * @returns The string payload, or `''` when the data is not a string.
29
+ */
30
+ export declare const asString: (data: unknown) => string;
31
+ export declare const isToolInputErrorWireData: (data: unknown) => data is ToolInputErrorWireData;
32
+ export declare const isToolOutputAvailableWireData: (data: unknown) => data is ToolOutputAvailableWireData;
33
+ export declare const isAgentToolOutputErrorWireData: (data: unknown) => data is AgentToolOutputErrorWireData;
34
+ export declare const isClientToolResultErrorWireData: (data: unknown) => data is ClientToolResultErrorWireData;
@@ -2,4 +2,5 @@ export type { VercelInput, VercelOutput, VercelProjection } from './codec/index.
2
2
  export { UIMessageCodec } from './codec/index.js';
3
3
  export type { ChatTransport, ChatTransportOptions, SendMessagesRequestContext, VercelAgentSessionOptions, VercelClientSessionOptions, } from './transport/index.js';
4
4
  export { createAgentSession, createChatTransport, createClientSession } from './transport/index.js';
5
+ export type { VercelRunOutcome } from './run-end-reason.js';
5
6
  export { vercelRunOutcome } from './run-end-reason.js';