@convex-dev/agent 0.6.3 → 0.7.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/MIGRATION.md +60 -116
- package/dist/client/messages.d.ts +1 -398
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +3 -47
- package/dist/client/messages.js.map +1 -1
- package/dist/client/types.d.ts +1 -455
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +805 -45
- package/dist/component/_generated/component.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 +1252 -201
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +83 -47
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +2593 -332
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/streams.d.ts +7 -1
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +46 -47
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts.map +1 -1
- package/dist/component/threads.js +8 -9
- 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/react/index.d.ts +5 -5
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -5
- package/dist/react/index.js.map +1 -1
- package/dist/shared.d.ts +19 -7
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +4 -4
- package/dist/shared.js.map +1 -1
- package/dist/streaming/materializePersistedUIMessageChunks.d.ts +16 -0
- package/dist/streaming/materializePersistedUIMessageChunks.d.ts.map +1 -0
- package/dist/streaming/materializePersistedUIMessageChunks.js +302 -0
- package/dist/streaming/materializePersistedUIMessageChunks.js.map +1 -0
- package/dist/streaming/persistedUIMessageChunks.d.ts +106 -0
- package/dist/streaming/persistedUIMessageChunks.d.ts.map +1 -0
- package/dist/streaming/persistedUIMessageChunks.js +475 -0
- package/dist/streaming/persistedUIMessageChunks.js.map +1 -0
- package/dist/validators.d.ts +10026 -1055
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +71 -9
- package/dist/validators.js.map +1 -1
- package/dist/{UIMessages.d.ts → vercel/UIMessages.d.ts} +1 -1
- package/dist/vercel/UIMessages.d.ts.map +1 -0
- package/dist/{UIMessages.js → vercel/UIMessages.js} +63 -1
- package/dist/vercel/UIMessages.js.map +1 -0
- package/dist/{client → vercel/client}/createTool.d.ts +23 -16
- package/dist/vercel/client/createTool.d.ts.map +1 -0
- package/dist/{client → vercel/client}/createTool.js +2 -0
- package/dist/vercel/client/createTool.js.map +1 -0
- package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts +593 -134
- package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/{client → vercel/client}/definePlaygroundAPI.js +13 -9
- package/dist/vercel/client/definePlaygroundAPI.js.map +1 -0
- package/dist/{client → vercel/client}/files.d.ts +2 -2
- package/dist/vercel/client/files.d.ts.map +1 -0
- package/dist/vercel/client/files.js.map +1 -0
- package/dist/vercel/client/messages.d.ts +542 -0
- package/dist/vercel/client/messages.d.ts.map +1 -0
- package/dist/vercel/client/messages.js +66 -0
- package/dist/vercel/client/messages.js.map +1 -0
- package/dist/{client → vercel/client}/mockModel.d.ts +17 -17
- package/dist/vercel/client/mockModel.d.ts.map +1 -0
- package/dist/{client → vercel/client}/mockModel.js +12 -13
- package/dist/vercel/client/mockModel.js.map +1 -0
- package/dist/{client → vercel/client}/saveInputMessages.d.ts +2 -2
- package/dist/vercel/client/saveInputMessages.d.ts.map +1 -0
- package/dist/vercel/client/saveInputMessages.js.map +1 -0
- package/dist/{client → vercel/client}/search.d.ts +119 -22
- package/dist/vercel/client/search.d.ts.map +1 -0
- package/dist/{client → vercel/client}/search.js +2 -2
- package/dist/vercel/client/search.js.map +1 -0
- package/dist/{client → vercel/client}/start.d.ts +26 -11
- package/dist/vercel/client/start.d.ts.map +1 -0
- package/dist/{client → vercel/client}/start.js +64 -22
- package/dist/vercel/client/start.js.map +1 -0
- package/dist/{client → vercel/client}/streamText.d.ts +13 -12
- package/dist/vercel/client/streamText.d.ts.map +1 -0
- package/dist/{client → vercel/client}/streamText.js +43 -9
- package/dist/vercel/client/streamText.js.map +1 -0
- package/dist/{client → vercel/client}/streaming.d.ts +1410 -183
- package/dist/vercel/client/streaming.d.ts.map +1 -0
- package/dist/{client → vercel/client}/streaming.js +120 -52
- package/dist/vercel/client/streaming.js.map +1 -0
- package/dist/vercel/client/types.d.ts +456 -0
- package/dist/vercel/client/types.d.ts.map +1 -0
- package/dist/vercel/client/types.js +3 -0
- package/dist/vercel/client/types.js.map +1 -0
- package/dist/{client → vercel/client}/utils.d.ts +3 -2
- package/dist/vercel/client/utils.d.ts.map +1 -0
- package/dist/{client → vercel/client}/utils.js +2 -2
- package/dist/vercel/client/utils.js.map +1 -0
- package/dist/vercel/deltas.d.ts +31 -0
- package/dist/vercel/deltas.d.ts.map +1 -0
- package/dist/vercel/deltas.js +525 -0
- package/dist/vercel/deltas.js.map +1 -0
- package/dist/{client → vercel}/index.d.ts +484 -97
- package/dist/vercel/index.d.ts.map +1 -0
- package/dist/{client → vercel}/index.js +65 -38
- package/dist/vercel/index.js.map +1 -0
- package/dist/{mapping.d.ts → vercel/mapping.d.ts} +5 -13
- package/dist/vercel/mapping.d.ts.map +1 -0
- package/dist/{mapping.js → vercel/mapping.js} +411 -112
- package/dist/vercel/mapping.js.map +1 -0
- package/dist/{react → vercel/react}/optimisticallySendMessage.d.ts +2 -2
- package/dist/vercel/react/optimisticallySendMessage.d.ts.map +1 -0
- package/dist/vercel/react/optimisticallySendMessage.js.map +1 -0
- package/dist/{react → vercel/react}/types.d.ts +1 -1
- package/dist/vercel/react/types.d.ts.map +1 -0
- package/dist/{react → vercel/react}/types.js.map +1 -1
- package/dist/{react → vercel/react}/useDeltaStreams.d.ts +1 -1
- package/dist/vercel/react/useDeltaStreams.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useDeltaStreams.js +1 -1
- package/dist/vercel/react/useDeltaStreams.js.map +1 -0
- package/dist/{react → vercel/react}/useStreamingUIMessages.d.ts +1 -1
- package/dist/vercel/react/useStreamingUIMessages.d.ts.map +1 -0
- package/dist/vercel/react/useStreamingUIMessages.js +112 -0
- package/dist/vercel/react/useStreamingUIMessages.js.map +1 -0
- package/dist/{react → vercel/react}/useThreadMessages.d.ts +1 -1
- package/dist/vercel/react/useThreadMessages.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useThreadMessages.js +1 -1
- package/dist/vercel/react/useThreadMessages.js.map +1 -0
- package/dist/{react → vercel/react}/useUIMessages.d.ts +2 -1
- package/dist/vercel/react/useUIMessages.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useUIMessages.js +8 -4
- package/dist/vercel/react/useUIMessages.js.map +1 -0
- package/package.json +30 -22
- package/src/client/messages.ts +5 -107
- package/src/client/types.ts +0 -621
- package/src/component/_generated/component.ts +1089 -48
- 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.test.ts +273 -1
- package/src/component/messages.ts +99 -57
- package/src/component/streams.ts +80 -64
- package/src/component/threads.ts +12 -9
- package/src/component/users.ts +2 -2
- package/src/component/vector/index.ts +14 -7
- package/src/eslintConfig.test.ts +44 -0
- package/src/react/index.ts +8 -5
- package/src/shared.ts +26 -36
- package/src/streaming/materializePersistedUIMessageChunks.test.ts +513 -0
- package/src/streaming/materializePersistedUIMessageChunks.ts +398 -0
- package/src/streaming/persistedUIMessageChunks.test.ts +205 -0
- package/src/streaming/persistedUIMessageChunks.ts +658 -0
- package/src/validators.test.ts +4 -1
- package/src/validators.ts +89 -8
- package/src/{UIMessages.combineUIMessages.test.ts → vercel/UIMessages.combineUIMessages.test.ts} +4 -1
- package/src/{UIMessages.ts → vercel/UIMessages.ts} +96 -5
- package/src/{client → vercel/client}/approval.test.ts +48 -19
- package/src/{client → vercel/client}/createTool.ts +63 -23
- package/src/{client → vercel/client}/definePlaygroundAPI.ts +44 -29
- package/src/{client → vercel/client}/files.ts +2 -2
- package/src/{client → vercel/client}/index.test.ts +162 -3
- package/src/vercel/client/messages.ts +182 -0
- package/src/{client → vercel/client}/mockModel.ts +38 -39
- package/src/{client → vercel/client}/saveInputMessages.test.ts +1 -1
- package/src/{client → vercel/client}/saveInputMessages.ts +7 -4
- package/src/{client → vercel/client}/search.test.ts +3 -3
- package/src/{client → vercel/client}/search.ts +4 -4
- package/src/{client → vercel/client}/setup.test.ts +6 -3
- package/src/vercel/client/start.test.ts +14 -0
- package/src/{client → vercel/client}/start.ts +147 -36
- package/src/vercel/client/streamText.test.ts +223 -0
- package/src/{client → vercel/client}/streamText.ts +102 -31
- package/src/{client → vercel/client}/streaming.integration.test.ts +40 -115
- package/src/{client → vercel/client}/streaming.test.ts +178 -4
- package/src/{client → vercel/client}/streaming.ts +114 -52
- package/src/vercel/client/types.test.ts +13 -0
- package/src/vercel/client/types.ts +679 -0
- package/src/{client → vercel/client}/utils.ts +13 -7
- package/src/vercel/deltas.test.ts +898 -0
- package/src/vercel/deltas.ts +633 -0
- package/src/{fromUIMessages.test.ts → vercel/fromUIMessages.test.ts} +17 -1
- package/src/{client → vercel}/index.ts +169 -62
- package/src/vercel/mapping.test.ts +965 -0
- package/src/{mapping.ts → vercel/mapping.ts} +501 -131
- package/src/{react → vercel/react}/optimisticallySendMessage.ts +2 -2
- package/src/{react → vercel/react}/types.ts +1 -1
- package/src/{react → vercel/react}/useDeltaStreams.ts +6 -2
- package/src/{react → vercel/react}/useStreamingUIMessages.ts +67 -35
- package/src/{react → vercel/react}/useThreadMessages.ts +2 -2
- package/src/{react → vercel/react}/useUIMessages.test.ts +80 -1
- package/src/{react → vercel/react}/useUIMessages.ts +13 -5
- package/src/{toUIMessages.test.ts → vercel/toUIMessages.test.ts} +49 -2
- package/dist/UIMessages.d.ts.map +0 -1
- package/dist/UIMessages.js.map +0 -1
- package/dist/client/createTool.d.ts.map +0 -1
- package/dist/client/createTool.js.map +0 -1
- package/dist/client/definePlaygroundAPI.js.map +0 -1
- package/dist/client/files.d.ts.map +0 -1
- package/dist/client/files.js.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/client/mockModel.d.ts.map +0 -1
- package/dist/client/mockModel.js.map +0 -1
- package/dist/client/saveInputMessages.d.ts.map +0 -1
- package/dist/client/saveInputMessages.js.map +0 -1
- package/dist/client/search.d.ts.map +0 -1
- package/dist/client/search.js.map +0 -1
- package/dist/client/start.d.ts.map +0 -1
- package/dist/client/start.js.map +0 -1
- package/dist/client/streamText.d.ts.map +0 -1
- package/dist/client/streamText.js.map +0 -1
- package/dist/client/streaming.d.ts.map +0 -1
- package/dist/client/streaming.js.map +0 -1
- package/dist/client/utils.d.ts.map +0 -1
- package/dist/client/utils.js.map +0 -1
- package/dist/deltas.d.ts +0 -43
- package/dist/deltas.d.ts.map +0 -1
- package/dist/deltas.js +0 -446
- package/dist/deltas.js.map +0 -1
- package/dist/mapping.d.ts.map +0 -1
- package/dist/mapping.js.map +0 -1
- package/dist/react/optimisticallySendMessage.d.ts.map +0 -1
- package/dist/react/optimisticallySendMessage.js.map +0 -1
- package/dist/react/types.d.ts.map +0 -1
- package/dist/react/useDeltaStreams.d.ts.map +0 -1
- package/dist/react/useDeltaStreams.js.map +0 -1
- package/dist/react/useStreamingUIMessages.d.ts.map +0 -1
- package/dist/react/useStreamingUIMessages.js +0 -92
- package/dist/react/useStreamingUIMessages.js.map +0 -1
- package/dist/react/useThreadMessages.d.ts.map +0 -1
- package/dist/react/useThreadMessages.js.map +0 -1
- package/dist/react/useUIMessages.d.ts.map +0 -1
- package/dist/react/useUIMessages.js.map +0 -1
- package/src/client/streamText.test.ts +0 -102
- package/src/deltas.test.ts +0 -626
- package/src/deltas.ts +0 -569
- package/src/mapping.test.ts +0 -402
- /package/dist/{client → vercel/client}/files.js +0 -0
- /package/dist/{client → vercel/client}/saveInputMessages.js +0 -0
- /package/dist/{react → vercel/react}/optimisticallySendMessage.js +0 -0
- /package/dist/{react → vercel/react}/types.js +0 -0
- /package/src/{UIMessages.test.ts → vercel/UIMessages.test.ts} +0 -0
package/src/mapping.test.ts
DELETED
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, vi } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
guessMimeType,
|
|
4
|
-
serializeDataOrUrl,
|
|
5
|
-
toModelMessageDataOrUrl,
|
|
6
|
-
serializeMessage,
|
|
7
|
-
toModelMessage,
|
|
8
|
-
serializeContent,
|
|
9
|
-
toModelMessageContent,
|
|
10
|
-
autoDenyUnresolvedApprovals,
|
|
11
|
-
} from "./mapping.js";
|
|
12
|
-
import { api } from "./component/_generated/api.js";
|
|
13
|
-
import type { AgentComponent, ActionCtx } from "./client/types.js";
|
|
14
|
-
import { vMessage, vToolResultPart } from "./validators.js";
|
|
15
|
-
import fs from "fs";
|
|
16
|
-
import path from "path";
|
|
17
|
-
import type { SerializedContent } from "./mapping.js";
|
|
18
|
-
import { validate } from "convex-helpers/validators";
|
|
19
|
-
import type { ToolResultPart } from "ai";
|
|
20
|
-
import type { Infer } from "convex/values";
|
|
21
|
-
|
|
22
|
-
const testAssetsDir = path.join(__dirname, "../test-assets");
|
|
23
|
-
const testFiles = [
|
|
24
|
-
"book.svg",
|
|
25
|
-
"bump.jpeg",
|
|
26
|
-
"stack.png",
|
|
27
|
-
"favicon.ico",
|
|
28
|
-
"convex-logo.svg",
|
|
29
|
-
"stack-light@3x.webp",
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
function fileToArrayBuffer(filePath: string): ArrayBuffer {
|
|
33
|
-
const buf = fs.readFileSync(filePath);
|
|
34
|
-
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
describe("mapping", () => {
|
|
38
|
-
test("infers correct mimeType for all test-assets", () => {
|
|
39
|
-
const expected: { [key: string]: string } = {
|
|
40
|
-
"book.svg": "image/svg+xml", // <svg
|
|
41
|
-
"bump.jpeg": "image/jpeg",
|
|
42
|
-
"stack.png": "image/png",
|
|
43
|
-
"favicon.ico": "application/octet-stream", // fallback for ico
|
|
44
|
-
"convex-logo.svg": "image/svg+xml", // <?xm
|
|
45
|
-
"stack-light@3x.webp": "image/webp",
|
|
46
|
-
"cat.gif": "image/gif",
|
|
47
|
-
};
|
|
48
|
-
for (const file of testFiles) {
|
|
49
|
-
const ab = fileToArrayBuffer(path.join(testAssetsDir, file));
|
|
50
|
-
const mime = guessMimeType(ab);
|
|
51
|
-
expect(mime).toBe(expected[file]);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("turns Uint8Array into ArrayBuffer and round-trips", () => {
|
|
56
|
-
const arr = new Uint8Array([1, 2, 3, 4, 5]);
|
|
57
|
-
// serializeDataOrUrl should return the same ArrayBuffer
|
|
58
|
-
const ser = serializeDataOrUrl(arr);
|
|
59
|
-
expect(ser).toBeInstanceOf(ArrayBuffer);
|
|
60
|
-
expect(new Uint8Array(ser as ArrayBuffer)).toEqual(arr);
|
|
61
|
-
// toModelMessageDataOrUrl should return the same ArrayBuffer
|
|
62
|
-
const deser = toModelMessageDataOrUrl(ser);
|
|
63
|
-
expect(deser).toBeInstanceOf(ArrayBuffer);
|
|
64
|
-
expect(new Uint8Array(deser as ArrayBuffer)).toEqual(arr);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("round-trip serialize/deserialize message", async () => {
|
|
68
|
-
const message = {
|
|
69
|
-
role: "user" as const,
|
|
70
|
-
content: "hello world",
|
|
71
|
-
providerOptions: {},
|
|
72
|
-
};
|
|
73
|
-
// Fake ctx and component
|
|
74
|
-
const ctx = {
|
|
75
|
-
runAction: async () => undefined,
|
|
76
|
-
runMutation: async () => undefined,
|
|
77
|
-
storage: {
|
|
78
|
-
store: async () => "storageId",
|
|
79
|
-
getUrl: async () => "https://example.com/file",
|
|
80
|
-
delete: async () => undefined,
|
|
81
|
-
},
|
|
82
|
-
} as unknown as ActionCtx;
|
|
83
|
-
const component = api as unknown as AgentComponent;
|
|
84
|
-
const { message: ser } = await serializeMessage(ctx, component, message);
|
|
85
|
-
// Use is for type validation
|
|
86
|
-
expect(validate(vMessage, ser)).toBeTruthy();
|
|
87
|
-
const round = toModelMessage(ser);
|
|
88
|
-
expect(round).toEqual(message);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("tool output round-trips", async () => {
|
|
92
|
-
const toolResult = {
|
|
93
|
-
type: "tool-result" as const,
|
|
94
|
-
toolCallId: "tool-call-id",
|
|
95
|
-
toolName: "tool-name",
|
|
96
|
-
output: {
|
|
97
|
-
type: "text",
|
|
98
|
-
value: "hello world",
|
|
99
|
-
},
|
|
100
|
-
} satisfies ToolResultPart;
|
|
101
|
-
const [result] = toModelMessageContent([toolResult]);
|
|
102
|
-
expect(result).toMatchObject(toolResult);
|
|
103
|
-
const {
|
|
104
|
-
content: [roundtrip],
|
|
105
|
-
} = await serializeContent({} as ActionCtx, {} as AgentComponent, [
|
|
106
|
-
result as ToolResultPart,
|
|
107
|
-
]);
|
|
108
|
-
expect(roundtrip).toMatchObject(toolResult);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("tool results get normalized to output", async () => {
|
|
112
|
-
const toolResult = {
|
|
113
|
-
type: "tool-result" as const,
|
|
114
|
-
toolCallId: "tool-call-id",
|
|
115
|
-
toolName: "tool-name",
|
|
116
|
-
result: "hello world",
|
|
117
|
-
} satisfies Infer<typeof vToolResultPart>;
|
|
118
|
-
const expected = {
|
|
119
|
-
type: "tool-result",
|
|
120
|
-
toolCallId: "tool-call-id",
|
|
121
|
-
toolName: "tool-name",
|
|
122
|
-
output: {
|
|
123
|
-
type: "text",
|
|
124
|
-
value: "hello world",
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
const [deserialized] = toModelMessageContent([toolResult]);
|
|
128
|
-
expect(deserialized).toMatchObject(expected);
|
|
129
|
-
const {
|
|
130
|
-
content: [serialized],
|
|
131
|
-
} = await serializeContent({} as ActionCtx, {} as AgentComponent, [
|
|
132
|
-
toolResult,
|
|
133
|
-
]);
|
|
134
|
-
expect(serialized).toMatchObject(expected);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("saving files returns fileIds when too big", async () => {
|
|
138
|
-
// Make a big file
|
|
139
|
-
const bigArr = new Uint8Array(1024 * 65).fill(1);
|
|
140
|
-
const ab = bigArr.buffer.slice(
|
|
141
|
-
bigArr.byteOffset,
|
|
142
|
-
bigArr.byteOffset + bigArr.byteLength,
|
|
143
|
-
);
|
|
144
|
-
let called = false;
|
|
145
|
-
const ctx = {
|
|
146
|
-
runAction: async () => undefined,
|
|
147
|
-
runMutation: async (_fn: unknown, _args: unknown) => {
|
|
148
|
-
called = true;
|
|
149
|
-
return { fileId: "file-123", storageId: "storage-123" };
|
|
150
|
-
},
|
|
151
|
-
storage: {
|
|
152
|
-
store: async () => "storageId",
|
|
153
|
-
getUrl: async () => "https://example.com/file",
|
|
154
|
-
delete: async () => undefined,
|
|
155
|
-
},
|
|
156
|
-
} as unknown as ActionCtx;
|
|
157
|
-
const component = api as unknown as AgentComponent;
|
|
158
|
-
const content = [
|
|
159
|
-
{
|
|
160
|
-
type: "file" as const,
|
|
161
|
-
data: ab,
|
|
162
|
-
filename: "bigfile.bin",
|
|
163
|
-
mimeType: "application/octet-stream",
|
|
164
|
-
providerOptions: {},
|
|
165
|
-
},
|
|
166
|
-
];
|
|
167
|
-
const { content: ser, fileIds } = await serializeContent(
|
|
168
|
-
ctx,
|
|
169
|
-
component,
|
|
170
|
-
content,
|
|
171
|
-
);
|
|
172
|
-
expect(called).toBe(true);
|
|
173
|
-
expect(fileIds).toEqual(["file-123"]);
|
|
174
|
-
// Should have replaced data with a URL
|
|
175
|
-
const serArr = ser as SerializedContent;
|
|
176
|
-
expect(typeof (serArr as { data: unknown }[])[0].data).toBe("string");
|
|
177
|
-
expect((serArr as { data: unknown }[])[0].data as string).toMatch(
|
|
178
|
-
/^https?:\/\//,
|
|
179
|
-
);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("sanity: fileIds are not returned for small files", async () => {
|
|
183
|
-
const arr = new Uint8Array([1, 2, 3, 4, 5]);
|
|
184
|
-
const ab = arr.buffer.slice(
|
|
185
|
-
arr.byteOffset,
|
|
186
|
-
arr.byteOffset + arr.byteLength,
|
|
187
|
-
);
|
|
188
|
-
const ctx = {
|
|
189
|
-
runAction: async () => undefined,
|
|
190
|
-
runMutation: async () => ({
|
|
191
|
-
fileId: "file-123",
|
|
192
|
-
storageId: "storage-123",
|
|
193
|
-
}),
|
|
194
|
-
storage: {
|
|
195
|
-
store: async () => "storageId",
|
|
196
|
-
getUrl: async () => "https://example.com/file",
|
|
197
|
-
delete: async () => undefined,
|
|
198
|
-
},
|
|
199
|
-
} as unknown as ActionCtx;
|
|
200
|
-
const component = api as unknown as AgentComponent;
|
|
201
|
-
const content = [
|
|
202
|
-
{
|
|
203
|
-
type: "file" as const,
|
|
204
|
-
data: ab,
|
|
205
|
-
filename: "smallfile.bin",
|
|
206
|
-
mimeType: "application/octet-stream",
|
|
207
|
-
providerOptions: {},
|
|
208
|
-
},
|
|
209
|
-
];
|
|
210
|
-
const { fileIds } = await serializeContent(ctx, component, content);
|
|
211
|
-
expect(fileIds).toBeUndefined();
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test("tool-approval-request is preserved after serialization", async () => {
|
|
215
|
-
const approvalRequest = {
|
|
216
|
-
type: "tool-approval-request" as const,
|
|
217
|
-
approvalId: "approval-123",
|
|
218
|
-
toolCallId: "tool-call-456",
|
|
219
|
-
};
|
|
220
|
-
const { content } = await serializeContent(
|
|
221
|
-
{} as ActionCtx,
|
|
222
|
-
{} as AgentComponent,
|
|
223
|
-
[approvalRequest],
|
|
224
|
-
);
|
|
225
|
-
expect(content).toHaveLength(1);
|
|
226
|
-
expect((content as unknown[])[0]).toMatchObject(approvalRequest);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test("tool-approval-response with approved: true is preserved", async () => {
|
|
230
|
-
const approvalResponse = {
|
|
231
|
-
type: "tool-approval-response" as const,
|
|
232
|
-
approvalId: "approval-123",
|
|
233
|
-
approved: true,
|
|
234
|
-
reason: "User approved",
|
|
235
|
-
};
|
|
236
|
-
const { content } = await serializeContent(
|
|
237
|
-
{} as ActionCtx,
|
|
238
|
-
{} as AgentComponent,
|
|
239
|
-
[approvalResponse],
|
|
240
|
-
);
|
|
241
|
-
expect(content).toHaveLength(1);
|
|
242
|
-
expect((content as unknown[])[0]).toMatchObject(approvalResponse);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("tool-approval-response with approved: false is preserved", async () => {
|
|
246
|
-
const approvalResponse = {
|
|
247
|
-
type: "tool-approval-response" as const,
|
|
248
|
-
approvalId: "approval-123",
|
|
249
|
-
approved: false,
|
|
250
|
-
reason: "User denied",
|
|
251
|
-
providerExecuted: false,
|
|
252
|
-
};
|
|
253
|
-
const { content } = await serializeContent(
|
|
254
|
-
{} as ActionCtx,
|
|
255
|
-
{} as AgentComponent,
|
|
256
|
-
[approvalResponse],
|
|
257
|
-
);
|
|
258
|
-
expect(content).toHaveLength(1);
|
|
259
|
-
expect((content as unknown[])[0]).toMatchObject(approvalResponse);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
describe("autoDenyUnresolvedApprovals", () => {
|
|
263
|
-
test("returns messages unchanged when no unresolved approvals", () => {
|
|
264
|
-
const messages = [
|
|
265
|
-
{ role: "user" as const, content: "hello" },
|
|
266
|
-
{
|
|
267
|
-
role: "assistant" as const,
|
|
268
|
-
content: [
|
|
269
|
-
{ type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
|
|
270
|
-
{ type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
|
|
271
|
-
],
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
role: "tool" as const,
|
|
275
|
-
content: [
|
|
276
|
-
{ type: "tool-approval-response", approvalId: "ap1", approved: true },
|
|
277
|
-
],
|
|
278
|
-
},
|
|
279
|
-
] as any;
|
|
280
|
-
|
|
281
|
-
const result = autoDenyUnresolvedApprovals(messages);
|
|
282
|
-
expect(result).toBe(messages); // same reference, no changes
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test("injects synthetic denial for a single unresolved approval", () => {
|
|
286
|
-
const messages = [
|
|
287
|
-
{ role: "user" as const, content: "hello" },
|
|
288
|
-
{
|
|
289
|
-
role: "assistant" as const,
|
|
290
|
-
content: [
|
|
291
|
-
{ type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
|
|
292
|
-
{ type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
|
|
293
|
-
],
|
|
294
|
-
},
|
|
295
|
-
{ role: "user" as const, content: "new message" },
|
|
296
|
-
] as any;
|
|
297
|
-
|
|
298
|
-
const result = autoDenyUnresolvedApprovals(messages);
|
|
299
|
-
expect(result).toHaveLength(4); // original 3 + 1 synthetic tool message
|
|
300
|
-
// Synthetic denial should be inserted right after the assistant message (index 1)
|
|
301
|
-
expect(result[2].role).toBe("tool");
|
|
302
|
-
const denialContent = result[2].content as any[];
|
|
303
|
-
expect(denialContent).toHaveLength(1);
|
|
304
|
-
expect(denialContent[0].type).toBe("tool-approval-response");
|
|
305
|
-
expect(denialContent[0].approvalId).toBe("ap1");
|
|
306
|
-
expect(denialContent[0].approved).toBe(false);
|
|
307
|
-
expect(denialContent[0].reason).toBe("auto-denied: new generation started");
|
|
308
|
-
// The new user message should follow
|
|
309
|
-
expect(result[3].role).toBe("user");
|
|
310
|
-
expect(result[3].content).toBe("new message");
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
test("groups multiple unresolved approvals from the same step into a single synthetic message", () => {
|
|
314
|
-
const messages = [
|
|
315
|
-
{
|
|
316
|
-
role: "assistant" as const,
|
|
317
|
-
content: [
|
|
318
|
-
{ type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
|
|
319
|
-
{ type: "tool-call", toolCallId: "tc2", toolName: "b", input: {} },
|
|
320
|
-
{ type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
|
|
321
|
-
{ type: "tool-approval-request", approvalId: "ap2", toolCallId: "tc2" },
|
|
322
|
-
],
|
|
323
|
-
},
|
|
324
|
-
] as any;
|
|
325
|
-
|
|
326
|
-
const result = autoDenyUnresolvedApprovals(messages);
|
|
327
|
-
expect(result).toHaveLength(2); // assistant + 1 synthetic tool message
|
|
328
|
-
expect(result[1].role).toBe("tool");
|
|
329
|
-
const denialContent = result[1].content as any[];
|
|
330
|
-
expect(denialContent).toHaveLength(2);
|
|
331
|
-
expect(denialContent[0].approvalId).toBe("ap1");
|
|
332
|
-
expect(denialContent[0].approved).toBe(false);
|
|
333
|
-
expect(denialContent[1].approvalId).toBe("ap2");
|
|
334
|
-
expect(denialContent[1].approved).toBe(false);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
test("only auto-denies unresolved approvals, leaves resolved ones alone", () => {
|
|
338
|
-
const messages = [
|
|
339
|
-
{
|
|
340
|
-
role: "assistant" as const,
|
|
341
|
-
content: [
|
|
342
|
-
{ type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
|
|
343
|
-
{ type: "tool-call", toolCallId: "tc2", toolName: "b", input: {} },
|
|
344
|
-
{ type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
|
|
345
|
-
{ type: "tool-approval-request", approvalId: "ap2", toolCallId: "tc2" },
|
|
346
|
-
],
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
role: "tool" as const,
|
|
350
|
-
content: [
|
|
351
|
-
{ type: "tool-approval-response", approvalId: "ap1", approved: true },
|
|
352
|
-
],
|
|
353
|
-
},
|
|
354
|
-
{ role: "user" as const, content: "next question" },
|
|
355
|
-
] as any;
|
|
356
|
-
|
|
357
|
-
const result = autoDenyUnresolvedApprovals(messages);
|
|
358
|
-
// Should inject a denial for ap2 (unresolved) after the assistant message
|
|
359
|
-
expect(result).toHaveLength(4); // assistant + existing tool + synthetic denial + user
|
|
360
|
-
// The synthetic denial is inserted after the assistant (index 0)
|
|
361
|
-
expect(result[0].role).toBe("assistant");
|
|
362
|
-
expect(result[1].role).toBe("tool"); // synthetic denial for ap2
|
|
363
|
-
const denialContent = result[1].content as any[];
|
|
364
|
-
expect(denialContent).toHaveLength(1);
|
|
365
|
-
expect(denialContent[0].approvalId).toBe("ap2");
|
|
366
|
-
expect(denialContent[0].approved).toBe(false);
|
|
367
|
-
// Original tool message (ap1 response) follows
|
|
368
|
-
expect(result[2].role).toBe("tool");
|
|
369
|
-
const originalToolContent = result[2].content as any[];
|
|
370
|
-
expect(originalToolContent[0].approvalId).toBe("ap1");
|
|
371
|
-
expect(originalToolContent[0].approved).toBe(true);
|
|
372
|
-
// User message last
|
|
373
|
-
expect(result[3].role).toBe("user");
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
test("emits console.warn for each auto-denied approval", () => {
|
|
377
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
378
|
-
const messages = [
|
|
379
|
-
{
|
|
380
|
-
role: "assistant" as const,
|
|
381
|
-
content: [
|
|
382
|
-
{ type: "tool-call", toolCallId: "tc1", toolName: "a", input: {} },
|
|
383
|
-
{ type: "tool-call", toolCallId: "tc2", toolName: "b", input: {} },
|
|
384
|
-
{ type: "tool-approval-request", approvalId: "ap1", toolCallId: "tc1" },
|
|
385
|
-
{ type: "tool-approval-request", approvalId: "ap2", toolCallId: "tc2" },
|
|
386
|
-
],
|
|
387
|
-
},
|
|
388
|
-
] as any;
|
|
389
|
-
|
|
390
|
-
autoDenyUnresolvedApprovals(messages);
|
|
391
|
-
|
|
392
|
-
expect(warnSpy).toHaveBeenCalledTimes(2);
|
|
393
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
394
|
-
expect.stringContaining("ap1"),
|
|
395
|
-
);
|
|
396
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
397
|
-
expect.stringContaining("ap2"),
|
|
398
|
-
);
|
|
399
|
-
warnSpy.mockRestore();
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|