@convex-dev/agent 0.6.3 → 0.6.4
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/dist/UIMessages.d.ts.map +1 -1
- package/dist/UIMessages.js.map +1 -1
- package/dist/client/definePlaygroundAPI.d.ts +69 -64
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/definePlaygroundAPI.js +8 -5
- package/dist/client/definePlaygroundAPI.js.map +1 -1
- package/dist/client/index.d.ts +12 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +11 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/saveInputMessages.d.ts.map +1 -1
- package/dist/client/saveInputMessages.js.map +1 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/apiKeys.js +5 -5
- package/dist/component/apiKeys.js.map +1 -1
- package/dist/component/files.d.ts.map +1 -1
- package/dist/component/files.js +13 -11
- package/dist/component/files.js.map +1 -1
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +37 -27
- package/dist/component/messages.js.map +1 -1
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +22 -17
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.js +7 -7
- package/dist/component/threads.js.map +1 -1
- package/dist/component/users.js +2 -2
- package/dist/component/users.js.map +1 -1
- package/dist/component/vector/index.d.ts.map +1 -1
- package/dist/component/vector/index.js +14 -8
- package/dist/component/vector/index.js.map +1 -1
- package/dist/deltas.d.ts +16 -27
- package/dist/deltas.d.ts.map +1 -1
- package/dist/deltas.js +269 -286
- package/dist/deltas.js.map +1 -1
- package/dist/mapping.d.ts +9 -3
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +16 -14
- package/dist/mapping.js.map +1 -1
- package/dist/react/useStreamingUIMessages.d.ts.map +1 -1
- package/dist/react/useStreamingUIMessages.js +42 -26
- package/dist/react/useStreamingUIMessages.js.map +1 -1
- package/dist/react/useUIMessages.d.ts +1 -0
- package/dist/react/useUIMessages.d.ts.map +1 -1
- package/dist/react/useUIMessages.js +7 -3
- package/dist/react/useUIMessages.js.map +1 -1
- package/package.json +2 -1
- package/src/UIMessages.ts +1 -2
- package/src/client/approval.test.ts +25 -6
- package/src/client/createTool.ts +1 -1
- package/src/client/definePlaygroundAPI.ts +33 -17
- package/src/client/index.test.ts +91 -0
- package/src/client/index.ts +25 -1
- package/src/client/saveInputMessages.ts +4 -1
- package/src/client/streaming.integration.test.ts +39 -117
- package/src/client/types.ts +4 -17
- package/src/component/apiKeys.ts +5 -5
- package/src/component/files.test.ts +1 -1
- package/src/component/files.ts +14 -12
- package/src/component/messages.ts +40 -28
- package/src/component/streams.ts +33 -17
- package/src/component/threads.ts +7 -7
- package/src/component/users.ts +2 -2
- package/src/component/vector/index.ts +14 -7
- package/src/deltas.test.ts +373 -392
- package/src/deltas.ts +339 -378
- package/src/mapping.test.ts +296 -18
- package/src/mapping.ts +17 -11
- package/src/react/useStreamingUIMessages.ts +62 -34
- package/src/react/useUIMessages.test.ts +80 -1
- package/src/react/useUIMessages.ts +11 -3
package/dist/deltas.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { readUIMessageStream, } from "ai";
|
|
2
|
-
import { assert
|
|
2
|
+
import { assert } from "convex-helpers";
|
|
3
3
|
import {} from "./UIMessages.js";
|
|
4
4
|
import { joinText, sorted } from "./shared.js";
|
|
5
5
|
import {} from "./validators.js";
|
|
6
|
-
import { getErrorMessage } from "@ai-sdk/provider-utils";
|
|
7
6
|
export function blankUIMessage(streamMessage, threadId) {
|
|
8
7
|
return {
|
|
9
8
|
id: `stream:${streamMessage.streamId}`,
|
|
@@ -32,6 +31,9 @@ export function statusFromStreamStatus(status) {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
export async function updateFromUIMessageChunks(uiMessage, parts) {
|
|
34
|
+
if (parts.length === 0) {
|
|
35
|
+
return uiMessage;
|
|
36
|
+
}
|
|
35
37
|
const partsStream = new ReadableStream({
|
|
36
38
|
start(controller) {
|
|
37
39
|
for (const part of parts) {
|
|
@@ -47,11 +49,7 @@ export async function updateFromUIMessageChunks(uiMessage, parts) {
|
|
|
47
49
|
stream: partsStream,
|
|
48
50
|
onError: (e) => {
|
|
49
51
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
50
|
-
// Tool invocation errors can be safely ignored when streaming continuation
|
|
51
|
-
// after tool approval - the stored messages have the complete tool context
|
|
52
52
|
if (errorMessage.toLowerCase().includes("no tool invocation found")) {
|
|
53
|
-
// Silently suppress - this is expected after tool approval when the
|
|
54
|
-
// continuation stream has tool-result without the original tool-call
|
|
55
53
|
suppressError = true;
|
|
56
54
|
return;
|
|
57
55
|
}
|
|
@@ -68,8 +66,6 @@ export async function updateFromUIMessageChunks(uiMessage, parts) {
|
|
|
68
66
|
}
|
|
69
67
|
}
|
|
70
68
|
catch (e) {
|
|
71
|
-
// If we've already handled this error in onError and marked it as suppressed,
|
|
72
|
-
// don't rethrow - the stored messages provide the fallback
|
|
73
69
|
if (!suppressError) {
|
|
74
70
|
throw e;
|
|
75
71
|
}
|
|
@@ -80,349 +76,336 @@ export async function updateFromUIMessageChunks(uiMessage, parts) {
|
|
|
80
76
|
message.text = joinText(message.parts);
|
|
81
77
|
return message;
|
|
82
78
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const uiMessage = await updateFromUIMessageChunks(blankUIMessage(streamMessage, threadId), parts);
|
|
89
|
-
messages.push(uiMessage);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
const [uiMessages] = deriveUIMessagesFromTextStreamParts(threadId, [streamMessage], [], allDeltas);
|
|
93
|
-
messages.push(...uiMessages);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return sorted(messages);
|
|
79
|
+
function transitionToolPart(part, updates) {
|
|
80
|
+
Object.assign(part, updates);
|
|
81
|
+
}
|
|
82
|
+
export function emptyIncrementalStreamState() {
|
|
83
|
+
return { activeText: {}, activeReasoning: {}, toolInputText: {} };
|
|
97
84
|
}
|
|
98
85
|
/**
|
|
99
|
-
*
|
|
86
|
+
* Apply a batch of new UIMessageChunks to an existing UIMessage without
|
|
87
|
+
* replaying prior chunks. `prev` carries the ephemeral stream state that the
|
|
88
|
+
* UIMessage itself can't hold (which text/reasoning parts are still streaming,
|
|
89
|
+
* and the raw accumulated tool input text). Parts are append-only, so part
|
|
90
|
+
* indices stay stable across the structuredClone between batches. Behavior
|
|
91
|
+
* mirrors the AI SDK's processUIMessageStream.
|
|
100
92
|
*/
|
|
101
|
-
export function
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
for (const { streamId } of existingStreams) {
|
|
114
|
-
if (!newStreams.find((s) => s.streamId === streamId)) {
|
|
115
|
-
// There's a stream that's no longer active.
|
|
116
|
-
changed = true;
|
|
93
|
+
export function applyUIMessageChunksIncremental(uiMessage, newParts, prev) {
|
|
94
|
+
const message = structuredClone(uiMessage);
|
|
95
|
+
const activeText = { ...prev.activeText };
|
|
96
|
+
const activeReasoning = { ...prev.activeReasoning };
|
|
97
|
+
const toolInputText = { ...prev.toolInputText };
|
|
98
|
+
const touchedTools = new Set();
|
|
99
|
+
const toolIndexById = new Map();
|
|
100
|
+
message.parts.forEach((p, i) => {
|
|
101
|
+
if ("toolCallId" in p &&
|
|
102
|
+
(p.type.startsWith("tool-") || p.type === "dynamic-tool")) {
|
|
103
|
+
toolIndexById.set(p.toolCallId, i);
|
|
117
104
|
}
|
|
118
|
-
}
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (delta.parts.length === 0) {
|
|
127
|
-
console.debug(`Got delta with no parts: ${JSON.stringify(delta)}`);
|
|
128
|
-
continue;
|
|
105
|
+
});
|
|
106
|
+
const toolPartAt = (toolCallId) => {
|
|
107
|
+
const idx = toolIndexById.get(toolCallId);
|
|
108
|
+
return idx === undefined ? undefined : message.parts[idx];
|
|
109
|
+
};
|
|
110
|
+
const mergeMetadata = (metadata) => {
|
|
111
|
+
if (metadata == null) {
|
|
112
|
+
return;
|
|
129
113
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
114
|
+
message.metadata = {
|
|
115
|
+
...message.metadata,
|
|
116
|
+
...metadata,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
for (const part of newParts) {
|
|
120
|
+
switch (part.type) {
|
|
121
|
+
case "text-start": {
|
|
122
|
+
const newPart = {
|
|
123
|
+
type: "text",
|
|
124
|
+
text: "",
|
|
125
|
+
state: "streaming",
|
|
126
|
+
providerMetadata: part.providerMetadata,
|
|
127
|
+
};
|
|
128
|
+
message.parts.push(newPart);
|
|
129
|
+
activeText[part.id] = message.parts.length - 1;
|
|
136
130
|
break;
|
|
137
131
|
}
|
|
138
|
-
else {
|
|
139
|
-
throw new Error(`Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${cursor}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
parts.push(...delta.parts);
|
|
143
|
-
cursor = delta.end;
|
|
144
|
-
}
|
|
145
|
-
return { parts, cursor };
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* This is historically from when we would use the onChunk callback instead of
|
|
149
|
-
* consuming the full UIMessageStream.
|
|
150
|
-
*/
|
|
151
|
-
// exported for testing
|
|
152
|
-
export function updateFromTextStreamParts(threadId, streamMessage, existing, deltas) {
|
|
153
|
-
const { cursor, parts } = getParts(deltas, existing?.cursor);
|
|
154
|
-
const changed = parts.length > 0 ||
|
|
155
|
-
(existing &&
|
|
156
|
-
statusFromStreamStatus(streamMessage.status) !== existing.message.status);
|
|
157
|
-
const existingMessage = existing?.message ?? blankUIMessage(streamMessage, threadId);
|
|
158
|
-
if (!changed) {
|
|
159
|
-
return [
|
|
160
|
-
existing ?? {
|
|
161
|
-
streamId: streamMessage.streamId,
|
|
162
|
-
cursor,
|
|
163
|
-
message: existingMessage,
|
|
164
|
-
},
|
|
165
|
-
false,
|
|
166
|
-
];
|
|
167
|
-
}
|
|
168
|
-
const message = structuredClone(existingMessage);
|
|
169
|
-
message.status = statusFromStreamStatus(streamMessage.status);
|
|
170
|
-
const textPartsById = new Map();
|
|
171
|
-
const toolPartsById = new Map(message.parts
|
|
172
|
-
.filter((p) => p.type.startsWith("tool-") || p.type === "dynamic-tool")
|
|
173
|
-
.map((p) => [p.toolCallId, p]));
|
|
174
|
-
const reasoningPartsById = new Map();
|
|
175
|
-
for (const part of parts) {
|
|
176
|
-
switch (part.type) {
|
|
177
|
-
case "text-start":
|
|
178
132
|
case "text-delta": {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
const newPart = {
|
|
186
|
-
type: "text",
|
|
187
|
-
text: "",
|
|
188
|
-
providerMetadata: part.providerMetadata,
|
|
189
|
-
};
|
|
190
|
-
textPartsById.set(part.id, newPart);
|
|
191
|
-
message.parts.push(newPart);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (part.type === "text-delta") {
|
|
195
|
-
const textPart = textPartsById.get(part.id);
|
|
196
|
-
textPart.text += part.text;
|
|
133
|
+
const idx = activeText[part.id];
|
|
134
|
+
if (idx !== undefined) {
|
|
135
|
+
const textPart = message.parts[idx];
|
|
136
|
+
textPart.text += part.delta;
|
|
197
137
|
textPart.providerMetadata = mergeProviderMetadata(textPart.providerMetadata, part.providerMetadata);
|
|
198
138
|
}
|
|
199
139
|
break;
|
|
200
140
|
}
|
|
201
|
-
case "
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
state: "input-streaming",
|
|
209
|
-
input: "",
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
newPart = {
|
|
214
|
-
type: `tool-${part.toolName}`,
|
|
215
|
-
toolCallId: part.id,
|
|
216
|
-
state: "input-streaming",
|
|
217
|
-
input: "",
|
|
218
|
-
providerExecuted: part.providerExecuted,
|
|
219
|
-
};
|
|
141
|
+
case "text-end": {
|
|
142
|
+
const idx = activeText[part.id];
|
|
143
|
+
if (idx !== undefined) {
|
|
144
|
+
const textPart = message.parts[idx];
|
|
145
|
+
textPart.state = "done";
|
|
146
|
+
textPart.providerMetadata = mergeProviderMetadata(textPart.providerMetadata, part.providerMetadata);
|
|
147
|
+
delete activeText[part.id];
|
|
220
148
|
}
|
|
221
|
-
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "reasoning-start": {
|
|
152
|
+
const newPart = {
|
|
153
|
+
type: "reasoning",
|
|
154
|
+
text: "",
|
|
155
|
+
state: "streaming",
|
|
156
|
+
providerMetadata: part.providerMetadata,
|
|
157
|
+
};
|
|
222
158
|
message.parts.push(newPart);
|
|
159
|
+
activeReasoning[part.id] = message.parts.length - 1;
|
|
223
160
|
break;
|
|
224
161
|
}
|
|
225
|
-
case "
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
162
|
+
case "reasoning-delta": {
|
|
163
|
+
const idx = activeReasoning[part.id];
|
|
164
|
+
if (idx !== undefined) {
|
|
165
|
+
const reasoningPart = message.parts[idx];
|
|
166
|
+
reasoningPart.text += part.delta;
|
|
167
|
+
reasoningPart.providerMetadata = mergeProviderMetadata(reasoningPart.providerMetadata, part.providerMetadata);
|
|
230
168
|
}
|
|
231
169
|
break;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
170
|
+
}
|
|
171
|
+
case "reasoning-end": {
|
|
172
|
+
const idx = activeReasoning[part.id];
|
|
173
|
+
if (idx !== undefined) {
|
|
174
|
+
const reasoningPart = message.parts[idx];
|
|
175
|
+
reasoningPart.state = "done";
|
|
176
|
+
reasoningPart.providerMetadata = mergeProviderMetadata(reasoningPart.providerMetadata, part.providerMetadata);
|
|
177
|
+
delete activeReasoning[part.id];
|
|
241
178
|
}
|
|
242
179
|
break;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
180
|
+
}
|
|
181
|
+
case "tool-input-start": {
|
|
182
|
+
const newToolPart = part.dynamic
|
|
183
|
+
? {
|
|
247
184
|
type: "dynamic-tool",
|
|
248
185
|
toolCallId: part.toolCallId,
|
|
249
186
|
toolName: part.toolName,
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
else {
|
|
255
|
-
newPart = {
|
|
187
|
+
state: "input-streaming",
|
|
188
|
+
input: undefined,
|
|
189
|
+
}
|
|
190
|
+
: {
|
|
256
191
|
type: `tool-${part.toolName}`,
|
|
257
192
|
toolCallId: part.toolCallId,
|
|
258
|
-
|
|
259
|
-
|
|
193
|
+
state: "input-streaming",
|
|
194
|
+
input: undefined,
|
|
195
|
+
providerExecuted: part.providerExecuted,
|
|
260
196
|
};
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
if (part.providerMetadata) {
|
|
266
|
-
newPart.callProviderMetadata = part.providerMetadata;
|
|
267
|
-
}
|
|
268
|
-
if (toolPartsById.has(part.toolCallId)) {
|
|
269
|
-
const toUpdate = toolPartsById.get(part.toolCallId);
|
|
270
|
-
Object.assign(toUpdate, newPart);
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
toolPartsById.set(part.toolCallId, newPart);
|
|
274
|
-
message.parts.push(newPart);
|
|
275
|
-
}
|
|
197
|
+
message.parts.push(newToolPart);
|
|
198
|
+
toolIndexById.set(part.toolCallId, message.parts.length - 1);
|
|
199
|
+
toolInputText[part.toolCallId] = "";
|
|
276
200
|
break;
|
|
277
201
|
}
|
|
278
|
-
case "tool-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
newPart = {
|
|
284
|
-
...toolCall,
|
|
285
|
-
state: "output-available",
|
|
286
|
-
input: part.input ?? toolCall.input,
|
|
287
|
-
output: part.output ?? toolCall.output,
|
|
288
|
-
...pick(part, ["preliminary"]),
|
|
289
|
-
};
|
|
202
|
+
case "tool-input-delta": {
|
|
203
|
+
if (toolIndexById.has(part.toolCallId)) {
|
|
204
|
+
toolInputText[part.toolCallId] =
|
|
205
|
+
(toolInputText[part.toolCallId] ?? "") + part.inputTextDelta;
|
|
206
|
+
touchedTools.add(part.toolCallId);
|
|
290
207
|
}
|
|
291
208
|
else {
|
|
292
|
-
|
|
293
|
-
...toolCall,
|
|
294
|
-
state: "output-available",
|
|
295
|
-
input: part.input ?? toolCall.input,
|
|
296
|
-
output: part.output ?? toolCall.output,
|
|
297
|
-
preliminary: part.preliminary,
|
|
298
|
-
};
|
|
209
|
+
console.warn(`tool-input-delta for unknown toolCallId ${part.toolCallId}`);
|
|
299
210
|
}
|
|
300
|
-
Object.assign(toolCall, newPart);
|
|
301
211
|
break;
|
|
302
212
|
}
|
|
303
|
-
case "
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
type: "reasoning",
|
|
313
|
-
state: "streaming",
|
|
314
|
-
text: "",
|
|
315
|
-
providerMetadata: part.providerMetadata,
|
|
316
|
-
};
|
|
317
|
-
reasoningPartsById.set(part.id, newPart);
|
|
318
|
-
message.parts.push(newPart);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
const reasoningPart = reasoningPartsById.get(part.id);
|
|
322
|
-
if (part.type === "reasoning-delta") {
|
|
323
|
-
reasoningPart.text += part.text;
|
|
324
|
-
reasoningPart.providerMetadata = mergeProviderMetadata(reasoningPart.providerMetadata, part.providerMetadata);
|
|
213
|
+
case "tool-input-available": {
|
|
214
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
215
|
+
if (toolPart) {
|
|
216
|
+
transitionToolPart(toolPart, {
|
|
217
|
+
state: "input-available",
|
|
218
|
+
input: part.input,
|
|
219
|
+
callProviderMetadata: mergeProviderMetadata(toolPart
|
|
220
|
+
.callProviderMetadata, part.providerMetadata),
|
|
221
|
+
});
|
|
325
222
|
}
|
|
223
|
+
touchedTools.delete(part.toolCallId);
|
|
224
|
+
// The raw JSON buffer is no longer needed; drop it so it doesn't get
|
|
225
|
+
// carried through every later batch on the hot path.
|
|
226
|
+
delete toolInputText[part.toolCallId];
|
|
326
227
|
break;
|
|
327
228
|
}
|
|
328
|
-
case "
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
229
|
+
case "tool-input-error": {
|
|
230
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
231
|
+
if (toolPart) {
|
|
232
|
+
transitionToolPart(toolPart, {
|
|
233
|
+
state: "output-error",
|
|
234
|
+
errorText: part.errorText,
|
|
235
|
+
providerExecuted: part.providerExecuted,
|
|
236
|
+
...(toolPart.type === "dynamic-tool"
|
|
237
|
+
? { input: part.input }
|
|
238
|
+
: { input: undefined, rawInput: part.input }),
|
|
239
|
+
callProviderMetadata: mergeProviderMetadata(toolPart
|
|
240
|
+
.callProviderMetadata, part.providerMetadata),
|
|
241
|
+
});
|
|
336
242
|
}
|
|
243
|
+
touchedTools.delete(part.toolCallId);
|
|
244
|
+
delete toolInputText[part.toolCallId];
|
|
337
245
|
break;
|
|
338
246
|
}
|
|
339
|
-
case "
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
247
|
+
case "tool-output-available": {
|
|
248
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
249
|
+
if (toolPart) {
|
|
250
|
+
transitionToolPart(toolPart, {
|
|
251
|
+
state: "output-available",
|
|
252
|
+
output: part.output,
|
|
253
|
+
preliminary: part.preliminary,
|
|
254
|
+
providerExecuted: part.providerExecuted,
|
|
347
255
|
});
|
|
348
256
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
case "tool-output-error": {
|
|
260
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
261
|
+
if (toolPart) {
|
|
262
|
+
transitionToolPart(toolPart, {
|
|
263
|
+
state: "output-error",
|
|
264
|
+
errorText: part.errorText,
|
|
265
|
+
providerExecuted: part.providerExecuted,
|
|
357
266
|
});
|
|
358
267
|
}
|
|
359
|
-
else {
|
|
360
|
-
console.warn("Got source part with unknown source type", part);
|
|
361
|
-
}
|
|
362
|
-
break;
|
|
363
|
-
case "abort":
|
|
364
|
-
message.status = "failed";
|
|
365
|
-
break;
|
|
366
|
-
case "error":
|
|
367
|
-
message.status = "failed";
|
|
368
|
-
console.warn("Generation failed with error", part.error);
|
|
369
268
|
break;
|
|
370
|
-
|
|
371
|
-
|
|
269
|
+
}
|
|
270
|
+
case "tool-output-denied": {
|
|
271
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
372
272
|
if (toolPart) {
|
|
373
|
-
toolPart
|
|
273
|
+
transitionToolPart(toolPart, { state: "output-denied" });
|
|
374
274
|
}
|
|
375
275
|
break;
|
|
376
276
|
}
|
|
377
277
|
case "tool-approval-request": {
|
|
378
|
-
const
|
|
379
|
-
const toolPart = toolPartsById.get(typedPart.toolCallId);
|
|
278
|
+
const toolPart = toolPartAt(part.toolCallId);
|
|
380
279
|
if (toolPart) {
|
|
381
|
-
toolPart
|
|
382
|
-
|
|
383
|
-
id:
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
console.warn(`Expected tool call part ${typedPart.toolCallId} for approval request`);
|
|
280
|
+
transitionToolPart(toolPart, {
|
|
281
|
+
state: "approval-requested",
|
|
282
|
+
approval: { id: part.approvalId },
|
|
283
|
+
});
|
|
388
284
|
}
|
|
389
285
|
break;
|
|
390
286
|
}
|
|
287
|
+
case "source-url":
|
|
288
|
+
message.parts.push({
|
|
289
|
+
type: "source-url",
|
|
290
|
+
url: part.url,
|
|
291
|
+
sourceId: part.sourceId,
|
|
292
|
+
title: part.title,
|
|
293
|
+
providerMetadata: part.providerMetadata,
|
|
294
|
+
});
|
|
295
|
+
break;
|
|
296
|
+
case "source-document":
|
|
297
|
+
message.parts.push({
|
|
298
|
+
type: "source-document",
|
|
299
|
+
mediaType: part.mediaType,
|
|
300
|
+
sourceId: part.sourceId,
|
|
301
|
+
title: part.title,
|
|
302
|
+
filename: part.filename,
|
|
303
|
+
providerMetadata: part.providerMetadata,
|
|
304
|
+
});
|
|
305
|
+
break;
|
|
391
306
|
case "file":
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
307
|
+
message.parts.push({
|
|
308
|
+
type: "file",
|
|
309
|
+
mediaType: part.mediaType,
|
|
310
|
+
url: part.url,
|
|
311
|
+
});
|
|
312
|
+
break;
|
|
396
313
|
case "start-step":
|
|
314
|
+
message.parts.push({ type: "step-start" });
|
|
315
|
+
break;
|
|
316
|
+
case "finish-step":
|
|
317
|
+
// Match the SDK: a new step starts fresh streaming parts; the prior
|
|
318
|
+
// parts keep their state rather than being forced to "done".
|
|
319
|
+
for (const id of Object.keys(activeText))
|
|
320
|
+
delete activeText[id];
|
|
321
|
+
for (const id of Object.keys(activeReasoning))
|
|
322
|
+
delete activeReasoning[id];
|
|
323
|
+
break;
|
|
397
324
|
case "start":
|
|
398
|
-
|
|
325
|
+
case "finish":
|
|
326
|
+
case "message-metadata":
|
|
327
|
+
mergeMetadata(part.messageMetadata);
|
|
328
|
+
break;
|
|
329
|
+
case "abort":
|
|
330
|
+
case "error":
|
|
331
|
+
// The stream-level status (statusFromStreamStatus) is authoritative and
|
|
332
|
+
// is applied by the caller; nothing to mutate on the message here.
|
|
399
333
|
break;
|
|
400
334
|
default: {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
335
|
+
if (typeof part.type === "string" && part.type.startsWith("data-")) {
|
|
336
|
+
const dataPart = part;
|
|
337
|
+
const existingIdx = dataPart.id != null
|
|
338
|
+
? message.parts.findIndex((p) => p.type === dataPart.type &&
|
|
339
|
+
p.id === dataPart.id)
|
|
340
|
+
: -1;
|
|
341
|
+
if (existingIdx >= 0) {
|
|
342
|
+
message.parts[existingIdx].data =
|
|
343
|
+
dataPart.data;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
message.parts.push(dataPart);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
console.warn(`applyUIMessageChunksIncremental: unhandled chunk type ${String(part.type)}`);
|
|
351
|
+
}
|
|
406
352
|
break;
|
|
407
353
|
}
|
|
408
354
|
}
|
|
409
355
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
356
|
+
for (const toolCallId of touchedTools) {
|
|
357
|
+
const toolPart = toolPartAt(toolCallId);
|
|
358
|
+
if (toolPart && toolPart.state === "input-streaming") {
|
|
359
|
+
try {
|
|
360
|
+
toolPart.input = JSON.parse(toolInputText[toolCallId] ?? "");
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// partial JSON — leave input unset until complete
|
|
364
|
+
}
|
|
415
365
|
}
|
|
416
366
|
}
|
|
417
367
|
message.text = joinText(message.parts);
|
|
418
|
-
return
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
368
|
+
return {
|
|
369
|
+
message,
|
|
370
|
+
streamState: { activeText, activeReasoning, toolInputText },
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
export async function deriveUIMessagesFromDeltas(threadId, streamMessages, allDeltas) {
|
|
374
|
+
const messages = [];
|
|
375
|
+
for (const streamMessage of streamMessages) {
|
|
376
|
+
if (streamMessage.format !== "UIMessageChunk") {
|
|
377
|
+
throw new Error(`deriveUIMessagesFromDeltas: unsupported stream format "${streamMessage.format ?? "text"}" for stream ${streamMessage.streamId}`);
|
|
378
|
+
}
|
|
379
|
+
const { parts } = getParts(allDeltas.filter((d) => d.streamId === streamMessage.streamId), 0);
|
|
380
|
+
const uiMessage = await updateFromUIMessageChunks(blankUIMessage(streamMessage, threadId), parts);
|
|
381
|
+
messages.push(uiMessage);
|
|
382
|
+
}
|
|
383
|
+
return sorted(messages);
|
|
384
|
+
}
|
|
385
|
+
export function getParts(deltas, fromCursor) {
|
|
386
|
+
const parts = [];
|
|
387
|
+
let cursor = fromCursor ?? 0;
|
|
388
|
+
for (const delta of deltas.sort((a, b) => a.start - b.start)) {
|
|
389
|
+
if (delta.parts.length === 0) {
|
|
390
|
+
console.debug(`Got delta with no parts: ${JSON.stringify(delta)}`);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (cursor !== delta.start) {
|
|
394
|
+
if (cursor >= delta.end) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
else if (cursor < delta.start) {
|
|
398
|
+
console.warn(`Got delta for stream ${delta.streamId} that has a gap ${cursor} -> ${delta.start}`);
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
throw new Error(`Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${cursor}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
parts.push(...delta.parts);
|
|
406
|
+
cursor = delta.end;
|
|
407
|
+
}
|
|
408
|
+
return { parts, cursor };
|
|
426
409
|
}
|
|
427
410
|
function mergeProviderMetadata(existing, part) {
|
|
428
411
|
if (!existing && !part) {
|