@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,548 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vercel AI SDK encoder.
|
|
3
|
-
*
|
|
4
|
-
* Two publish methods enforce direction at the call site:
|
|
5
|
-
*
|
|
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.
|
|
13
|
-
*
|
|
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.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import * as Ably from 'ably';
|
|
22
|
-
import type * as AI from 'ai';
|
|
23
|
-
import { isDataUIPart } from 'ai';
|
|
24
|
-
|
|
25
|
-
import { EVENT_AI_INPUT, EVENT_AI_OUTPUT, HEADER_ROLE, HEADER_STATUS } from '../../constants.js';
|
|
26
|
-
import type { EncoderCore, EncoderCoreOptions } from '../../core/codec/encoder.js';
|
|
27
|
-
import { createEncoderCore } from '../../core/codec/encoder.js';
|
|
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';
|
|
38
|
-
import { ErrorCode, errorInfoIs } from '../../errors.js';
|
|
39
|
-
import { headerWriter } from '../../utils.js';
|
|
40
|
-
import type {
|
|
41
|
-
VercelInput,
|
|
42
|
-
VercelOutput,
|
|
43
|
-
VercelToolApprovalResponsePayload,
|
|
44
|
-
VercelToolResultErrorPayload,
|
|
45
|
-
VercelToolResultPayload,
|
|
46
|
-
} from './events.js';
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// Default implementation
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
class DefaultUIMessageEncoder implements Encoder<VercelInput, VercelOutput> {
|
|
53
|
-
private readonly _core: EncoderCore;
|
|
54
|
-
private readonly _messageId: string | undefined;
|
|
55
|
-
private _cancelled = false;
|
|
56
|
-
|
|
57
|
-
constructor(writer: ChannelWriter, options: EncoderCoreOptions = {}) {
|
|
58
|
-
this._core = createEncoderCore(writer, options);
|
|
59
|
-
this._messageId = options.messageId;
|
|
60
|
-
}
|
|
61
|
-
|
|
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
|
-
}
|
|
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 -----------------------------------------------------
|
|
114
|
-
case 'text-start': {
|
|
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;
|
|
122
|
-
}
|
|
123
|
-
case 'reasoning-start': {
|
|
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
|
-
}
|
|
132
|
-
case 'tool-input-start': {
|
|
133
|
-
const h = headerWriter()
|
|
134
|
-
.str('type', 'tool-input')
|
|
135
|
-
.str('toolCallId', chunk.toolCallId)
|
|
136
|
-
.str('toolName', chunk.toolName)
|
|
137
|
-
.bool('dynamic', chunk.dynamic)
|
|
138
|
-
.str('title', chunk.title)
|
|
139
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
140
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
141
|
-
.build();
|
|
142
|
-
await this._core.startStream(chunk.toolCallId, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// -- Stream append ----------------------------------------------------
|
|
147
|
-
case 'text-delta': {
|
|
148
|
-
this._core.appendStream(chunk.id, chunk.delta);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
case 'reasoning-delta': {
|
|
152
|
-
this._core.appendStream(chunk.id, chunk.delta);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
case 'tool-input-delta': {
|
|
156
|
-
this._core.appendStream(chunk.toolCallId, chunk.inputTextDelta);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// -- Stream close -----------------------------------------------------
|
|
161
|
-
case 'text-end': {
|
|
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
|
-
}
|
|
170
|
-
case 'reasoning-end': {
|
|
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;
|
|
178
|
-
}
|
|
179
|
-
case 'tool-input-available': {
|
|
180
|
-
try {
|
|
181
|
-
const h = headerWriter()
|
|
182
|
-
.str('type', 'tool-input')
|
|
183
|
-
.str('toolCallId', chunk.toolCallId)
|
|
184
|
-
.str('toolName', chunk.toolName)
|
|
185
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
186
|
-
.build();
|
|
187
|
-
await this._core.closeStream(chunk.toolCallId, { name: EVENT_AI_OUTPUT, data: '', codecHeaders: h });
|
|
188
|
-
} catch (error: unknown) {
|
|
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.
|
|
190
|
-
if (!(error instanceof Ably.ErrorInfo && errorInfoIs(error, ErrorCode.InvalidArgument))) {
|
|
191
|
-
throw error;
|
|
192
|
-
}
|
|
193
|
-
const h = headerWriter()
|
|
194
|
-
.str('type', 'tool-input')
|
|
195
|
-
.str('toolCallId', chunk.toolCallId)
|
|
196
|
-
.str('toolName', chunk.toolName)
|
|
197
|
-
.bool('dynamic', chunk.dynamic)
|
|
198
|
-
.str('title', chunk.title)
|
|
199
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
200
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
201
|
-
.build();
|
|
202
|
-
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.input, codecHeaders: h });
|
|
203
|
-
}
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// -- Lifecycle (discrete) ---------------------------------------------
|
|
208
|
-
case 'start': {
|
|
209
|
-
const h = headerWriter()
|
|
210
|
-
.str('type', 'start')
|
|
211
|
-
.str('messageId', chunk.messageId ?? this._messageId)
|
|
212
|
-
.json('messageMetadata', chunk.messageMetadata)
|
|
213
|
-
.build();
|
|
214
|
-
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
case 'start-step': {
|
|
218
|
-
const h = headerWriter().str('type', 'start-step').build();
|
|
219
|
-
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
case 'finish-step': {
|
|
223
|
-
const h = headerWriter().str('type', 'finish-step').build();
|
|
224
|
-
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
case 'finish': {
|
|
228
|
-
const h = headerWriter()
|
|
229
|
-
.str('type', 'finish')
|
|
230
|
-
.str('finishReason', chunk.finishReason)
|
|
231
|
-
.json('messageMetadata', chunk.messageMetadata)
|
|
232
|
-
.build();
|
|
233
|
-
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
case 'error': {
|
|
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;
|
|
240
|
-
}
|
|
241
|
-
case 'abort': {
|
|
242
|
-
this._cancelled = true;
|
|
243
|
-
await this._core.cancelAllStreams(perWrite);
|
|
244
|
-
await this._core.publishDiscrete(
|
|
245
|
-
{
|
|
246
|
-
name: EVENT_AI_OUTPUT,
|
|
247
|
-
data: chunk.reason ?? '',
|
|
248
|
-
codecHeaders: headerWriter().str('type', 'abort').build(),
|
|
249
|
-
transportHeaders: { [HEADER_STATUS]: 'cancelled' },
|
|
250
|
-
},
|
|
251
|
-
perWrite,
|
|
252
|
-
);
|
|
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;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// -- Tool lifecycle (discrete) ----------------------------------------
|
|
262
|
-
case 'tool-input-error': {
|
|
263
|
-
const h = headerWriter()
|
|
264
|
-
.str('type', 'tool-input-error')
|
|
265
|
-
.str('toolCallId', chunk.toolCallId)
|
|
266
|
-
.str('toolName', chunk.toolName)
|
|
267
|
-
.bool('dynamic', chunk.dynamic)
|
|
268
|
-
.str('title', chunk.title)
|
|
269
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
270
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
271
|
-
.build();
|
|
272
|
-
await this._core.publishDiscrete(
|
|
273
|
-
{ name: EVENT_AI_OUTPUT, data: { errorText: chunk.errorText, input: chunk.input }, codecHeaders: h },
|
|
274
|
-
perWrite,
|
|
275
|
-
);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
case 'tool-output-available':
|
|
279
|
-
case 'tool-output-error':
|
|
280
|
-
case 'tool-approval-request':
|
|
281
|
-
case 'tool-output-denied': {
|
|
282
|
-
await this._core.publishDiscrete(buildToolOutputPayload(chunk), perWrite);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// -- Content parts (discrete) -----------------------------------------
|
|
287
|
-
case 'file': {
|
|
288
|
-
const h = headerWriter()
|
|
289
|
-
.str('type', 'file')
|
|
290
|
-
.str('mediaType', chunk.mediaType)
|
|
291
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
292
|
-
.build();
|
|
293
|
-
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.url, codecHeaders: h }, perWrite);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
case 'source-url': {
|
|
297
|
-
const h = headerWriter()
|
|
298
|
-
.str('type', 'source-url')
|
|
299
|
-
.str('sourceId', chunk.sourceId)
|
|
300
|
-
.str('title', chunk.title)
|
|
301
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
302
|
-
.build();
|
|
303
|
-
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: chunk.url, codecHeaders: h }, perWrite);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
case 'source-document': {
|
|
307
|
-
const h = headerWriter()
|
|
308
|
-
.str('type', 'source-document')
|
|
309
|
-
.str('sourceId', chunk.sourceId)
|
|
310
|
-
.str('mediaType', chunk.mediaType)
|
|
311
|
-
.str('title', chunk.title)
|
|
312
|
-
.str('filename', chunk.filename)
|
|
313
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
314
|
-
.build();
|
|
315
|
-
await this._core.publishDiscrete({ name: EVENT_AI_OUTPUT, data: '', codecHeaders: h }, perWrite);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// -- data-* (discrete) ------------------------------------------------
|
|
320
|
-
default: {
|
|
321
|
-
if (chunk.type.startsWith('data-')) {
|
|
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;
|
|
336
|
-
}
|
|
337
|
-
throw new Ably.ErrorInfo(
|
|
338
|
-
`unable to publish output; unsupported chunk type '${chunk.type}'`,
|
|
339
|
-
ErrorCode.InvalidArgument,
|
|
340
|
-
400,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
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);
|
|
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 },
|
|
393
|
-
perWrite,
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
|
|
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);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
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
|
-
}
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
// ---------------------------------------------------------------------------
|
|
480
|
-
// User-message per-part payload encoding
|
|
481
|
-
// ---------------------------------------------------------------------------
|
|
482
|
-
|
|
483
|
-
const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
484
|
-
const messageId = message.id;
|
|
485
|
-
const payloads: MessagePayload[] = [];
|
|
486
|
-
|
|
487
|
-
for (const part of message.parts) {
|
|
488
|
-
switch (part.type) {
|
|
489
|
-
case 'text': {
|
|
490
|
-
payloads.push({
|
|
491
|
-
name: EVENT_AI_INPUT,
|
|
492
|
-
data: part.text,
|
|
493
|
-
codecHeaders: headerWriter().str('type', 'text').str('messageId', messageId).build(),
|
|
494
|
-
});
|
|
495
|
-
break;
|
|
496
|
-
}
|
|
497
|
-
case 'file': {
|
|
498
|
-
payloads.push({
|
|
499
|
-
name: EVENT_AI_INPUT,
|
|
500
|
-
data: part.url,
|
|
501
|
-
codecHeaders: headerWriter()
|
|
502
|
-
.str('type', 'file')
|
|
503
|
-
.str('messageId', messageId)
|
|
504
|
-
.str('mediaType', part.mediaType)
|
|
505
|
-
.build(),
|
|
506
|
-
});
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
|
-
default: {
|
|
510
|
-
if (isDataUIPart(part)) {
|
|
511
|
-
payloads.push({
|
|
512
|
-
name: EVENT_AI_INPUT,
|
|
513
|
-
data: part.data,
|
|
514
|
-
codecHeaders: headerWriter().str('type', part.type).str('messageId', messageId).str('id', part.id).build(),
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
break;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (payloads.length === 0) {
|
|
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
|
-
});
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
return payloads;
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
// ---------------------------------------------------------------------------
|
|
535
|
-
// Factory
|
|
536
|
-
// ---------------------------------------------------------------------------
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Create a Vercel AI SDK encoder that maps VercelInput / VercelOutput to
|
|
540
|
-
* Ably channel operations via the encoder core.
|
|
541
|
-
* @param writer - The channel writer to publish messages through.
|
|
542
|
-
* @param options - Encoder configuration (clientId, extras, hooks, logger).
|
|
543
|
-
* @returns An {@link Encoder} typed in both directions for the Vercel codec.
|
|
544
|
-
*/
|
|
545
|
-
export const createEncoder = (
|
|
546
|
-
writer: ChannelWriter,
|
|
547
|
-
options: EncoderCoreOptions = {},
|
|
548
|
-
): Encoder<VercelInput, VercelOutput> => new DefaultUIMessageEncoder(writer, options);
|