@convex-dev/agent 0.3.2 → 0.6.0-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/UIMessages.d.ts +3 -2
- package/dist/UIMessages.d.ts.map +1 -1
- package/dist/UIMessages.js +100 -19
- package/dist/UIMessages.js.map +1 -1
- package/dist/client/createTool.d.ts +129 -25
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +65 -8
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/definePlaygroundAPI.d.ts +560 -35
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/files.d.ts.map +1 -1
- package/dist/client/files.js +4 -2
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +462 -37
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +26 -7
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +114 -9
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/mockModel.d.ts +17 -17
- package/dist/client/mockModel.d.ts.map +1 -1
- package/dist/client/mockModel.js +8 -6
- package/dist/client/mockModel.js.map +1 -1
- package/dist/client/saveInputMessages.d.ts +1 -1
- package/dist/client/saveInputMessages.d.ts.map +1 -1
- package/dist/client/saveInputMessages.js +1 -1
- package/dist/client/saveInputMessages.js.map +1 -1
- package/dist/client/search.d.ts +132 -14
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js +66 -19
- package/dist/client/search.js.map +1 -1
- package/dist/client/streamText.d.ts +4 -4
- package/dist/client/streamText.d.ts.map +1 -1
- package/dist/client/streamText.js.map +1 -1
- package/dist/client/streaming.d.ts +1491 -87
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/types.d.ts +26 -6
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +862 -19
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/files.d.ts +12 -6
- package/dist/component/files.d.ts.map +1 -1
- package/dist/component/files.js +10 -2
- package/dist/component/files.js.map +1 -1
- package/dist/component/messages.d.ts +1247 -92
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/schema.d.ts +2823 -217
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +3 -1
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.js +1 -1
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts +10 -10
- package/dist/component/vector/index.d.ts +1 -1
- package/dist/deltas.d.ts.map +1 -1
- package/dist/deltas.js +40 -5
- package/dist/deltas.js.map +1 -1
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +117 -25
- package/dist/mapping.js.map +1 -1
- package/dist/react/useThreadMessages.d.ts.map +1 -1
- package/dist/react/useThreadMessages.js +17 -9
- package/dist/react/useThreadMessages.js.map +1 -1
- package/dist/shared.d.ts +2 -2
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js.map +1 -1
- package/dist/validators.d.ts +12196 -624
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +152 -9
- package/dist/validators.js.map +1 -1
- package/package.json +24 -22
- package/src/UIMessages.combineUIMessages.test.ts +239 -0
- package/src/UIMessages.ts +184 -66
- package/src/client/createTool.ts +291 -67
- package/src/client/files.ts +4 -2
- package/src/client/index.test.ts +1 -0
- package/src/client/index.ts +46 -25
- package/src/client/mockModel.ts +36 -34
- package/src/client/saveInputMessages.ts +2 -2
- package/src/client/search.test.ts +166 -0
- package/src/client/search.ts +121 -38
- package/src/client/streamText.ts +5 -5
- package/src/client/types.ts +31 -18
- package/src/component/_generated/component.ts +2329 -338
- package/src/component/files.ts +11 -2
- package/src/component/messages.test.ts +7 -7
- package/src/component/schema.ts +3 -1
- package/src/component/setup.test.ts +7 -0
- package/src/component/streams.ts +1 -1
- package/src/deltas.test.ts +90 -0
- package/src/deltas.ts +47 -8
- package/src/fromUIMessages.test.ts +32 -27
- package/src/mapping.test.ts +48 -0
- package/src/mapping.ts +234 -142
- package/src/react/useThreadMessages.ts +21 -9
- package/src/shared.ts +2 -0
- package/src/toUIMessages.test.ts +539 -1
- package/src/validators.ts +179 -20
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { combineUIMessages, type UIMessage } from "./UIMessages.js";
|
|
3
|
+
|
|
4
|
+
describe("combineUIMessages", () => {
|
|
5
|
+
it("should preserve all tool calls when combining messages", () => {
|
|
6
|
+
const messages: UIMessage[] = [
|
|
7
|
+
{
|
|
8
|
+
id: "msg1",
|
|
9
|
+
key: "thread-1-0",
|
|
10
|
+
order: 1,
|
|
11
|
+
stepOrder: 0,
|
|
12
|
+
status: "streaming",
|
|
13
|
+
role: "assistant",
|
|
14
|
+
parts: [
|
|
15
|
+
{
|
|
16
|
+
type: "tool-toolA",
|
|
17
|
+
toolCallId: "call_A",
|
|
18
|
+
state: "input-available",
|
|
19
|
+
input: {},
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
text: "",
|
|
23
|
+
_creationTime: Date.now(),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "msg1",
|
|
27
|
+
key: "thread-1-0",
|
|
28
|
+
order: 1,
|
|
29
|
+
stepOrder: 0,
|
|
30
|
+
status: "streaming",
|
|
31
|
+
role: "assistant",
|
|
32
|
+
parts: [
|
|
33
|
+
{
|
|
34
|
+
type: "tool-toolB",
|
|
35
|
+
toolCallId: "call_B",
|
|
36
|
+
state: "input-available",
|
|
37
|
+
input: {},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
text: "",
|
|
41
|
+
_creationTime: Date.now(),
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const result = combineUIMessages(messages);
|
|
46
|
+
|
|
47
|
+
expect(result).toHaveLength(1);
|
|
48
|
+
expect(result[0].parts).toHaveLength(2);
|
|
49
|
+
|
|
50
|
+
const toolCallIds = result[0].parts
|
|
51
|
+
.filter((p) => p.type.startsWith("tool-"))
|
|
52
|
+
.map((p: any) => p.toolCallId);
|
|
53
|
+
|
|
54
|
+
expect(toolCallIds).toContain("call_A");
|
|
55
|
+
expect(toolCallIds).toContain("call_B");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should accumulate tool calls progressively (issue #182)", () => {
|
|
59
|
+
// Simulating: A(started) → B → C → A(result)
|
|
60
|
+
const messages: UIMessage[] = [
|
|
61
|
+
{
|
|
62
|
+
id: "msg1",
|
|
63
|
+
key: "thread-1-0",
|
|
64
|
+
order: 1,
|
|
65
|
+
stepOrder: 0,
|
|
66
|
+
status: "streaming",
|
|
67
|
+
role: "assistant",
|
|
68
|
+
parts: [
|
|
69
|
+
{
|
|
70
|
+
type: "tool-toolA",
|
|
71
|
+
toolCallId: "call_A",
|
|
72
|
+
state: "input-available",
|
|
73
|
+
input: {},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
text: "",
|
|
77
|
+
_creationTime: Date.now(),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "msg1",
|
|
81
|
+
key: "thread-1-0",
|
|
82
|
+
order: 1,
|
|
83
|
+
stepOrder: 0,
|
|
84
|
+
status: "streaming",
|
|
85
|
+
role: "assistant",
|
|
86
|
+
parts: [
|
|
87
|
+
{
|
|
88
|
+
type: "tool-toolA",
|
|
89
|
+
toolCallId: "call_A",
|
|
90
|
+
state: "input-available",
|
|
91
|
+
input: {},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "tool-toolB",
|
|
95
|
+
toolCallId: "call_B",
|
|
96
|
+
state: "input-available",
|
|
97
|
+
input: {},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
text: "",
|
|
101
|
+
_creationTime: Date.now(),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "msg1",
|
|
105
|
+
key: "thread-1-0",
|
|
106
|
+
order: 1,
|
|
107
|
+
stepOrder: 0,
|
|
108
|
+
status: "streaming",
|
|
109
|
+
role: "assistant",
|
|
110
|
+
parts: [
|
|
111
|
+
{
|
|
112
|
+
type: "tool-toolA",
|
|
113
|
+
toolCallId: "call_A",
|
|
114
|
+
state: "input-available",
|
|
115
|
+
input: {},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: "tool-toolB",
|
|
119
|
+
toolCallId: "call_B",
|
|
120
|
+
state: "input-available",
|
|
121
|
+
input: {},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: "tool-toolC",
|
|
125
|
+
toolCallId: "call_C",
|
|
126
|
+
state: "input-available",
|
|
127
|
+
input: {},
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
text: "",
|
|
131
|
+
_creationTime: Date.now(),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "msg1",
|
|
135
|
+
key: "thread-1-0",
|
|
136
|
+
order: 1,
|
|
137
|
+
stepOrder: 0,
|
|
138
|
+
status: "success",
|
|
139
|
+
role: "assistant",
|
|
140
|
+
parts: [
|
|
141
|
+
{
|
|
142
|
+
type: "tool-toolA",
|
|
143
|
+
toolCallId: "call_A",
|
|
144
|
+
state: "output-available",
|
|
145
|
+
input: {},
|
|
146
|
+
output: "success",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: "tool-toolB",
|
|
150
|
+
toolCallId: "call_B",
|
|
151
|
+
state: "input-available",
|
|
152
|
+
input: {},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: "tool-toolC",
|
|
156
|
+
toolCallId: "call_C",
|
|
157
|
+
state: "input-available",
|
|
158
|
+
input: {},
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
text: "",
|
|
162
|
+
_creationTime: Date.now(),
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const result = combineUIMessages(messages);
|
|
167
|
+
|
|
168
|
+
expect(result).toHaveLength(1);
|
|
169
|
+
expect(result[0].parts).toHaveLength(3);
|
|
170
|
+
|
|
171
|
+
const toolCallIds = result[0].parts
|
|
172
|
+
.filter((p) => p.type.startsWith("tool-"))
|
|
173
|
+
.map((p: any) => p.toolCallId);
|
|
174
|
+
|
|
175
|
+
// All tool calls should be present
|
|
176
|
+
expect(toolCallIds).toContain("call_A");
|
|
177
|
+
expect(toolCallIds).toContain("call_B");
|
|
178
|
+
expect(toolCallIds).toContain("call_C");
|
|
179
|
+
|
|
180
|
+
// Tool A should have the final state (output-available)
|
|
181
|
+
const toolA = result[0].parts.find(
|
|
182
|
+
(p: any) => p.type === "tool-toolA" && p.toolCallId === "call_A",
|
|
183
|
+
) as any;
|
|
184
|
+
expect(toolA.state).toBe("output-available");
|
|
185
|
+
expect(toolA.output).toBe("success");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should merge tool calls with same toolCallId", () => {
|
|
189
|
+
const messages: UIMessage[] = [
|
|
190
|
+
{
|
|
191
|
+
id: "msg1",
|
|
192
|
+
key: "thread-1-0",
|
|
193
|
+
order: 1,
|
|
194
|
+
stepOrder: 0,
|
|
195
|
+
status: "streaming",
|
|
196
|
+
role: "assistant",
|
|
197
|
+
parts: [
|
|
198
|
+
{
|
|
199
|
+
type: "tool-toolA",
|
|
200
|
+
toolCallId: "call_A",
|
|
201
|
+
state: "input-available",
|
|
202
|
+
input: { test: "input" },
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
text: "",
|
|
206
|
+
_creationTime: Date.now(),
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: "msg1",
|
|
210
|
+
key: "thread-1-0",
|
|
211
|
+
order: 1,
|
|
212
|
+
stepOrder: 0,
|
|
213
|
+
status: "success",
|
|
214
|
+
role: "assistant",
|
|
215
|
+
parts: [
|
|
216
|
+
{
|
|
217
|
+
type: "tool-toolA",
|
|
218
|
+
toolCallId: "call_A",
|
|
219
|
+
state: "output-available",
|
|
220
|
+
input: { test: "input" },
|
|
221
|
+
output: "completed",
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
text: "",
|
|
225
|
+
_creationTime: Date.now(),
|
|
226
|
+
},
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const result = combineUIMessages(messages);
|
|
230
|
+
|
|
231
|
+
expect(result).toHaveLength(1);
|
|
232
|
+
expect(result[0].parts).toHaveLength(1);
|
|
233
|
+
|
|
234
|
+
const toolCall = result[0].parts[0] as any;
|
|
235
|
+
expect(toolCall.toolCallId).toBe("call_A");
|
|
236
|
+
expect(toolCall.state).toBe("output-available");
|
|
237
|
+
expect(toolCall.output).toBe("completed");
|
|
238
|
+
});
|
|
239
|
+
});
|
package/src/UIMessages.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type SourceUrlUIPart,
|
|
9
9
|
type StepStartUIPart,
|
|
10
10
|
type TextUIPart,
|
|
11
|
+
type ToolResultPart,
|
|
11
12
|
type ToolUIPart,
|
|
12
13
|
type UIDataTypes,
|
|
13
14
|
type UITools,
|
|
@@ -42,6 +43,7 @@ export type UIMessage<
|
|
|
42
43
|
stepOrder: number;
|
|
43
44
|
status: UIStatus;
|
|
44
45
|
agentName?: string;
|
|
46
|
+
userId?: string;
|
|
45
47
|
text: string;
|
|
46
48
|
_creationTime: number;
|
|
47
49
|
};
|
|
@@ -53,7 +55,7 @@ export type UIMessage<
|
|
|
53
55
|
* @param meta - The metadata to add to the MessageDocs.
|
|
54
56
|
* @returns
|
|
55
57
|
*/
|
|
56
|
-
export function fromUIMessages<METADATA = unknown>(
|
|
58
|
+
export async function fromUIMessages<METADATA = unknown>(
|
|
57
59
|
messages: UIMessage<METADATA>[],
|
|
58
60
|
meta: {
|
|
59
61
|
threadId: string;
|
|
@@ -63,58 +65,71 @@ export function fromUIMessages<METADATA = unknown>(
|
|
|
63
65
|
providerOptions?: ProviderOptions;
|
|
64
66
|
metadata?: METADATA;
|
|
65
67
|
},
|
|
66
|
-
): (MessageDoc & { streaming: boolean; metadata?: METADATA })[] {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const tool = isTool(message);
|
|
93
|
-
const doc: MessageDoc & { streaming: boolean; metadata?: METADATA } = {
|
|
94
|
-
...commonFields,
|
|
95
|
-
_id: uiMessage.id + `-${i}`,
|
|
96
|
-
stepOrder: stepOrder + i,
|
|
97
|
-
message,
|
|
98
|
-
tool,
|
|
99
|
-
text: extractText(message),
|
|
100
|
-
reasoning: extractReasoning(message),
|
|
101
|
-
finishReason: tool ? "tool-calls" : "stop",
|
|
102
|
-
sources: fromSourceParts(uiMessage.parts),
|
|
103
|
-
};
|
|
104
|
-
if (Array.isArray(modelMessage.content)) {
|
|
105
|
-
const providerOptions = modelMessage.content.find(
|
|
106
|
-
(c) => c.providerOptions,
|
|
107
|
-
)?.providerOptions;
|
|
108
|
-
if (providerOptions) {
|
|
109
|
-
// convertToModelMessages changes providerMetadata to providerOptions
|
|
110
|
-
doc.providerMetadata = providerOptions;
|
|
111
|
-
doc.providerOptions ??= providerOptions;
|
|
68
|
+
): Promise<(MessageDoc & { streaming: boolean; metadata?: METADATA })[]> {
|
|
69
|
+
const nested = await Promise.all(
|
|
70
|
+
messages.map(async (uiMessage) => {
|
|
71
|
+
const stepOrder = uiMessage.stepOrder;
|
|
72
|
+
const commonFields = {
|
|
73
|
+
...pick(meta, [
|
|
74
|
+
"threadId",
|
|
75
|
+
"userId",
|
|
76
|
+
"model",
|
|
77
|
+
"provider",
|
|
78
|
+
"providerOptions",
|
|
79
|
+
"metadata",
|
|
80
|
+
]),
|
|
81
|
+
...omit(uiMessage, ["parts", "role", "key", "text", "userId"]),
|
|
82
|
+
userId: uiMessage.userId ?? meta.userId,
|
|
83
|
+
status: uiMessage.status === "streaming" ? "pending" : "success",
|
|
84
|
+
streaming: uiMessage.status === "streaming",
|
|
85
|
+
// to override
|
|
86
|
+
_id: uiMessage.id,
|
|
87
|
+
tool: false,
|
|
88
|
+
} satisfies MessageDoc & { streaming: boolean; metadata?: METADATA };
|
|
89
|
+
const modelMessages = await convertToModelMessages([uiMessage]);
|
|
90
|
+
return modelMessages
|
|
91
|
+
.map((modelMessage, i) => {
|
|
92
|
+
if (modelMessage.content.length === 0) {
|
|
93
|
+
return undefined;
|
|
112
94
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
95
|
+
const message = fromModelMessage(modelMessage);
|
|
96
|
+
const tool = isTool(message);
|
|
97
|
+
const doc: MessageDoc & { streaming: boolean; metadata?: METADATA } =
|
|
98
|
+
{
|
|
99
|
+
...commonFields,
|
|
100
|
+
_id: uiMessage.id + `-${i}`,
|
|
101
|
+
stepOrder: stepOrder + i,
|
|
102
|
+
message,
|
|
103
|
+
tool,
|
|
104
|
+
text: extractText(message),
|
|
105
|
+
reasoning: extractReasoning(message),
|
|
106
|
+
finishReason: tool ? "tool-calls" : "stop",
|
|
107
|
+
sources: fromSourceParts(uiMessage.parts),
|
|
108
|
+
};
|
|
109
|
+
if (Array.isArray(modelMessage.content)) {
|
|
110
|
+
// Find a content part with providerOptions (type assertion needed for SDK compatibility)
|
|
111
|
+
const partWithProviderOptions = modelMessage.content.find(
|
|
112
|
+
(c): c is typeof c & { providerOptions: unknown } =>
|
|
113
|
+
"providerOptions" in c && c.providerOptions !== undefined,
|
|
114
|
+
);
|
|
115
|
+
if (partWithProviderOptions?.providerOptions) {
|
|
116
|
+
// convertToModelMessages changes providerMetadata to providerOptions
|
|
117
|
+
const providerOptions =
|
|
118
|
+
partWithProviderOptions.providerOptions as
|
|
119
|
+
| Record<string, Record<string, unknown>>
|
|
120
|
+
| undefined;
|
|
121
|
+
if (providerOptions) {
|
|
122
|
+
doc.providerMetadata = providerOptions;
|
|
123
|
+
doc.providerOptions ??= providerOptions;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return doc;
|
|
128
|
+
})
|
|
129
|
+
.filter((d) => d !== undefined);
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
return nested.flat();
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
function fromSourceParts(parts: UIMessage["parts"]): Infer<typeof vSource>[] {
|
|
@@ -288,6 +303,7 @@ function createSystemUIMessage<
|
|
|
288
303
|
text,
|
|
289
304
|
role: "system",
|
|
290
305
|
agentName: message.agentName,
|
|
306
|
+
userId: message.userId,
|
|
291
307
|
parts: [{ type: "text", text, ...partCommon } satisfies TextUIPart],
|
|
292
308
|
metadata: message.metadata,
|
|
293
309
|
};
|
|
@@ -347,6 +363,7 @@ function createUserUIMessage<
|
|
|
347
363
|
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
|
|
348
364
|
text,
|
|
349
365
|
role: "user",
|
|
366
|
+
userId: message.userId,
|
|
350
367
|
parts,
|
|
351
368
|
metadata: message.metadata,
|
|
352
369
|
};
|
|
@@ -370,6 +387,7 @@ function createAssistantUIMessage<
|
|
|
370
387
|
stepOrder: firstMessage.stepOrder,
|
|
371
388
|
key: `${firstMessage.threadId}-${firstMessage.order}-${firstMessage.stepOrder}`,
|
|
372
389
|
agentName: firstMessage.agentName,
|
|
390
|
+
userId: firstMessage.userId,
|
|
373
391
|
};
|
|
374
392
|
|
|
375
393
|
// Get status from last message
|
|
@@ -459,10 +477,50 @@ function createAssistantUIMessage<
|
|
|
459
477
|
break;
|
|
460
478
|
}
|
|
461
479
|
case "tool-result": {
|
|
480
|
+
const typedPart = contentPart as unknown as ToolResultPart & {
|
|
481
|
+
output: { type: string; value?: unknown; reason?: string };
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// Check if this is an execution-denied result
|
|
485
|
+
if (typedPart.output?.type === "execution-denied") {
|
|
486
|
+
const call = allParts.find(
|
|
487
|
+
(part) =>
|
|
488
|
+
part.type === `tool-${contentPart.toolName}` &&
|
|
489
|
+
"toolCallId" in part &&
|
|
490
|
+
part.toolCallId === contentPart.toolCallId,
|
|
491
|
+
) as ToolUIPart | undefined;
|
|
492
|
+
|
|
493
|
+
if (call) {
|
|
494
|
+
call.state = "output-denied";
|
|
495
|
+
if (!("approval" in call) || !call.approval) {
|
|
496
|
+
(call as ToolUIPart & { approval?: object }).approval = {
|
|
497
|
+
id: "",
|
|
498
|
+
approved: false,
|
|
499
|
+
reason: typedPart.output.reason,
|
|
500
|
+
};
|
|
501
|
+
} else {
|
|
502
|
+
const approval = (
|
|
503
|
+
call as ToolUIPart & {
|
|
504
|
+
approval: { approved?: boolean; reason?: string };
|
|
505
|
+
}
|
|
506
|
+
).approval;
|
|
507
|
+
approval.approved = false;
|
|
508
|
+
approval.reason = typedPart.output.reason;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
|
|
462
514
|
const output =
|
|
463
|
-
typeof
|
|
464
|
-
?
|
|
465
|
-
:
|
|
515
|
+
typeof typedPart.output?.type === "string"
|
|
516
|
+
? typedPart.output.value
|
|
517
|
+
: typedPart.output;
|
|
518
|
+
// Check for error at both the content part level (isError) and message level
|
|
519
|
+
// isError may exist on stored tool results but isn't in ToolResultPart type
|
|
520
|
+
const hasError =
|
|
521
|
+
(contentPart as { isError?: boolean }).isError || message.error;
|
|
522
|
+
const errorText =
|
|
523
|
+
message.error || (hasError ? String(output) : undefined);
|
|
466
524
|
const call = allParts.find(
|
|
467
525
|
(part) =>
|
|
468
526
|
part.type === `tool-${contentPart.toolName}` &&
|
|
@@ -470,26 +528,23 @@ function createAssistantUIMessage<
|
|
|
470
528
|
part.toolCallId === contentPart.toolCallId,
|
|
471
529
|
) as ToolUIPart | undefined;
|
|
472
530
|
if (call) {
|
|
473
|
-
if (
|
|
531
|
+
if (hasError) {
|
|
474
532
|
call.state = "output-error";
|
|
475
|
-
call.errorText =
|
|
533
|
+
call.errorText = errorText ?? "Unknown error";
|
|
476
534
|
call.output = output;
|
|
477
535
|
} else {
|
|
478
536
|
call.state = "output-available";
|
|
479
537
|
call.output = output;
|
|
480
538
|
}
|
|
481
539
|
} else {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
contentPart,
|
|
485
|
-
);
|
|
486
|
-
if (message.error) {
|
|
540
|
+
// Tool call is on a previous page - create standalone tool part
|
|
541
|
+
if (hasError) {
|
|
487
542
|
allParts.push({
|
|
488
543
|
type: `tool-${contentPart.toolName}`,
|
|
489
544
|
toolCallId: contentPart.toolCallId,
|
|
490
545
|
state: "output-error",
|
|
491
546
|
input: undefined,
|
|
492
|
-
errorText:
|
|
547
|
+
errorText: errorText ?? "Unknown error",
|
|
493
548
|
callProviderMetadata: message.providerMetadata,
|
|
494
549
|
} satisfies ToolUIPart<TOOLS>);
|
|
495
550
|
} else {
|
|
@@ -505,8 +560,70 @@ function createAssistantUIMessage<
|
|
|
505
560
|
}
|
|
506
561
|
break;
|
|
507
562
|
}
|
|
563
|
+
case "tool-approval-request": {
|
|
564
|
+
// Find the matching tool call
|
|
565
|
+
const typedPart = contentPart as {
|
|
566
|
+
toolCallId: string;
|
|
567
|
+
approvalId: string;
|
|
568
|
+
};
|
|
569
|
+
const toolCallPart = allParts.find(
|
|
570
|
+
(part) =>
|
|
571
|
+
"toolCallId" in part && part.toolCallId === typedPart.toolCallId,
|
|
572
|
+
) as ToolUIPart | undefined;
|
|
573
|
+
|
|
574
|
+
if (toolCallPart) {
|
|
575
|
+
toolCallPart.state = "approval-requested";
|
|
576
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
577
|
+
id: typedPart.approvalId,
|
|
578
|
+
};
|
|
579
|
+
} else {
|
|
580
|
+
console.warn(
|
|
581
|
+
"Tool approval request without preceding tool call",
|
|
582
|
+
contentPart,
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case "tool-approval-response": {
|
|
588
|
+
// Find the tool call that has this approval by matching approval.id
|
|
589
|
+
const typedPart = contentPart as {
|
|
590
|
+
approvalId: string;
|
|
591
|
+
approved: boolean;
|
|
592
|
+
reason?: string;
|
|
593
|
+
};
|
|
594
|
+
const toolCallPart = allParts.find(
|
|
595
|
+
(part) =>
|
|
596
|
+
"approval" in part &&
|
|
597
|
+
(part as ToolUIPart & { approval?: { id: string } }).approval
|
|
598
|
+
?.id === typedPart.approvalId,
|
|
599
|
+
) as ToolUIPart | undefined;
|
|
600
|
+
|
|
601
|
+
if (toolCallPart) {
|
|
602
|
+
if (typedPart.approved) {
|
|
603
|
+
toolCallPart.state = "approval-responded";
|
|
604
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
605
|
+
id: typedPart.approvalId,
|
|
606
|
+
approved: true,
|
|
607
|
+
reason: typedPart.reason,
|
|
608
|
+
};
|
|
609
|
+
} else {
|
|
610
|
+
toolCallPart.state = "output-denied";
|
|
611
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
612
|
+
id: typedPart.approvalId,
|
|
613
|
+
approved: false,
|
|
614
|
+
reason: typedPart.reason,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
} else {
|
|
618
|
+
console.warn(
|
|
619
|
+
"Tool approval response without matching approval request",
|
|
620
|
+
contentPart,
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
508
625
|
default: {
|
|
509
|
-
const maybeSource = contentPart as SourcePart;
|
|
626
|
+
const maybeSource = contentPart as unknown as SourcePart;
|
|
510
627
|
if (maybeSource.type === "source") {
|
|
511
628
|
allParts.push(toSourcePart(maybeSource));
|
|
512
629
|
} else {
|
|
@@ -583,11 +700,12 @@ export function combineUIMessages(messages: UIMessage[]): UIMessage[] {
|
|
|
583
700
|
const previousPartIndex = newParts.findIndex(
|
|
584
701
|
(p) => getToolCallId(p) === toolCallId,
|
|
585
702
|
);
|
|
586
|
-
|
|
587
|
-
|
|
703
|
+
if (previousPartIndex === -1) {
|
|
704
|
+
// Tool call not found in previous parts, add it as new
|
|
588
705
|
newParts.push(part);
|
|
589
706
|
continue;
|
|
590
707
|
}
|
|
708
|
+
const previousPart = newParts.splice(previousPartIndex, 1)[0];
|
|
591
709
|
newParts.push(mergeParts(previousPart, part));
|
|
592
710
|
}
|
|
593
711
|
acc.push({
|