@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.
- package/README.md +10 -19
- package/dist/ably-ai-transport.js +1790 -1091
- 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 +2 -2
- package/dist/core/agent.d.ts +20 -5
- 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 +4 -1
- package/dist/core/codec/define-codec.d.ts +100 -0
- package/dist/core/codec/encoder.d.ts +2 -7
- 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 -1
- 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/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 +95 -36
- package/dist/core/codec/well-known-inputs.d.ts +52 -0
- package/dist/core/transport/agent-view.d.ts +296 -0
- package/dist/core/transport/decode-fold.d.ts +40 -32
- package/dist/core/transport/headers.d.ts +30 -1
- package/dist/core/transport/index.d.ts +1 -1
- package/dist/core/transport/invocation.d.ts +1 -1
- package/dist/core/transport/load-history-pages.d.ts +71 -0
- package/dist/core/transport/load-history.d.ts +21 -16
- package/dist/core/transport/run-manager.d.ts +9 -11
- package/dist/core/transport/session-support.d.ts +55 -0
- package/dist/core/transport/tree.d.ts +165 -15
- package/dist/core/transport/types/agent.d.ts +120 -98
- package/dist/core/transport/types/client.d.ts +45 -12
- package/dist/core/transport/types/tree.d.ts +52 -10
- package/dist/core/transport/types/view.d.ts +55 -28
- package/dist/core/transport/view.d.ts +176 -58
- package/dist/core/transport/wire-log.d.ts +102 -0
- package/dist/errors.d.ts +10 -4
- package/dist/index.d.ts +6 -5
- package/dist/react/ably-ai-transport-react.js +784 -415
- 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 +2 -1
- package/dist/react/contexts/client-session-provider.d.ts +3 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/internal/skipped-session.d.ts +8 -0
- package/dist/react/use-view.d.ts +3 -3
- package/dist/utils.d.ts +22 -54
- package/dist/vercel/ably-ai-transport-vercel.js +2297 -2026
- 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 +1 -2
- 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 +5 -30
- 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 +20 -102
- package/dist/vercel/codec/tool-transitions.d.ts +0 -6
- package/dist/vercel/codec/wire-data.d.ts +34 -0
- package/dist/vercel/index.d.ts +1 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2013 -9500
- 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 -70
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +2 -1
- package/dist/vercel/run-end-reason.d.ts +66 -11
- package/dist/vercel/tool-part.d.ts +21 -0
- package/dist/vercel/transport/chat-transport.d.ts +0 -2
- package/dist/vercel/transport/index.d.ts +1 -1
- package/dist/vercel/transport/run-output-stream.d.ts +6 -8
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/constants.ts +2 -2
- package/src/core/agent.ts +43 -19
- package/src/core/channel-options.ts +89 -0
- package/src/core/codec/codec-event.ts +27 -0
- package/src/core/codec/decoder.ts +145 -21
- package/src/core/codec/define-codec.ts +432 -0
- package/src/core/codec/encoder.ts +13 -54
- package/src/core/codec/field-bag.ts +142 -0
- package/src/core/codec/fields.ts +193 -0
- package/src/core/codec/index.ts +43 -0
- 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/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 +99 -36
- package/src/core/codec/well-known-inputs.ts +96 -0
- package/src/core/transport/agent-session.ts +330 -589
- package/src/core/transport/agent-view.ts +738 -0
- package/src/core/transport/client-session.ts +74 -69
- package/src/core/transport/decode-fold.ts +57 -47
- package/src/core/transport/headers.ts +57 -4
- package/src/core/transport/index.ts +2 -1
- package/src/core/transport/invocation.ts +1 -1
- package/src/core/transport/load-history-pages.ts +220 -0
- package/src/core/transport/load-history.ts +63 -61
- package/src/core/transport/pipe-stream.ts +10 -1
- package/src/core/transport/run-manager.ts +25 -31
- package/src/core/transport/session-support.ts +96 -0
- package/src/core/transport/tree.ts +414 -47
- package/src/core/transport/types/agent.ts +129 -102
- package/src/core/transport/types/client.ts +49 -13
- package/src/core/transport/types/tree.ts +61 -12
- package/src/core/transport/types/view.ts +57 -28
- package/src/core/transport/view.ts +520 -172
- package/src/core/transport/wire-log.ts +189 -0
- package/src/errors.ts +10 -3
- package/src/index.ts +44 -11
- package/src/react/contexts/client-session-context.ts +1 -1
- package/src/react/contexts/client-session-provider.tsx +38 -2
- package/src/react/index.ts +2 -1
- package/src/react/internal/skipped-session.ts +62 -0
- package/src/react/use-client-session.ts +7 -30
- package/src/react/use-view.ts +3 -3
- package/src/utils.ts +31 -97
- package/src/vercel/codec/decode-lifecycle.ts +70 -0
- package/src/vercel/codec/events.ts +1 -3
- 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 +23 -63
- 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 +52 -838
- package/src/vercel/codec/tool-transitions.ts +1 -12
- package/src/vercel/codec/wire-data.ts +64 -0
- package/src/vercel/index.ts +1 -0
- package/src/vercel/react/contexts/chat-transport-context.ts +1 -1
- package/src/vercel/react/use-chat-transport.ts +8 -28
- package/src/vercel/react/use-message-sync.ts +5 -10
- package/src/vercel/run-end-reason.ts +95 -16
- package/src/vercel/tool-part.ts +25 -0
- package/src/vercel/transport/chat-transport.ts +10 -22
- package/src/vercel/transport/index.ts +1 -1
- package/src/vercel/transport/run-output-stream.ts +7 -8
- package/src/version.ts +1 -1
- package/dist/core/transport/branch-chain.d.ts +0 -43
- package/dist/core/transport/load-conversation.d.ts +0 -128
- package/dist/vercel/codec/decoder.d.ts +0 -9
- package/dist/vercel/codec/encoder.d.ts +0 -11
- package/src/core/transport/branch-chain.ts +0 -58
- package/src/core/transport/load-conversation.ts +0 -355
- package/src/vercel/codec/decoder.ts +0 -696
- package/src/vercel/codec/encoder.ts +0 -548
|
@@ -1,696 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vercel AI SDK Decoder.
|
|
3
|
-
*
|
|
4
|
-
* Maps Ably inbound messages to {@link DecodedMessage} — a `{ inputs,
|
|
5
|
-
* outputs }` tagged result. The decoder routes by the wire `name`
|
|
6
|
-
* (`ai-input` vs `ai-output`) so the SDK never has to inspect direction:
|
|
7
|
-
* input-side messages produce `VercelInput` variants; output-side
|
|
8
|
-
* messages produce `VercelOutput` (`UIMessageChunk`) variants.
|
|
9
|
-
*
|
|
10
|
-
* The `LifecycleTracker` is an internal helper used to pre-roll missing
|
|
11
|
-
* `start` / `start-step` chunks on mid-stream join (history compaction,
|
|
12
|
-
* rewind miss, partial page) so the reducer always sees a clean event
|
|
13
|
-
* sequence for streamed output.
|
|
14
|
-
*
|
|
15
|
-
* Receive-side dispatch reads the wire `name` first and then routes by
|
|
16
|
-
* the codec `type` header carrying the codec event type. Codec headers live
|
|
17
|
-
* under `extras.ai.codec` and transport headers under `extras.ai.transport`;
|
|
18
|
-
* both are read unprefixed from their respective tier.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type * as Ably from 'ably';
|
|
22
|
-
import type * as AI from 'ai';
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
EVENT_AI_INPUT,
|
|
26
|
-
EVENT_AI_OUTPUT,
|
|
27
|
-
HEADER_CODEC_MESSAGE_ID,
|
|
28
|
-
HEADER_DISCRETE,
|
|
29
|
-
HEADER_ROLE,
|
|
30
|
-
HEADER_RUN_ID,
|
|
31
|
-
} from '../../constants.js';
|
|
32
|
-
import type { DecoderCore, DecoderCoreHooks, DecoderCoreOptions } from '../../core/codec/decoder.js';
|
|
33
|
-
import { createDecoderCore } from '../../core/codec/decoder.js';
|
|
34
|
-
import type { LifecycleTracker } from '../../core/codec/lifecycle-tracker.js';
|
|
35
|
-
import { createLifecycleTracker } from '../../core/codec/lifecycle-tracker.js';
|
|
36
|
-
import type {
|
|
37
|
-
DecodedMessage,
|
|
38
|
-
Decoder,
|
|
39
|
-
MessagePayload,
|
|
40
|
-
StreamTrackerState,
|
|
41
|
-
UserMessage,
|
|
42
|
-
} from '../../core/codec/types.js';
|
|
43
|
-
import { type DomainHeaderReader, headerReader as rawHeaderReader, stripUndefined } from '../../utils.js';
|
|
44
|
-
import type { VercelInput, VercelOutput } from './events.js';
|
|
45
|
-
|
|
46
|
-
// Decoder-internal union — the codec emits inputs and outputs through the
|
|
47
|
-
// same flat list from the underlying core and partitions on the way out.
|
|
48
|
-
type AnyEvent = VercelInput | VercelOutput;
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Vercel-specific header reader (casts providerMetadata to AI.ProviderMetadata)
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
interface VercelHeaderReader extends DomainHeaderReader {
|
|
55
|
-
/** Read the `providerMetadata` domain header, cast to the AI SDK type. */
|
|
56
|
-
providerMetadata(): AI.ProviderMetadata | undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Create a header reader that adds Vercel-specific `providerMetadata` typing.
|
|
61
|
-
* @param headers - The raw headers record to read domain headers from.
|
|
62
|
-
* @returns A typed accessor with Vercel-specific providerMetadata typing.
|
|
63
|
-
*/
|
|
64
|
-
const headerReader = (headers: Record<string, string>): VercelHeaderReader => {
|
|
65
|
-
const base = rawHeaderReader(headers);
|
|
66
|
-
return {
|
|
67
|
-
...base,
|
|
68
|
-
// CAST: Trust boundary — the encoder serialized a valid ProviderMetadata value.
|
|
69
|
-
providerMetadata: () => base.json('providerMetadata') as AI.ProviderMetadata | undefined,
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// Wire format types (trust boundaries for JSON-parsed data)
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
/** Wire format for the agent-side `tool-input-error` chunk data payload. */
|
|
78
|
-
interface ToolInputErrorWireData {
|
|
79
|
-
errorText?: string;
|
|
80
|
-
input?: unknown;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Wire format for the `tool-output-available` (agent) / `tool-result` (client) data payload. */
|
|
84
|
-
interface ToolOutputAvailableWireData {
|
|
85
|
-
output?: unknown;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Wire format for the agent-side `tool-output-error` chunk data payload. */
|
|
89
|
-
interface AgentToolOutputErrorWireData {
|
|
90
|
-
errorText?: string;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Wire format for the client-side `tool-result-error` input data payload. */
|
|
94
|
-
interface ClientToolResultErrorWireData {
|
|
95
|
-
message?: string;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// JSON boundary helpers
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
|
|
102
|
-
const parseFinishReason = (value: string | undefined, fallback: AI.FinishReason): AI.FinishReason => {
|
|
103
|
-
if (
|
|
104
|
-
value === 'stop' ||
|
|
105
|
-
value === 'length' ||
|
|
106
|
-
value === 'content-filter' ||
|
|
107
|
-
value === 'tool-calls' ||
|
|
108
|
-
value === 'error' ||
|
|
109
|
-
value === 'other'
|
|
110
|
-
) {
|
|
111
|
-
return value;
|
|
112
|
-
}
|
|
113
|
-
return fallback;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const isDataEventName = (name: string): name is `data-${string}` => name.startsWith('data-');
|
|
117
|
-
|
|
118
|
-
const parseJsonOrString = (value: string): unknown => {
|
|
119
|
-
if (!value) return undefined;
|
|
120
|
-
try {
|
|
121
|
-
// CAST: JSON.parse returns any; unknown is the safe trust-boundary type.
|
|
122
|
-
return JSON.parse(value) as unknown;
|
|
123
|
-
} catch {
|
|
124
|
-
return value;
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
// Streamed message event builders (output-side)
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Read the codec event type from a tracker's codec headers. The encoder
|
|
134
|
-
* stamps the codec `type` header on every `ai-output` publish; the value
|
|
135
|
-
* carries the AI-SDK chunk family (`text` / `reasoning` / `tool-input`)
|
|
136
|
-
* that the stream represents.
|
|
137
|
-
* @param tracker - The stream tracker carrying the persistent headers.
|
|
138
|
-
* @returns The codec event type, or the empty string when absent.
|
|
139
|
-
*/
|
|
140
|
-
const codecTypeOf = (tracker: StreamTrackerState): string => headerReader(tracker.codecHeaders).strOr('type', '');
|
|
141
|
-
|
|
142
|
-
const buildStartChunk = (tracker: StreamTrackerState): AI.UIMessageChunk => {
|
|
143
|
-
const r = headerReader(tracker.codecHeaders);
|
|
144
|
-
switch (codecTypeOf(tracker)) {
|
|
145
|
-
case 'text': {
|
|
146
|
-
return stripUndefined({
|
|
147
|
-
type: 'text-start' as const,
|
|
148
|
-
id: tracker.streamId,
|
|
149
|
-
providerMetadata: r.providerMetadata(),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
case 'reasoning': {
|
|
153
|
-
return stripUndefined({
|
|
154
|
-
type: 'reasoning-start' as const,
|
|
155
|
-
id: tracker.streamId,
|
|
156
|
-
providerMetadata: r.providerMetadata(),
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
case 'tool-input': {
|
|
160
|
-
return stripUndefined({
|
|
161
|
-
type: 'tool-input-start' as const,
|
|
162
|
-
toolCallId: tracker.streamId,
|
|
163
|
-
toolName: r.strOr('toolName', ''),
|
|
164
|
-
dynamic: r.bool('dynamic'),
|
|
165
|
-
title: r.str('title'),
|
|
166
|
-
providerExecuted: r.bool('providerExecuted'),
|
|
167
|
-
providerMetadata: r.providerMetadata(),
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
default: {
|
|
171
|
-
return { type: 'text-start', id: tracker.streamId };
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const buildDeltaChunk = (tracker: StreamTrackerState, delta: string): AI.UIMessageChunk => {
|
|
177
|
-
switch (codecTypeOf(tracker)) {
|
|
178
|
-
case 'text': {
|
|
179
|
-
return { type: 'text-delta', id: tracker.streamId, delta };
|
|
180
|
-
}
|
|
181
|
-
case 'reasoning': {
|
|
182
|
-
return { type: 'reasoning-delta', id: tracker.streamId, delta };
|
|
183
|
-
}
|
|
184
|
-
case 'tool-input': {
|
|
185
|
-
return { type: 'tool-input-delta', toolCallId: tracker.streamId, inputTextDelta: delta };
|
|
186
|
-
}
|
|
187
|
-
default: {
|
|
188
|
-
return { type: 'text-delta', id: tracker.streamId, delta };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const buildEndChunk = (tracker: StreamTrackerState, closingHeaders: Record<string, string>): AI.UIMessageChunk => {
|
|
194
|
-
const r = headerReader(closingHeaders);
|
|
195
|
-
switch (codecTypeOf(tracker)) {
|
|
196
|
-
case 'text': {
|
|
197
|
-
return stripUndefined({
|
|
198
|
-
type: 'text-end' as const,
|
|
199
|
-
id: tracker.streamId,
|
|
200
|
-
providerMetadata: r.providerMetadata(),
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
case 'reasoning': {
|
|
204
|
-
return stripUndefined({
|
|
205
|
-
type: 'reasoning-end' as const,
|
|
206
|
-
id: tracker.streamId,
|
|
207
|
-
providerMetadata: r.providerMetadata(),
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
case 'tool-input': {
|
|
211
|
-
return stripUndefined({
|
|
212
|
-
type: 'tool-input-available' as const,
|
|
213
|
-
toolCallId: tracker.streamId,
|
|
214
|
-
toolName: r.strOr('toolName', headerReader(tracker.codecHeaders).strOr('toolName', '')),
|
|
215
|
-
input: parseJsonOrString(tracker.accumulated),
|
|
216
|
-
providerMetadata: r.providerMetadata(),
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
default: {
|
|
220
|
-
return { type: 'text-end', id: tracker.streamId };
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
// ---------------------------------------------------------------------------
|
|
226
|
-
// Lifecycle tracker configuration (synthetic event phases on mid-stream join)
|
|
227
|
-
// ---------------------------------------------------------------------------
|
|
228
|
-
|
|
229
|
-
const createVercelLifecycleTracker = (): LifecycleTracker<AI.UIMessageChunk> =>
|
|
230
|
-
createLifecycleTracker<AI.UIMessageChunk>([
|
|
231
|
-
{
|
|
232
|
-
key: 'start',
|
|
233
|
-
build: (ctx) => [stripUndefined({ type: 'start' as const, messageId: ctx.messageId })],
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
key: 'start-step',
|
|
237
|
-
build: () => [{ type: 'start-step' as const }],
|
|
238
|
-
},
|
|
239
|
-
]);
|
|
240
|
-
|
|
241
|
-
// ---------------------------------------------------------------------------
|
|
242
|
-
// Discrete output decoders (ai-output → UIMessageChunk)
|
|
243
|
-
// ---------------------------------------------------------------------------
|
|
244
|
-
|
|
245
|
-
const decodeStart = (
|
|
246
|
-
r: VercelHeaderReader,
|
|
247
|
-
runId: string,
|
|
248
|
-
lifecycle: LifecycleTracker<AI.UIMessageChunk>,
|
|
249
|
-
): AI.UIMessageChunk[] => {
|
|
250
|
-
lifecycle.markEmitted(runId, 'start');
|
|
251
|
-
return [
|
|
252
|
-
stripUndefined({
|
|
253
|
-
type: 'start' as const,
|
|
254
|
-
messageId: r.str('messageId'),
|
|
255
|
-
messageMetadata: r.json('messageMetadata'),
|
|
256
|
-
}),
|
|
257
|
-
];
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const decodeStartStep = (runId: string, lifecycle: LifecycleTracker<AI.UIMessageChunk>): AI.UIMessageChunk[] => {
|
|
261
|
-
lifecycle.markEmitted(runId, 'start-step');
|
|
262
|
-
return [{ type: 'start-step' }];
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const decodeFinishStep = (runId: string, lifecycle: LifecycleTracker<AI.UIMessageChunk>): AI.UIMessageChunk[] => {
|
|
266
|
-
lifecycle.resetPhase(runId, 'start-step');
|
|
267
|
-
return [{ type: 'finish-step' }];
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const decodeFinish = (
|
|
271
|
-
r: VercelHeaderReader,
|
|
272
|
-
runId: string,
|
|
273
|
-
lifecycle: LifecycleTracker<AI.UIMessageChunk>,
|
|
274
|
-
): AI.UIMessageChunk[] => {
|
|
275
|
-
lifecycle.clearScope(runId);
|
|
276
|
-
return [
|
|
277
|
-
stripUndefined({
|
|
278
|
-
type: 'finish' as const,
|
|
279
|
-
finishReason: parseFinishReason(r.str('finishReason'), 'stop'),
|
|
280
|
-
messageMetadata: r.json('messageMetadata'),
|
|
281
|
-
}),
|
|
282
|
-
];
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const decodeError = (
|
|
286
|
-
data: unknown,
|
|
287
|
-
runId: string,
|
|
288
|
-
lifecycle: LifecycleTracker<AI.UIMessageChunk>,
|
|
289
|
-
): AI.UIMessageChunk[] => {
|
|
290
|
-
lifecycle.clearScope(runId);
|
|
291
|
-
const errorText = typeof data === 'string' ? data : '';
|
|
292
|
-
return [{ type: 'error', errorText }];
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const decodeAbort = (
|
|
296
|
-
data: unknown,
|
|
297
|
-
runId: string,
|
|
298
|
-
lifecycle: LifecycleTracker<AI.UIMessageChunk>,
|
|
299
|
-
): AI.UIMessageChunk[] => {
|
|
300
|
-
lifecycle.clearScope(runId);
|
|
301
|
-
const reason = typeof data === 'string' && data ? data : undefined;
|
|
302
|
-
return [stripUndefined({ type: 'abort' as const, reason })];
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
const decodeMessageMetadata = (r: VercelHeaderReader): AI.UIMessageChunk[] => [
|
|
306
|
-
{ type: 'message-metadata', messageMetadata: r.json('messageMetadata') },
|
|
307
|
-
];
|
|
308
|
-
|
|
309
|
-
const decodeFile = (r: VercelHeaderReader, data: unknown): AI.UIMessageChunk[] => [
|
|
310
|
-
stripUndefined({
|
|
311
|
-
type: 'file' as const,
|
|
312
|
-
url: typeof data === 'string' ? data : '',
|
|
313
|
-
mediaType: r.strOr('mediaType', ''),
|
|
314
|
-
providerMetadata: r.providerMetadata(),
|
|
315
|
-
}),
|
|
316
|
-
];
|
|
317
|
-
|
|
318
|
-
const decodeSourceUrl = (r: VercelHeaderReader, data: unknown): AI.UIMessageChunk[] => [
|
|
319
|
-
stripUndefined({
|
|
320
|
-
type: 'source-url' as const,
|
|
321
|
-
sourceId: r.strOr('sourceId', ''),
|
|
322
|
-
url: typeof data === 'string' ? data : '',
|
|
323
|
-
title: r.str('title'),
|
|
324
|
-
providerMetadata: r.providerMetadata(),
|
|
325
|
-
}),
|
|
326
|
-
];
|
|
327
|
-
|
|
328
|
-
const decodeSourceDocument = (r: VercelHeaderReader): AI.UIMessageChunk[] => [
|
|
329
|
-
stripUndefined({
|
|
330
|
-
type: 'source-document' as const,
|
|
331
|
-
sourceId: r.strOr('sourceId', ''),
|
|
332
|
-
mediaType: r.strOr('mediaType', ''),
|
|
333
|
-
title: r.strOr('title', ''),
|
|
334
|
-
filename: r.str('filename'),
|
|
335
|
-
providerMetadata: r.providerMetadata(),
|
|
336
|
-
}),
|
|
337
|
-
];
|
|
338
|
-
|
|
339
|
-
const decodeToolInputError = (r: VercelHeaderReader, data: unknown): AI.UIMessageChunk[] => {
|
|
340
|
-
// CAST: Trust boundary — encoder produced the expected object shape.
|
|
341
|
-
const parsed = data as ToolInputErrorWireData | undefined;
|
|
342
|
-
return [
|
|
343
|
-
stripUndefined({
|
|
344
|
-
type: 'tool-input-error' as const,
|
|
345
|
-
toolCallId: r.strOr('toolCallId', ''),
|
|
346
|
-
toolName: r.strOr('toolName', ''),
|
|
347
|
-
errorText: parsed?.errorText ?? '',
|
|
348
|
-
input: parsed?.input,
|
|
349
|
-
dynamic: r.bool('dynamic'),
|
|
350
|
-
title: r.str('title'),
|
|
351
|
-
providerExecuted: r.bool('providerExecuted'),
|
|
352
|
-
providerMetadata: r.providerMetadata(),
|
|
353
|
-
}),
|
|
354
|
-
];
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
const decodeAgentToolOutputAvailable = (r: VercelHeaderReader, data: unknown): AI.UIMessageChunk[] => {
|
|
358
|
-
// CAST: Trust boundary — encoder produced the expected object shape.
|
|
359
|
-
const parsed = data as ToolOutputAvailableWireData | undefined;
|
|
360
|
-
return [
|
|
361
|
-
stripUndefined({
|
|
362
|
-
type: 'tool-output-available' as const,
|
|
363
|
-
toolCallId: r.strOr('toolCallId', ''),
|
|
364
|
-
output: parsed?.output,
|
|
365
|
-
dynamic: r.bool('dynamic'),
|
|
366
|
-
providerExecuted: r.bool('providerExecuted'),
|
|
367
|
-
preliminary: r.bool('preliminary'),
|
|
368
|
-
}),
|
|
369
|
-
];
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const decodeAgentToolOutputError = (r: VercelHeaderReader, data: unknown): AI.UIMessageChunk[] => {
|
|
373
|
-
// CAST: Trust boundary — encoder produced the expected object shape.
|
|
374
|
-
const parsed = data as AgentToolOutputErrorWireData | undefined;
|
|
375
|
-
return [
|
|
376
|
-
stripUndefined({
|
|
377
|
-
type: 'tool-output-error' as const,
|
|
378
|
-
toolCallId: r.strOr('toolCallId', ''),
|
|
379
|
-
errorText: parsed?.errorText ?? '',
|
|
380
|
-
dynamic: r.bool('dynamic'),
|
|
381
|
-
providerExecuted: r.bool('providerExecuted'),
|
|
382
|
-
}),
|
|
383
|
-
];
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
const decodeToolApprovalRequest = (r: VercelHeaderReader): AI.UIMessageChunk[] => [
|
|
387
|
-
{
|
|
388
|
-
type: 'tool-approval-request',
|
|
389
|
-
toolCallId: r.strOr('toolCallId', ''),
|
|
390
|
-
approvalId: r.strOr('approvalId', ''),
|
|
391
|
-
},
|
|
392
|
-
];
|
|
393
|
-
|
|
394
|
-
const decodeToolOutputDenied = (r: VercelHeaderReader): AI.UIMessageChunk[] => [
|
|
395
|
-
{ type: 'tool-output-denied', toolCallId: r.strOr('toolCallId', '') },
|
|
396
|
-
];
|
|
397
|
-
|
|
398
|
-
const decodeDataEvent = (name: `data-${string}`, r: VercelHeaderReader, data: unknown): AI.UIMessageChunk[] => [
|
|
399
|
-
stripUndefined({
|
|
400
|
-
type: name,
|
|
401
|
-
data,
|
|
402
|
-
id: r.str('id'),
|
|
403
|
-
transient: r.bool('transient'),
|
|
404
|
-
}),
|
|
405
|
-
];
|
|
406
|
-
|
|
407
|
-
// ---------------------------------------------------------------------------
|
|
408
|
-
// Non-streaming tool-input helper (agent-side)
|
|
409
|
-
// ---------------------------------------------------------------------------
|
|
410
|
-
|
|
411
|
-
const decodeNonStreamingToolInput = (
|
|
412
|
-
r: VercelHeaderReader,
|
|
413
|
-
data: unknown,
|
|
414
|
-
runId: string,
|
|
415
|
-
lifecycle: LifecycleTracker<AI.UIMessageChunk>,
|
|
416
|
-
): AI.UIMessageChunk[] => [
|
|
417
|
-
...lifecycle.ensurePhases(runId, { messageId: r.str('messageId') }),
|
|
418
|
-
stripUndefined({
|
|
419
|
-
type: 'tool-input-start' as const,
|
|
420
|
-
toolCallId: r.strOr('toolCallId', ''),
|
|
421
|
-
toolName: r.strOr('toolName', ''),
|
|
422
|
-
dynamic: r.bool('dynamic'),
|
|
423
|
-
title: r.str('title'),
|
|
424
|
-
providerExecuted: r.bool('providerExecuted'),
|
|
425
|
-
providerMetadata: r.providerMetadata(),
|
|
426
|
-
}),
|
|
427
|
-
stripUndefined({
|
|
428
|
-
type: 'tool-input-available' as const,
|
|
429
|
-
toolCallId: r.strOr('toolCallId', ''),
|
|
430
|
-
toolName: r.strOr('toolName', ''),
|
|
431
|
-
input: data,
|
|
432
|
-
providerMetadata: r.providerMetadata(),
|
|
433
|
-
}),
|
|
434
|
-
];
|
|
435
|
-
|
|
436
|
-
// ---------------------------------------------------------------------------
|
|
437
|
-
// Input-side decoders (ai-input → VercelInput)
|
|
438
|
-
// ---------------------------------------------------------------------------
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Decode a single discrete message part (from the user-message multi-part
|
|
442
|
-
* wire format) into a {@link UserMessage} carrying a one-part
|
|
443
|
-
* UIMessage. The reducer's `_foldUserMessage` merges parts that share
|
|
444
|
-
* the same codec-message-id.
|
|
445
|
-
* @param input - The discrete message payload (name, data, headers).
|
|
446
|
-
* @returns A single `user-message` input, or an empty array when the part type is unrecognised.
|
|
447
|
-
*/
|
|
448
|
-
const decodeDiscreteMessagePart = (input: MessagePayload): VercelInput[] => {
|
|
449
|
-
const r = headerReader(input.codecHeaders ?? {});
|
|
450
|
-
const role = (input.transportHeaders?.[HEADER_ROLE] ?? 'user') as AI.UIMessage['role'];
|
|
451
|
-
const messageId = r.str('messageId') ?? '';
|
|
452
|
-
const codecType = r.strOr('type', '');
|
|
453
|
-
|
|
454
|
-
let part: AI.UIMessage['parts'][number] | undefined;
|
|
455
|
-
|
|
456
|
-
switch (codecType) {
|
|
457
|
-
case 'text': {
|
|
458
|
-
part = { type: 'text', text: typeof input.data === 'string' ? input.data : '' };
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
case 'file': {
|
|
462
|
-
part = {
|
|
463
|
-
type: 'file',
|
|
464
|
-
mediaType: r.strOr('mediaType', ''),
|
|
465
|
-
url: typeof input.data === 'string' ? input.data : '',
|
|
466
|
-
};
|
|
467
|
-
break;
|
|
468
|
-
}
|
|
469
|
-
default: {
|
|
470
|
-
if (isDataEventName(codecType)) {
|
|
471
|
-
part = stripUndefined({ type: codecType, id: r.str('id'), data: input.data });
|
|
472
|
-
}
|
|
473
|
-
break;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (!part) return [];
|
|
478
|
-
|
|
479
|
-
const message: AI.UIMessage = { id: messageId, role, parts: [part] };
|
|
480
|
-
const userMessage: UserMessage<AI.UIMessage> = { kind: 'user-message', message };
|
|
481
|
-
return [userMessage];
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
const isDiscreteMessagePart = (codecType: string, headers: Record<string, string>): boolean =>
|
|
485
|
-
(codecType === 'text' || codecType === 'file' || isDataEventName(codecType)) && HEADER_DISCRETE in headers;
|
|
486
|
-
|
|
487
|
-
const decodeClientToolResult = (codecMessageId: string, r: VercelHeaderReader, data: unknown): VercelInput[] => {
|
|
488
|
-
// CAST: Trust boundary — encoder produced the expected object shape.
|
|
489
|
-
const parsed = data as ToolOutputAvailableWireData | undefined;
|
|
490
|
-
return [
|
|
491
|
-
{
|
|
492
|
-
kind: 'tool-result',
|
|
493
|
-
codecMessageId,
|
|
494
|
-
payload: { toolCallId: r.strOr('toolCallId', ''), output: parsed?.output },
|
|
495
|
-
},
|
|
496
|
-
];
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
const decodeClientToolResultError = (codecMessageId: string, r: VercelHeaderReader, data: unknown): VercelInput[] => {
|
|
500
|
-
// CAST: Trust boundary — encoder produced the expected object shape.
|
|
501
|
-
const parsed = data as ClientToolResultErrorWireData | undefined;
|
|
502
|
-
return [
|
|
503
|
-
{
|
|
504
|
-
kind: 'tool-result-error',
|
|
505
|
-
codecMessageId,
|
|
506
|
-
payload: { toolCallId: r.strOr('toolCallId', ''), message: parsed?.message ?? '' },
|
|
507
|
-
},
|
|
508
|
-
];
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
const decodeClientToolApprovalResponse = (codecMessageId: string, r: VercelHeaderReader): VercelInput[] => [
|
|
512
|
-
{
|
|
513
|
-
kind: 'tool-approval-response',
|
|
514
|
-
codecMessageId,
|
|
515
|
-
payload: stripUndefined({
|
|
516
|
-
toolCallId: r.strOr('toolCallId', ''),
|
|
517
|
-
approved: r.bool('approved') ?? false,
|
|
518
|
-
reason: r.str('reason'),
|
|
519
|
-
}),
|
|
520
|
-
},
|
|
521
|
-
];
|
|
522
|
-
|
|
523
|
-
// ---------------------------------------------------------------------------
|
|
524
|
-
// Discrete payload dispatch
|
|
525
|
-
// ---------------------------------------------------------------------------
|
|
526
|
-
|
|
527
|
-
const decodeAiOutputPayload = (
|
|
528
|
-
codecType: string,
|
|
529
|
-
r: VercelHeaderReader,
|
|
530
|
-
data: unknown,
|
|
531
|
-
runId: string,
|
|
532
|
-
lifecycle: LifecycleTracker<AI.UIMessageChunk>,
|
|
533
|
-
): AnyEvent[] => {
|
|
534
|
-
switch (codecType) {
|
|
535
|
-
case 'start': {
|
|
536
|
-
return decodeStart(r, runId, lifecycle);
|
|
537
|
-
}
|
|
538
|
-
case 'start-step': {
|
|
539
|
-
return decodeStartStep(runId, lifecycle);
|
|
540
|
-
}
|
|
541
|
-
case 'finish-step': {
|
|
542
|
-
return decodeFinishStep(runId, lifecycle);
|
|
543
|
-
}
|
|
544
|
-
case 'finish': {
|
|
545
|
-
return decodeFinish(r, runId, lifecycle);
|
|
546
|
-
}
|
|
547
|
-
case 'error': {
|
|
548
|
-
return decodeError(data, runId, lifecycle);
|
|
549
|
-
}
|
|
550
|
-
case 'abort': {
|
|
551
|
-
return decodeAbort(data, runId, lifecycle);
|
|
552
|
-
}
|
|
553
|
-
case 'message-metadata': {
|
|
554
|
-
return decodeMessageMetadata(r);
|
|
555
|
-
}
|
|
556
|
-
case 'file': {
|
|
557
|
-
return decodeFile(r, data);
|
|
558
|
-
}
|
|
559
|
-
case 'source-url': {
|
|
560
|
-
return decodeSourceUrl(r, data);
|
|
561
|
-
}
|
|
562
|
-
case 'source-document': {
|
|
563
|
-
return decodeSourceDocument(r);
|
|
564
|
-
}
|
|
565
|
-
case 'tool-input': {
|
|
566
|
-
return decodeNonStreamingToolInput(r, data, runId, lifecycle);
|
|
567
|
-
}
|
|
568
|
-
case 'tool-input-error': {
|
|
569
|
-
return decodeToolInputError(r, data);
|
|
570
|
-
}
|
|
571
|
-
case 'tool-output-available': {
|
|
572
|
-
return decodeAgentToolOutputAvailable(r, data);
|
|
573
|
-
}
|
|
574
|
-
case 'tool-output-error': {
|
|
575
|
-
return decodeAgentToolOutputError(r, data);
|
|
576
|
-
}
|
|
577
|
-
case 'tool-approval-request': {
|
|
578
|
-
return decodeToolApprovalRequest(r);
|
|
579
|
-
}
|
|
580
|
-
case 'tool-output-denied': {
|
|
581
|
-
return decodeToolOutputDenied(r);
|
|
582
|
-
}
|
|
583
|
-
default: {
|
|
584
|
-
return isDataEventName(codecType) ? decodeDataEvent(codecType, r, data) : [];
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
const decodeAiInputPayload = (codecType: string, input: MessagePayload, r: VercelHeaderReader): AnyEvent[] => {
|
|
590
|
-
// Multi-part user-message parts (text / file / data-*) carry discrete
|
|
591
|
-
// because they ride publishDiscreteBatch; the receive-side fans them back
|
|
592
|
-
// out into a UserMessage.
|
|
593
|
-
if (isDiscreteMessagePart(codecType, input.transportHeaders ?? {})) {
|
|
594
|
-
return decodeDiscreteMessagePart(input);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
const codecMessageId = input.transportHeaders?.[HEADER_CODEC_MESSAGE_ID] ?? '';
|
|
598
|
-
|
|
599
|
-
switch (codecType) {
|
|
600
|
-
case 'tool-result': {
|
|
601
|
-
return decodeClientToolResult(codecMessageId, r, input.data);
|
|
602
|
-
}
|
|
603
|
-
case 'tool-result-error': {
|
|
604
|
-
return decodeClientToolResultError(codecMessageId, r, input.data);
|
|
605
|
-
}
|
|
606
|
-
case 'tool-approval-response': {
|
|
607
|
-
return decodeClientToolApprovalResponse(codecMessageId, r);
|
|
608
|
-
}
|
|
609
|
-
case 'regenerate': {
|
|
610
|
-
// Wire-only signal — carries `parent` / `msg-regenerate` on transport
|
|
611
|
-
// headers, no domain payload. The agent's input-event lookup reads
|
|
612
|
-
// transport headers directly from the inbound Ably message; no
|
|
613
|
-
// projection fold is needed here.
|
|
614
|
-
return [];
|
|
615
|
-
}
|
|
616
|
-
default: {
|
|
617
|
-
return [];
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
const decodeDiscretePayload = (input: MessagePayload, lifecycle: LifecycleTracker<AI.UIMessageChunk>): AnyEvent[] => {
|
|
623
|
-
const r = headerReader(input.codecHeaders ?? {});
|
|
624
|
-
const runId = input.transportHeaders?.[HEADER_RUN_ID] ?? '';
|
|
625
|
-
const codecType = r.strOr('type', '');
|
|
626
|
-
|
|
627
|
-
if (input.name === EVENT_AI_INPUT) {
|
|
628
|
-
return decodeAiInputPayload(codecType, input, r);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
if (input.name === EVENT_AI_OUTPUT) {
|
|
632
|
-
return decodeAiOutputPayload(codecType, r, input.data, runId, lifecycle);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return [];
|
|
636
|
-
};
|
|
637
|
-
|
|
638
|
-
// ---------------------------------------------------------------------------
|
|
639
|
-
// Decoder core hooks
|
|
640
|
-
// ---------------------------------------------------------------------------
|
|
641
|
-
|
|
642
|
-
const createHooks = (lifecycle: LifecycleTracker<AI.UIMessageChunk>): DecoderCoreHooks<AnyEvent> => ({
|
|
643
|
-
buildStartEvents: (tracker: StreamTrackerState): AnyEvent[] => {
|
|
644
|
-
const runId = tracker.transportHeaders[HEADER_RUN_ID] ?? '';
|
|
645
|
-
const messageId = headerReader(tracker.codecHeaders).str('messageId');
|
|
646
|
-
return [...lifecycle.ensurePhases(runId, { messageId }), buildStartChunk(tracker)];
|
|
647
|
-
},
|
|
648
|
-
|
|
649
|
-
buildDeltaEvents: (tracker: StreamTrackerState, delta: string): AnyEvent[] => [buildDeltaChunk(tracker, delta)],
|
|
650
|
-
|
|
651
|
-
buildEndEvents: (tracker: StreamTrackerState, closingHeaders: Record<string, string>): AnyEvent[] => [
|
|
652
|
-
buildEndChunk(tracker, closingHeaders),
|
|
653
|
-
],
|
|
654
|
-
|
|
655
|
-
decodeDiscrete: (payload: MessagePayload): AnyEvent[] => decodeDiscretePayload(payload, lifecycle),
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
// ---------------------------------------------------------------------------
|
|
659
|
-
// Default implementation
|
|
660
|
-
// ---------------------------------------------------------------------------
|
|
661
|
-
|
|
662
|
-
const isInput = (event: AnyEvent): event is VercelInput => 'kind' in event;
|
|
663
|
-
|
|
664
|
-
class DefaultUIMessageDecoder implements Decoder<VercelInput, VercelOutput> {
|
|
665
|
-
private readonly _core: DecoderCore<AnyEvent>;
|
|
666
|
-
|
|
667
|
-
constructor(options: DecoderCoreOptions = {}) {
|
|
668
|
-
this._core = createDecoderCore<AnyEvent>(createHooks(createVercelLifecycleTracker()), options);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
decode(message: Ably.InboundMessage): DecodedMessage<VercelInput, VercelOutput> {
|
|
672
|
-
const events = this._core.decode(message);
|
|
673
|
-
const inputs: VercelInput[] = [];
|
|
674
|
-
const outputs: VercelOutput[] = [];
|
|
675
|
-
for (const event of events) {
|
|
676
|
-
if (isInput(event)) {
|
|
677
|
-
inputs.push(event);
|
|
678
|
-
} else {
|
|
679
|
-
outputs.push(event);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
return { inputs, outputs };
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// ---------------------------------------------------------------------------
|
|
687
|
-
// Factory
|
|
688
|
-
// ---------------------------------------------------------------------------
|
|
689
|
-
|
|
690
|
-
/**
|
|
691
|
-
* Create a Vercel AI SDK decoder that maps Ably messages to {@link DecodedMessage}.
|
|
692
|
-
* @param options - Decoder configuration (callbacks, logger).
|
|
693
|
-
* @returns A {@link Decoder} typed in both directions for the Vercel codec.
|
|
694
|
-
*/
|
|
695
|
-
export const createDecoder = (options: DecoderCoreOptions = {}): Decoder<VercelInput, VercelOutput> =>
|
|
696
|
-
new DefaultUIMessageDecoder(options);
|