@ably/ai-transport 0.0.1 → 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 +114 -116
- package/dist/ably-ai-transport.js +1743 -961
- 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 +117 -39
- 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 +410 -101
- 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 +97 -17
- package/dist/core/transport/index.d.ts +5 -3
- 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 -8
- package/dist/core/transport/run-manager.d.ts +78 -0
- package/dist/core/transport/tree.d.ts +435 -0
- 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 -402
- package/dist/core/transport/view.d.ts +354 -0
- package/dist/errors.d.ts +37 -9
- package/dist/index.d.ts +6 -6
- package/dist/logger.d.ts +12 -0
- package/dist/react/ably-ai-transport-react.js +1164 -645
- 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 +16 -10
- package/dist/react/internal/use-resolved-session.d.ts +36 -0
- package/dist/react/use-ably-messages.d.ts +20 -11
- package/dist/react/use-client-session.d.ts +81 -0
- package/dist/react/use-create-view.d.ts +23 -0
- package/dist/react/use-tree.d.ts +35 -0
- package/dist/react/use-view.d.ts +110 -0
- package/dist/utils.d.ts +32 -23
- package/dist/vercel/ably-ai-transport-vercel.js +2748 -1625
- 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 +50 -0
- package/dist/vercel/index.d.ts +4 -2
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +10298 -1410
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +70 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +33 -0
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +96 -0
- package/dist/vercel/react/index.d.ts +4 -0
- package/dist/vercel/react/use-chat-transport.d.ts +66 -21
- package/dist/vercel/react/use-message-sync.d.ts +31 -12
- package/dist/vercel/run-end-reason.d.ts +29 -0
- package/dist/vercel/transport/chat-transport.d.ts +71 -30
- package/dist/vercel/transport/index.d.ts +25 -18
- package/dist/vercel/transport/run-output-stream.d.ts +56 -0
- package/dist/version.d.ts +2 -0
- package/package.json +47 -34
- package/src/constants.ts +126 -47
- package/src/core/agent.ts +68 -0
- package/src/core/codec/decoder.ts +71 -98
- package/src/core/codec/encoder.ts +115 -58
- package/src/core/codec/index.ts +13 -6
- package/src/core/codec/lifecycle-tracker.ts +10 -9
- package/src/core/codec/types.ts +438 -106
- 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 +182 -19
- package/src/core/transport/index.ts +29 -22
- 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 +58 -40
- package/src/core/transport/run-manager.ts +249 -0
- package/src/core/transport/tree.ts +1167 -0
- 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 -527
- package/src/core/transport/view.ts +1271 -0
- package/src/errors.ts +42 -9
- package/src/event-emitter.ts +3 -2
- package/src/index.ts +55 -39
- 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 +27 -10
- package/src/react/internal/use-resolved-session.ts +63 -0
- package/src/react/use-ably-messages.ts +47 -19
- package/src/react/use-client-session.ts +201 -0
- package/src/react/use-create-view.ts +72 -0
- package/src/react/use-tree.ts +84 -0
- package/src/react/use-view.ts +275 -0
- package/src/react/vite.config.ts +4 -1
- package/src/utils.ts +63 -45
- package/src/vercel/codec/decoder.ts +336 -255
- package/src/vercel/codec/encoder.ts +348 -196
- package/src/vercel/codec/events.ts +87 -0
- package/src/vercel/codec/index.ts +59 -14
- package/src/vercel/codec/reducer.ts +977 -0
- package/src/vercel/codec/tool-transitions.ts +122 -0
- package/src/vercel/index.ts +7 -3
- package/src/vercel/react/contexts/chat-transport-context.ts +41 -0
- package/src/vercel/react/contexts/chat-transport-provider.tsx +150 -0
- package/src/vercel/react/index.ts +13 -1
- package/src/vercel/react/use-chat-transport.ts +162 -42
- package/src/vercel/react/use-message-sync.ts +121 -22
- package/src/vercel/react/vite.config.ts +4 -2
- package/src/vercel/run-end-reason.ts +78 -0
- package/src/vercel/transport/chat-transport.ts +553 -113
- package/src/vercel/transport/index.ts +40 -28
- 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/conversation-tree.d.ts +0 -9
- package/dist/core/transport/decode-history.d.ts +0 -41
- package/dist/core/transport/server-transport.d.ts +0 -7
- package/dist/core/transport/stream-router.d.ts +0 -19
- package/dist/core/transport/turn-manager.d.ts +0 -34
- package/dist/react/use-active-turns.d.ts +0 -8
- package/dist/react/use-client-transport.d.ts +0 -7
- package/dist/react/use-conversation-tree.d.ts +0 -20
- package/dist/react/use-edit.d.ts +0 -7
- package/dist/react/use-history.d.ts +0 -19
- package/dist/react/use-messages.d.ts +0 -7
- package/dist/react/use-regenerate.d.ts +0 -7
- package/dist/react/use-send.d.ts +0 -7
- package/dist/vercel/codec/accumulator.d.ts +0 -21
- package/src/core/transport/client-transport.ts +0 -959
- package/src/core/transport/conversation-tree.ts +0 -434
- package/src/core/transport/decode-history.ts +0 -337
- package/src/core/transport/server-transport.ts +0 -458
- package/src/core/transport/stream-router.ts +0 -118
- package/src/core/transport/turn-manager.ts +0 -147
- package/src/react/use-active-turns.ts +0 -61
- package/src/react/use-client-transport.ts +0 -37
- package/src/react/use-conversation-tree.ts +0 -71
- package/src/react/use-edit.ts +0 -24
- package/src/react/use-history.ts +0 -111
- package/src/react/use-messages.ts +0 -32
- package/src/react/use-regenerate.ts +0 -24
- package/src/react/use-send.ts +0 -25
- package/src/vercel/codec/accumulator.ts +0 -603
|
@@ -1,75 +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
|
-
class DefaultUIMessageEncoder implements
|
|
52
|
+
class DefaultUIMessageEncoder implements Encoder<VercelInput, VercelOutput> {
|
|
48
53
|
private readonly _core: EncoderCore;
|
|
49
|
-
private
|
|
54
|
+
private readonly _messageId: string | undefined;
|
|
55
|
+
private _cancelled = false;
|
|
50
56
|
|
|
51
57
|
constructor(writer: ChannelWriter, options: EncoderCoreOptions = {}) {
|
|
52
58
|
this._core = createEncoderCore(writer, options);
|
|
59
|
+
this._messageId = options.messageId;
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
async
|
|
56
|
-
switch (
|
|
57
|
-
|
|
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;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async publishOutput(output: VercelOutput, options?: WriteOptions): Promise<void> {
|
|
88
|
+
await this._publishChunk(output, options);
|
|
89
|
+
}
|
|
90
|
+
|
|
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
|
+
}
|
|
58
102
|
|
|
103
|
+
async close(): Promise<void> {
|
|
104
|
+
await this._core.close();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// VercelOutput routing — UIMessageChunk
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
private async _publishChunk(chunk: AI.UIMessageChunk, perWrite?: WriteOptions): Promise<void> {
|
|
112
|
+
switch (chunk.type) {
|
|
113
|
+
// -- Stream start -----------------------------------------------------
|
|
59
114
|
case 'text-start': {
|
|
60
|
-
const h = headerWriter()
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
|
63
122
|
}
|
|
64
|
-
|
|
65
123
|
case 'reasoning-start': {
|
|
66
|
-
const h = headerWriter()
|
|
67
|
-
|
|
68
|
-
|
|
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;
|
|
69
131
|
}
|
|
70
|
-
|
|
71
132
|
case 'tool-input-start': {
|
|
72
133
|
const h = headerWriter()
|
|
134
|
+
.str('type', 'tool-input')
|
|
73
135
|
.str('toolCallId', chunk.toolCallId)
|
|
74
136
|
.str('toolName', chunk.toolName)
|
|
75
137
|
.bool('dynamic', chunk.dynamic)
|
|
@@ -77,57 +139,59 @@ class DefaultUIMessageEncoder implements StreamEncoder<AI.UIMessageChunk, AI.UIM
|
|
|
77
139
|
.bool('providerExecuted', chunk.providerExecuted)
|
|
78
140
|
.json('providerMetadata', chunk.providerMetadata)
|
|
79
141
|
.build();
|
|
80
|
-
await this._core.startStream(chunk.toolCallId, { name:
|
|
81
|
-
|
|
142
|
+
await this._core.startStream(chunk.toolCallId, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
143
|
+
return;
|
|
82
144
|
}
|
|
83
145
|
|
|
84
|
-
// -- Stream append
|
|
85
|
-
|
|
146
|
+
// -- Stream append ----------------------------------------------------
|
|
86
147
|
case 'text-delta': {
|
|
87
148
|
this._core.appendStream(chunk.id, chunk.delta);
|
|
88
|
-
|
|
149
|
+
return;
|
|
89
150
|
}
|
|
90
|
-
|
|
91
151
|
case 'reasoning-delta': {
|
|
92
152
|
this._core.appendStream(chunk.id, chunk.delta);
|
|
93
|
-
|
|
153
|
+
return;
|
|
94
154
|
}
|
|
95
|
-
|
|
96
155
|
case 'tool-input-delta': {
|
|
97
156
|
this._core.appendStream(chunk.toolCallId, chunk.inputTextDelta);
|
|
98
|
-
|
|
157
|
+
return;
|
|
99
158
|
}
|
|
100
159
|
|
|
101
|
-
// -- Stream close
|
|
102
|
-
|
|
160
|
+
// -- Stream close -----------------------------------------------------
|
|
103
161
|
case 'text-end': {
|
|
104
|
-
const h = headerWriter()
|
|
105
|
-
|
|
106
|
-
|
|
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;
|
|
107
169
|
}
|
|
108
|
-
|
|
109
170
|
case 'reasoning-end': {
|
|
110
|
-
const h = headerWriter()
|
|
111
|
-
|
|
112
|
-
|
|
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;
|
|
113
178
|
}
|
|
114
|
-
|
|
115
179
|
case 'tool-input-available': {
|
|
116
|
-
// If a stream tracker exists, this tool call was streamed — close it.
|
|
117
|
-
// Otherwise it's a non-streaming tool call — publish discrete.
|
|
118
180
|
try {
|
|
119
181
|
const h = headerWriter()
|
|
182
|
+
.str('type', 'tool-input')
|
|
120
183
|
.str('toolCallId', chunk.toolCallId)
|
|
121
184
|
.str('toolName', chunk.toolName)
|
|
122
185
|
.json('providerMetadata', chunk.providerMetadata)
|
|
123
186
|
.build();
|
|
124
|
-
await this._core.closeStream(chunk.toolCallId, { name:
|
|
187
|
+
await this._core.closeStream(chunk.toolCallId, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h });
|
|
125
188
|
} catch (error: unknown) {
|
|
126
|
-
//
|
|
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.
|
|
127
190
|
if (!(error instanceof Ably.ErrorInfo && errorInfoIs(error, ErrorCode.InvalidArgument))) {
|
|
128
191
|
throw error;
|
|
129
192
|
}
|
|
130
193
|
const h = headerWriter()
|
|
194
|
+
.str('type', 'tool-input')
|
|
131
195
|
.str('toolCallId', chunk.toolCallId)
|
|
132
196
|
.str('toolName', chunk.toolName)
|
|
133
197
|
.bool('dynamic', chunk.dynamic)
|
|
@@ -135,60 +199,69 @@ class DefaultUIMessageEncoder implements StreamEncoder<AI.UIMessageChunk, AI.UIM
|
|
|
135
199
|
.bool('providerExecuted', chunk.providerExecuted)
|
|
136
200
|
.json('providerMetadata', chunk.providerMetadata)
|
|
137
201
|
.build();
|
|
138
|
-
await this._core.publishDiscrete({ name:
|
|
202
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.input, codecHeaders: h });
|
|
139
203
|
}
|
|
140
|
-
|
|
204
|
+
return;
|
|
141
205
|
}
|
|
142
206
|
|
|
143
|
-
// --
|
|
144
|
-
|
|
207
|
+
// -- Lifecycle (discrete) ---------------------------------------------
|
|
145
208
|
case 'start': {
|
|
146
209
|
const h = headerWriter()
|
|
147
|
-
.str('
|
|
210
|
+
.str('type', 'start')
|
|
211
|
+
.str('messageId', chunk.messageId ?? this._messageId)
|
|
148
212
|
.json('messageMetadata', chunk.messageMetadata)
|
|
149
213
|
.build();
|
|
150
|
-
await this._core.publishDiscrete({ name:
|
|
151
|
-
|
|
214
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
215
|
+
return;
|
|
152
216
|
}
|
|
153
|
-
|
|
154
217
|
case 'start-step': {
|
|
155
|
-
|
|
156
|
-
|
|
218
|
+
const h = headerWriter().str('type', 'start-step').build();
|
|
219
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
220
|
+
return;
|
|
157
221
|
}
|
|
158
|
-
|
|
159
222
|
case 'finish-step': {
|
|
160
|
-
|
|
161
|
-
|
|
223
|
+
const h = headerWriter().str('type', 'finish-step').build();
|
|
224
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
225
|
+
return;
|
|
162
226
|
}
|
|
163
|
-
|
|
164
227
|
case 'finish': {
|
|
165
228
|
const h = headerWriter()
|
|
229
|
+
.str('type', 'finish')
|
|
166
230
|
.str('finishReason', chunk.finishReason)
|
|
167
231
|
.json('messageMetadata', chunk.messageMetadata)
|
|
168
232
|
.build();
|
|
169
|
-
await this._core.publishDiscrete({ name:
|
|
170
|
-
|
|
233
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
234
|
+
return;
|
|
171
235
|
}
|
|
172
|
-
|
|
173
236
|
case 'error': {
|
|
174
|
-
|
|
175
|
-
|
|
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;
|
|
176
240
|
}
|
|
177
|
-
|
|
178
241
|
case 'abort': {
|
|
179
|
-
this.
|
|
180
|
-
await this._core.
|
|
242
|
+
this._cancelled = true;
|
|
243
|
+
await this._core.cancelAllStreams(perWrite);
|
|
181
244
|
await this._core.publishDiscrete(
|
|
182
|
-
{
|
|
245
|
+
{
|
|
246
|
+
name: EVENT_AI_OUTPUT,
|
|
247
|
+
data: chunk.reason ?? '',
|
|
248
|
+
codecHeaders: headerWriter().str('type', 'abort').build(),
|
|
249
|
+
transportHeaders: { [HEADER_STATUS]: 'cancelled' },
|
|
250
|
+
},
|
|
183
251
|
perWrite,
|
|
184
252
|
);
|
|
185
|
-
|
|
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;
|
|
186
259
|
}
|
|
187
260
|
|
|
188
|
-
// --
|
|
189
|
-
|
|
261
|
+
// -- Tool lifecycle (discrete) ----------------------------------------
|
|
190
262
|
case 'tool-input-error': {
|
|
191
263
|
const h = headerWriter()
|
|
264
|
+
.str('type', 'tool-input-error')
|
|
192
265
|
.str('toolCallId', chunk.toolCallId)
|
|
193
266
|
.str('toolName', chunk.toolName)
|
|
194
267
|
.bool('dynamic', chunk.dynamic)
|
|
@@ -196,149 +269,215 @@ class DefaultUIMessageEncoder implements StreamEncoder<AI.UIMessageChunk, AI.UIM
|
|
|
196
269
|
.bool('providerExecuted', chunk.providerExecuted)
|
|
197
270
|
.json('providerMetadata', chunk.providerMetadata)
|
|
198
271
|
.build();
|
|
199
|
-
await this._core.publishDiscrete(
|
|
200
|
-
name:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
case 'tool-output-available': {
|
|
208
|
-
const h = headerWriter()
|
|
209
|
-
.str('toolCallId', chunk.toolCallId)
|
|
210
|
-
.bool('dynamic', chunk.dynamic)
|
|
211
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
212
|
-
.bool('preliminary', chunk.preliminary)
|
|
213
|
-
.build();
|
|
214
|
-
await this._core.publishDiscrete({
|
|
215
|
-
name: 'tool-output-available',
|
|
216
|
-
data: { output: chunk.output },
|
|
217
|
-
headers: h,
|
|
218
|
-
});
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
case 'tool-output-error': {
|
|
223
|
-
const h = headerWriter()
|
|
224
|
-
.str('toolCallId', chunk.toolCallId)
|
|
225
|
-
.bool('dynamic', chunk.dynamic)
|
|
226
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
227
|
-
.build();
|
|
228
|
-
await this._core.publishDiscrete({
|
|
229
|
-
name: 'tool-output-error',
|
|
230
|
-
data: { errorText: chunk.errorText },
|
|
231
|
-
headers: h,
|
|
232
|
-
});
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
case 'tool-approval-request': {
|
|
237
|
-
const h = headerWriter().str('toolCallId', chunk.toolCallId).str('approvalId', chunk.approvalId).build();
|
|
238
|
-
await this._core.publishDiscrete({ name: 'tool-approval-request', data: '', headers: h }, perWrite);
|
|
239
|
-
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;
|
|
240
277
|
}
|
|
241
|
-
|
|
278
|
+
case 'tool-output-available':
|
|
279
|
+
case 'tool-output-error':
|
|
280
|
+
case 'tool-approval-request':
|
|
242
281
|
case 'tool-output-denied': {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
break;
|
|
282
|
+
await this._core.publishDiscrete(buildToolOutputPayload(chunk), perWrite);
|
|
283
|
+
return;
|
|
246
284
|
}
|
|
247
285
|
|
|
248
|
-
// --
|
|
249
|
-
|
|
286
|
+
// -- Content parts (discrete) -----------------------------------------
|
|
250
287
|
case 'file': {
|
|
251
288
|
const h = headerWriter()
|
|
289
|
+
.str('type', 'file')
|
|
252
290
|
.str('mediaType', chunk.mediaType)
|
|
253
291
|
.json('providerMetadata', chunk.providerMetadata)
|
|
254
292
|
.build();
|
|
255
|
-
await this._core.publishDiscrete({ name:
|
|
256
|
-
|
|
293
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.url, codecHeaders: h }, perWrite);
|
|
294
|
+
return;
|
|
257
295
|
}
|
|
258
|
-
|
|
259
296
|
case 'source-url': {
|
|
260
297
|
const h = headerWriter()
|
|
298
|
+
.str('type', 'source-url')
|
|
261
299
|
.str('sourceId', chunk.sourceId)
|
|
262
300
|
.str('title', chunk.title)
|
|
263
301
|
.json('providerMetadata', chunk.providerMetadata)
|
|
264
302
|
.build();
|
|
265
|
-
await this._core.publishDiscrete({ name:
|
|
266
|
-
|
|
303
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.url, codecHeaders: h }, perWrite);
|
|
304
|
+
return;
|
|
267
305
|
}
|
|
268
|
-
|
|
269
306
|
case 'source-document': {
|
|
270
307
|
const h = headerWriter()
|
|
308
|
+
.str('type', 'source-document')
|
|
271
309
|
.str('sourceId', chunk.sourceId)
|
|
272
310
|
.str('mediaType', chunk.mediaType)
|
|
273
311
|
.str('title', chunk.title)
|
|
274
312
|
.str('filename', chunk.filename)
|
|
275
313
|
.json('providerMetadata', chunk.providerMetadata)
|
|
276
314
|
.build();
|
|
277
|
-
await this._core.publishDiscrete({ name:
|
|
278
|
-
|
|
315
|
+
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
316
|
+
return;
|
|
279
317
|
}
|
|
280
318
|
|
|
281
|
-
|
|
282
|
-
const h = headerWriter().json('messageMetadata', chunk.messageMetadata).build();
|
|
283
|
-
await this._core.publishDiscrete({ name: 'message-metadata', data: '', headers: h }, perWrite);
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// -- Discrete: data-* custom chunks -----------------------------------
|
|
288
|
-
|
|
319
|
+
// -- data-* (discrete) ------------------------------------------------
|
|
289
320
|
default: {
|
|
290
321
|
if (chunk.type.startsWith('data-')) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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;
|
|
294
336
|
}
|
|
295
|
-
|
|
337
|
+
throw new Ably.ErrorInfo(
|
|
338
|
+
`unable to publish output; unsupported chunk type '${chunk.type}'`,
|
|
339
|
+
ErrorCode.InvalidArgument,
|
|
340
|
+
400,
|
|
341
|
+
);
|
|
296
342
|
}
|
|
297
343
|
}
|
|
298
344
|
}
|
|
299
345
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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' };
|
|
307
364
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
365
|
+
await this._core.publishDiscreteBatch(payloads, perWrite);
|
|
366
|
+
}
|
|
367
|
+
|
|
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);
|
|
379
|
+
}
|
|
380
|
+
|
|
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 },
|
|
315
393
|
perWrite,
|
|
316
394
|
);
|
|
317
395
|
}
|
|
318
396
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
+
);
|
|
322
412
|
}
|
|
323
413
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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);
|
|
333
431
|
}
|
|
432
|
+
}
|
|
334
433
|
|
|
335
|
-
|
|
336
|
-
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
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
|
+
}
|
|
337
476
|
}
|
|
338
|
-
}
|
|
477
|
+
};
|
|
339
478
|
|
|
340
479
|
// ---------------------------------------------------------------------------
|
|
341
|
-
//
|
|
480
|
+
// User-message per-part payload encoding
|
|
342
481
|
// ---------------------------------------------------------------------------
|
|
343
482
|
|
|
344
483
|
const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
@@ -348,23 +487,31 @@ const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
|
348
487
|
for (const part of message.parts) {
|
|
349
488
|
switch (part.type) {
|
|
350
489
|
case 'text': {
|
|
351
|
-
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
|
+
});
|
|
352
495
|
break;
|
|
353
496
|
}
|
|
354
497
|
case 'file': {
|
|
355
498
|
payloads.push({
|
|
356
|
-
name:
|
|
499
|
+
name: EVENT_AI_INPUT,
|
|
357
500
|
data: part.url,
|
|
358
|
-
|
|
501
|
+
codecHeaders: headerWriter()
|
|
502
|
+
.str('type', 'file')
|
|
503
|
+
.str('messageId', messageId)
|
|
504
|
+
.str('mediaType', part.mediaType)
|
|
505
|
+
.build(),
|
|
359
506
|
});
|
|
360
507
|
break;
|
|
361
508
|
}
|
|
362
509
|
default: {
|
|
363
510
|
if (isDataUIPart(part)) {
|
|
364
511
|
payloads.push({
|
|
365
|
-
name:
|
|
512
|
+
name: EVENT_AI_INPUT,
|
|
366
513
|
data: part.data,
|
|
367
|
-
|
|
514
|
+
codecHeaders: headerWriter().str('type', part.type).str('messageId', messageId).str('id', part.id).build(),
|
|
368
515
|
});
|
|
369
516
|
}
|
|
370
517
|
break;
|
|
@@ -373,7 +520,12 @@ const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
|
373
520
|
}
|
|
374
521
|
|
|
375
522
|
if (payloads.length === 0) {
|
|
376
|
-
|
|
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
|
+
});
|
|
377
529
|
}
|
|
378
530
|
|
|
379
531
|
return payloads;
|
|
@@ -384,13 +536,13 @@ const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
|
384
536
|
// ---------------------------------------------------------------------------
|
|
385
537
|
|
|
386
538
|
/**
|
|
387
|
-
* Create a Vercel AI SDK encoder that maps
|
|
388
|
-
* 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.
|
|
389
541
|
* @param writer - The channel writer to publish messages through.
|
|
390
542
|
* @param options - Encoder configuration (clientId, extras, hooks, logger).
|
|
391
|
-
* @returns
|
|
543
|
+
* @returns An {@link Encoder} typed in both directions for the Vercel codec.
|
|
392
544
|
*/
|
|
393
545
|
export const createEncoder = (
|
|
394
546
|
writer: ChannelWriter,
|
|
395
547
|
options: EncoderCoreOptions = {},
|
|
396
|
-
):
|
|
548
|
+
): Encoder<VercelInput, VercelOutput> => new DefaultUIMessageEncoder(writer, options);
|