@alexkroman1/aai 1.7.1 → 1.8.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/.turbo/turbo-build.log +11 -9
- package/CHANGELOG.md +10 -0
- package/dist/{_internal-types-CrnTi9Ew.js → _internal-types-CfOAbK6V.js} +22 -35
- package/dist/constants-y68COEGj.js +29 -0
- package/dist/host/_base64.d.ts +2 -0
- package/dist/host/_mock-ws.d.ts +0 -61
- package/dist/host/_pipeline-test-fakes.d.ts +7 -4
- package/dist/host/_run-code.d.ts +0 -25
- package/dist/host/_runtime-conformance.d.ts +3 -34
- package/dist/host/memory-vector.d.ts +0 -11
- package/dist/host/providers/resolve-kv.d.ts +0 -7
- package/dist/host/providers/resolve-vector.d.ts +0 -8
- package/dist/host/providers/stt/assemblyai.d.ts +0 -14
- package/dist/host/providers/stt/deepgram.d.ts +2 -14
- package/dist/host/providers/stt/soniox.d.ts +0 -22
- package/dist/host/providers/tts/rime.d.ts +10 -31
- package/dist/host/runtime-barrel.js +619 -630
- package/dist/host/runtime-config.d.ts +9 -6
- package/dist/host/runtime.d.ts +3 -0
- package/dist/host/to-vercel-tools.d.ts +3 -33
- package/dist/host/transports/openai-realtime-transport.d.ts +43 -0
- package/dist/host/unstorage-kv.d.ts +0 -26
- package/dist/index.js +3 -3
- package/dist/openai-realtime-cjPAHMMx.js +10 -0
- package/dist/sdk/_internal-types.d.ts +6 -55
- package/dist/sdk/allowed-hosts.d.ts +4 -3
- package/dist/sdk/constants.d.ts +4 -29
- package/dist/sdk/define.d.ts +7 -4
- package/dist/sdk/kv.d.ts +13 -37
- package/dist/sdk/manifest-barrel.js +1 -1
- package/dist/sdk/manifest.d.ts +8 -2
- package/dist/sdk/protocol.js +1 -1
- package/dist/sdk/providers/s2s/openai-realtime.d.ts +17 -0
- package/dist/sdk/providers/s2s-barrel.d.ts +9 -0
- package/dist/sdk/providers/s2s-barrel.js +2 -0
- package/dist/sdk/providers/tts/rime.d.ts +1 -1
- package/dist/sdk/providers.d.ts +6 -2
- package/dist/sdk/types.d.ts +7 -1
- package/dist/{types-KUgezM6u.js → types-DOWVZhb9.js} +1 -7
- package/dist/{ws-upgrade-BeOQ7fXL.js → ws-upgrade-CG8-by1n.js} +2 -3
- package/host/_base64.ts +9 -0
- package/host/_mock-ws.ts +0 -65
- package/host/_pipeline-test-fakes.ts +19 -31
- package/host/_run-code.ts +10 -53
- package/host/_runtime-conformance.ts +3 -44
- package/host/_test-utils.ts +20 -42
- package/host/builtin-tools.test.ts +127 -222
- package/host/builtin-tools.ts +6 -10
- package/host/cleanup.test.ts +30 -73
- package/host/integration/pipeline-reference.integration.test.ts +12 -17
- package/host/integration.test.ts +0 -7
- package/host/memory-vector.test.ts +3 -1
- package/host/memory-vector.ts +16 -21
- package/host/pinecone-vector.test.ts +14 -17
- package/host/pinecone-vector.ts +10 -19
- package/host/providers/providers.test-d.ts +5 -3
- package/host/providers/resolve-kv.ts +23 -41
- package/host/providers/resolve-vector.ts +3 -12
- package/host/providers/resolve.test.ts +15 -28
- package/host/providers/resolve.ts +24 -24
- package/host/providers/stt/assemblyai.test.ts +2 -14
- package/host/providers/stt/assemblyai.ts +12 -35
- package/host/providers/stt/deepgram.test.ts +23 -83
- package/host/providers/stt/deepgram.ts +15 -40
- package/host/providers/stt/elevenlabs.test.ts +26 -38
- package/host/providers/stt/elevenlabs.ts +10 -9
- package/host/providers/stt/soniox.test.ts +35 -85
- package/host/providers/stt/soniox.ts +8 -53
- package/host/providers/tts/cartesia.test.ts +19 -58
- package/host/providers/tts/cartesia.ts +36 -66
- package/host/providers/tts/rime.test.ts +12 -38
- package/host/providers/tts/rime.ts +23 -86
- package/host/runtime-config.test.ts +9 -9
- package/host/runtime-config.ts +16 -22
- package/host/runtime.test.ts +111 -73
- package/host/runtime.ts +138 -86
- package/host/s2s.test.ts +92 -191
- package/host/s2s.ts +55 -49
- package/host/server-shutdown.test.ts +9 -30
- package/host/server.test.ts +2 -13
- package/host/server.ts +85 -100
- package/host/session-core.test.ts +15 -30
- package/host/session-core.ts +10 -13
- package/host/session-prompt.test.ts +1 -5
- package/host/to-vercel-tools.test.ts +53 -72
- package/host/to-vercel-tools.ts +9 -39
- package/host/tool-executor.test.ts +25 -51
- package/host/tool-executor.ts +18 -12
- package/host/transports/openai-realtime-transport.test.ts +371 -0
- package/host/transports/openai-realtime-transport.ts +319 -0
- package/host/transports/pipeline-transport.test.ts +125 -298
- package/host/transports/pipeline-transport.ts +20 -68
- package/host/transports/s2s-transport-fixtures.test.ts +31 -92
- package/host/transports/s2s-transport.test.ts +65 -134
- package/host/transports/s2s-transport.ts +15 -43
- package/host/transports/types.test.ts +4 -8
- package/host/unstorage-kv.test.ts +3 -2
- package/host/unstorage-kv.ts +5 -35
- package/host/ws-handler.test.ts +72 -176
- package/host/ws-handler.ts +6 -12
- package/package.json +6 -1
- package/sdk/__snapshots__/exports.test.ts.snap +7 -0
- package/sdk/__snapshots__/schema-shapes.test.ts.snap +1 -0
- package/sdk/_internal-types.test.ts +6 -9
- package/sdk/_internal-types.ts +16 -57
- package/sdk/_test-matchers.ts +25 -15
- package/sdk/allowed-hosts.test.ts +50 -114
- package/sdk/allowed-hosts.ts +8 -14
- package/sdk/constants.ts +5 -52
- package/sdk/define.test.ts +7 -6
- package/sdk/define.ts +7 -3
- package/sdk/exports.test.ts +6 -1
- package/sdk/kv.ts +13 -37
- package/sdk/manifest.test-d.ts +5 -0
- package/sdk/manifest.test.ts +61 -9
- package/sdk/manifest.ts +11 -11
- package/sdk/protocol-compat.test.ts +66 -98
- package/sdk/protocol-snapshot.test.ts +2 -16
- package/sdk/protocol.test.ts +13 -22
- package/sdk/providers/s2s/openai-realtime.ts +36 -0
- package/sdk/providers/s2s-barrel.ts +12 -0
- package/sdk/providers/tts/rime.ts +1 -1
- package/sdk/providers.ts +24 -5
- package/sdk/schema-alignment.test.ts +25 -73
- package/sdk/schema-shapes.test.ts +1 -29
- package/sdk/system-prompt.test.ts +0 -1
- package/sdk/system-prompt.ts +17 -19
- package/sdk/types-inference.test.ts +10 -36
- package/sdk/types.ts +7 -0
- package/sdk/ws-upgrade.test.ts +24 -23
- package/sdk/ws-upgrade.ts +2 -3
- package/tsdown.config.ts +8 -11
- package/dist/constants-C2nirZUI.js +0 -54
|
@@ -4,6 +4,11 @@ import { describe, expect, test, vi } from "vitest";
|
|
|
4
4
|
import { createMockToolContext } from "./_test-utils.ts";
|
|
5
5
|
import { executeInIsolate, resolveAllBuiltins } from "./builtin-tools.ts";
|
|
6
6
|
|
|
7
|
+
function runCode(code: string): Promise<unknown> {
|
|
8
|
+
const { defs } = resolveAllBuiltins(["run_code"]);
|
|
9
|
+
return defs.run_code?.execute({ code }, createMockToolContext()) as Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
describe("resolveAllBuiltins schemas", () => {
|
|
8
13
|
test("returns requested tools", () => {
|
|
9
14
|
const { schemas } = resolveAllBuiltins([
|
|
@@ -47,34 +52,23 @@ describe("resolveAllBuiltins defs", () => {
|
|
|
47
52
|
// ─── run_code ──────────────────────────────────────────────────────────
|
|
48
53
|
|
|
49
54
|
test("run_code executes and returns stdout", async () => {
|
|
50
|
-
const
|
|
51
|
-
const ctx = createMockToolContext();
|
|
52
|
-
const result = await defs.run_code?.execute({ code: 'console.log("hello")' }, ctx);
|
|
55
|
+
const result = await runCode('console.log("hello")');
|
|
53
56
|
expect(result).toBe("hello");
|
|
54
57
|
});
|
|
55
58
|
|
|
56
59
|
test("run_code returns error for syntax errors", async () => {
|
|
57
|
-
const
|
|
58
|
-
const ctx = createMockToolContext();
|
|
59
|
-
const result = await defs.run_code?.execute({ code: "%%%" }, ctx);
|
|
60
|
+
const result = await runCode("%%%");
|
|
60
61
|
expect(result).toHaveProperty("error");
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
test("run_code returns no-output message for silent code", async () => {
|
|
64
|
-
const
|
|
65
|
-
const ctx = createMockToolContext();
|
|
66
|
-
const result = await defs.run_code?.execute({ code: "const x = 1 + 1;" }, ctx);
|
|
65
|
+
const result = await runCode("const x = 1 + 1;");
|
|
67
66
|
expect(result).toBe("Code ran successfully (no output)");
|
|
68
67
|
});
|
|
69
68
|
|
|
70
69
|
test("run_code captures console.warn and console.error", async () => {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const result = await defs.run_code?.execute(
|
|
74
|
-
{
|
|
75
|
-
code: 'console.warn("w"); console.error("e"); console.debug("d"); console.info("i")',
|
|
76
|
-
},
|
|
77
|
-
ctx,
|
|
70
|
+
const result = await runCode(
|
|
71
|
+
'console.warn("w"); console.error("e"); console.debug("d"); console.info("i")',
|
|
78
72
|
);
|
|
79
73
|
expect(result).toBe("w\ne\nd\ni");
|
|
80
74
|
});
|
|
@@ -82,276 +76,187 @@ describe("resolveAllBuiltins defs", () => {
|
|
|
82
76
|
// ─── run_code security: vm sandbox prevents host access ──────────────
|
|
83
77
|
|
|
84
78
|
test("run_code sandbox blocks network access", async () => {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
} catch(e) {
|
|
94
|
-
console.log("BLOCKED:" + e.message);
|
|
95
|
-
}
|
|
96
|
-
`,
|
|
97
|
-
},
|
|
98
|
-
ctx,
|
|
99
|
-
);
|
|
79
|
+
const result = await runCode(`
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetch("https://example.com");
|
|
82
|
+
console.log("ESCAPED:" + res.status);
|
|
83
|
+
} catch(e) {
|
|
84
|
+
console.log("BLOCKED:" + e.message);
|
|
85
|
+
}
|
|
86
|
+
`);
|
|
100
87
|
expect(result).toBeTypeOf("string");
|
|
101
88
|
expect(result as string).toMatch(/BLOCKED/);
|
|
102
89
|
});
|
|
103
90
|
|
|
104
91
|
test("run_code sandbox blocks filesystem writes", async () => {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
} catch(e) {
|
|
115
|
-
console.log("BLOCKED:" + e.message);
|
|
116
|
-
}
|
|
117
|
-
`,
|
|
118
|
-
},
|
|
119
|
-
ctx,
|
|
120
|
-
);
|
|
92
|
+
const result = await runCode(`
|
|
93
|
+
try {
|
|
94
|
+
const fs = await import("node:fs");
|
|
95
|
+
fs.writeFileSync("/tmp/pwned.txt", "owned");
|
|
96
|
+
console.log("ESCAPED");
|
|
97
|
+
} catch(e) {
|
|
98
|
+
console.log("BLOCKED:" + e.message);
|
|
99
|
+
}
|
|
100
|
+
`);
|
|
121
101
|
expect(result).toBeTypeOf("string");
|
|
122
102
|
expect(result as string).toMatch(/BLOCKED/);
|
|
123
103
|
});
|
|
124
104
|
|
|
125
105
|
test("run_code sandbox blocks child process spawning", async () => {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} catch(e) {
|
|
136
|
-
console.log("BLOCKED:" + e.message);
|
|
137
|
-
}
|
|
138
|
-
`,
|
|
139
|
-
},
|
|
140
|
-
ctx,
|
|
141
|
-
);
|
|
106
|
+
const result = await runCode(`
|
|
107
|
+
try {
|
|
108
|
+
const cp = await import("node:child_process");
|
|
109
|
+
const out = cp.execSync("id").toString();
|
|
110
|
+
console.log("ESCAPED:" + out);
|
|
111
|
+
} catch(e) {
|
|
112
|
+
console.log("BLOCKED:" + e.message);
|
|
113
|
+
}
|
|
114
|
+
`);
|
|
142
115
|
expect(result).toBeTypeOf("string");
|
|
143
116
|
expect(result as string).toMatch(/BLOCKED/);
|
|
144
117
|
});
|
|
145
118
|
|
|
146
119
|
test("run_code sandbox blocks env var access", async () => {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} catch(e) {
|
|
158
|
-
console.log("SAFE:" + e.message);
|
|
159
|
-
}
|
|
160
|
-
`,
|
|
161
|
-
},
|
|
162
|
-
ctx,
|
|
163
|
-
);
|
|
120
|
+
const result = await runCode(`
|
|
121
|
+
try {
|
|
122
|
+
const keys = process.env ? Object.keys(process.env) : [];
|
|
123
|
+
const hasPath = keys.includes("PATH");
|
|
124
|
+
const hasHome = keys.includes("HOME");
|
|
125
|
+
console.log(hasPath || hasHome ? "LEAKED_ENV" : "SAFE:" + keys.length);
|
|
126
|
+
} catch(e) {
|
|
127
|
+
console.log("SAFE:" + e.message);
|
|
128
|
+
}
|
|
129
|
+
`);
|
|
164
130
|
expect(result).toBeTypeOf("string");
|
|
165
131
|
expect(result as string).not.toMatch(/LEAKED_ENV/);
|
|
166
132
|
});
|
|
167
133
|
|
|
168
134
|
test("run_code sandbox prevents constructor chain escape", async () => {
|
|
169
|
-
const { defs } = resolveAllBuiltins(["run_code"]);
|
|
170
|
-
const ctx = createMockToolContext();
|
|
171
135
|
// This was the critical bypass in the old regex approach — the VM context
|
|
172
136
|
// doesn't expose `process` so host secrets can't be exfiltrated.
|
|
173
|
-
const result = await
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
`,
|
|
187
|
-
},
|
|
188
|
-
ctx,
|
|
189
|
-
);
|
|
137
|
+
const result = await runCode(`
|
|
138
|
+
const c = "con" + "stru" + "ctor";
|
|
139
|
+
const F = ""[c][c];
|
|
140
|
+
try {
|
|
141
|
+
const p = F("return process")();
|
|
142
|
+
const keys = p && p.env ? Object.keys(p.env) : [];
|
|
143
|
+
const hasPath = keys.includes("PATH");
|
|
144
|
+
console.log(hasPath ? "LEAKED_ENV" : "SAFE:" + keys.length);
|
|
145
|
+
} catch(e) {
|
|
146
|
+
console.log("SAFE:" + e.message);
|
|
147
|
+
}
|
|
148
|
+
`);
|
|
190
149
|
expect(result).toBeTypeOf("string");
|
|
191
150
|
expect(result as string).not.toMatch(/LEAKED_ENV/);
|
|
192
151
|
});
|
|
193
152
|
|
|
194
153
|
test("run_code allows normal .constructor property check", async () => {
|
|
195
|
-
const
|
|
196
|
-
const ctx = createMockToolContext();
|
|
197
|
-
const result = await defs.run_code?.execute(
|
|
198
|
-
{ code: 'console.log("hello".constructor.name)' },
|
|
199
|
-
ctx,
|
|
200
|
-
);
|
|
154
|
+
const result = await runCode('console.log("hello".constructor.name)');
|
|
201
155
|
expect(result).toBe("String");
|
|
202
156
|
});
|
|
203
157
|
|
|
204
158
|
test("run_code sandbox blocks console.log.constructor code generation", async () => {
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
} catch(e) {
|
|
214
|
-
console.log("BLOCKED:" + e.message);
|
|
215
|
-
}
|
|
216
|
-
`,
|
|
217
|
-
},
|
|
218
|
-
ctx,
|
|
219
|
-
);
|
|
159
|
+
const result = await runCode(`
|
|
160
|
+
try {
|
|
161
|
+
const fn = console.log.constructor('return 1')();
|
|
162
|
+
console.log("ESCAPED:" + fn);
|
|
163
|
+
} catch(e) {
|
|
164
|
+
console.log("BLOCKED:" + e.message);
|
|
165
|
+
}
|
|
166
|
+
`);
|
|
220
167
|
expect(result).toBeTypeOf("string");
|
|
221
168
|
expect(result as string).toMatch(/BLOCKED/);
|
|
222
169
|
expect(result as string).not.toMatch(/ESCAPED/);
|
|
223
170
|
});
|
|
224
171
|
|
|
225
172
|
test("run_code sandbox blocks URL.constructor.constructor code generation", async () => {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
} catch(e) {
|
|
235
|
-
console.log("BLOCKED:" + e.message);
|
|
236
|
-
}
|
|
237
|
-
`,
|
|
238
|
-
},
|
|
239
|
-
ctx,
|
|
240
|
-
);
|
|
173
|
+
const result = await runCode(`
|
|
174
|
+
try {
|
|
175
|
+
const fn = URL.constructor.constructor('return 1')();
|
|
176
|
+
console.log("ESCAPED:" + fn);
|
|
177
|
+
} catch(e) {
|
|
178
|
+
console.log("BLOCKED:" + e.message);
|
|
179
|
+
}
|
|
180
|
+
`);
|
|
241
181
|
expect(result).toBeTypeOf("string");
|
|
242
182
|
expect(result as string).toMatch(/BLOCKED/);
|
|
243
183
|
expect(result as string).not.toMatch(/ESCAPED/);
|
|
244
184
|
});
|
|
245
185
|
|
|
246
186
|
test("run_code sandbox blocks template literal constructor bypass", async () => {
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
{
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
} catch(e) {
|
|
260
|
-
console.log("SAFE:" + e.message);
|
|
261
|
-
}
|
|
262
|
-
`,
|
|
263
|
-
},
|
|
264
|
-
ctx,
|
|
265
|
-
);
|
|
187
|
+
const result = await runCode(`
|
|
188
|
+
const c = \`\${"con"}\${"stru"}\${"ctor"}\`;
|
|
189
|
+
const F = ""[c][c];
|
|
190
|
+
try {
|
|
191
|
+
const p = F("return process")();
|
|
192
|
+
const keys = p && p.env ? Object.keys(p.env) : [];
|
|
193
|
+
const hasPath = keys.includes("PATH");
|
|
194
|
+
console.log(hasPath ? "LEAKED_ENV" : "SAFE:" + keys.length + " keys");
|
|
195
|
+
} catch(e) {
|
|
196
|
+
console.log("SAFE:" + e.message);
|
|
197
|
+
}
|
|
198
|
+
`);
|
|
266
199
|
expect(result).toBeTypeOf("string");
|
|
267
200
|
expect(result as string).not.toMatch(/LEAKED_ENV/);
|
|
268
201
|
});
|
|
269
202
|
|
|
270
203
|
test("run_code sandbox blocks Array.join constructor bypass", async () => {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
{
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
} catch(e) {
|
|
284
|
-
console.log("SAFE:" + e.message);
|
|
285
|
-
}
|
|
286
|
-
`,
|
|
287
|
-
},
|
|
288
|
-
ctx,
|
|
289
|
-
);
|
|
204
|
+
const result = await runCode(`
|
|
205
|
+
const c = ["con","stru","ctor"].join("");
|
|
206
|
+
const F = ""[c][c];
|
|
207
|
+
try {
|
|
208
|
+
const p = F("return process")();
|
|
209
|
+
const keys = p && p.env ? Object.keys(p.env) : [];
|
|
210
|
+
const hasPath = keys.includes("PATH");
|
|
211
|
+
console.log(hasPath ? "LEAKED_ENV" : "SAFE:" + keys.length + " keys");
|
|
212
|
+
} catch(e) {
|
|
213
|
+
console.log("SAFE:" + e.message);
|
|
214
|
+
}
|
|
215
|
+
`);
|
|
290
216
|
expect(result).toBeTypeOf("string");
|
|
291
217
|
expect(result as string).not.toMatch(/LEAKED_ENV/);
|
|
292
218
|
});
|
|
293
219
|
|
|
294
220
|
test("run_code sandbox blocks fromCharCode constructor bypass", async () => {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
{
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
} catch(e) {
|
|
308
|
-
console.log("SAFE:" + e.message);
|
|
309
|
-
}
|
|
310
|
-
`,
|
|
311
|
-
},
|
|
312
|
-
ctx,
|
|
313
|
-
);
|
|
221
|
+
const result = await runCode(`
|
|
222
|
+
const s = String.fromCharCode(99,111,110,115,116,114,117,99,116,111,114);
|
|
223
|
+
const F = ""[s][s];
|
|
224
|
+
try {
|
|
225
|
+
const p = F("return process")();
|
|
226
|
+
const keys = p && p.env ? Object.keys(p.env) : [];
|
|
227
|
+
const hasPath = keys.includes("PATH");
|
|
228
|
+
console.log(hasPath ? "LEAKED_ENV" : "SAFE:" + keys.length + " keys");
|
|
229
|
+
} catch(e) {
|
|
230
|
+
console.log("SAFE:" + e.message);
|
|
231
|
+
}
|
|
232
|
+
`);
|
|
314
233
|
expect(result).toBeTypeOf("string");
|
|
315
234
|
expect(result as string).not.toMatch(/LEAKED_ENV/);
|
|
316
235
|
});
|
|
317
236
|
|
|
318
237
|
test("run_code sandbox blocks dynamic import of node:os", async () => {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
} catch(e) {
|
|
328
|
-
console.log("BLOCKED: " + e.message);
|
|
329
|
-
}
|
|
330
|
-
`,
|
|
331
|
-
},
|
|
332
|
-
ctx,
|
|
333
|
-
);
|
|
238
|
+
const result = await runCode(`
|
|
239
|
+
try {
|
|
240
|
+
const m = await import("node:os");
|
|
241
|
+
console.log("ESCAPED: " + m.hostname());
|
|
242
|
+
} catch(e) {
|
|
243
|
+
console.log("BLOCKED: " + e.message);
|
|
244
|
+
}
|
|
245
|
+
`);
|
|
334
246
|
expect(result).toBeTypeOf("string");
|
|
335
247
|
expect(result as string).toMatch(/BLOCKED/);
|
|
336
248
|
expect(result as string).not.toMatch(/ESCAPED/);
|
|
337
249
|
});
|
|
338
250
|
|
|
339
251
|
test("run_code sandbox blocks fetch to cloud metadata endpoint", async () => {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
} catch(e) {
|
|
349
|
-
console.log("BLOCKED:" + e.message);
|
|
350
|
-
}
|
|
351
|
-
`,
|
|
352
|
-
},
|
|
353
|
-
ctx,
|
|
354
|
-
);
|
|
252
|
+
const result = await runCode(`
|
|
253
|
+
try {
|
|
254
|
+
const res = await fetch("http://169.254.169.254/latest/meta-data/");
|
|
255
|
+
console.log("ESCAPED:" + res.status);
|
|
256
|
+
} catch(e) {
|
|
257
|
+
console.log("BLOCKED:" + e.message);
|
|
258
|
+
}
|
|
259
|
+
`);
|
|
355
260
|
expect(result).toBeTypeOf("string");
|
|
356
261
|
expect(result as string).toMatch(/BLOCKED/);
|
|
357
262
|
});
|
package/host/builtin-tools.ts
CHANGED
|
@@ -113,15 +113,12 @@ function createVisitWebpage(
|
|
|
113
113
|
if (!resp.ok) {
|
|
114
114
|
return { error: `Failed to fetch: ${resp.status} ${resp.statusText}`, url };
|
|
115
115
|
}
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
htmlContent.length > MAX_HTML_BYTES ? htmlContent.slice(0, MAX_HTML_BYTES) : htmlContent;
|
|
119
|
-
const text = htmlToText(trimmedHtml);
|
|
116
|
+
const html = await resp.text();
|
|
117
|
+
const text = htmlToText(html.slice(0, MAX_HTML_BYTES));
|
|
120
118
|
const truncated = text.length > MAX_PAGE_CHARS;
|
|
121
|
-
const content = truncated ? text.slice(0, MAX_PAGE_CHARS) : text;
|
|
122
119
|
return {
|
|
123
120
|
url,
|
|
124
|
-
content,
|
|
121
|
+
content: text.slice(0, MAX_PAGE_CHARS),
|
|
125
122
|
...(truncated ? { truncated: true, totalChars: text.length } : {}),
|
|
126
123
|
};
|
|
127
124
|
},
|
|
@@ -202,9 +199,9 @@ export type BuiltinToolOptions = {
|
|
|
202
199
|
};
|
|
203
200
|
|
|
204
201
|
type ToolDefRecord = Record<string, ToolDef<z.ZodObject<z.ZodRawShape>>>;
|
|
202
|
+
type BuiltinEntry = [string, ToolDef & { guidance?: string }];
|
|
205
203
|
|
|
206
|
-
|
|
207
|
-
function resolveBuiltin(name: string, opts?: BuiltinToolOptions): [string, ToolDef][] {
|
|
204
|
+
function resolveBuiltin(name: string, opts?: BuiltinToolOptions): BuiltinEntry[] {
|
|
208
205
|
switch (name) {
|
|
209
206
|
case "web_search":
|
|
210
207
|
return [["web_search", createWebSearch(opts?.fetch)]];
|
|
@@ -247,8 +244,7 @@ export function resolveAllBuiltins(
|
|
|
247
244
|
description: def.description,
|
|
248
245
|
parameters: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS) as ToolSchema["parameters"],
|
|
249
246
|
});
|
|
250
|
-
|
|
251
|
-
if (g) guidance.push(g);
|
|
247
|
+
if (def.guidance) guidance.push(def.guidance);
|
|
252
248
|
}
|
|
253
249
|
}
|
|
254
250
|
|
package/host/cleanup.test.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
-
/**
|
|
3
|
-
* Resource cleanup and leak detection tests for server-side components.
|
|
4
|
-
*
|
|
5
|
-
* Verifies that WebSocket connections, S2S handles, timers,
|
|
6
|
-
* message buffers, and hook promises are properly cleaned up on disconnect,
|
|
7
|
-
* error, and reset to prevent memory leaks in long-running processes.
|
|
8
|
-
*/
|
|
9
2
|
|
|
10
3
|
import { describe, expect, test, vi } from "vitest";
|
|
11
4
|
import { MockWebSocket } from "./_mock-ws.ts";
|
|
@@ -15,21 +8,32 @@ import { wireSessionSocket } from "./ws-handler.ts";
|
|
|
15
8
|
|
|
16
9
|
const defaultConfig = { audioFormat: "pcm16" as const, sampleRate: 16_000, ttsSampleRate: 24_000 };
|
|
17
10
|
|
|
18
|
-
|
|
11
|
+
function makeOpenWs(): MockWebSocket {
|
|
12
|
+
const ws = new MockWebSocket("ws://test");
|
|
13
|
+
ws.readyState = MockWebSocket.OPEN;
|
|
14
|
+
return ws;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function wire(
|
|
18
|
+
ws: MockWebSocket,
|
|
19
|
+
core: SessionCore,
|
|
20
|
+
sessions: Map<string, SessionCore> = new Map(),
|
|
21
|
+
): Map<string, SessionCore> {
|
|
22
|
+
wireSessionSocket(ws, {
|
|
23
|
+
sessions,
|
|
24
|
+
createSession: () => core,
|
|
25
|
+
readyConfig: defaultConfig,
|
|
26
|
+
logger: silentLogger,
|
|
27
|
+
});
|
|
28
|
+
return sessions;
|
|
29
|
+
}
|
|
19
30
|
|
|
20
31
|
describe("wireSessionSocket resource cleanup", () => {
|
|
21
32
|
test("session.stop() is called exactly once on normal close", async () => {
|
|
22
33
|
const core = makeMockCore();
|
|
23
|
-
const ws =
|
|
24
|
-
ws.readyState = MockWebSocket.OPEN;
|
|
25
|
-
|
|
26
|
-
wireSessionSocket(ws, {
|
|
27
|
-
sessions: new Map(),
|
|
28
|
-
createSession: () => core,
|
|
29
|
-
readyConfig: defaultConfig,
|
|
30
|
-
logger: silentLogger,
|
|
31
|
-
});
|
|
34
|
+
const ws = makeOpenWs();
|
|
32
35
|
|
|
36
|
+
wire(ws, core);
|
|
33
37
|
ws.close();
|
|
34
38
|
|
|
35
39
|
await vi.waitFor(() => {
|
|
@@ -38,18 +42,9 @@ describe("wireSessionSocket resource cleanup", () => {
|
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
test("session is removed from sessions map even when stop() rejects", async () => {
|
|
41
|
-
const sessions = new Map<string, SessionCore>();
|
|
42
45
|
const core = makeMockCore({ stop: vi.fn(() => Promise.reject(new Error("stop failed"))) });
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
ws.readyState = MockWebSocket.OPEN;
|
|
46
|
-
|
|
47
|
-
wireSessionSocket(ws, {
|
|
48
|
-
sessions,
|
|
49
|
-
createSession: () => core,
|
|
50
|
-
readyConfig: defaultConfig,
|
|
51
|
-
logger: silentLogger,
|
|
52
|
-
});
|
|
46
|
+
const ws = makeOpenWs();
|
|
47
|
+
const sessions = wire(ws, core);
|
|
53
48
|
|
|
54
49
|
expect(sessions.size).toBe(1);
|
|
55
50
|
ws.close();
|
|
@@ -61,26 +56,15 @@ describe("wireSessionSocket resource cleanup", () => {
|
|
|
61
56
|
|
|
62
57
|
test("message buffer is cleared when start() fails", async () => {
|
|
63
58
|
const core = makeMockCore({ start: vi.fn(() => Promise.reject(new Error("start failed"))) });
|
|
64
|
-
const
|
|
59
|
+
const ws = makeOpenWs();
|
|
60
|
+
const sessions = wire(ws, core);
|
|
65
61
|
|
|
66
|
-
const ws = new MockWebSocket("ws://test");
|
|
67
|
-
ws.readyState = MockWebSocket.OPEN;
|
|
68
|
-
|
|
69
|
-
wireSessionSocket(ws, {
|
|
70
|
-
sessions,
|
|
71
|
-
createSession: () => core,
|
|
72
|
-
readyConfig: defaultConfig,
|
|
73
|
-
logger: silentLogger,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Send a binary frame while start is failing (string frames are now dropped as non-binary)
|
|
77
62
|
ws.simulateMessage(new ArrayBuffer(4));
|
|
78
63
|
|
|
79
64
|
await vi.waitFor(() => {
|
|
80
65
|
expect(sessions.size).toBe(0);
|
|
81
66
|
});
|
|
82
67
|
|
|
83
|
-
// Session is null, further messages should be silently ignored (no throw)
|
|
84
68
|
ws.simulateMessage(new ArrayBuffer(4));
|
|
85
69
|
});
|
|
86
70
|
|
|
@@ -88,22 +72,11 @@ describe("wireSessionSocket resource cleanup", () => {
|
|
|
88
72
|
const core = makeMockCore({
|
|
89
73
|
stop: vi.fn(() => new Promise<void>((r) => setTimeout(r, 50))),
|
|
90
74
|
});
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const ws = new MockWebSocket("ws://test");
|
|
94
|
-
ws.readyState = MockWebSocket.OPEN;
|
|
95
|
-
|
|
96
|
-
wireSessionSocket(ws, {
|
|
97
|
-
sessions,
|
|
98
|
-
createSession: () => core,
|
|
99
|
-
readyConfig: defaultConfig,
|
|
100
|
-
logger: silentLogger,
|
|
101
|
-
});
|
|
75
|
+
const ws = makeOpenWs();
|
|
76
|
+
wire(ws, core);
|
|
102
77
|
|
|
103
78
|
ws.close();
|
|
104
79
|
|
|
105
|
-
// Even if close event fires again, stop should only be called once
|
|
106
|
-
// because the session reference is captured on first close
|
|
107
80
|
await vi.waitFor(() => {
|
|
108
81
|
expect(core.stop).toHaveBeenCalledOnce();
|
|
109
82
|
});
|
|
@@ -112,38 +85,22 @@ describe("wireSessionSocket resource cleanup", () => {
|
|
|
112
85
|
test("close before open does not throw or leak", () => {
|
|
113
86
|
const ws = new MockWebSocket("ws://test");
|
|
114
87
|
ws.readyState = MockWebSocket.CONNECTING;
|
|
115
|
-
const sessions =
|
|
88
|
+
const sessions = wire(ws, makeMockCore());
|
|
116
89
|
|
|
117
|
-
wireSessionSocket(ws, {
|
|
118
|
-
sessions,
|
|
119
|
-
createSession: () => makeMockCore(),
|
|
120
|
-
readyConfig: defaultConfig,
|
|
121
|
-
logger: silentLogger,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Close before open — session is null, should not throw
|
|
125
90
|
ws.close();
|
|
126
91
|
expect(sessions.size).toBe(0);
|
|
127
92
|
});
|
|
128
93
|
|
|
129
94
|
test("error event after close does not throw", async () => {
|
|
130
95
|
const core = makeMockCore();
|
|
131
|
-
const ws =
|
|
132
|
-
ws
|
|
133
|
-
|
|
134
|
-
wireSessionSocket(ws, {
|
|
135
|
-
sessions: new Map(),
|
|
136
|
-
createSession: () => core,
|
|
137
|
-
readyConfig: defaultConfig,
|
|
138
|
-
logger: silentLogger,
|
|
139
|
-
});
|
|
96
|
+
const ws = makeOpenWs();
|
|
97
|
+
wire(ws, core);
|
|
140
98
|
|
|
141
99
|
ws.close();
|
|
142
100
|
await vi.waitFor(() => {
|
|
143
101
|
expect(core.stop).toHaveBeenCalled();
|
|
144
102
|
});
|
|
145
103
|
|
|
146
|
-
// Error after close should not throw
|
|
147
104
|
ws.dispatchEvent(new Event("error"));
|
|
148
105
|
});
|
|
149
106
|
});
|