@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
package/src/client/mockModel.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
LanguageModelV3,
|
|
3
|
+
LanguageModelV3Content,
|
|
4
|
+
LanguageModelV3StreamPart,
|
|
5
5
|
} from "@ai-sdk/provider";
|
|
6
6
|
import { simulateReadableStream, type ProviderMetadata } from "ai";
|
|
7
7
|
import { assert, pick } from "convex-helpers";
|
|
@@ -12,14 +12,20 @@ B B B B B B B B B B B B B B B
|
|
|
12
12
|
C C C C C C C C C C C C C C C
|
|
13
13
|
D D D D D D D D D D D D D D D
|
|
14
14
|
`;
|
|
15
|
-
const DEFAULT_USAGE = {
|
|
15
|
+
const DEFAULT_USAGE = {
|
|
16
|
+
outputTokens: 10,
|
|
17
|
+
inputTokens: 3,
|
|
18
|
+
totalTokens: 13,
|
|
19
|
+
inputTokenDetails: undefined,
|
|
20
|
+
outputTokenDetails: undefined,
|
|
21
|
+
};
|
|
16
22
|
|
|
17
23
|
export type MockModelArgs = {
|
|
18
|
-
provider?:
|
|
19
|
-
modelId?:
|
|
24
|
+
provider?: LanguageModelV3["provider"];
|
|
25
|
+
modelId?: LanguageModelV3["modelId"];
|
|
20
26
|
supportedUrls?:
|
|
21
|
-
|
|
|
22
|
-
| (() =>
|
|
27
|
+
| LanguageModelV3["supportedUrls"]
|
|
28
|
+
| (() => LanguageModelV3["supportedUrls"]);
|
|
23
29
|
chunkDelayInMs?: number;
|
|
24
30
|
initialDelayInMs?: number;
|
|
25
31
|
/** A list of the responses for multiple steps.
|
|
@@ -27,15 +33,15 @@ export type MockModelArgs = {
|
|
|
27
33
|
* then the next list would be after the tool response or another tool call.
|
|
28
34
|
* Tool responses come from actual tool calls!
|
|
29
35
|
*/
|
|
30
|
-
contentSteps?:
|
|
36
|
+
contentSteps?: LanguageModelV3Content[][];
|
|
31
37
|
/** A single list of content responded from each step.
|
|
32
38
|
* Provide contentSteps instead if you want to do multi-step responses with
|
|
33
39
|
* tool calls.
|
|
34
40
|
*/
|
|
35
|
-
content?:
|
|
41
|
+
content?: LanguageModelV3Content[];
|
|
36
42
|
// provide either content, contentResponses or doGenerate & doStream
|
|
37
|
-
doGenerate?:
|
|
38
|
-
doStream?:
|
|
43
|
+
doGenerate?: LanguageModelV3["doGenerate"];
|
|
44
|
+
doStream?: LanguageModelV3["doStream"];
|
|
39
45
|
providerMetadata?: ProviderMetadata;
|
|
40
46
|
fail?:
|
|
41
47
|
| boolean
|
|
@@ -49,23 +55,23 @@ function atMostOneOf(...args: unknown[]) {
|
|
|
49
55
|
return args.filter(Boolean).length <= 1;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
export function mockModel(args?: MockModelArgs):
|
|
58
|
+
export function mockModel(args?: MockModelArgs): LanguageModelV3 {
|
|
53
59
|
return new MockLanguageModel(args ?? {});
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
export class MockLanguageModel implements
|
|
57
|
-
readonly specificationVersion = "
|
|
62
|
+
export class MockLanguageModel implements LanguageModelV3 {
|
|
63
|
+
readonly specificationVersion = "v3";
|
|
58
64
|
|
|
59
|
-
private _supportedUrls: () =>
|
|
65
|
+
private _supportedUrls: () => LanguageModelV3["supportedUrls"];
|
|
60
66
|
|
|
61
|
-
readonly provider:
|
|
62
|
-
readonly modelId:
|
|
67
|
+
readonly provider: LanguageModelV3["provider"];
|
|
68
|
+
readonly modelId: LanguageModelV3["modelId"];
|
|
63
69
|
|
|
64
|
-
doGenerate:
|
|
65
|
-
doStream:
|
|
70
|
+
doGenerate: LanguageModelV3["doGenerate"];
|
|
71
|
+
doStream: LanguageModelV3["doStream"];
|
|
66
72
|
|
|
67
|
-
doGenerateCalls: Parameters<
|
|
68
|
-
doStreamCalls: Parameters<
|
|
73
|
+
doGenerateCalls: Parameters<LanguageModelV3["doGenerate"]>[0][] = [];
|
|
74
|
+
doStreamCalls: Parameters<LanguageModelV3["doStream"]>[0][] = [];
|
|
69
75
|
|
|
70
76
|
constructor(args: MockModelArgs) {
|
|
71
77
|
assert(
|
|
@@ -95,19 +101,19 @@ export class MockLanguageModel implements LanguageModelV2 {
|
|
|
95
101
|
"Mock error message";
|
|
96
102
|
const metadata = pick(args, ["providerMetadata"]);
|
|
97
103
|
|
|
98
|
-
const chunkResponses:
|
|
104
|
+
const chunkResponses: LanguageModelV3StreamPart[][] = contentSteps.map(
|
|
99
105
|
(content) => {
|
|
100
|
-
const chunks:
|
|
106
|
+
const chunks: LanguageModelV3StreamPart[] = [
|
|
101
107
|
{ type: "stream-start", warnings: [] },
|
|
102
108
|
];
|
|
103
109
|
chunks.push(
|
|
104
|
-
...content.flatMap((c, ci):
|
|
110
|
+
...content.flatMap((c, ci): LanguageModelV3StreamPart[] => {
|
|
105
111
|
if (c.type !== "text" && c.type !== "reasoning") {
|
|
106
112
|
return [c];
|
|
107
113
|
}
|
|
108
114
|
const metadata = pick(c, ["providerMetadata"]);
|
|
109
115
|
const deltas = c.text.split(" ");
|
|
110
|
-
const parts:
|
|
116
|
+
const parts: LanguageModelV3StreamPart[] = [];
|
|
111
117
|
if (c.type === "reasoning") {
|
|
112
118
|
parts.push({
|
|
113
119
|
type: "reasoning-start",
|
|
@@ -122,7 +128,7 @@ export class MockLanguageModel implements LanguageModelV2 {
|
|
|
122
128
|
delta: (di ? " " : "") + delta,
|
|
123
129
|
id: `reasoning-${ci}`,
|
|
124
130
|
...metadata,
|
|
125
|
-
}) satisfies
|
|
131
|
+
}) satisfies LanguageModelV3StreamPart,
|
|
126
132
|
),
|
|
127
133
|
);
|
|
128
134
|
parts.push({
|
|
@@ -144,7 +150,7 @@ export class MockLanguageModel implements LanguageModelV2 {
|
|
|
144
150
|
delta: (di ? " " : "") + delta,
|
|
145
151
|
id: `txt-${ci}`,
|
|
146
152
|
...metadata,
|
|
147
|
-
}) satisfies
|
|
153
|
+
}) satisfies LanguageModelV3StreamPart,
|
|
148
154
|
),
|
|
149
155
|
);
|
|
150
156
|
parts.push({
|
|
@@ -166,7 +172,7 @@ export class MockLanguageModel implements LanguageModelV2 {
|
|
|
166
172
|
type: "finish",
|
|
167
173
|
finishReason: fail ? "error" : "stop",
|
|
168
174
|
usage: DEFAULT_USAGE,
|
|
169
|
-
...metadata,
|
|
175
|
+
...(metadata as any),
|
|
170
176
|
});
|
|
171
177
|
return chunks;
|
|
172
178
|
},
|
|
@@ -187,7 +193,7 @@ export class MockLanguageModel implements LanguageModelV2 {
|
|
|
187
193
|
content: contentSteps[callIndex % contentSteps.length],
|
|
188
194
|
finishReason: "stop" as const,
|
|
189
195
|
usage: DEFAULT_USAGE,
|
|
190
|
-
...metadata,
|
|
196
|
+
...(metadata as any),
|
|
191
197
|
warnings: [],
|
|
192
198
|
};
|
|
193
199
|
callIndex++;
|
|
@@ -196,10 +202,6 @@ export class MockLanguageModel implements LanguageModelV2 {
|
|
|
196
202
|
throw new Error("Unexpected: no content or doGenerate");
|
|
197
203
|
}
|
|
198
204
|
};
|
|
199
|
-
this._supportedUrls =
|
|
200
|
-
typeof supportedUrls === "function"
|
|
201
|
-
? supportedUrls
|
|
202
|
-
: async () => supportedUrls;
|
|
203
205
|
this.doStream = async (options) => {
|
|
204
206
|
this.doStreamCalls.push(options);
|
|
205
207
|
|
|
@@ -31,7 +31,7 @@ export async function saveInputMessages(
|
|
|
31
31
|
storageOptions?: {
|
|
32
32
|
saveMessages?: "all" | "promptAndOutput";
|
|
33
33
|
};
|
|
34
|
-
} & Pick<Config, "usageHandler" | "textEmbeddingModel" | "callSettings">,
|
|
34
|
+
} & Pick<Config, "usageHandler" | "textEmbeddingModel" | "embeddingModel" | "callSettings">,
|
|
35
35
|
): Promise<{
|
|
36
36
|
promptMessageId: string | undefined;
|
|
37
37
|
pendingMessage: MessageDoc;
|
|
@@ -64,7 +64,7 @@ export async function saveInputMessages(
|
|
|
64
64
|
model: string;
|
|
65
65
|
}
|
|
66
66
|
| undefined;
|
|
67
|
-
if (args.textEmbeddingModel && toSave.length) {
|
|
67
|
+
if ((args.embeddingModel ?? args.textEmbeddingModel) && toSave.length) {
|
|
68
68
|
assert(
|
|
69
69
|
"runAction" in ctx,
|
|
70
70
|
"You must be in an action context to generate embeddings",
|
|
@@ -159,6 +159,7 @@ describe("search.ts", () => {
|
|
|
159
159
|
type: "tool-call",
|
|
160
160
|
toolCallId: "call_123",
|
|
161
161
|
toolName: "test",
|
|
162
|
+
input: {},
|
|
162
163
|
args: {},
|
|
163
164
|
},
|
|
164
165
|
],
|
|
@@ -202,6 +203,7 @@ describe("search.ts", () => {
|
|
|
202
203
|
type: "tool-call",
|
|
203
204
|
toolCallId: "call_orphaned",
|
|
204
205
|
toolName: "test",
|
|
206
|
+
input: {},
|
|
205
207
|
args: {},
|
|
206
208
|
},
|
|
207
209
|
],
|
|
@@ -234,6 +236,170 @@ describe("search.ts", () => {
|
|
|
234
236
|
expect(result[0]._id).toBe("0");
|
|
235
237
|
expect(result[1]._id).toBe("3");
|
|
236
238
|
});
|
|
239
|
+
|
|
240
|
+
it("should keep tool calls with approval responses (but no tool-result yet)", () => {
|
|
241
|
+
const messages: MessageDoc[] = [
|
|
242
|
+
{
|
|
243
|
+
_id: "1",
|
|
244
|
+
message: {
|
|
245
|
+
role: "assistant",
|
|
246
|
+
content: [
|
|
247
|
+
{ type: "text", text: "I'll run the dangerous tool" },
|
|
248
|
+
{
|
|
249
|
+
type: "tool-call",
|
|
250
|
+
toolCallId: "call_123",
|
|
251
|
+
toolName: "dangerousTool",
|
|
252
|
+
input: { action: "delete" },
|
|
253
|
+
args: { action: "delete" },
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
type: "tool-approval-request",
|
|
257
|
+
toolCallId: "call_123",
|
|
258
|
+
approvalId: "approval_456",
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
order: 1,
|
|
263
|
+
} as MessageDoc,
|
|
264
|
+
{
|
|
265
|
+
_id: "2",
|
|
266
|
+
message: {
|
|
267
|
+
role: "tool",
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "tool-approval-response",
|
|
271
|
+
approvalId: "approval_456",
|
|
272
|
+
approved: true,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
order: 2,
|
|
277
|
+
} as MessageDoc,
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const result = filterOutOrphanedToolMessages(messages);
|
|
281
|
+
expect(result).toHaveLength(2);
|
|
282
|
+
// The assistant message should still contain the tool-call
|
|
283
|
+
expect(result[0]._id).toBe("1");
|
|
284
|
+
const assistantContent = result[0].message?.content;
|
|
285
|
+
expect(Array.isArray(assistantContent)).toBe(true);
|
|
286
|
+
if (Array.isArray(assistantContent)) {
|
|
287
|
+
const toolCall = assistantContent.find((p) => p.type === "tool-call");
|
|
288
|
+
expect(toolCall).toBeDefined();
|
|
289
|
+
expect(toolCall?.toolCallId).toBe("call_123");
|
|
290
|
+
}
|
|
291
|
+
// The tool message with approval response should be kept
|
|
292
|
+
expect(result[1]._id).toBe("2");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should filter out tool calls with approval request but NO approval response", () => {
|
|
296
|
+
const messages: MessageDoc[] = [
|
|
297
|
+
{
|
|
298
|
+
_id: "1",
|
|
299
|
+
message: {
|
|
300
|
+
role: "assistant",
|
|
301
|
+
content: [
|
|
302
|
+
{ type: "text", text: "I'll run the dangerous tool" },
|
|
303
|
+
{
|
|
304
|
+
type: "tool-call",
|
|
305
|
+
toolCallId: "call_123",
|
|
306
|
+
toolName: "dangerousTool",
|
|
307
|
+
input: { action: "delete" },
|
|
308
|
+
args: { action: "delete" },
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
type: "tool-approval-request",
|
|
312
|
+
toolCallId: "call_123",
|
|
313
|
+
approvalId: "approval_456",
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
order: 1,
|
|
318
|
+
} as MessageDoc,
|
|
319
|
+
// No approval response provided
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
const result = filterOutOrphanedToolMessages(messages);
|
|
323
|
+
expect(result).toHaveLength(1);
|
|
324
|
+
// The assistant message should have the tool-call filtered out
|
|
325
|
+
const assistantContent = result[0].message?.content;
|
|
326
|
+
expect(Array.isArray(assistantContent)).toBe(true);
|
|
327
|
+
if (Array.isArray(assistantContent)) {
|
|
328
|
+
// Text and approval-request should remain, but tool-call should be filtered
|
|
329
|
+
expect(assistantContent).toHaveLength(2);
|
|
330
|
+
expect(assistantContent.find((p) => p.type === "text")).toBeDefined();
|
|
331
|
+
expect(
|
|
332
|
+
assistantContent.find((p) => p.type === "tool-approval-request"),
|
|
333
|
+
).toBeDefined();
|
|
334
|
+
expect(
|
|
335
|
+
assistantContent.find((p) => p.type === "tool-call"),
|
|
336
|
+
).toBeUndefined();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should handle mix of tool calls with results and with approvals", () => {
|
|
341
|
+
const messages: MessageDoc[] = [
|
|
342
|
+
{
|
|
343
|
+
_id: "1",
|
|
344
|
+
message: {
|
|
345
|
+
role: "assistant",
|
|
346
|
+
content: [
|
|
347
|
+
{
|
|
348
|
+
type: "tool-call",
|
|
349
|
+
toolCallId: "call_with_result",
|
|
350
|
+
toolName: "safeTool",
|
|
351
|
+
input: {},
|
|
352
|
+
args: {},
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
type: "tool-call",
|
|
356
|
+
toolCallId: "call_with_approval",
|
|
357
|
+
toolName: "dangerousTool",
|
|
358
|
+
input: {},
|
|
359
|
+
args: {},
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
type: "tool-approval-request",
|
|
363
|
+
toolCallId: "call_with_approval",
|
|
364
|
+
approvalId: "approval_789",
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
},
|
|
368
|
+
order: 1,
|
|
369
|
+
} as MessageDoc,
|
|
370
|
+
{
|
|
371
|
+
_id: "2",
|
|
372
|
+
message: {
|
|
373
|
+
role: "tool",
|
|
374
|
+
content: [
|
|
375
|
+
{
|
|
376
|
+
type: "tool-result",
|
|
377
|
+
toolCallId: "call_with_result",
|
|
378
|
+
result: "success",
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
type: "tool-approval-response",
|
|
382
|
+
approvalId: "approval_789",
|
|
383
|
+
approved: true,
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
order: 2,
|
|
388
|
+
} as MessageDoc,
|
|
389
|
+
];
|
|
390
|
+
|
|
391
|
+
const result = filterOutOrphanedToolMessages(messages);
|
|
392
|
+
expect(result).toHaveLength(2);
|
|
393
|
+
// Both tool calls should be kept
|
|
394
|
+
const assistantContent = result[0].message?.content;
|
|
395
|
+
expect(Array.isArray(assistantContent)).toBe(true);
|
|
396
|
+
if (Array.isArray(assistantContent)) {
|
|
397
|
+
const toolCalls = assistantContent.filter(
|
|
398
|
+
(p) => p.type === "tool-call",
|
|
399
|
+
);
|
|
400
|
+
expect(toolCalls).toHaveLength(2);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
237
403
|
});
|
|
238
404
|
|
|
239
405
|
describe("fetchContextMessages", () => {
|
package/src/client/search.ts
CHANGED
|
@@ -36,10 +36,20 @@ const DEFAULT_VECTOR_SCORE_THRESHOLD = 0.0;
|
|
|
36
36
|
// the 8k token limit for some models.
|
|
37
37
|
const MAX_EMBEDDING_TEXT_LENGTH = 10_000;
|
|
38
38
|
|
|
39
|
-
export type GetEmbedding = (text: string) => Promise<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
export type GetEmbedding = (text: string) => Promise<
|
|
40
|
+
| {
|
|
41
|
+
embedding: number[];
|
|
42
|
+
/** @deprecated Use embeddingModel instead. */
|
|
43
|
+
textEmbeddingModel: string | EmbeddingModel;
|
|
44
|
+
embeddingModel?: string | EmbeddingModel;
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
embedding: number[];
|
|
48
|
+
/** @deprecated Use embeddingModel instead. */
|
|
49
|
+
textEmbeddingModel?: string | EmbeddingModel;
|
|
50
|
+
embeddingModel: string | EmbeddingModel;
|
|
51
|
+
}
|
|
52
|
+
>;
|
|
43
53
|
|
|
44
54
|
/**
|
|
45
55
|
* Fetch the context messages for a thread.
|
|
@@ -178,8 +188,10 @@ export async function fetchRecentAndSearchMessages(
|
|
|
178
188
|
if (!embedding && args.getEmbedding) {
|
|
179
189
|
const embeddingFields = await args.getEmbedding(text);
|
|
180
190
|
embedding = embeddingFields.embedding;
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
const effectiveModel =
|
|
192
|
+
embeddingFields.embeddingModel ?? embeddingFields.textEmbeddingModel;
|
|
193
|
+
embeddingModel = effectiveModel
|
|
194
|
+
? getModelName(effectiveModel)
|
|
183
195
|
: undefined;
|
|
184
196
|
// TODO: if the text matches the target message, save the embedding
|
|
185
197
|
// for the target message and return the embeddingId on the message.
|
|
@@ -225,12 +237,19 @@ export async function fetchRecentAndSearchMessages(
|
|
|
225
237
|
|
|
226
238
|
/**
|
|
227
239
|
* Filter out tool messages that don't have both a tool call and response.
|
|
240
|
+
* For the approval workflow, tool calls with approval responses (but no tool-results yet)
|
|
241
|
+
* should also be kept.
|
|
228
242
|
* @param docs The messages to filter.
|
|
229
243
|
* @returns The filtered messages.
|
|
230
244
|
*/
|
|
231
245
|
export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
|
|
232
246
|
const toolCallIds = new Set<string>();
|
|
233
247
|
const toolResultIds = new Set<string>();
|
|
248
|
+
// Track approval workflow: toolCallId → approvalId
|
|
249
|
+
const approvalRequestsByToolCallId = new Map<string, string>();
|
|
250
|
+
// Track which approvalIds have responses
|
|
251
|
+
const approvalResponseIds = new Set<string>();
|
|
252
|
+
|
|
234
253
|
const result: MessageDoc[] = [];
|
|
235
254
|
for (const doc of docs) {
|
|
236
255
|
if (doc.message && Array.isArray(doc.message.content)) {
|
|
@@ -239,17 +258,43 @@ export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
|
|
|
239
258
|
toolCallIds.add(content.toolCallId);
|
|
240
259
|
} else if (content.type === "tool-result") {
|
|
241
260
|
toolResultIds.add(content.toolCallId);
|
|
261
|
+
} else if (content.type === "tool-approval-request") {
|
|
262
|
+
const approvalRequest = content as {
|
|
263
|
+
type: "tool-approval-request";
|
|
264
|
+
toolCallId: string;
|
|
265
|
+
approvalId: string;
|
|
266
|
+
};
|
|
267
|
+
approvalRequestsByToolCallId.set(
|
|
268
|
+
approvalRequest.toolCallId,
|
|
269
|
+
approvalRequest.approvalId,
|
|
270
|
+
);
|
|
271
|
+
} else if (content.type === "tool-approval-response") {
|
|
272
|
+
const approvalResponse = content as {
|
|
273
|
+
type: "tool-approval-response";
|
|
274
|
+
approvalId: string;
|
|
275
|
+
};
|
|
276
|
+
approvalResponseIds.add(approvalResponse.approvalId);
|
|
242
277
|
}
|
|
243
278
|
}
|
|
244
279
|
}
|
|
245
280
|
}
|
|
281
|
+
|
|
282
|
+
// Helper: check if tool call has a corresponding approval response
|
|
283
|
+
const hasApprovalResponse = (toolCallId: string) => {
|
|
284
|
+
const approvalId = approvalRequestsByToolCallId.get(toolCallId);
|
|
285
|
+
return approvalId !== undefined && approvalResponseIds.has(approvalId);
|
|
286
|
+
};
|
|
287
|
+
|
|
246
288
|
for (const doc of docs) {
|
|
247
289
|
if (
|
|
248
290
|
doc.message?.role === "assistant" &&
|
|
249
291
|
Array.isArray(doc.message.content)
|
|
250
292
|
) {
|
|
251
293
|
const content = doc.message.content.filter(
|
|
252
|
-
(p) =>
|
|
294
|
+
(p) =>
|
|
295
|
+
p.type !== "tool-call" ||
|
|
296
|
+
toolResultIds.has(p.toolCallId) ||
|
|
297
|
+
hasApprovalResponse(p.toolCallId),
|
|
253
298
|
);
|
|
254
299
|
if (content.length) {
|
|
255
300
|
result.push({
|
|
@@ -261,9 +306,14 @@ export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
|
|
|
261
306
|
});
|
|
262
307
|
}
|
|
263
308
|
} else if (doc.message?.role === "tool") {
|
|
264
|
-
const content = doc.message.content.filter((c) =>
|
|
265
|
-
|
|
266
|
-
|
|
309
|
+
const content = doc.message.content.filter((c) => {
|
|
310
|
+
// tool-result parts have toolCallId
|
|
311
|
+
if (c.type === "tool-result") {
|
|
312
|
+
return toolCallIds.has(c.toolCallId);
|
|
313
|
+
}
|
|
314
|
+
// tool-approval-response parts don't have toolCallId, so include them
|
|
315
|
+
return true;
|
|
316
|
+
});
|
|
267
317
|
if (content.length) {
|
|
268
318
|
result.push({
|
|
269
319
|
...doc,
|
|
@@ -294,7 +344,10 @@ export async function embedMessages(
|
|
|
294
344
|
userId: string | undefined;
|
|
295
345
|
threadId: string | undefined;
|
|
296
346
|
agentName?: string;
|
|
297
|
-
} & Pick<
|
|
347
|
+
} & Pick<
|
|
348
|
+
Config,
|
|
349
|
+
"usageHandler" | "textEmbeddingModel" | "embeddingModel" | "callSettings"
|
|
350
|
+
>,
|
|
298
351
|
messages: (ModelMessage | Message)[],
|
|
299
352
|
): Promise<
|
|
300
353
|
| {
|
|
@@ -304,7 +357,9 @@ export async function embedMessages(
|
|
|
304
357
|
}
|
|
305
358
|
| undefined
|
|
306
359
|
> {
|
|
307
|
-
|
|
360
|
+
const textEmbeddingModel =
|
|
361
|
+
options.embeddingModel ?? options.textEmbeddingModel;
|
|
362
|
+
if (!textEmbeddingModel) {
|
|
308
363
|
return undefined;
|
|
309
364
|
}
|
|
310
365
|
let embeddings:
|
|
@@ -340,7 +395,7 @@ export async function embedMessages(
|
|
|
340
395
|
if (textEmbeddings.embeddings.length > 0) {
|
|
341
396
|
const dimension = textEmbeddings.embeddings[0].length;
|
|
342
397
|
validateVectorDimension(dimension);
|
|
343
|
-
const model = getModelName(
|
|
398
|
+
const model = getModelName(textEmbeddingModel);
|
|
344
399
|
embeddings = { vectors: embeddingsOrNull, dimension, model };
|
|
345
400
|
}
|
|
346
401
|
return embeddings;
|
|
@@ -355,7 +410,19 @@ export async function embedMessages(
|
|
|
355
410
|
*/
|
|
356
411
|
export async function embedMany(
|
|
357
412
|
ctx: ActionCtx,
|
|
358
|
-
{
|
|
413
|
+
args: {
|
|
414
|
+
userId: string | undefined;
|
|
415
|
+
threadId: string | undefined;
|
|
416
|
+
values: string[];
|
|
417
|
+
abortSignal?: AbortSignal;
|
|
418
|
+
headers?: Record<string, string>;
|
|
419
|
+
agentName?: string;
|
|
420
|
+
} & Pick<
|
|
421
|
+
Config,
|
|
422
|
+
"usageHandler" | "textEmbeddingModel" | "embeddingModel" | "callSettings"
|
|
423
|
+
>,
|
|
424
|
+
): Promise<{ embeddings: number[][] }> {
|
|
425
|
+
const {
|
|
359
426
|
userId,
|
|
360
427
|
threadId,
|
|
361
428
|
values,
|
|
@@ -364,24 +431,17 @@ export async function embedMany(
|
|
|
364
431
|
agentName,
|
|
365
432
|
usageHandler,
|
|
366
433
|
textEmbeddingModel,
|
|
434
|
+
embeddingModel,
|
|
367
435
|
callSettings,
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
threadId: string | undefined;
|
|
371
|
-
values: string[];
|
|
372
|
-
abortSignal?: AbortSignal;
|
|
373
|
-
headers?: Record<string, string>;
|
|
374
|
-
agentName?: string;
|
|
375
|
-
} & Pick<Config, "usageHandler" | "textEmbeddingModel" | "callSettings">,
|
|
376
|
-
): Promise<{ embeddings: number[][] }> {
|
|
377
|
-
const embeddingModel = textEmbeddingModel;
|
|
436
|
+
} = args;
|
|
437
|
+
const effectiveEmbeddingModel = embeddingModel ?? textEmbeddingModel;
|
|
378
438
|
assert(
|
|
379
|
-
|
|
380
|
-
"
|
|
439
|
+
effectiveEmbeddingModel,
|
|
440
|
+
"an embeddingModel (or textEmbeddingModel) is required to be set for vector search",
|
|
381
441
|
);
|
|
382
442
|
const result = await embedMany_({
|
|
383
443
|
...callSettings,
|
|
384
|
-
model:
|
|
444
|
+
model: effectiveEmbeddingModel,
|
|
385
445
|
values,
|
|
386
446
|
abortSignal,
|
|
387
447
|
headers,
|
|
@@ -391,13 +451,24 @@ export async function embedMany(
|
|
|
391
451
|
userId,
|
|
392
452
|
threadId,
|
|
393
453
|
agentName,
|
|
394
|
-
model: getModelName(
|
|
395
|
-
provider: getProviderName(
|
|
454
|
+
model: getModelName(effectiveEmbeddingModel),
|
|
455
|
+
provider: getProviderName(effectiveEmbeddingModel),
|
|
396
456
|
providerMetadata: undefined,
|
|
397
457
|
usage: {
|
|
398
458
|
inputTokens: result.usage.tokens,
|
|
399
459
|
outputTokens: 0,
|
|
400
460
|
totalTokens: result.usage.tokens,
|
|
461
|
+
// These detail fields are required by LanguageModelUsage type but we don't
|
|
462
|
+
// have the granular data, so we provide objects with undefined values.
|
|
463
|
+
inputTokenDetails: {
|
|
464
|
+
cacheReadTokens: undefined,
|
|
465
|
+
cacheWriteTokens: undefined,
|
|
466
|
+
noCacheTokens: undefined,
|
|
467
|
+
},
|
|
468
|
+
outputTokenDetails: {
|
|
469
|
+
textTokens: undefined,
|
|
470
|
+
reasoningTokens: undefined,
|
|
471
|
+
},
|
|
401
472
|
},
|
|
402
473
|
});
|
|
403
474
|
}
|
|
@@ -418,17 +489,28 @@ export async function generateAndSaveEmbeddings(
|
|
|
418
489
|
threadId: string | undefined;
|
|
419
490
|
userId: string | undefined;
|
|
420
491
|
agentName?: string;
|
|
421
|
-
|
|
492
|
+
/**
|
|
493
|
+
* @deprecated Use embeddingModel instead.
|
|
494
|
+
*/
|
|
495
|
+
textEmbeddingModel?: EmbeddingModel;
|
|
496
|
+
embeddingModel?: EmbeddingModel;
|
|
422
497
|
} & Pick<Config, "usageHandler" | "callSettings">,
|
|
423
498
|
messages: MessageDoc[],
|
|
424
499
|
) {
|
|
500
|
+
const effectiveEmbeddingModel =
|
|
501
|
+
args.embeddingModel ?? args.textEmbeddingModel;
|
|
502
|
+
if (!effectiveEmbeddingModel) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
"an embeddingModel (or textEmbeddingModel) is required to generate and save embeddings",
|
|
505
|
+
);
|
|
506
|
+
}
|
|
425
507
|
const toEmbed = messages.filter((m) => !m.embeddingId && m.message);
|
|
426
508
|
if (toEmbed.length === 0) {
|
|
427
509
|
return;
|
|
428
510
|
}
|
|
429
511
|
const embeddings = await embedMessages(
|
|
430
512
|
ctx,
|
|
431
|
-
args,
|
|
513
|
+
{ ...args, embeddingModel: effectiveEmbeddingModel },
|
|
432
514
|
toEmbed.map((m) => m.message!),
|
|
433
515
|
);
|
|
434
516
|
if (embeddings && embeddings.vectors.some((v) => v !== null)) {
|
|
@@ -473,7 +555,8 @@ export async function fetchContextWithPrompt(
|
|
|
473
555
|
order: number | undefined;
|
|
474
556
|
stepOrder: number | undefined;
|
|
475
557
|
}> {
|
|
476
|
-
const { threadId, userId, textEmbeddingModel } = args;
|
|
558
|
+
const { threadId, userId, textEmbeddingModel, embeddingModel } = args;
|
|
559
|
+
const effectiveEmbeddingModel = embeddingModel ?? textEmbeddingModel;
|
|
477
560
|
|
|
478
561
|
const promptArray = getPromptArray(args.prompt);
|
|
479
562
|
|
|
@@ -496,8 +579,8 @@ export async function fetchContextWithPrompt(
|
|
|
496
579
|
contextOptions: args.contextOptions ?? {},
|
|
497
580
|
getEmbedding: async (text) => {
|
|
498
581
|
assert(
|
|
499
|
-
|
|
500
|
-
"
|
|
582
|
+
effectiveEmbeddingModel,
|
|
583
|
+
"An embeddingModel (or textEmbeddingModel) is required to be set on the Agent that you're doing vector search with",
|
|
501
584
|
);
|
|
502
585
|
return {
|
|
503
586
|
embedding: (
|
|
@@ -505,10 +588,10 @@ export async function fetchContextWithPrompt(
|
|
|
505
588
|
...args,
|
|
506
589
|
userId,
|
|
507
590
|
values: [text],
|
|
508
|
-
|
|
591
|
+
embeddingModel: effectiveEmbeddingModel,
|
|
509
592
|
})
|
|
510
593
|
).embeddings[0],
|
|
511
|
-
|
|
594
|
+
embeddingModel: effectiveEmbeddingModel,
|
|
512
595
|
};
|
|
513
596
|
},
|
|
514
597
|
},
|
|
@@ -531,7 +614,7 @@ export async function fetchContextWithPrompt(
|
|
|
531
614
|
promptArray.push(promptMessage.message);
|
|
532
615
|
}
|
|
533
616
|
}
|
|
534
|
-
if (!promptMessage.embeddingId &&
|
|
617
|
+
if (!promptMessage.embeddingId && effectiveEmbeddingModel) {
|
|
535
618
|
// Lazily generate embeddings for the prompt message, if it doesn't have
|
|
536
619
|
// embeddings yet. This can happen if the message was saved in a mutation
|
|
537
620
|
// where the LLM is not available.
|
|
@@ -541,7 +624,7 @@ export async function fetchContextWithPrompt(
|
|
|
541
624
|
{
|
|
542
625
|
...args,
|
|
543
626
|
userId,
|
|
544
|
-
|
|
627
|
+
embeddingModel: effectiveEmbeddingModel,
|
|
545
628
|
},
|
|
546
629
|
[promptMessage],
|
|
547
630
|
);
|