@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.
- package/README.md +93 -111
- package/dist/ably-ai-transport.js +2401 -1387
- package/dist/ably-ai-transport.js.map +1 -1
- package/dist/ably-ai-transport.umd.cjs +1 -1
- package/dist/ably-ai-transport.umd.cjs.map +1 -1
- package/dist/constants.d.ts +116 -42
- package/dist/core/agent.d.ts +44 -0
- package/dist/core/channel-options.d.ts +57 -0
- package/dist/core/codec/codec-event.d.ts +9 -0
- package/dist/core/codec/decoder.d.ts +24 -24
- package/dist/core/codec/define-codec.d.ts +100 -0
- package/dist/core/codec/encoder.d.ts +10 -12
- package/dist/core/codec/field-bag.d.ts +85 -0
- package/dist/core/codec/fields.d.ts +141 -0
- package/dist/core/codec/index.d.ts +8 -2
- package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
- package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
- package/dist/core/codec/input-descriptors.d.ts +281 -0
- package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
- package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
- package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
- package/dist/core/codec/output-descriptors.d.ts +237 -0
- package/dist/core/codec/types.d.ts +470 -119
- package/dist/core/codec/well-known-inputs.d.ts +52 -0
- package/dist/core/transport/agent-session.d.ts +10 -0
- package/dist/core/transport/agent-view.d.ts +296 -0
- package/dist/core/transport/client-session.d.ts +13 -0
- package/dist/core/transport/decode-fold.d.ts +55 -0
- package/dist/core/transport/headers.d.ts +121 -14
- package/dist/core/transport/index.d.ts +5 -6
- package/dist/core/transport/internal/bounded-map.d.ts +20 -0
- package/dist/core/transport/invocation.d.ts +74 -0
- package/dist/core/transport/load-history-pages.d.ts +71 -0
- package/dist/core/transport/load-history.d.ts +44 -0
- package/dist/core/transport/pipe-stream.d.ts +9 -9
- package/dist/core/transport/run-manager.d.ts +76 -0
- package/dist/core/transport/session-support.d.ts +55 -0
- package/dist/core/transport/tree.d.ts +523 -109
- package/dist/core/transport/types/agent.d.ts +375 -0
- package/dist/core/transport/types/client.d.ts +201 -0
- package/dist/core/transport/types/shared.d.ts +24 -0
- package/dist/core/transport/types/tree.d.ts +357 -0
- package/dist/core/transport/types/view.d.ts +249 -0
- package/dist/core/transport/types.d.ts +13 -553
- package/dist/core/transport/view.d.ts +390 -84
- package/dist/core/transport/wire-log.d.ts +102 -0
- package/dist/errors.d.ts +27 -10
- package/dist/index.d.ts +8 -9
- package/dist/logger.d.ts +12 -0
- package/dist/react/ably-ai-transport-react.js +1365 -1010
- package/dist/react/ably-ai-transport-react.js.map +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
- package/dist/react/contexts/client-session-context.d.ts +37 -0
- package/dist/react/contexts/client-session-provider.d.ts +56 -0
- package/dist/react/create-session-hooks.d.ts +116 -0
- package/dist/react/index.d.ts +13 -12
- package/dist/react/internal/skipped-session.d.ts +8 -0
- package/dist/react/internal/use-resolved-session.d.ts +36 -0
- package/dist/react/use-ably-messages.d.ts +17 -14
- package/dist/react/use-client-session.d.ts +81 -0
- package/dist/react/use-create-view.d.ts +14 -13
- package/dist/react/use-tree.d.ts +30 -15
- package/dist/react/use-view.d.ts +81 -50
- package/dist/utils.d.ts +48 -71
- package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
- package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
- package/dist/vercel/codec/events.d.ts +50 -0
- package/dist/vercel/codec/fields.d.ts +44 -0
- package/dist/vercel/codec/fold-content.d.ts +16 -0
- package/dist/vercel/codec/fold-data.d.ts +16 -0
- package/dist/vercel/codec/fold-input.d.ts +67 -0
- package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
- package/dist/vercel/codec/fold-text.d.ts +16 -0
- package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
- package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
- package/dist/vercel/codec/index.d.ts +7 -20
- package/dist/vercel/codec/inputs.d.ts +11 -0
- package/dist/vercel/codec/outputs.d.ts +11 -0
- package/dist/vercel/codec/reducer-state.d.ts +121 -0
- package/dist/vercel/codec/reducer.d.ts +62 -0
- package/dist/vercel/codec/tool-transitions.d.ts +2 -8
- package/dist/vercel/codec/wire-data.d.ts +34 -0
- package/dist/vercel/index.d.ts +5 -5
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
- package/dist/vercel/react/index.d.ts +1 -2
- package/dist/vercel/react/use-chat-transport.d.ts +30 -26
- package/dist/vercel/react/use-message-sync.d.ts +17 -30
- package/dist/vercel/run-end-reason.d.ts +84 -0
- package/dist/vercel/tool-part.d.ts +21 -0
- package/dist/vercel/transport/chat-transport.d.ts +41 -24
- package/dist/vercel/transport/index.d.ts +24 -20
- package/dist/vercel/transport/run-output-stream.d.ts +54 -0
- package/dist/version.d.ts +2 -0
- package/package.json +31 -24
- package/src/constants.ts +124 -51
- package/src/core/agent.ts +92 -0
- package/src/core/channel-options.ts +89 -0
- package/src/core/codec/codec-event.ts +27 -0
- package/src/core/codec/decoder.ts +202 -105
- package/src/core/codec/define-codec.ts +432 -0
- package/src/core/codec/encoder.ts +114 -107
- package/src/core/codec/field-bag.ts +142 -0
- package/src/core/codec/fields.ts +193 -0
- package/src/core/codec/index.ts +56 -6
- package/src/core/codec/input-descriptor-decoder.ts +97 -0
- package/src/core/codec/input-descriptor-encoder.ts +150 -0
- package/src/core/codec/input-descriptors.ts +373 -0
- package/src/core/codec/lifecycle-tracker.ts +10 -9
- package/src/core/codec/output-descriptor-decoder.ts +139 -0
- package/src/core/codec/output-descriptor-encoder.ts +101 -0
- package/src/core/codec/output-descriptors.ts +307 -0
- package/src/core/codec/types.ts +505 -126
- package/src/core/codec/well-known-inputs.ts +96 -0
- package/src/core/transport/agent-session.ts +1085 -0
- package/src/core/transport/agent-view.ts +738 -0
- package/src/core/transport/client-session.ts +780 -0
- package/src/core/transport/decode-fold.ts +101 -0
- package/src/core/transport/headers.ts +234 -22
- package/src/core/transport/index.ts +27 -27
- package/src/core/transport/internal/bounded-map.ts +27 -0
- package/src/core/transport/invocation.ts +98 -0
- package/src/core/transport/load-history-pages.ts +220 -0
- package/src/core/transport/load-history.ts +271 -0
- package/src/core/transport/pipe-stream.ts +63 -39
- package/src/core/transport/run-manager.ts +243 -0
- package/src/core/transport/session-support.ts +96 -0
- package/src/core/transport/tree.ts +1293 -308
- package/src/core/transport/types/agent.ts +434 -0
- package/src/core/transport/types/client.ts +247 -0
- package/src/core/transport/types/shared.ts +27 -0
- package/src/core/transport/types/tree.ts +393 -0
- package/src/core/transport/types/view.ts +288 -0
- package/src/core/transport/types.ts +13 -706
- package/src/core/transport/view.ts +1229 -450
- package/src/core/transport/wire-log.ts +189 -0
- package/src/errors.ts +29 -9
- package/src/event-emitter.ts +3 -2
- package/src/index.ts +86 -42
- package/src/logger.ts +14 -1
- package/src/react/contexts/client-session-context.ts +41 -0
- package/src/react/contexts/client-session-provider.tsx +222 -0
- package/src/react/create-session-hooks.ts +141 -0
- package/src/react/index.ts +24 -13
- package/src/react/internal/skipped-session.ts +62 -0
- package/src/react/internal/use-resolved-session.ts +63 -0
- package/src/react/use-ably-messages.ts +32 -22
- package/src/react/use-client-session.ts +178 -0
- package/src/react/use-create-view.ts +33 -29
- package/src/react/use-tree.ts +61 -30
- package/src/react/use-view.ts +138 -96
- package/src/utils.ts +83 -131
- package/src/vercel/codec/decode-lifecycle.ts +70 -0
- package/src/vercel/codec/events.ts +85 -0
- package/src/vercel/codec/fields.ts +58 -0
- package/src/vercel/codec/fold-content.ts +54 -0
- package/src/vercel/codec/fold-data.ts +46 -0
- package/src/vercel/codec/fold-input.ts +255 -0
- package/src/vercel/codec/fold-lifecycle.ts +85 -0
- package/src/vercel/codec/fold-text.ts +55 -0
- package/src/vercel/codec/fold-tool-input.ts +86 -0
- package/src/vercel/codec/fold-tool-output.ts +79 -0
- package/src/vercel/codec/index.ts +28 -21
- package/src/vercel/codec/inputs.ts +116 -0
- package/src/vercel/codec/outputs.ts +207 -0
- package/src/vercel/codec/reducer-state.ts +169 -0
- package/src/vercel/codec/reducer.ts +191 -0
- package/src/vercel/codec/tool-transitions.ts +3 -14
- package/src/vercel/codec/wire-data.ts +64 -0
- package/src/vercel/index.ts +7 -19
- package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
- package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
- package/src/vercel/react/index.ts +3 -5
- package/src/vercel/react/use-chat-transport.ts +44 -66
- package/src/vercel/react/use-message-sync.ts +75 -39
- package/src/vercel/run-end-reason.ts +157 -0
- package/src/vercel/tool-part.ts +25 -0
- package/src/vercel/transport/chat-transport.ts +380 -98
- package/src/vercel/transport/index.ts +38 -37
- package/src/vercel/transport/run-output-stream.ts +169 -0
- package/src/version.ts +2 -0
- package/dist/core/transport/client-transport.d.ts +0 -10
- package/dist/core/transport/decode-history.d.ts +0 -43
- package/dist/core/transport/server-transport.d.ts +0 -7
- package/dist/core/transport/stream-router.d.ts +0 -29
- package/dist/core/transport/turn-manager.d.ts +0 -37
- package/dist/react/contexts/transport-context.d.ts +0 -31
- package/dist/react/contexts/transport-provider.d.ts +0 -49
- package/dist/react/create-transport-hooks.d.ts +0 -124
- package/dist/react/use-active-turns.d.ts +0 -12
- package/dist/react/use-client-transport.d.ts +0 -80
- package/dist/vercel/codec/accumulator.d.ts +0 -21
- package/dist/vercel/codec/decoder.d.ts +0 -22
- package/dist/vercel/codec/encoder.d.ts +0 -41
- package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
- package/dist/vercel/tool-approvals.d.ts +0 -124
- package/dist/vercel/tool-events.d.ts +0 -26
- package/src/core/transport/client-transport.ts +0 -977
- package/src/core/transport/decode-history.ts +0 -485
- package/src/core/transport/server-transport.ts +0 -612
- package/src/core/transport/stream-router.ts +0 -136
- package/src/core/transport/turn-manager.ts +0 -165
- package/src/react/contexts/transport-context.ts +0 -37
- package/src/react/contexts/transport-provider.tsx +0 -164
- package/src/react/create-transport-hooks.ts +0 -144
- package/src/react/use-active-turns.ts +0 -72
- package/src/react/use-client-transport.ts +0 -197
- package/src/vercel/codec/accumulator.ts +0 -588
- package/src/vercel/codec/decoder.ts +0 -618
- package/src/vercel/codec/encoder.ts +0 -410
- package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
- package/src/vercel/tool-approvals.ts +0 -380
- 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
|
-
*
|
|
5
|
-
*
|
|
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
|
|
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');
|
package/src/vercel/index.ts
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
10
|
+
VercelAgentSessionOptions,
|
|
11
|
+
VercelClientSessionOptions,
|
|
11
12
|
} from './transport/index.js';
|
|
12
|
-
export {
|
|
13
|
+
export { createAgentSession, createChatTransport, createClientSession } from './transport/index.js';
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
-
export {
|
|
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 {
|
|
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
|
|
11
|
+
* underlying {@link ClientSession} and the {@link ChatTransport} wrapping it.
|
|
11
12
|
*/
|
|
12
13
|
export interface ChatTransportSlot {
|
|
13
|
-
/** The underlying client
|
|
14
|
-
readonly
|
|
15
|
-
/** Construction error from the underlying {@link
|
|
16
|
-
readonly
|
|
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
|
-
|
|
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. */
|