@convex-dev/agent 0.2.3-alpha.0 → 0.2.4-alpha.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/dist/client/definePlaygroundAPI.d.ts +59 -5
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/files.d.ts +1 -1
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +46 -4
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +14 -13
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +14 -2
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/search.d.ts +14 -2
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +2 -4
- package/dist/client/search.js.map +1 -1
- package/dist/client/streaming.d.ts +132 -23
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +189 -0
- package/dist/component/messages.d.ts +149 -20
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +4 -4
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +342 -63
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/mapping.d.ts +4 -4
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +67 -36
- package/dist/mapping.js.map +1 -1
- package/dist/react/deltas.d.ts +3 -3
- package/dist/react/deltas.d.ts.map +1 -1
- package/dist/react/deltas.js +302 -30
- package/dist/react/deltas.js.map +1 -1
- package/dist/react/toUIMessages.d.ts.map +1 -1
- package/dist/react/toUIMessages.js +289 -180
- package/dist/react/toUIMessages.js.map +1 -1
- package/dist/react/useSmoothText.d.ts.map +1 -1
- package/dist/react/useSmoothText.js +15 -6
- package/dist/react/useSmoothText.js.map +1 -1
- package/dist/shared.d.ts +4 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +8 -0
- package/dist/shared.js.map +1 -1
- package/dist/validators.d.ts +662 -104
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +8 -0
- package/dist/validators.js.map +1 -1
- package/package.json +1 -1
- package/src/client/files.ts +4 -4
- package/src/client/index.ts +21 -20
- package/src/client/search.ts +2 -6
- package/src/component/_generated/api.d.ts +189 -0
- package/src/component/messages.ts +4 -3
- package/src/mapping.ts +99 -51
- package/src/react/deltas.ts +368 -48
- package/src/react/toUIMessages.test.ts +186 -0
- package/src/react/toUIMessages.ts +366 -192
- package/src/react/useSmoothText.ts +25 -13
- package/src/shared.ts +14 -0
- package/src/validators.ts +8 -0
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
import { extractText, type MessageDoc } from "../client/index.js";
|
|
14
14
|
import { deserializeMessage, toUIFilePart } from "../mapping.js";
|
|
15
15
|
import type { MessageStatus } from "../validators.js";
|
|
16
|
+
import { sorted } from "../shared.js";
|
|
16
17
|
|
|
17
18
|
export type UIMessage<
|
|
18
19
|
METADATA = unknown,
|
|
@@ -35,216 +36,389 @@ export function toUIMessages<
|
|
|
35
36
|
>(
|
|
36
37
|
messages: (MessageDoc & { streaming?: boolean })[],
|
|
37
38
|
): UIMessage<METADATA, DATA_PARTS, TOOLS>[] {
|
|
39
|
+
// Group assistant and tool messages together
|
|
40
|
+
const assistantGroups = groupAssistantMessages(messages);
|
|
41
|
+
|
|
38
42
|
const uiMessages: UIMessage<METADATA, DATA_PARTS, TOOLS>[] = [];
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
for (const group of assistantGroups) {
|
|
44
|
+
if (group.role === "system") {
|
|
45
|
+
uiMessages.push(createSystemUIMessage(group.message));
|
|
46
|
+
} else if (group.role === "user") {
|
|
47
|
+
uiMessages.push(createUserUIMessage(group.message));
|
|
48
|
+
} else {
|
|
49
|
+
// Assistant/tool group
|
|
50
|
+
uiMessages.push(createAssistantUIMessage(group.messages));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return uiMessages;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type Group =
|
|
58
|
+
| {
|
|
59
|
+
role: "user";
|
|
60
|
+
message: MessageDoc & { streaming?: boolean };
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
role: "system";
|
|
64
|
+
message: MessageDoc & { streaming?: boolean };
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
role: "assistant";
|
|
68
|
+
messages: (MessageDoc & { streaming?: boolean })[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function groupAssistantMessages(
|
|
72
|
+
messages: (MessageDoc & { streaming?: boolean })[],
|
|
73
|
+
): Group[] {
|
|
74
|
+
const groups: Group[] = [];
|
|
75
|
+
|
|
76
|
+
// Sort messages by order and stepOrder first to handle out-of-order arrivals
|
|
77
|
+
const sortedMessages = sorted(messages);
|
|
78
|
+
|
|
79
|
+
let currentAssistantGroup: (MessageDoc & { streaming?: boolean })[] = [];
|
|
80
|
+
let currentOrder: number | undefined;
|
|
81
|
+
|
|
82
|
+
for (const message of sortedMessages) {
|
|
41
83
|
const coreMessage = message.message && deserializeMessage(message.message);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
84
|
+
if (!coreMessage) continue;
|
|
85
|
+
|
|
86
|
+
if (coreMessage.role === "user" || coreMessage.role === "system") {
|
|
87
|
+
// Finish any current assistant group
|
|
88
|
+
if (currentAssistantGroup.length > 0) {
|
|
89
|
+
groups.push({
|
|
90
|
+
role: "assistant",
|
|
91
|
+
messages: currentAssistantGroup,
|
|
92
|
+
});
|
|
93
|
+
currentAssistantGroup = [];
|
|
94
|
+
currentOrder = undefined;
|
|
95
|
+
}
|
|
96
|
+
// Add singleton group
|
|
97
|
+
groups.push({
|
|
98
|
+
role: coreMessage.role,
|
|
99
|
+
message,
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
// Assistant or tool message
|
|
103
|
+
|
|
104
|
+
// Start new group if order changes or this is the first assistant/tool message
|
|
105
|
+
if (currentOrder !== undefined && message.order !== currentOrder) {
|
|
106
|
+
if (currentAssistantGroup.length > 0) {
|
|
107
|
+
groups.push({
|
|
108
|
+
role: "assistant",
|
|
109
|
+
messages: currentAssistantGroup,
|
|
110
|
+
});
|
|
111
|
+
currentAssistantGroup = [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
currentOrder = message.order;
|
|
116
|
+
currentAssistantGroup.push(message);
|
|
117
|
+
|
|
118
|
+
// End group if this is an assistant message without tool calls
|
|
119
|
+
// But only if we're processing messages in order (which we are now due to sorting)
|
|
120
|
+
if (coreMessage.role === "assistant" && !message.tool) {
|
|
121
|
+
groups.push({
|
|
122
|
+
role: "assistant",
|
|
123
|
+
messages: currentAssistantGroup,
|
|
124
|
+
});
|
|
125
|
+
currentAssistantGroup = [];
|
|
126
|
+
currentOrder = undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Add any remaining assistant group
|
|
132
|
+
if (currentAssistantGroup.length > 0) {
|
|
133
|
+
groups.push({
|
|
134
|
+
role: "assistant",
|
|
135
|
+
messages: currentAssistantGroup,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return groups;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function createSystemUIMessage<
|
|
143
|
+
METADATA = unknown,
|
|
144
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
145
|
+
TOOLS extends UITools = UITools,
|
|
146
|
+
>(
|
|
147
|
+
message: MessageDoc & { streaming?: boolean },
|
|
148
|
+
): UIMessage<METADATA, DATA_PARTS, TOOLS> {
|
|
149
|
+
const text = extractTextFromMessageDoc(message);
|
|
150
|
+
const partCommon = {
|
|
151
|
+
state: message.streaming ? ("streaming" as const) : ("done" as const),
|
|
152
|
+
...(message.providerMetadata
|
|
153
|
+
? { providerMetadata: message.providerMetadata }
|
|
154
|
+
: {}),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
id: message._id,
|
|
159
|
+
_creationTime: message._creationTime,
|
|
160
|
+
order: message.order,
|
|
161
|
+
stepOrder: message.stepOrder,
|
|
162
|
+
status: message.streaming ? ("streaming" as const) : message.status,
|
|
163
|
+
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
|
|
164
|
+
text,
|
|
165
|
+
role: "system",
|
|
166
|
+
agentName: message.agentName,
|
|
167
|
+
parts: [{ type: "text", text, ...partCommon } satisfies TextUIPart],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function extractTextFromMessageDoc(message: MessageDoc): string {
|
|
172
|
+
return (
|
|
173
|
+
message.text || (message.message && extractText(message.message)) || ""
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function createUserUIMessage<
|
|
178
|
+
METADATA = unknown,
|
|
179
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
180
|
+
TOOLS extends UITools = UITools,
|
|
181
|
+
>(
|
|
182
|
+
message: MessageDoc & { streaming?: boolean },
|
|
183
|
+
): UIMessage<METADATA, DATA_PARTS, TOOLS> {
|
|
184
|
+
const text = extractTextFromMessageDoc(message);
|
|
185
|
+
const coreMessage = deserializeMessage(message.message!);
|
|
186
|
+
const content = coreMessage.content;
|
|
187
|
+
const nonStringContent =
|
|
188
|
+
content && typeof content !== "string" ? content : [];
|
|
189
|
+
|
|
190
|
+
const partCommon = {
|
|
191
|
+
state: message.streaming ? ("streaming" as const) : ("done" as const),
|
|
192
|
+
...(message.providerMetadata
|
|
193
|
+
? { providerMetadata: message.providerMetadata }
|
|
194
|
+
: {}),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const parts: UIMessage<METADATA, DATA_PARTS, TOOLS>["parts"] = [];
|
|
198
|
+
if (text && !nonStringContent.length) {
|
|
199
|
+
parts.push({ type: "text", text });
|
|
200
|
+
}
|
|
201
|
+
for (const contentPart of nonStringContent) {
|
|
202
|
+
switch (contentPart.type) {
|
|
203
|
+
case "text":
|
|
204
|
+
parts.push({ type: "text", text: contentPart.text, ...partCommon });
|
|
205
|
+
break;
|
|
206
|
+
case "file":
|
|
207
|
+
case "image":
|
|
208
|
+
parts.push(toUIFilePart(contentPart));
|
|
209
|
+
break;
|
|
210
|
+
default:
|
|
211
|
+
console.warn("Unknown content part type for user", contentPart);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
id: message._id,
|
|
218
|
+
_creationTime: message._creationTime,
|
|
219
|
+
order: message.order,
|
|
220
|
+
stepOrder: message.stepOrder,
|
|
221
|
+
status: message.streaming ? ("streaming" as const) : message.status,
|
|
222
|
+
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
|
|
223
|
+
text,
|
|
224
|
+
role: "user",
|
|
225
|
+
parts,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function createAssistantUIMessage<
|
|
230
|
+
METADATA = unknown,
|
|
231
|
+
DATA_PARTS extends UIDataTypes = UIDataTypes,
|
|
232
|
+
TOOLS extends UITools = UITools,
|
|
233
|
+
>(
|
|
234
|
+
groupUnordered: (MessageDoc & { streaming?: boolean })[],
|
|
235
|
+
): UIMessage<METADATA, DATA_PARTS, TOOLS> {
|
|
236
|
+
const group = sorted(groupUnordered);
|
|
237
|
+
const firstMessage = group[0];
|
|
238
|
+
|
|
239
|
+
// Use first message for special fields
|
|
240
|
+
const common = {
|
|
241
|
+
id: firstMessage._id,
|
|
242
|
+
_creationTime: firstMessage._creationTime,
|
|
243
|
+
order: firstMessage.order,
|
|
244
|
+
stepOrder: firstMessage.stepOrder,
|
|
245
|
+
key: `${firstMessage.threadId}-${firstMessage.order}-${firstMessage.stepOrder}`,
|
|
246
|
+
agentName: firstMessage.agentName,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Concatenate text from all messages in group
|
|
250
|
+
const allText = group
|
|
251
|
+
.map((msg) => msg.text || ((msg.message && extractText(msg.message)) ?? ""))
|
|
252
|
+
.filter(Boolean)
|
|
253
|
+
.join("");
|
|
254
|
+
|
|
255
|
+
// Get status from last message
|
|
256
|
+
const lastMessage = group[group.length - 1];
|
|
257
|
+
const status = lastMessage.streaming
|
|
258
|
+
? ("streaming" as const)
|
|
259
|
+
: lastMessage.status;
|
|
260
|
+
|
|
261
|
+
// Collect all parts from all messages
|
|
262
|
+
const allParts: UIMessage<METADATA, DATA_PARTS, TOOLS>["parts"] = [];
|
|
263
|
+
|
|
264
|
+
for (const message of group) {
|
|
265
|
+
const coreMessage = message.message && deserializeMessage(message.message);
|
|
266
|
+
if (!coreMessage) continue;
|
|
267
|
+
|
|
268
|
+
const content = coreMessage.content;
|
|
45
269
|
const nonStringContent =
|
|
46
270
|
content && typeof content !== "string" ? content : [];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
_creationTime: message._creationTime,
|
|
51
|
-
order: message.order,
|
|
52
|
-
stepOrder: message.stepOrder,
|
|
53
|
-
status: message.streaming ? ("streaming" as const) : message.status,
|
|
54
|
-
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
|
|
55
|
-
text,
|
|
56
|
-
} satisfies Partial<UIMessage<METADATA, DATA_PARTS, TOOLS>>;
|
|
271
|
+
const text =
|
|
272
|
+
message.text || ((message.message && extractText(message.message)) ?? "");
|
|
273
|
+
|
|
57
274
|
const partCommon = {
|
|
58
275
|
state: message.streaming ? ("streaming" as const) : ("done" as const),
|
|
59
276
|
...(message.providerMetadata
|
|
60
277
|
? { providerMetadata: message.providerMetadata }
|
|
61
278
|
: {}),
|
|
62
279
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
280
|
+
|
|
281
|
+
// Add reasoning parts
|
|
282
|
+
if (
|
|
283
|
+
message.reasoning &&
|
|
284
|
+
!nonStringContent.some((c) => c.type === "reasoning")
|
|
285
|
+
) {
|
|
286
|
+
allParts.push({
|
|
287
|
+
type: "reasoning",
|
|
288
|
+
text: message.reasoning,
|
|
289
|
+
...partCommon,
|
|
290
|
+
} satisfies ReasoningUIPart);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Add text parts if no structured content
|
|
294
|
+
if (text && !nonStringContent.length) {
|
|
295
|
+
allParts.push({
|
|
296
|
+
type: "text",
|
|
297
|
+
text: text,
|
|
298
|
+
...partCommon,
|
|
299
|
+
} satisfies TextUIPart);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Add all structured content parts
|
|
303
|
+
for (const contentPart of nonStringContent) {
|
|
304
|
+
switch (contentPart.type) {
|
|
305
|
+
case "text":
|
|
306
|
+
allParts.push({
|
|
307
|
+
...partCommon,
|
|
308
|
+
...contentPart,
|
|
309
|
+
} satisfies TextUIPart);
|
|
310
|
+
break;
|
|
311
|
+
case "reasoning":
|
|
312
|
+
allParts.push({
|
|
313
|
+
...partCommon,
|
|
314
|
+
...contentPart,
|
|
315
|
+
} satisfies ReasoningUIPart);
|
|
316
|
+
break;
|
|
317
|
+
case "file":
|
|
318
|
+
case "image":
|
|
319
|
+
allParts.push(toUIFilePart(contentPart));
|
|
320
|
+
break;
|
|
321
|
+
case "tool-call": {
|
|
322
|
+
allParts.push({
|
|
323
|
+
type: "step-start",
|
|
324
|
+
} satisfies StepStartUIPart);
|
|
325
|
+
const toolPart: ToolUIPart<TOOLS> = {
|
|
326
|
+
type: `tool-${contentPart.toolName as keyof TOOLS & string}`,
|
|
327
|
+
toolCallId: contentPart.toolCallId,
|
|
328
|
+
input: contentPart.input as DeepPartial<
|
|
329
|
+
TOOLS[keyof TOOLS & string]["input"]
|
|
330
|
+
>,
|
|
331
|
+
providerExecuted: contentPart.providerExecuted,
|
|
332
|
+
...(message.streaming
|
|
333
|
+
? { state: "input-streaming" }
|
|
334
|
+
: {
|
|
335
|
+
state: "input-available",
|
|
336
|
+
callProviderMetadata: message.providerMetadata,
|
|
337
|
+
}),
|
|
338
|
+
};
|
|
339
|
+
allParts.push(toolPart);
|
|
340
|
+
break;
|
|
84
341
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
} satisfies TextUIPart);
|
|
126
|
-
}
|
|
127
|
-
for (const contentPart of nonStringContent) {
|
|
128
|
-
switch (contentPart.type) {
|
|
129
|
-
case "text":
|
|
130
|
-
assistantMessage.parts.push({
|
|
131
|
-
...partCommon,
|
|
132
|
-
...contentPart,
|
|
133
|
-
} satisfies TextUIPart);
|
|
134
|
-
break;
|
|
135
|
-
case "reasoning":
|
|
136
|
-
assistantMessage.parts.push({
|
|
137
|
-
...partCommon,
|
|
138
|
-
...contentPart,
|
|
139
|
-
} satisfies ReasoningUIPart);
|
|
140
|
-
break;
|
|
141
|
-
case "file":
|
|
142
|
-
case "image":
|
|
143
|
-
assistantMessage.parts.push(toUIFilePart(contentPart));
|
|
144
|
-
break;
|
|
145
|
-
case "tool-call": {
|
|
146
|
-
assistantMessage.parts.push({
|
|
147
|
-
type: "step-start",
|
|
148
|
-
} satisfies StepStartUIPart);
|
|
149
|
-
const toolPart: ToolUIPart<TOOLS> = {
|
|
150
|
-
type: `tool-${contentPart.toolName as keyof TOOLS & string}`,
|
|
151
|
-
toolCallId: contentPart.toolCallId,
|
|
152
|
-
input: contentPart.input as DeepPartial<
|
|
153
|
-
TOOLS[keyof TOOLS & string]["input"]
|
|
154
|
-
>,
|
|
155
|
-
providerExecuted: contentPart.providerExecuted,
|
|
156
|
-
...(message.streaming
|
|
157
|
-
? { state: "input-streaming" }
|
|
158
|
-
: {
|
|
159
|
-
state: "input-available",
|
|
160
|
-
callProviderMetadata: message.providerMetadata,
|
|
161
|
-
}),
|
|
162
|
-
};
|
|
163
|
-
assistantMessage.parts.push(toolPart);
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
case "tool-result": {
|
|
167
|
-
const call = assistantMessage.parts.find(
|
|
168
|
-
(part) =>
|
|
169
|
-
part.type === `tool-${contentPart.toolName}` &&
|
|
170
|
-
"toolCallId" in part &&
|
|
171
|
-
part.toolCallId === contentPart.toolCallId,
|
|
172
|
-
) as ToolUIPart | undefined;
|
|
173
|
-
if (call) {
|
|
174
|
-
if (message.error) {
|
|
175
|
-
call.state = "output-error";
|
|
176
|
-
call.errorText = message.error;
|
|
177
|
-
call.output = contentPart.output;
|
|
178
|
-
} else {
|
|
179
|
-
call.state = "output-available";
|
|
180
|
-
call.output =
|
|
342
|
+
case "tool-result": {
|
|
343
|
+
const call = allParts.find(
|
|
344
|
+
(part) =>
|
|
345
|
+
part.type === `tool-${contentPart.toolName}` &&
|
|
346
|
+
"toolCallId" in part &&
|
|
347
|
+
part.toolCallId === contentPart.toolCallId,
|
|
348
|
+
) as ToolUIPart | undefined;
|
|
349
|
+
if (call) {
|
|
350
|
+
if (message.error) {
|
|
351
|
+
call.state = "output-error";
|
|
352
|
+
call.errorText = message.error;
|
|
353
|
+
call.output = contentPart.output;
|
|
354
|
+
} else {
|
|
355
|
+
call.state = "output-available";
|
|
356
|
+
call.output =
|
|
357
|
+
contentPart.output?.type === "json"
|
|
358
|
+
? contentPart.output.value
|
|
359
|
+
: contentPart.output;
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
console.warn(
|
|
363
|
+
"Tool result without preceding tool call.. adding anyways",
|
|
364
|
+
contentPart,
|
|
365
|
+
);
|
|
366
|
+
if (message.error) {
|
|
367
|
+
allParts.push({
|
|
368
|
+
type: `tool-${contentPart.toolName}`,
|
|
369
|
+
toolCallId: contentPart.toolCallId,
|
|
370
|
+
state: "output-error",
|
|
371
|
+
input: undefined,
|
|
372
|
+
errorText: message.error,
|
|
373
|
+
callProviderMetadata: message.providerMetadata,
|
|
374
|
+
} satisfies ToolUIPart<TOOLS>);
|
|
375
|
+
} else {
|
|
376
|
+
allParts.push({
|
|
377
|
+
type: `tool-${contentPart.toolName}`,
|
|
378
|
+
toolCallId: contentPart.toolCallId,
|
|
379
|
+
state: "output-available",
|
|
380
|
+
input: undefined,
|
|
381
|
+
output:
|
|
181
382
|
contentPart.output?.type === "json"
|
|
182
383
|
? contentPart.output.value
|
|
183
|
-
: contentPart.output
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// call.providerExecuted = contentPart.providerExecuted;
|
|
187
|
-
}
|
|
188
|
-
} else {
|
|
189
|
-
console.warn(
|
|
190
|
-
"Tool result without preceding tool call.. adding anyways",
|
|
191
|
-
contentPart,
|
|
192
|
-
);
|
|
193
|
-
if (message.error) {
|
|
194
|
-
assistantMessage.parts.push({
|
|
195
|
-
type: `tool-${contentPart.toolName}`,
|
|
196
|
-
toolCallId: contentPart.toolCallId,
|
|
197
|
-
state: "output-error",
|
|
198
|
-
input: undefined,
|
|
199
|
-
errorText: message.error,
|
|
200
|
-
// Technically we could pull this from the doc.message
|
|
201
|
-
// but the ModelMessage doesn't have it
|
|
202
|
-
// providerExecuted: contentPart.providerExecuted,
|
|
203
|
-
callProviderMetadata: message.providerMetadata,
|
|
204
|
-
} satisfies ToolUIPart<TOOLS>);
|
|
205
|
-
} else {
|
|
206
|
-
assistantMessage.parts.push({
|
|
207
|
-
type: `tool-${contentPart.toolName}`,
|
|
208
|
-
toolCallId: contentPart.toolCallId,
|
|
209
|
-
state: "output-available",
|
|
210
|
-
input: undefined,
|
|
211
|
-
output:
|
|
212
|
-
contentPart.output?.type === "json"
|
|
213
|
-
? contentPart.output.value
|
|
214
|
-
: contentPart.output,
|
|
215
|
-
callProviderMetadata: message.providerMetadata,
|
|
216
|
-
} satisfies ToolUIPart<TOOLS>);
|
|
217
|
-
}
|
|
384
|
+
: contentPart.output,
|
|
385
|
+
callProviderMetadata: message.providerMetadata,
|
|
386
|
+
} satisfies ToolUIPart<TOOLS>);
|
|
218
387
|
}
|
|
219
|
-
break;
|
|
220
388
|
}
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
for (const source of message.sources ?? []) {
|
|
224
|
-
if (source.sourceType === "url") {
|
|
225
|
-
assistantMessage.parts.push({
|
|
226
|
-
type: "source-url",
|
|
227
|
-
url: source.url!,
|
|
228
|
-
sourceId: source.id,
|
|
229
|
-
providerMetadata: message.providerMetadata,
|
|
230
|
-
title: source.title,
|
|
231
|
-
} satisfies SourceUrlUIPart);
|
|
232
|
-
} else {
|
|
233
|
-
assistantMessage.parts.push({
|
|
234
|
-
type: "source-document",
|
|
235
|
-
mediaType: source.mediaType,
|
|
236
|
-
sourceId: source.id,
|
|
237
|
-
title: source.title,
|
|
238
|
-
filename: source.filename,
|
|
239
|
-
providerMetadata: message.providerMetadata,
|
|
240
|
-
} satisfies SourceDocumentUIPart);
|
|
389
|
+
break;
|
|
241
390
|
}
|
|
242
391
|
}
|
|
243
392
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
393
|
+
|
|
394
|
+
// Add source parts
|
|
395
|
+
for (const source of message.sources ?? []) {
|
|
396
|
+
if (source.sourceType === "url") {
|
|
397
|
+
allParts.push({
|
|
398
|
+
type: "source-url",
|
|
399
|
+
url: source.url!,
|
|
400
|
+
sourceId: source.id,
|
|
401
|
+
providerMetadata: message.providerMetadata,
|
|
402
|
+
title: source.title,
|
|
403
|
+
} satisfies SourceUrlUIPart);
|
|
404
|
+
} else {
|
|
405
|
+
allParts.push({
|
|
406
|
+
type: "source-document",
|
|
407
|
+
mediaType: source.mediaType,
|
|
408
|
+
sourceId: source.id,
|
|
409
|
+
title: source.title,
|
|
410
|
+
filename: source.filename,
|
|
411
|
+
providerMetadata: message.providerMetadata,
|
|
412
|
+
} satisfies SourceDocumentUIPart);
|
|
413
|
+
}
|
|
247
414
|
}
|
|
248
415
|
}
|
|
249
|
-
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
...common,
|
|
419
|
+
role: "assistant",
|
|
420
|
+
text: allText,
|
|
421
|
+
status,
|
|
422
|
+
parts: allParts,
|
|
423
|
+
};
|
|
250
424
|
}
|
|
@@ -30,7 +30,9 @@ export function useSmoothText(
|
|
|
30
30
|
text: string,
|
|
31
31
|
{ charsPerSec = 256, startStreaming = false }: SmoothTextOptions = {},
|
|
32
32
|
): [string, { cursor: number; isStreaming: boolean }] {
|
|
33
|
-
const [visibleText, setVisibleText] = useState(
|
|
33
|
+
const [visibleText, setVisibleText] = useState(
|
|
34
|
+
startStreaming ? "" : text || "",
|
|
35
|
+
);
|
|
34
36
|
const smoothState = useRef({
|
|
35
37
|
tick: Date.now() + (visibleText.length * 1000) / charsPerSec,
|
|
36
38
|
cursor: visibleText.length,
|
|
@@ -45,18 +47,28 @@ export function useSmoothText(
|
|
|
45
47
|
if (!isStreaming) {
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
smoothState.current.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
if (
|
|
51
|
+
smoothState.current.cursor === 0 &&
|
|
52
|
+
smoothState.current.initialLength === 0
|
|
53
|
+
) {
|
|
54
|
+
smoothState.current.cursor = text.length;
|
|
55
|
+
smoothState.current.start = Date.now();
|
|
56
|
+
smoothState.current.initialLength = text.length;
|
|
57
|
+
smoothState.current.charsPerMs = charsPerSec / 1000;
|
|
58
|
+
} else {
|
|
59
|
+
const latestCharsPerMs =
|
|
60
|
+
(text.length - smoothState.current.initialLength) /
|
|
61
|
+
(Date.now() - smoothState.current.start);
|
|
62
|
+
// Smooth out the charsPerSec by averaging it with the previous value.
|
|
63
|
+
smoothState.current.charsPerMs = Math.min(
|
|
64
|
+
(2 * latestCharsPerMs + smoothState.current.charsPerMs) / 3,
|
|
65
|
+
smoothState.current.charsPerMs * 2,
|
|
66
|
+
);
|
|
67
|
+
smoothState.current.tick = Math.max(
|
|
68
|
+
smoothState.current.tick,
|
|
69
|
+
Date.now() - 2 * MS_PER_FRAME,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
60
72
|
|
|
61
73
|
function update() {
|
|
62
74
|
if (smoothState.current.cursor >= text.length) {
|
package/src/shared.ts
CHANGED
|
@@ -30,8 +30,22 @@ export function extractText(message: Message | ModelMessage) {
|
|
|
30
30
|
.filter((c) => c.type === "text")
|
|
31
31
|
.map((c) => c.text)
|
|
32
32
|
.join("");
|
|
33
|
+
case "system":
|
|
34
|
+
return message.content;
|
|
35
|
+
// we don't extract text from tool messages
|
|
33
36
|
}
|
|
34
37
|
return undefined;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
export const DEFAULT_MESSAGE_RANGE = { before: 2, after: 1 };
|
|
41
|
+
|
|
42
|
+
export function sorted<T extends { order: number; stepOrder: number }>(
|
|
43
|
+
messages: T[],
|
|
44
|
+
order: "asc" | "desc" = "asc",
|
|
45
|
+
): T[] {
|
|
46
|
+
return [...messages].sort(
|
|
47
|
+
order === "asc"
|
|
48
|
+
? (a, b) => a.order - b.order || a.stepOrder - b.stepOrder
|
|
49
|
+
: (a, b) => b.order - a.order || b.stepOrder - a.stepOrder,
|
|
50
|
+
);
|
|
51
|
+
}
|