@ably/ai-transport 0.1.0 → 0.2.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 +91 -100
- package/dist/ably-ai-transport.js +1553 -1238
- 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 +29 -0
- package/dist/core/codec/decoder.d.ts +20 -23
- package/dist/core/codec/encoder.d.ts +11 -8
- package/dist/core/codec/index.d.ts +1 -2
- package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
- package/dist/core/codec/types.d.ts +407 -115
- package/dist/core/transport/agent-session.d.ts +10 -0
- package/dist/core/transport/branch-chain.d.ts +43 -0
- package/dist/core/transport/client-session.d.ts +13 -0
- package/dist/core/transport/decode-fold.d.ts +47 -0
- package/dist/core/transport/headers.d.ts +96 -18
- 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-conversation.d.ts +128 -0
- package/dist/core/transport/load-history.d.ts +39 -0
- package/dist/core/transport/pipe-stream.d.ts +9 -9
- package/dist/core/transport/run-manager.d.ts +78 -0
- package/dist/core/transport/tree.d.ts +373 -109
- package/dist/core/transport/types/agent.d.ts +353 -0
- package/dist/core/transport/types/client.d.ts +168 -0
- package/dist/core/transport/types/shared.d.ts +24 -0
- package/dist/core/transport/types/tree.d.ts +315 -0
- package/dist/core/transport/types/view.d.ts +222 -0
- package/dist/core/transport/types.d.ts +13 -553
- package/dist/core/transport/view.d.ts +272 -84
- package/dist/errors.d.ts +21 -10
- package/dist/index.d.ts +6 -8
- package/dist/logger.d.ts +12 -0
- package/dist/react/ably-ai-transport-react.js +976 -990
- 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 +36 -0
- package/dist/react/contexts/client-session-provider.d.ts +53 -0
- package/dist/react/create-session-hooks.d.ts +116 -0
- package/dist/react/index.d.ts +12 -12
- 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 +82 -51
- package/dist/utils.d.ts +32 -23
- package/dist/vercel/ably-ai-transport-vercel.js +2573 -2086
- 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/decoder.d.ts +5 -18
- package/dist/vercel/codec/encoder.d.ts +6 -36
- package/dist/vercel/codec/events.d.ts +51 -0
- package/dist/vercel/codec/index.d.ts +24 -12
- package/dist/vercel/codec/reducer.d.ts +144 -0
- package/dist/vercel/codec/tool-transitions.d.ts +2 -2
- package/dist/vercel/index.d.ts +4 -5
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +3907 -3266
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +33 -8
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +7 -6
- 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 +29 -0
- package/dist/vercel/transport/chat-transport.d.ts +43 -24
- package/dist/vercel/transport/index.d.ts +25 -21
- package/dist/vercel/transport/run-output-stream.d.ts +56 -0
- package/dist/version.d.ts +2 -0
- package/package.json +30 -23
- package/src/constants.ts +124 -51
- package/src/core/agent.ts +68 -0
- package/src/core/codec/decoder.ts +71 -98
- package/src/core/codec/encoder.ts +113 -65
- package/src/core/codec/index.ts +13 -6
- package/src/core/codec/lifecycle-tracker.ts +10 -9
- package/src/core/codec/types.ts +436 -120
- package/src/core/transport/agent-session.ts +1344 -0
- package/src/core/transport/branch-chain.ts +58 -0
- package/src/core/transport/client-session.ts +775 -0
- package/src/core/transport/decode-fold.ts +91 -0
- package/src/core/transport/headers.ts +181 -22
- package/src/core/transport/index.ts +25 -26
- package/src/core/transport/internal/bounded-map.ts +27 -0
- package/src/core/transport/invocation.ts +98 -0
- package/src/core/transport/load-conversation.ts +355 -0
- package/src/core/transport/load-history.ts +269 -0
- package/src/core/transport/pipe-stream.ts +54 -39
- package/src/core/transport/run-manager.ts +249 -0
- package/src/core/transport/tree.ts +926 -308
- package/src/core/transport/types/agent.ts +407 -0
- package/src/core/transport/types/client.ts +211 -0
- package/src/core/transport/types/shared.ts +27 -0
- package/src/core/transport/types/tree.ts +344 -0
- package/src/core/transport/types/view.ts +259 -0
- package/src/core/transport/types.ts +13 -706
- package/src/core/transport/view.ts +864 -433
- package/src/errors.ts +22 -9
- package/src/event-emitter.ts +3 -2
- package/src/index.ts +52 -41
- package/src/logger.ts +14 -1
- package/src/react/contexts/client-session-context.ts +41 -0
- package/src/react/contexts/client-session-provider.tsx +186 -0
- package/src/react/create-session-hooks.ts +141 -0
- package/src/react/index.ts +23 -13
- 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 +201 -0
- package/src/react/use-create-view.ts +33 -29
- package/src/react/use-tree.ts +61 -30
- package/src/react/use-view.ts +139 -97
- package/src/utils.ts +63 -45
- package/src/vercel/codec/decoder.ts +336 -258
- package/src/vercel/codec/encoder.ts +343 -205
- package/src/vercel/codec/events.ts +87 -0
- package/src/vercel/codec/index.ts +60 -13
- package/src/vercel/codec/reducer.ts +977 -0
- package/src/vercel/codec/tool-transitions.ts +2 -2
- package/src/vercel/index.ts +6 -19
- package/src/vercel/react/contexts/chat-transport-context.ts +7 -6
- 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 +47 -49
- package/src/vercel/react/use-message-sync.ts +80 -39
- package/src/vercel/run-end-reason.ts +78 -0
- package/src/vercel/transport/chat-transport.ts +392 -98
- package/src/vercel/transport/index.ts +39 -38
- package/src/vercel/transport/run-output-stream.ts +170 -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/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/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
|
@@ -1,137 +1,137 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vercel AI SDK
|
|
2
|
+
* Vercel AI SDK encoder.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* operations (publish, appendMessage, updateMessage).
|
|
4
|
+
* Two publish methods enforce direction at the call site:
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* - {@link DefaultUIMessageEncoder.publishInput} encodes a `VercelInput`
|
|
7
|
+
* variant and publishes it on the `ai-input` wire.
|
|
8
|
+
* - {@link DefaultUIMessageEncoder.publishOutput} encodes a `VercelOutput`
|
|
9
|
+
* (`AI.UIMessageChunk`) and publishes it on the `ai-output` wire,
|
|
10
|
+
* driving the underlying stream-tracker for streamed chunks
|
|
11
|
+
* (text / reasoning / tool-input) and falling back to discrete
|
|
12
|
+
* publishes for everything else.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* Each UIMessageChunk maps to exactly one encoder core operation. Domain
|
|
17
|
-
* headers are passed to every operation that accepts them — the core handles
|
|
18
|
-
* merging, persistence, and deduplication:
|
|
19
|
-
*
|
|
20
|
-
* - **`startStream`**: Opens a message stream. Domain headers become
|
|
21
|
-
* "persistent headers" — the core repeats them on every subsequent append.
|
|
22
|
-
* - **`appendStream`**: Appends a text delta. Data only, no headers parameter.
|
|
23
|
-
* The core automatically carries persistent headers from start.
|
|
24
|
-
* - **`closeStream`**: Closes the stream. Pass all domain headers from the
|
|
25
|
-
* chunk — the core merges them on top of persistent headers, so changed
|
|
26
|
-
* values (e.g. updated providerMetadata) are picked up and unchanged
|
|
27
|
-
* values are harmlessly deduplicated.
|
|
28
|
-
* - **`publishDiscrete`**: Publishes a standalone message. All domain headers
|
|
29
|
-
* for the chunk are passed directly.
|
|
14
|
+
* The codec event's own discriminator (`kind` for inputs, `type` for
|
|
15
|
+
* outputs) is carried in the codec tier's `type` header so the
|
|
16
|
+
* decoder can dispatch. Stream-tracker state lives inside the encoder
|
|
17
|
+
* core; only the output direction (text / reasoning / tool-input chunks)
|
|
18
|
+
* drives it — inputs are always published as discrete messages.
|
|
30
19
|
*/
|
|
31
20
|
|
|
32
21
|
import * as Ably from 'ably';
|
|
33
22
|
import type * as AI from 'ai';
|
|
34
23
|
import { isDataUIPart } from 'ai';
|
|
35
24
|
|
|
36
|
-
import { HEADER_STATUS } from '../../constants.js';
|
|
25
|
+
import { EVENT_AI_INPUT, EVENT_AI_OUTPUT, HEADER_ROLE, HEADER_STATUS } from '../../constants.js';
|
|
37
26
|
import type { EncoderCore, EncoderCoreOptions } from '../../core/codec/encoder.js';
|
|
38
27
|
import { createEncoderCore } from '../../core/codec/encoder.js';
|
|
39
|
-
import type {
|
|
28
|
+
import type {
|
|
29
|
+
ChannelWriter,
|
|
30
|
+
Encoder,
|
|
31
|
+
MessagePayload,
|
|
32
|
+
ToolApprovalResponse,
|
|
33
|
+
ToolResult,
|
|
34
|
+
ToolResultError,
|
|
35
|
+
UserMessage,
|
|
36
|
+
WriteOptions,
|
|
37
|
+
} from '../../core/codec/types.js';
|
|
40
38
|
import { ErrorCode, errorInfoIs } from '../../errors.js';
|
|
41
39
|
import { headerWriter } from '../../utils.js';
|
|
40
|
+
import type {
|
|
41
|
+
VercelInput,
|
|
42
|
+
VercelOutput,
|
|
43
|
+
VercelToolApprovalResponsePayload,
|
|
44
|
+
VercelToolResultErrorPayload,
|
|
45
|
+
VercelToolResultPayload,
|
|
46
|
+
} from './events.js';
|
|
42
47
|
|
|
43
48
|
// ---------------------------------------------------------------------------
|
|
44
|
-
//
|
|
49
|
+
// Default implementation
|
|
45
50
|
// ---------------------------------------------------------------------------
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* @param chunk - The UI message chunk to encode.
|
|
52
|
-
* @returns The message payload for publishing to the channel.
|
|
53
|
-
*/
|
|
54
|
-
const buildDiscretePayload = (chunk: AI.UIMessageChunk): MessagePayload => {
|
|
55
|
-
switch (chunk.type) {
|
|
56
|
-
case 'tool-output-available': {
|
|
57
|
-
const h = headerWriter()
|
|
58
|
-
.str('toolCallId', chunk.toolCallId)
|
|
59
|
-
.bool('dynamic', chunk.dynamic)
|
|
60
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
61
|
-
.bool('preliminary', chunk.preliminary)
|
|
62
|
-
.build();
|
|
63
|
-
return { name: 'tool-output-available', data: { output: chunk.output }, headers: h };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
case 'tool-output-error': {
|
|
67
|
-
const h = headerWriter()
|
|
68
|
-
.str('toolCallId', chunk.toolCallId)
|
|
69
|
-
.bool('dynamic', chunk.dynamic)
|
|
70
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
71
|
-
.build();
|
|
72
|
-
return { name: 'tool-output-error', data: { errorText: chunk.errorText }, headers: h };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
case 'tool-approval-request': {
|
|
76
|
-
const h = headerWriter().str('toolCallId', chunk.toolCallId).str('approvalId', chunk.approvalId).build();
|
|
77
|
-
return { name: 'tool-approval-request', data: '', headers: h };
|
|
78
|
-
}
|
|
52
|
+
class DefaultUIMessageEncoder implements Encoder<VercelInput, VercelOutput> {
|
|
53
|
+
private readonly _core: EncoderCore;
|
|
54
|
+
private readonly _messageId: string | undefined;
|
|
55
|
+
private _cancelled = false;
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
57
|
+
constructor(writer: ChannelWriter, options: EncoderCoreOptions = {}) {
|
|
58
|
+
this._core = createEncoderCore(writer, options);
|
|
59
|
+
this._messageId = options.messageId;
|
|
60
|
+
}
|
|
84
61
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
62
|
+
async publishInput(input: VercelInput, options?: WriteOptions): Promise<void> {
|
|
63
|
+
switch (input.kind) {
|
|
64
|
+
case 'user-message': {
|
|
65
|
+
await this._publishUserMessage(input, options);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
case 'regenerate': {
|
|
69
|
+
await this._publishRegenerate(options);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
case 'tool-result': {
|
|
73
|
+
await this._publishToolResult(input, options);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
case 'tool-result-error': {
|
|
77
|
+
await this._publishToolResultError(input, options);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
case 'tool-approval-response': {
|
|
81
|
+
await this._publishToolApprovalResponse(input, options);
|
|
82
|
+
return;
|
|
93
83
|
}
|
|
94
|
-
throw new Ably.ErrorInfo(
|
|
95
|
-
`unable to write event; unsupported chunk type '${chunk.type}'`,
|
|
96
|
-
ErrorCode.InvalidArgument,
|
|
97
|
-
400,
|
|
98
|
-
);
|
|
99
84
|
}
|
|
100
85
|
}
|
|
101
|
-
};
|
|
102
86
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
87
|
+
async publishOutput(output: VercelOutput, options?: WriteOptions): Promise<void> {
|
|
88
|
+
await this._publishChunk(output, options);
|
|
89
|
+
}
|
|
106
90
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
91
|
+
async cancel(reason?: string): Promise<void> {
|
|
92
|
+
if (this._cancelled) return;
|
|
93
|
+
this._cancelled = true;
|
|
94
|
+
await this._core.cancelAllStreams();
|
|
95
|
+
await this._core.publishDiscrete({
|
|
96
|
+
name: EVENT_AI_OUTPUT,
|
|
97
|
+
data: reason ?? '',
|
|
98
|
+
codecHeaders: headerWriter().str('type', 'abort').build(),
|
|
99
|
+
transportHeaders: { [HEADER_STATUS]: 'cancelled' },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
111
102
|
|
|
112
|
-
|
|
113
|
-
this._core
|
|
114
|
-
this._messageId = options.messageId;
|
|
103
|
+
async close(): Promise<void> {
|
|
104
|
+
await this._core.close();
|
|
115
105
|
}
|
|
116
106
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// VercelOutput routing — UIMessageChunk
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
120
110
|
|
|
111
|
+
private async _publishChunk(chunk: AI.UIMessageChunk, perWrite?: WriteOptions): Promise<void> {
|
|
112
|
+
switch (chunk.type) {
|
|
113
|
+
// -- Stream start -----------------------------------------------------
|
|
121
114
|
case 'text-start': {
|
|
122
|
-
const h = headerWriter()
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
const h = headerWriter()
|
|
116
|
+
.str('type', 'text')
|
|
117
|
+
.str('id', chunk.id)
|
|
118
|
+
.json('providerMetadata', chunk.providerMetadata)
|
|
119
|
+
.build();
|
|
120
|
+
await this._core.startStream(chunk.id, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
121
|
+
return;
|
|
125
122
|
}
|
|
126
|
-
|
|
127
123
|
case 'reasoning-start': {
|
|
128
|
-
const h = headerWriter()
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
const h = headerWriter()
|
|
125
|
+
.str('type', 'reasoning')
|
|
126
|
+
.str('id', chunk.id)
|
|
127
|
+
.json('providerMetadata', chunk.providerMetadata)
|
|
128
|
+
.build();
|
|
129
|
+
await this._core.startStream(chunk.id, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
130
|
+
return;
|
|
131
131
|
}
|
|
132
|
-
|
|
133
132
|
case 'tool-input-start': {
|
|
134
133
|
const h = headerWriter()
|
|
134
|
+
.str('type', 'tool-input')
|
|
135
135
|
.str('toolCallId', chunk.toolCallId)
|
|
136
136
|
.str('toolName', chunk.toolName)
|
|
137
137
|
.bool('dynamic', chunk.dynamic)
|
|
@@ -139,57 +139,59 @@ class DefaultUIMessageEncoder implements StreamEncoder<AI.UIMessageChunk, AI.UIM
|
|
|
139
139
|
.bool('providerExecuted', chunk.providerExecuted)
|
|
140
140
|
.json('providerMetadata', chunk.providerMetadata)
|
|
141
141
|
.build();
|
|
142
|
-
await this._core.startStream(chunk.toolCallId, { name:
|
|
143
|
-
|
|
142
|
+
await this._core.startStream(chunk.toolCallId, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
143
|
+
return;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
// -- Stream append
|
|
147
|
-
|
|
146
|
+
// -- Stream append ----------------------------------------------------
|
|
148
147
|
case 'text-delta': {
|
|
149
148
|
this._core.appendStream(chunk.id, chunk.delta);
|
|
150
|
-
|
|
149
|
+
return;
|
|
151
150
|
}
|
|
152
|
-
|
|
153
151
|
case 'reasoning-delta': {
|
|
154
152
|
this._core.appendStream(chunk.id, chunk.delta);
|
|
155
|
-
|
|
153
|
+
return;
|
|
156
154
|
}
|
|
157
|
-
|
|
158
155
|
case 'tool-input-delta': {
|
|
159
156
|
this._core.appendStream(chunk.toolCallId, chunk.inputTextDelta);
|
|
160
|
-
|
|
157
|
+
return;
|
|
161
158
|
}
|
|
162
159
|
|
|
163
|
-
// -- Stream close
|
|
164
|
-
|
|
160
|
+
// -- Stream close -----------------------------------------------------
|
|
165
161
|
case 'text-end': {
|
|
166
|
-
const h = headerWriter()
|
|
167
|
-
|
|
168
|
-
|
|
162
|
+
const h = headerWriter()
|
|
163
|
+
.str('type', 'text')
|
|
164
|
+
.str('id', chunk.id)
|
|
165
|
+
.json('providerMetadata', chunk.providerMetadata)
|
|
166
|
+
.build();
|
|
167
|
+
await this._core.closeStream(chunk.id, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h });
|
|
168
|
+
return;
|
|
169
169
|
}
|
|
170
|
-
|
|
171
170
|
case 'reasoning-end': {
|
|
172
|
-
const h = headerWriter()
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
const h = headerWriter()
|
|
172
|
+
.str('type', 'reasoning')
|
|
173
|
+
.str('id', chunk.id)
|
|
174
|
+
.json('providerMetadata', chunk.providerMetadata)
|
|
175
|
+
.build();
|
|
176
|
+
await this._core.closeStream(chunk.id, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h });
|
|
177
|
+
return;
|
|
175
178
|
}
|
|
176
|
-
|
|
177
179
|
case 'tool-input-available': {
|
|
178
|
-
// If a stream tracker exists, this tool call was streamed — close it.
|
|
179
|
-
// Otherwise it's a non-streaming tool call — publish discrete.
|
|
180
180
|
try {
|
|
181
181
|
const h = headerWriter()
|
|
182
|
+
.str('type', 'tool-input')
|
|
182
183
|
.str('toolCallId', chunk.toolCallId)
|
|
183
184
|
.str('toolName', chunk.toolName)
|
|
184
185
|
.json('providerMetadata', chunk.providerMetadata)
|
|
185
186
|
.build();
|
|
186
|
-
await this._core.closeStream(chunk.toolCallId, { name:
|
|
187
|
+
await this._core.closeStream(chunk.toolCallId, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h });
|
|
187
188
|
} catch (error: unknown) {
|
|
188
|
-
//
|
|
189
|
+
// closeStream raises ErrorCode.InvalidArgument when there is no active stream for this id; fall through to a discrete publish in that case and rethrow any other error.
|
|
189
190
|
if (!(error instanceof Ably.ErrorInfo && errorInfoIs(error, ErrorCode.InvalidArgument))) {
|
|
190
191
|
throw error;
|
|
191
192
|
}
|
|
192
193
|
const h = headerWriter()
|
|
194
|
+
.str('type', 'tool-input')
|
|
193
195
|
.str('toolCallId', chunk.toolCallId)
|
|
194
196
|
.str('toolName', chunk.toolName)
|
|
195
197
|
.bool('dynamic', chunk.dynamic)
|
|
@@ -197,60 +199,69 @@ class DefaultUIMessageEncoder implements StreamEncoder<AI.UIMessageChunk, AI.UIM
|
|
|
197
199
|
.bool('providerExecuted', chunk.providerExecuted)
|
|
198
200
|
.json('providerMetadata', chunk.providerMetadata)
|
|
199
201
|
.build();
|
|
200
|
-
await this._core.publishDiscrete({ name:
|
|
202
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.input, codecHeaders: h });
|
|
201
203
|
}
|
|
202
|
-
|
|
204
|
+
return;
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
// --
|
|
206
|
-
|
|
207
|
+
// -- Lifecycle (discrete) ---------------------------------------------
|
|
207
208
|
case 'start': {
|
|
208
209
|
const h = headerWriter()
|
|
210
|
+
.str('type', 'start')
|
|
209
211
|
.str('messageId', chunk.messageId ?? this._messageId)
|
|
210
212
|
.json('messageMetadata', chunk.messageMetadata)
|
|
211
213
|
.build();
|
|
212
|
-
await this._core.publishDiscrete({ name:
|
|
213
|
-
|
|
214
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
215
|
+
return;
|
|
214
216
|
}
|
|
215
|
-
|
|
216
217
|
case 'start-step': {
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
const h = headerWriter().str('type', 'start-step').build();
|
|
219
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
220
|
+
return;
|
|
219
221
|
}
|
|
220
|
-
|
|
221
222
|
case 'finish-step': {
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
const h = headerWriter().str('type', 'finish-step').build();
|
|
224
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
225
|
+
return;
|
|
224
226
|
}
|
|
225
|
-
|
|
226
227
|
case 'finish': {
|
|
227
228
|
const h = headerWriter()
|
|
229
|
+
.str('type', 'finish')
|
|
228
230
|
.str('finishReason', chunk.finishReason)
|
|
229
231
|
.json('messageMetadata', chunk.messageMetadata)
|
|
230
232
|
.build();
|
|
231
|
-
await this._core.publishDiscrete({ name:
|
|
232
|
-
|
|
233
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
234
|
+
return;
|
|
233
235
|
}
|
|
234
|
-
|
|
235
236
|
case 'error': {
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
const h = headerWriter().str('type', 'error').build();
|
|
238
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.errorText, codecHeaders: h }, perWrite);
|
|
239
|
+
return;
|
|
238
240
|
}
|
|
239
|
-
|
|
240
241
|
case 'abort': {
|
|
241
|
-
this.
|
|
242
|
-
await this._core.
|
|
242
|
+
this._cancelled = true;
|
|
243
|
+
await this._core.cancelAllStreams(perWrite);
|
|
243
244
|
await this._core.publishDiscrete(
|
|
244
|
-
{
|
|
245
|
+
{
|
|
246
|
+
name: EVENT_AI_OUTPUT,
|
|
247
|
+
data: chunk.reason ?? '',
|
|
248
|
+
codecHeaders: headerWriter().str('type', 'abort').build(),
|
|
249
|
+
transportHeaders: { [HEADER_STATUS]: 'cancelled' },
|
|
250
|
+
},
|
|
245
251
|
perWrite,
|
|
246
252
|
);
|
|
247
|
-
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
case 'message-metadata': {
|
|
256
|
+
const h = headerWriter().str('type', 'message-metadata').json('messageMetadata', chunk.messageMetadata).build();
|
|
257
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
258
|
+
return;
|
|
248
259
|
}
|
|
249
260
|
|
|
250
|
-
// --
|
|
251
|
-
|
|
261
|
+
// -- Tool lifecycle (discrete) ----------------------------------------
|
|
252
262
|
case 'tool-input-error': {
|
|
253
263
|
const h = headerWriter()
|
|
264
|
+
.str('type', 'tool-input-error')
|
|
254
265
|
.str('toolCallId', chunk.toolCallId)
|
|
255
266
|
.str('toolName', chunk.toolName)
|
|
256
267
|
.bool('dynamic', chunk.dynamic)
|
|
@@ -258,101 +269,215 @@ class DefaultUIMessageEncoder implements StreamEncoder<AI.UIMessageChunk, AI.UIM
|
|
|
258
269
|
.bool('providerExecuted', chunk.providerExecuted)
|
|
259
270
|
.json('providerMetadata', chunk.providerMetadata)
|
|
260
271
|
.build();
|
|
261
|
-
await this._core.publishDiscrete(
|
|
262
|
-
name:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
break;
|
|
272
|
+
await this._core.publishDiscrete(
|
|
273
|
+
{ name: EVENT_AI_OUTPUT, data: { errorText: chunk.errorText, input: chunk.input }, codecHeaders: h },
|
|
274
|
+
perWrite,
|
|
275
|
+
);
|
|
276
|
+
return;
|
|
267
277
|
}
|
|
268
|
-
|
|
269
278
|
case 'tool-output-available':
|
|
270
279
|
case 'tool-output-error':
|
|
271
280
|
case 'tool-approval-request':
|
|
272
281
|
case 'tool-output-denied': {
|
|
273
|
-
await this._core.publishDiscrete(
|
|
274
|
-
|
|
282
|
+
await this._core.publishDiscrete(buildToolOutputPayload(chunk), perWrite);
|
|
283
|
+
return;
|
|
275
284
|
}
|
|
276
285
|
|
|
277
|
-
// --
|
|
278
|
-
|
|
286
|
+
// -- Content parts (discrete) -----------------------------------------
|
|
279
287
|
case 'file': {
|
|
280
288
|
const h = headerWriter()
|
|
289
|
+
.str('type', 'file')
|
|
281
290
|
.str('mediaType', chunk.mediaType)
|
|
282
291
|
.json('providerMetadata', chunk.providerMetadata)
|
|
283
292
|
.build();
|
|
284
|
-
await this._core.publishDiscrete({ name:
|
|
285
|
-
|
|
293
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.url, codecHeaders: h }, perWrite);
|
|
294
|
+
return;
|
|
286
295
|
}
|
|
287
|
-
|
|
288
296
|
case 'source-url': {
|
|
289
297
|
const h = headerWriter()
|
|
298
|
+
.str('type', 'source-url')
|
|
290
299
|
.str('sourceId', chunk.sourceId)
|
|
291
300
|
.str('title', chunk.title)
|
|
292
301
|
.json('providerMetadata', chunk.providerMetadata)
|
|
293
302
|
.build();
|
|
294
|
-
await this._core.publishDiscrete({ name:
|
|
295
|
-
|
|
303
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.url, codecHeaders: h }, perWrite);
|
|
304
|
+
return;
|
|
296
305
|
}
|
|
297
|
-
|
|
298
306
|
case 'source-document': {
|
|
299
307
|
const h = headerWriter()
|
|
308
|
+
.str('type', 'source-document')
|
|
300
309
|
.str('sourceId', chunk.sourceId)
|
|
301
310
|
.str('mediaType', chunk.mediaType)
|
|
302
311
|
.str('title', chunk.title)
|
|
303
312
|
.str('filename', chunk.filename)
|
|
304
313
|
.json('providerMetadata', chunk.providerMetadata)
|
|
305
314
|
.build();
|
|
306
|
-
await this._core.publishDiscrete({ name:
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
case 'message-metadata': {
|
|
311
|
-
const h = headerWriter().json('messageMetadata', chunk.messageMetadata).build();
|
|
312
|
-
await this._core.publishDiscrete({ name: 'message-metadata', data: '', headers: h }, perWrite);
|
|
313
|
-
break;
|
|
315
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
316
|
+
return;
|
|
314
317
|
}
|
|
315
318
|
|
|
316
|
-
// --
|
|
317
|
-
|
|
319
|
+
// -- data-* (discrete) ------------------------------------------------
|
|
318
320
|
default: {
|
|
319
321
|
if (chunk.type.startsWith('data-')) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
// CAST: data-* chunks always have id, transient, and data fields per AI SDK types.
|
|
323
|
+
// TypeScript can't narrow the template literal union in a default case.
|
|
324
|
+
const dataChunk = chunk;
|
|
325
|
+
const h = headerWriter()
|
|
326
|
+
.str('type', dataChunk.type)
|
|
327
|
+
.str('id', dataChunk.id)
|
|
328
|
+
.bool('transient', dataChunk.transient)
|
|
329
|
+
.build();
|
|
330
|
+
const ephemeral = dataChunk.transient === true;
|
|
331
|
+
await this._core.publishDiscrete(
|
|
332
|
+
{ name: EVENT_AI_OUTPUT, data: dataChunk.data, codecHeaders: h, ephemeral },
|
|
333
|
+
perWrite,
|
|
334
|
+
);
|
|
335
|
+
return;
|
|
323
336
|
}
|
|
324
|
-
|
|
337
|
+
throw new Ably.ErrorInfo(
|
|
338
|
+
`unable to publish output; unsupported chunk type '${chunk.type}'`,
|
|
339
|
+
ErrorCode.InvalidArgument,
|
|
340
|
+
400,
|
|
341
|
+
);
|
|
325
342
|
}
|
|
326
343
|
}
|
|
327
344
|
}
|
|
328
345
|
|
|
329
|
-
|
|
330
|
-
|
|
346
|
+
// -------------------------------------------------------------------------
|
|
347
|
+
// VercelInput routing
|
|
348
|
+
// -------------------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Publish a user-message input as a batch of per-part discrete Ably
|
|
352
|
+
* messages on the `ai-input` wire. Wire format matches the multi-part
|
|
353
|
+
* user-message convention; the receive-side decoder fans the parts back
|
|
354
|
+
* out into a single `UserMessage`.
|
|
355
|
+
* @param input - The user-message input carrying the UIMessage to encode.
|
|
356
|
+
* @param perWrite - Optional per-write overrides.
|
|
357
|
+
*/
|
|
358
|
+
private async _publishUserMessage(input: UserMessage<AI.UIMessage>, perWrite?: WriteOptions): Promise<void> {
|
|
359
|
+
const payloads = encodeMessagePayloads(input.message);
|
|
360
|
+
// Stamp role (a transport header) on every payload so the decoder can
|
|
361
|
+
// reconstruct a `role: 'user'` UIMessage.
|
|
362
|
+
for (const payload of payloads) {
|
|
363
|
+
payload.transportHeaders = { ...payload.transportHeaders, [HEADER_ROLE]: 'user' };
|
|
364
|
+
}
|
|
365
|
+
await this._core.publishDiscreteBatch(payloads, perWrite);
|
|
331
366
|
}
|
|
332
367
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
368
|
+
/**
|
|
369
|
+
* Publish a regenerate input as a discrete `ai-input` Ably message
|
|
370
|
+
* carrying codec `type: 'regenerate'`. The wire carries no domain
|
|
371
|
+
* payload — `parent` / `target` are stamped on the transport headers by
|
|
372
|
+
* the client-session (it reads them off the input directly and builds
|
|
373
|
+
* `buildTransportHeaders`).
|
|
374
|
+
* @param perWrite - Per-write overrides carrying the transport headers built by client-session.
|
|
375
|
+
*/
|
|
376
|
+
private async _publishRegenerate(perWrite?: WriteOptions): Promise<void> {
|
|
377
|
+
const h = headerWriter().str('type', 'regenerate').build();
|
|
378
|
+
await this._core.publishDiscrete({ name: EVENT_AI_INPUT, data: '', codecHeaders: h }, perWrite);
|
|
336
379
|
}
|
|
337
380
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Publish a client-side tool output on the `ai-input` wire. Targets the
|
|
383
|
+
* assistant addressed by `input.codecMessageId`; the wire's
|
|
384
|
+
* `codec-message-id` is stamped via `perWrite.messageId` by the
|
|
385
|
+
* client-session.
|
|
386
|
+
* @param input - The tool-output input.
|
|
387
|
+
* @param perWrite - Per-write overrides carrying the wire codecMessageId.
|
|
388
|
+
*/
|
|
389
|
+
private async _publishToolResult(input: ToolResult<VercelToolResultPayload>, perWrite?: WriteOptions): Promise<void> {
|
|
390
|
+
const h = headerWriter().str('type', 'tool-result').str('toolCallId', input.payload.toolCallId).build();
|
|
391
|
+
await this._core.publishDiscrete(
|
|
392
|
+
{ name: EVENT_AI_INPUT, data: { output: input.payload.output }, codecHeaders: h },
|
|
393
|
+
perWrite,
|
|
394
|
+
);
|
|
347
395
|
}
|
|
348
396
|
|
|
349
|
-
|
|
350
|
-
|
|
397
|
+
/**
|
|
398
|
+
* Publish a client-side tool error on the `ai-input` wire. Targets the
|
|
399
|
+
* assistant addressed by `input.codecMessageId`.
|
|
400
|
+
* @param input - The tool-result-error input.
|
|
401
|
+
* @param perWrite - Per-write overrides.
|
|
402
|
+
*/
|
|
403
|
+
private async _publishToolResultError(
|
|
404
|
+
input: ToolResultError<VercelToolResultErrorPayload>,
|
|
405
|
+
perWrite?: WriteOptions,
|
|
406
|
+
): Promise<void> {
|
|
407
|
+
const h = headerWriter().str('type', 'tool-result-error').str('toolCallId', input.payload.toolCallId).build();
|
|
408
|
+
await this._core.publishDiscrete(
|
|
409
|
+
{ name: EVENT_AI_INPUT, data: { message: input.payload.message }, codecHeaders: h },
|
|
410
|
+
perWrite,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Publish a client-side tool approval response on the `ai-input` wire.
|
|
416
|
+
* Targets the assistant addressed by `input.codecMessageId`.
|
|
417
|
+
* @param input - The approval-response input.
|
|
418
|
+
* @param perWrite - Per-write overrides.
|
|
419
|
+
*/
|
|
420
|
+
private async _publishToolApprovalResponse(
|
|
421
|
+
input: ToolApprovalResponse<VercelToolApprovalResponsePayload>,
|
|
422
|
+
perWrite?: WriteOptions,
|
|
423
|
+
): Promise<void> {
|
|
424
|
+
const h = headerWriter()
|
|
425
|
+
.str('type', 'tool-approval-response')
|
|
426
|
+
.str('toolCallId', input.payload.toolCallId)
|
|
427
|
+
.bool('approved', input.payload.approved)
|
|
428
|
+
.str('reason', input.payload.reason)
|
|
429
|
+
.build();
|
|
430
|
+
await this._core.publishDiscrete({ name: EVENT_AI_INPUT, data: '', codecHeaders: h }, perWrite);
|
|
351
431
|
}
|
|
352
432
|
}
|
|
353
433
|
|
|
354
434
|
// ---------------------------------------------------------------------------
|
|
355
|
-
//
|
|
435
|
+
// Tool output discrete payload builder (agent-side `ai-output` wire)
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
const buildToolOutputPayload = (
|
|
439
|
+
chunk: Extract<
|
|
440
|
+
AI.UIMessageChunk,
|
|
441
|
+
{ type: 'tool-output-available' | 'tool-output-error' | 'tool-approval-request' | 'tool-output-denied' }
|
|
442
|
+
>,
|
|
443
|
+
): MessagePayload => {
|
|
444
|
+
switch (chunk.type) {
|
|
445
|
+
case 'tool-output-available': {
|
|
446
|
+
const h = headerWriter()
|
|
447
|
+
.str('type', 'tool-output-available')
|
|
448
|
+
.str('toolCallId', chunk.toolCallId)
|
|
449
|
+
.bool('dynamic', chunk.dynamic)
|
|
450
|
+
.bool('providerExecuted', chunk.providerExecuted)
|
|
451
|
+
.bool('preliminary', chunk.preliminary)
|
|
452
|
+
.build();
|
|
453
|
+
return { name: EVENT_AI_OUTPUT, data: { output: chunk.output }, codecHeaders: h };
|
|
454
|
+
}
|
|
455
|
+
case 'tool-output-error': {
|
|
456
|
+
const h = headerWriter()
|
|
457
|
+
.str('type', 'tool-output-error')
|
|
458
|
+
.str('toolCallId', chunk.toolCallId)
|
|
459
|
+
.bool('dynamic', chunk.dynamic)
|
|
460
|
+
.bool('providerExecuted', chunk.providerExecuted)
|
|
461
|
+
.build();
|
|
462
|
+
return { name: EVENT_AI_OUTPUT, data: { errorText: chunk.errorText }, codecHeaders: h };
|
|
463
|
+
}
|
|
464
|
+
case 'tool-approval-request': {
|
|
465
|
+
const h = headerWriter()
|
|
466
|
+
.str('type', 'tool-approval-request')
|
|
467
|
+
.str('toolCallId', chunk.toolCallId)
|
|
468
|
+
.str('approvalId', chunk.approvalId)
|
|
469
|
+
.build();
|
|
470
|
+
return { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h };
|
|
471
|
+
}
|
|
472
|
+
case 'tool-output-denied': {
|
|
473
|
+
const h = headerWriter().str('type', 'tool-output-denied').str('toolCallId', chunk.toolCallId).build();
|
|
474
|
+
return { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
// User-message per-part payload encoding
|
|
356
481
|
// ---------------------------------------------------------------------------
|
|
357
482
|
|
|
358
483
|
const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
@@ -362,23 +487,31 @@ const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
|
362
487
|
for (const part of message.parts) {
|
|
363
488
|
switch (part.type) {
|
|
364
489
|
case 'text': {
|
|
365
|
-
payloads.push({
|
|
490
|
+
payloads.push({
|
|
491
|
+
name: EVENT_AI_INPUT,
|
|
492
|
+
data: part.text,
|
|
493
|
+
codecHeaders: headerWriter().str('type', 'text').str('messageId', messageId).build(),
|
|
494
|
+
});
|
|
366
495
|
break;
|
|
367
496
|
}
|
|
368
497
|
case 'file': {
|
|
369
498
|
payloads.push({
|
|
370
|
-
name:
|
|
499
|
+
name: EVENT_AI_INPUT,
|
|
371
500
|
data: part.url,
|
|
372
|
-
|
|
501
|
+
codecHeaders: headerWriter()
|
|
502
|
+
.str('type', 'file')
|
|
503
|
+
.str('messageId', messageId)
|
|
504
|
+
.str('mediaType', part.mediaType)
|
|
505
|
+
.build(),
|
|
373
506
|
});
|
|
374
507
|
break;
|
|
375
508
|
}
|
|
376
509
|
default: {
|
|
377
510
|
if (isDataUIPart(part)) {
|
|
378
511
|
payloads.push({
|
|
379
|
-
name:
|
|
512
|
+
name: EVENT_AI_INPUT,
|
|
380
513
|
data: part.data,
|
|
381
|
-
|
|
514
|
+
codecHeaders: headerWriter().str('type', part.type).str('messageId', messageId).str('id', part.id).build(),
|
|
382
515
|
});
|
|
383
516
|
}
|
|
384
517
|
break;
|
|
@@ -387,7 +520,12 @@ const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
|
387
520
|
}
|
|
388
521
|
|
|
389
522
|
if (payloads.length === 0) {
|
|
390
|
-
|
|
523
|
+
// Always emit at least one part so the decoder can reconstruct the codec-message-id and role from headers, even when the user-message carried no encodable parts.
|
|
524
|
+
payloads.push({
|
|
525
|
+
name: EVENT_AI_INPUT,
|
|
526
|
+
data: '',
|
|
527
|
+
codecHeaders: headerWriter().str('type', 'text').str('messageId', messageId).build(),
|
|
528
|
+
});
|
|
391
529
|
}
|
|
392
530
|
|
|
393
531
|
return payloads;
|
|
@@ -398,13 +536,13 @@ const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
|
398
536
|
// ---------------------------------------------------------------------------
|
|
399
537
|
|
|
400
538
|
/**
|
|
401
|
-
* Create a Vercel AI SDK encoder that maps
|
|
402
|
-
* channel operations via the encoder core.
|
|
539
|
+
* Create a Vercel AI SDK encoder that maps VercelInput / VercelOutput to
|
|
540
|
+
* Ably channel operations via the encoder core.
|
|
403
541
|
* @param writer - The channel writer to publish messages through.
|
|
404
542
|
* @param options - Encoder configuration (clientId, extras, hooks, logger).
|
|
405
|
-
* @returns
|
|
543
|
+
* @returns An {@link Encoder} typed in both directions for the Vercel codec.
|
|
406
544
|
*/
|
|
407
545
|
export const createEncoder = (
|
|
408
546
|
writer: ChannelWriter,
|
|
409
547
|
options: EncoderCoreOptions = {},
|
|
410
|
-
):
|
|
548
|
+
): Encoder<VercelInput, VercelOutput> => new DefaultUIMessageEncoder(writer, options);
|