@ably/ai-transport 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -111
- package/dist/ably-ai-transport.js +2401 -1387
- package/dist/ably-ai-transport.js.map +1 -1
- package/dist/ably-ai-transport.umd.cjs +1 -1
- package/dist/ably-ai-transport.umd.cjs.map +1 -1
- package/dist/constants.d.ts +116 -42
- package/dist/core/agent.d.ts +44 -0
- package/dist/core/channel-options.d.ts +57 -0
- package/dist/core/codec/codec-event.d.ts +9 -0
- package/dist/core/codec/decoder.d.ts +24 -24
- package/dist/core/codec/define-codec.d.ts +100 -0
- package/dist/core/codec/encoder.d.ts +10 -12
- package/dist/core/codec/field-bag.d.ts +85 -0
- package/dist/core/codec/fields.d.ts +141 -0
- package/dist/core/codec/index.d.ts +8 -2
- package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
- package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
- package/dist/core/codec/input-descriptors.d.ts +281 -0
- package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
- package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
- package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
- package/dist/core/codec/output-descriptors.d.ts +237 -0
- package/dist/core/codec/types.d.ts +470 -119
- package/dist/core/codec/well-known-inputs.d.ts +52 -0
- package/dist/core/transport/agent-session.d.ts +10 -0
- package/dist/core/transport/agent-view.d.ts +296 -0
- package/dist/core/transport/client-session.d.ts +13 -0
- package/dist/core/transport/decode-fold.d.ts +55 -0
- package/dist/core/transport/headers.d.ts +121 -14
- package/dist/core/transport/index.d.ts +5 -6
- package/dist/core/transport/internal/bounded-map.d.ts +20 -0
- package/dist/core/transport/invocation.d.ts +74 -0
- package/dist/core/transport/load-history-pages.d.ts +71 -0
- package/dist/core/transport/load-history.d.ts +44 -0
- package/dist/core/transport/pipe-stream.d.ts +9 -9
- package/dist/core/transport/run-manager.d.ts +76 -0
- package/dist/core/transport/session-support.d.ts +55 -0
- package/dist/core/transport/tree.d.ts +523 -109
- package/dist/core/transport/types/agent.d.ts +375 -0
- package/dist/core/transport/types/client.d.ts +201 -0
- package/dist/core/transport/types/shared.d.ts +24 -0
- package/dist/core/transport/types/tree.d.ts +357 -0
- package/dist/core/transport/types/view.d.ts +249 -0
- package/dist/core/transport/types.d.ts +13 -553
- package/dist/core/transport/view.d.ts +390 -84
- package/dist/core/transport/wire-log.d.ts +102 -0
- package/dist/errors.d.ts +27 -10
- package/dist/index.d.ts +8 -9
- package/dist/logger.d.ts +12 -0
- package/dist/react/ably-ai-transport-react.js +1365 -1010
- package/dist/react/ably-ai-transport-react.js.map +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
- package/dist/react/contexts/client-session-context.d.ts +37 -0
- package/dist/react/contexts/client-session-provider.d.ts +56 -0
- package/dist/react/create-session-hooks.d.ts +116 -0
- package/dist/react/index.d.ts +13 -12
- package/dist/react/internal/skipped-session.d.ts +8 -0
- package/dist/react/internal/use-resolved-session.d.ts +36 -0
- package/dist/react/use-ably-messages.d.ts +17 -14
- package/dist/react/use-client-session.d.ts +81 -0
- package/dist/react/use-create-view.d.ts +14 -13
- package/dist/react/use-tree.d.ts +30 -15
- package/dist/react/use-view.d.ts +81 -50
- package/dist/utils.d.ts +48 -71
- package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
- package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
- package/dist/vercel/codec/events.d.ts +50 -0
- package/dist/vercel/codec/fields.d.ts +44 -0
- package/dist/vercel/codec/fold-content.d.ts +16 -0
- package/dist/vercel/codec/fold-data.d.ts +16 -0
- package/dist/vercel/codec/fold-input.d.ts +67 -0
- package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
- package/dist/vercel/codec/fold-text.d.ts +16 -0
- package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
- package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
- package/dist/vercel/codec/index.d.ts +7 -20
- package/dist/vercel/codec/inputs.d.ts +11 -0
- package/dist/vercel/codec/outputs.d.ts +11 -0
- package/dist/vercel/codec/reducer-state.d.ts +121 -0
- package/dist/vercel/codec/reducer.d.ts +62 -0
- package/dist/vercel/codec/tool-transitions.d.ts +2 -8
- package/dist/vercel/codec/wire-data.d.ts +34 -0
- package/dist/vercel/index.d.ts +5 -5
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
- package/dist/vercel/react/index.d.ts +1 -2
- package/dist/vercel/react/use-chat-transport.d.ts +30 -26
- package/dist/vercel/react/use-message-sync.d.ts +17 -30
- package/dist/vercel/run-end-reason.d.ts +84 -0
- package/dist/vercel/tool-part.d.ts +21 -0
- package/dist/vercel/transport/chat-transport.d.ts +41 -24
- package/dist/vercel/transport/index.d.ts +24 -20
- package/dist/vercel/transport/run-output-stream.d.ts +54 -0
- package/dist/version.d.ts +2 -0
- package/package.json +31 -24
- package/src/constants.ts +124 -51
- package/src/core/agent.ts +92 -0
- package/src/core/channel-options.ts +89 -0
- package/src/core/codec/codec-event.ts +27 -0
- package/src/core/codec/decoder.ts +202 -105
- package/src/core/codec/define-codec.ts +432 -0
- package/src/core/codec/encoder.ts +114 -107
- package/src/core/codec/field-bag.ts +142 -0
- package/src/core/codec/fields.ts +193 -0
- package/src/core/codec/index.ts +56 -6
- package/src/core/codec/input-descriptor-decoder.ts +97 -0
- package/src/core/codec/input-descriptor-encoder.ts +150 -0
- package/src/core/codec/input-descriptors.ts +373 -0
- package/src/core/codec/lifecycle-tracker.ts +10 -9
- package/src/core/codec/output-descriptor-decoder.ts +139 -0
- package/src/core/codec/output-descriptor-encoder.ts +101 -0
- package/src/core/codec/output-descriptors.ts +307 -0
- package/src/core/codec/types.ts +505 -126
- package/src/core/codec/well-known-inputs.ts +96 -0
- package/src/core/transport/agent-session.ts +1085 -0
- package/src/core/transport/agent-view.ts +738 -0
- package/src/core/transport/client-session.ts +780 -0
- package/src/core/transport/decode-fold.ts +101 -0
- package/src/core/transport/headers.ts +234 -22
- package/src/core/transport/index.ts +27 -27
- package/src/core/transport/internal/bounded-map.ts +27 -0
- package/src/core/transport/invocation.ts +98 -0
- package/src/core/transport/load-history-pages.ts +220 -0
- package/src/core/transport/load-history.ts +271 -0
- package/src/core/transport/pipe-stream.ts +63 -39
- package/src/core/transport/run-manager.ts +243 -0
- package/src/core/transport/session-support.ts +96 -0
- package/src/core/transport/tree.ts +1293 -308
- package/src/core/transport/types/agent.ts +434 -0
- package/src/core/transport/types/client.ts +247 -0
- package/src/core/transport/types/shared.ts +27 -0
- package/src/core/transport/types/tree.ts +393 -0
- package/src/core/transport/types/view.ts +288 -0
- package/src/core/transport/types.ts +13 -706
- package/src/core/transport/view.ts +1229 -450
- package/src/core/transport/wire-log.ts +189 -0
- package/src/errors.ts +29 -9
- package/src/event-emitter.ts +3 -2
- package/src/index.ts +86 -42
- package/src/logger.ts +14 -1
- package/src/react/contexts/client-session-context.ts +41 -0
- package/src/react/contexts/client-session-provider.tsx +222 -0
- package/src/react/create-session-hooks.ts +141 -0
- package/src/react/index.ts +24 -13
- package/src/react/internal/skipped-session.ts +62 -0
- package/src/react/internal/use-resolved-session.ts +63 -0
- package/src/react/use-ably-messages.ts +32 -22
- package/src/react/use-client-session.ts +178 -0
- package/src/react/use-create-view.ts +33 -29
- package/src/react/use-tree.ts +61 -30
- package/src/react/use-view.ts +138 -96
- package/src/utils.ts +83 -131
- package/src/vercel/codec/decode-lifecycle.ts +70 -0
- package/src/vercel/codec/events.ts +85 -0
- package/src/vercel/codec/fields.ts +58 -0
- package/src/vercel/codec/fold-content.ts +54 -0
- package/src/vercel/codec/fold-data.ts +46 -0
- package/src/vercel/codec/fold-input.ts +255 -0
- package/src/vercel/codec/fold-lifecycle.ts +85 -0
- package/src/vercel/codec/fold-text.ts +55 -0
- package/src/vercel/codec/fold-tool-input.ts +86 -0
- package/src/vercel/codec/fold-tool-output.ts +79 -0
- package/src/vercel/codec/index.ts +28 -21
- package/src/vercel/codec/inputs.ts +116 -0
- package/src/vercel/codec/outputs.ts +207 -0
- package/src/vercel/codec/reducer-state.ts +169 -0
- package/src/vercel/codec/reducer.ts +191 -0
- package/src/vercel/codec/tool-transitions.ts +3 -14
- package/src/vercel/codec/wire-data.ts +64 -0
- package/src/vercel/index.ts +7 -19
- package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
- package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
- package/src/vercel/react/index.ts +3 -5
- package/src/vercel/react/use-chat-transport.ts +44 -66
- package/src/vercel/react/use-message-sync.ts +75 -39
- package/src/vercel/run-end-reason.ts +157 -0
- package/src/vercel/tool-part.ts +25 -0
- package/src/vercel/transport/chat-transport.ts +380 -98
- package/src/vercel/transport/index.ts +38 -37
- package/src/vercel/transport/run-output-stream.ts +169 -0
- package/src/version.ts +2 -0
- package/dist/core/transport/client-transport.d.ts +0 -10
- package/dist/core/transport/decode-history.d.ts +0 -43
- package/dist/core/transport/server-transport.d.ts +0 -7
- package/dist/core/transport/stream-router.d.ts +0 -29
- package/dist/core/transport/turn-manager.d.ts +0 -37
- package/dist/react/contexts/transport-context.d.ts +0 -31
- package/dist/react/contexts/transport-provider.d.ts +0 -49
- package/dist/react/create-transport-hooks.d.ts +0 -124
- package/dist/react/use-active-turns.d.ts +0 -12
- package/dist/react/use-client-transport.d.ts +0 -80
- package/dist/vercel/codec/accumulator.d.ts +0 -21
- package/dist/vercel/codec/decoder.d.ts +0 -22
- package/dist/vercel/codec/encoder.d.ts +0 -41
- package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
- package/dist/vercel/tool-approvals.d.ts +0 -124
- package/dist/vercel/tool-events.d.ts +0 -26
- package/src/core/transport/client-transport.ts +0 -977
- package/src/core/transport/decode-history.ts +0 -485
- package/src/core/transport/server-transport.ts +0 -612
- package/src/core/transport/stream-router.ts +0 -136
- package/src/core/transport/turn-manager.ts +0 -165
- package/src/react/contexts/transport-context.ts +0 -37
- package/src/react/contexts/transport-provider.tsx +0 -164
- package/src/react/create-transport-hooks.ts +0 -144
- package/src/react/use-active-turns.ts +0 -72
- package/src/react/use-client-transport.ts +0 -197
- package/src/vercel/codec/accumulator.ts +0 -588
- package/src/vercel/codec/decoder.ts +0 -618
- package/src/vercel/codec/encoder.ts +0 -410
- package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
- package/src/vercel/tool-approvals.ts +0 -380
- package/src/vercel/tool-events.ts +0 -53
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vercel AI SDK Encoder
|
|
3
|
-
*
|
|
4
|
-
* Maps UIMessageChunk events and complete UIMessage objects to Ably channel
|
|
5
|
-
* operations (publish, appendMessage, updateMessage).
|
|
6
|
-
*
|
|
7
|
-
* Delegates the message append lifecycle (publish, append, close, abort,
|
|
8
|
-
* flush/recover) to the encoder core. This file contains only the
|
|
9
|
-
* Vercel-specific event-to-operation mapping.
|
|
10
|
-
*
|
|
11
|
-
* Domain-specific headers use the `x-domain-` prefix to distinguish them
|
|
12
|
-
* from transport-level `x-ably-` headers.
|
|
13
|
-
*
|
|
14
|
-
* ## Core operations and domain headers
|
|
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.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
import * as Ably from 'ably';
|
|
33
|
-
import type * as AI from 'ai';
|
|
34
|
-
import { isDataUIPart } from 'ai';
|
|
35
|
-
|
|
36
|
-
import { HEADER_STATUS } from '../../constants.js';
|
|
37
|
-
import type { EncoderCore, EncoderCoreOptions } from '../../core/codec/encoder.js';
|
|
38
|
-
import { createEncoderCore } from '../../core/codec/encoder.js';
|
|
39
|
-
import type { ChannelWriter, MessagePayload, StreamEncoder, WriteOptions } from '../../core/codec/types.js';
|
|
40
|
-
import { ErrorCode, errorInfoIs } from '../../errors.js';
|
|
41
|
-
import { headerWriter } from '../../utils.js';
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// Discrete event payload builder
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Build a MessagePayload for discrete (non-streaming) event types.
|
|
49
|
-
* Used by both `writeEvent` and `appendEvent` for tool output events,
|
|
50
|
-
* content parts, and data-* custom chunks.
|
|
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
|
-
}
|
|
79
|
-
|
|
80
|
-
case 'tool-output-denied': {
|
|
81
|
-
const h = headerWriter().str('toolCallId', chunk.toolCallId).build();
|
|
82
|
-
return { name: 'tool-output-denied', data: '', headers: h };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
default: {
|
|
86
|
-
if (chunk.type.startsWith('data-')) {
|
|
87
|
-
// CAST: data-* chunks always have id, transient, and data fields per AI SDK types.
|
|
88
|
-
// TypeScript can't narrow the template literal union in a default case.
|
|
89
|
-
const dataChunk = chunk as Extract<AI.UIMessageChunk, { type: `data-${string}` }>;
|
|
90
|
-
const h = headerWriter().str('id', dataChunk.id).bool('transient', dataChunk.transient).build();
|
|
91
|
-
const ephemeral = dataChunk.transient === true;
|
|
92
|
-
return { name: chunk.type, data: dataChunk.data, headers: h, ephemeral };
|
|
93
|
-
}
|
|
94
|
-
throw new Ably.ErrorInfo(
|
|
95
|
-
`unable to write event; unsupported chunk type '${chunk.type}'`,
|
|
96
|
-
ErrorCode.InvalidArgument,
|
|
97
|
-
400,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
// Default implementation
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
|
-
|
|
107
|
-
class DefaultUIMessageEncoder implements StreamEncoder<AI.UIMessageChunk, AI.UIMessage> {
|
|
108
|
-
private readonly _core: EncoderCore;
|
|
109
|
-
private readonly _messageId: string | undefined;
|
|
110
|
-
private _aborted = false;
|
|
111
|
-
|
|
112
|
-
constructor(writer: ChannelWriter, options: EncoderCoreOptions = {}) {
|
|
113
|
-
this._core = createEncoderCore(writer, options);
|
|
114
|
-
this._messageId = options.messageId;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async appendEvent(chunk: AI.UIMessageChunk, perWrite?: WriteOptions): Promise<void> {
|
|
118
|
-
switch (chunk.type) {
|
|
119
|
-
// -- Stream start: open a message stream with persistent headers -------
|
|
120
|
-
|
|
121
|
-
case 'text-start': {
|
|
122
|
-
const h = headerWriter().str('id', chunk.id).json('providerMetadata', chunk.providerMetadata).build();
|
|
123
|
-
await this._core.startStream(chunk.id, { name: 'text', data: '', headers: h }, perWrite);
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
case 'reasoning-start': {
|
|
128
|
-
const h = headerWriter().str('id', chunk.id).json('providerMetadata', chunk.providerMetadata).build();
|
|
129
|
-
await this._core.startStream(chunk.id, { name: 'reasoning', data: '', headers: h }, perWrite);
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
case 'tool-input-start': {
|
|
134
|
-
const h = headerWriter()
|
|
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: 'tool-input', data: '', headers: h }, perWrite);
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// -- Stream append: data only, core carries persistent headers --------
|
|
147
|
-
|
|
148
|
-
case 'text-delta': {
|
|
149
|
-
this._core.appendStream(chunk.id, chunk.delta);
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
case 'reasoning-delta': {
|
|
154
|
-
this._core.appendStream(chunk.id, chunk.delta);
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
case 'tool-input-delta': {
|
|
159
|
-
this._core.appendStream(chunk.toolCallId, chunk.inputTextDelta);
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// -- Stream close: pass all chunk headers, core merges with persistent
|
|
164
|
-
|
|
165
|
-
case 'text-end': {
|
|
166
|
-
const h = headerWriter().str('id', chunk.id).json('providerMetadata', chunk.providerMetadata).build();
|
|
167
|
-
await this._core.closeStream(chunk.id, { name: 'text', data: '', headers: h });
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
case 'reasoning-end': {
|
|
172
|
-
const h = headerWriter().str('id', chunk.id).json('providerMetadata', chunk.providerMetadata).build();
|
|
173
|
-
await this._core.closeStream(chunk.id, { name: 'reasoning', data: '', headers: h });
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
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
|
-
try {
|
|
181
|
-
const h = headerWriter()
|
|
182
|
-
.str('toolCallId', chunk.toolCallId)
|
|
183
|
-
.str('toolName', chunk.toolName)
|
|
184
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
185
|
-
.build();
|
|
186
|
-
await this._core.closeStream(chunk.toolCallId, { name: 'tool-input', data: '', headers: h });
|
|
187
|
-
} catch (error: unknown) {
|
|
188
|
-
// Only fall through to discrete for "no active stream" — rethrow real failures
|
|
189
|
-
if (!(error instanceof Ably.ErrorInfo && errorInfoIs(error, ErrorCode.InvalidArgument))) {
|
|
190
|
-
throw error;
|
|
191
|
-
}
|
|
192
|
-
const h = headerWriter()
|
|
193
|
-
.str('toolCallId', chunk.toolCallId)
|
|
194
|
-
.str('toolName', chunk.toolName)
|
|
195
|
-
.bool('dynamic', chunk.dynamic)
|
|
196
|
-
.str('title', chunk.title)
|
|
197
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
198
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
199
|
-
.build();
|
|
200
|
-
await this._core.publishDiscrete({ name: 'tool-input', data: chunk.input, headers: h });
|
|
201
|
-
}
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// -- Discrete: lifecycle events ---------------------------------------
|
|
206
|
-
|
|
207
|
-
case 'start': {
|
|
208
|
-
const h = headerWriter()
|
|
209
|
-
.str('messageId', chunk.messageId ?? this._messageId)
|
|
210
|
-
.json('messageMetadata', chunk.messageMetadata)
|
|
211
|
-
.build();
|
|
212
|
-
await this._core.publishDiscrete({ name: 'start', data: '', headers: h }, perWrite);
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
case 'start-step': {
|
|
217
|
-
await this._core.publishDiscrete({ name: 'start-step', data: '' }, perWrite);
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
case 'finish-step': {
|
|
222
|
-
await this._core.publishDiscrete({ name: 'finish-step', data: '' }, perWrite);
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
case 'finish': {
|
|
227
|
-
const h = headerWriter()
|
|
228
|
-
.str('finishReason', chunk.finishReason)
|
|
229
|
-
.json('messageMetadata', chunk.messageMetadata)
|
|
230
|
-
.build();
|
|
231
|
-
await this._core.publishDiscrete({ name: 'finish', data: '', headers: h }, perWrite);
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
case 'error': {
|
|
236
|
-
await this._core.publishDiscrete({ name: 'error', data: chunk.errorText }, perWrite);
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
case 'abort': {
|
|
241
|
-
this._aborted = true;
|
|
242
|
-
await this._core.abortAllStreams(perWrite);
|
|
243
|
-
await this._core.publishDiscrete(
|
|
244
|
-
{ name: 'abort', data: chunk.reason ?? '', headers: { [HEADER_STATUS]: 'aborted' } },
|
|
245
|
-
perWrite,
|
|
246
|
-
);
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// -- Discrete: tool lifecycle events ----------------------------------
|
|
251
|
-
|
|
252
|
-
case 'tool-input-error': {
|
|
253
|
-
const h = headerWriter()
|
|
254
|
-
.str('toolCallId', chunk.toolCallId)
|
|
255
|
-
.str('toolName', chunk.toolName)
|
|
256
|
-
.bool('dynamic', chunk.dynamic)
|
|
257
|
-
.str('title', chunk.title)
|
|
258
|
-
.bool('providerExecuted', chunk.providerExecuted)
|
|
259
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
260
|
-
.build();
|
|
261
|
-
await this._core.publishDiscrete({
|
|
262
|
-
name: 'tool-input-error',
|
|
263
|
-
data: { errorText: chunk.errorText, input: chunk.input },
|
|
264
|
-
headers: h,
|
|
265
|
-
});
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
case 'tool-output-available':
|
|
270
|
-
case 'tool-output-error':
|
|
271
|
-
case 'tool-approval-request':
|
|
272
|
-
case 'tool-output-denied': {
|
|
273
|
-
await this._core.publishDiscrete(buildDiscretePayload(chunk), perWrite);
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// -- Discrete: content parts ------------------------------------------
|
|
278
|
-
|
|
279
|
-
case 'file': {
|
|
280
|
-
const h = headerWriter()
|
|
281
|
-
.str('mediaType', chunk.mediaType)
|
|
282
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
283
|
-
.build();
|
|
284
|
-
await this._core.publishDiscrete({ name: 'file', data: chunk.url, headers: h }, perWrite);
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
case 'source-url': {
|
|
289
|
-
const h = headerWriter()
|
|
290
|
-
.str('sourceId', chunk.sourceId)
|
|
291
|
-
.str('title', chunk.title)
|
|
292
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
293
|
-
.build();
|
|
294
|
-
await this._core.publishDiscrete({ name: 'source-url', data: chunk.url, headers: h }, perWrite);
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
case 'source-document': {
|
|
299
|
-
const h = headerWriter()
|
|
300
|
-
.str('sourceId', chunk.sourceId)
|
|
301
|
-
.str('mediaType', chunk.mediaType)
|
|
302
|
-
.str('title', chunk.title)
|
|
303
|
-
.str('filename', chunk.filename)
|
|
304
|
-
.json('providerMetadata', chunk.providerMetadata)
|
|
305
|
-
.build();
|
|
306
|
-
await this._core.publishDiscrete({ name: 'source-document', data: '', headers: h }, perWrite);
|
|
307
|
-
break;
|
|
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;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// -- Discrete: data-* custom chunks -----------------------------------
|
|
317
|
-
|
|
318
|
-
default: {
|
|
319
|
-
if (chunk.type.startsWith('data-')) {
|
|
320
|
-
const h = headerWriter().str('id', chunk.id).bool('transient', chunk.transient).build();
|
|
321
|
-
const ephemeral = chunk.transient === true;
|
|
322
|
-
await this._core.publishDiscrete({ name: chunk.type, data: chunk.data, headers: h, ephemeral }, perWrite);
|
|
323
|
-
}
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async writeEvent(chunk: AI.UIMessageChunk, perWrite?: WriteOptions): Promise<Ably.PublishResult> {
|
|
330
|
-
return this._core.publishDiscrete(buildDiscretePayload(chunk), perWrite);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
async writeMessages(messages: AI.UIMessage[], perWrite?: WriteOptions): Promise<Ably.PublishResult> {
|
|
334
|
-
const payloads = messages.flatMap((msg) => encodeMessagePayloads(msg));
|
|
335
|
-
return this._core.publishDiscreteBatch(payloads, perWrite);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
async abort(reason?: string): Promise<void> {
|
|
339
|
-
if (this._aborted) return;
|
|
340
|
-
this._aborted = true;
|
|
341
|
-
await this._core.abortAllStreams();
|
|
342
|
-
await this._core.publishDiscrete({
|
|
343
|
-
name: 'abort',
|
|
344
|
-
data: reason ?? '',
|
|
345
|
-
headers: { [HEADER_STATUS]: 'aborted' },
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
async close(): Promise<void> {
|
|
350
|
-
await this._core.close();
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ---------------------------------------------------------------------------
|
|
355
|
-
// Message payload encoding (stateless helper)
|
|
356
|
-
// ---------------------------------------------------------------------------
|
|
357
|
-
|
|
358
|
-
const encodeMessagePayloads = (message: AI.UIMessage): MessagePayload[] => {
|
|
359
|
-
const messageId = message.id;
|
|
360
|
-
const payloads: MessagePayload[] = [];
|
|
361
|
-
|
|
362
|
-
for (const part of message.parts) {
|
|
363
|
-
switch (part.type) {
|
|
364
|
-
case 'text': {
|
|
365
|
-
payloads.push({ name: 'text', data: part.text, headers: headerWriter().str('messageId', messageId).build() });
|
|
366
|
-
break;
|
|
367
|
-
}
|
|
368
|
-
case 'file': {
|
|
369
|
-
payloads.push({
|
|
370
|
-
name: 'file',
|
|
371
|
-
data: part.url,
|
|
372
|
-
headers: headerWriter().str('messageId', messageId).str('mediaType', part.mediaType).build(),
|
|
373
|
-
});
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
default: {
|
|
377
|
-
if (isDataUIPart(part)) {
|
|
378
|
-
payloads.push({
|
|
379
|
-
name: part.type,
|
|
380
|
-
data: part.data,
|
|
381
|
-
headers: headerWriter().str('messageId', messageId).str('id', part.id).build(),
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (payloads.length === 0) {
|
|
390
|
-
payloads.push({ name: 'text', data: '', headers: headerWriter().str('messageId', messageId).build() });
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return payloads;
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
// ---------------------------------------------------------------------------
|
|
397
|
-
// Factory
|
|
398
|
-
// ---------------------------------------------------------------------------
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Create a Vercel AI SDK encoder that maps UIMessageChunk events to Ably
|
|
402
|
-
* channel operations via the encoder core.
|
|
403
|
-
* @param writer - The channel writer to publish messages through.
|
|
404
|
-
* @param options - Encoder configuration (clientId, extras, hooks, logger).
|
|
405
|
-
* @returns A {@link StreamEncoder} for UIMessageChunk/UIMessage.
|
|
406
|
-
*/
|
|
407
|
-
export const createEncoder = (
|
|
408
|
-
writer: ChannelWriter,
|
|
409
|
-
options: EncoderCoreOptions = {},
|
|
410
|
-
): StreamEncoder<AI.UIMessageChunk, AI.UIMessage> => new DefaultUIMessageEncoder(writer, options);
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useStagedAddToolApprovalResponse — wrap useChat's `addToolApprovalResponse`
|
|
3
|
-
* so the approval response is also applied to the transport tree
|
|
4
|
-
* synchronously at click time.
|
|
5
|
-
*
|
|
6
|
-
* Patching the tree at click time eliminates the useChat↔tree divergence
|
|
7
|
-
* the ChatTransport would otherwise have to reconcile via a history
|
|
8
|
-
* overlay, and closes the observer-turn race that could wipe the
|
|
9
|
-
* approval state between `addToolApprovalResponse` and
|
|
10
|
-
* `sendAutomaticallyWhen`'s evaluation.
|
|
11
|
-
*
|
|
12
|
-
* Use this in place of useChat's raw `addToolApprovalResponse` wherever
|
|
13
|
-
* you wire Approve / Deny buttons.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type * as AI from 'ai';
|
|
17
|
-
import type { ChatAddToolApproveResponseFunction } from 'ai';
|
|
18
|
-
import { useCallback } from 'react';
|
|
19
|
-
|
|
20
|
-
import type { ClientTransport } from '../../core/transport/types.js';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Returns a function with the same signature as useChat's
|
|
24
|
-
* `addToolApprovalResponse`, but additionally applies the approval
|
|
25
|
-
* response to the transport tree via `stageMessage` before delegating.
|
|
26
|
-
*
|
|
27
|
-
* If the tool call identified by `opts.id` isn't found in the tree,
|
|
28
|
-
* the tree update is skipped and the raw function is still called —
|
|
29
|
-
* matches useChat's tolerant behavior for stale approval ids.
|
|
30
|
-
* @param transport - The client transport whose tree to patch.
|
|
31
|
-
* @param addToolApprovalResponse - The raw function from `useChat()`.
|
|
32
|
-
* @returns A drop-in replacement that patches the tree then delegates.
|
|
33
|
-
*/
|
|
34
|
-
export const useStagedAddToolApprovalResponse = (
|
|
35
|
-
transport: ClientTransport<AI.UIMessageChunk, AI.UIMessage>,
|
|
36
|
-
addToolApprovalResponse: ChatAddToolApproveResponseFunction,
|
|
37
|
-
): ChatAddToolApproveResponseFunction =>
|
|
38
|
-
useCallback<ChatAddToolApproveResponseFunction>(
|
|
39
|
-
(opts) => {
|
|
40
|
-
stageApprovalResponseOnTree(transport, opts);
|
|
41
|
-
return addToolApprovalResponse(opts);
|
|
42
|
-
},
|
|
43
|
-
[transport, addToolApprovalResponse],
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Locate the assistant message whose `dynamic-tool` part carries the
|
|
48
|
-
* given `approval.id`, build a patched copy with the part transitioned
|
|
49
|
-
* to `approval-responded`, and stage the patched message on the tree.
|
|
50
|
-
* @param transport - The transport whose tree to patch.
|
|
51
|
-
* @param opts - The approval response being applied.
|
|
52
|
-
* @param opts.id - The approval id matching a dynamic-tool part in the tree.
|
|
53
|
-
* @param opts.approved - Whether the user approved or denied.
|
|
54
|
-
* @param opts.reason - Optional reason accompanying the response.
|
|
55
|
-
*/
|
|
56
|
-
const stageApprovalResponseOnTree = (
|
|
57
|
-
transport: ClientTransport<AI.UIMessageChunk, AI.UIMessage>,
|
|
58
|
-
opts: { id: string; approved: boolean; reason?: string },
|
|
59
|
-
): void => {
|
|
60
|
-
const nodes = transport.view.flattenNodes();
|
|
61
|
-
for (const node of nodes) {
|
|
62
|
-
const partIndex = node.message.parts.findIndex((p) => p.type === 'dynamic-tool' && p.approval?.id === opts.id);
|
|
63
|
-
if (partIndex === -1) continue;
|
|
64
|
-
|
|
65
|
-
// CAST: findIndex predicate above narrows this to a dynamic-tool part
|
|
66
|
-
// with a non-undefined approval.
|
|
67
|
-
const part = node.message.parts[partIndex] as AI.DynamicToolUIPart;
|
|
68
|
-
|
|
69
|
-
// Build the approval-responded variant directly rather than spreading
|
|
70
|
-
// `part`, which TypeScript narrows to whichever source-state variant
|
|
71
|
-
// the union discriminator inferred and then rejects when we change
|
|
72
|
-
// `state` to a variant with different approval/output constraints.
|
|
73
|
-
const patchedPart: AI.DynamicToolUIPart = {
|
|
74
|
-
type: 'dynamic-tool',
|
|
75
|
-
toolName: part.toolName,
|
|
76
|
-
toolCallId: part.toolCallId,
|
|
77
|
-
state: 'approval-responded',
|
|
78
|
-
input: part.input,
|
|
79
|
-
approval: { id: opts.id, approved: opts.approved, reason: opts.reason },
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const patchedParts = [...node.message.parts];
|
|
83
|
-
patchedParts[partIndex] = patchedPart;
|
|
84
|
-
transport.stageMessage(node.msgId, { ...node.message, parts: patchedParts });
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
};
|