@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.
Files changed (133) hide show
  1. package/.turbo/turbo-build.log +11 -9
  2. package/CHANGELOG.md +10 -0
  3. package/dist/{_internal-types-CrnTi9Ew.js → _internal-types-CfOAbK6V.js} +22 -35
  4. package/dist/constants-y68COEGj.js +29 -0
  5. package/dist/host/_base64.d.ts +2 -0
  6. package/dist/host/_mock-ws.d.ts +0 -61
  7. package/dist/host/_pipeline-test-fakes.d.ts +7 -4
  8. package/dist/host/_run-code.d.ts +0 -25
  9. package/dist/host/_runtime-conformance.d.ts +3 -34
  10. package/dist/host/memory-vector.d.ts +0 -11
  11. package/dist/host/providers/resolve-kv.d.ts +0 -7
  12. package/dist/host/providers/resolve-vector.d.ts +0 -8
  13. package/dist/host/providers/stt/assemblyai.d.ts +0 -14
  14. package/dist/host/providers/stt/deepgram.d.ts +2 -14
  15. package/dist/host/providers/stt/soniox.d.ts +0 -22
  16. package/dist/host/providers/tts/rime.d.ts +10 -31
  17. package/dist/host/runtime-barrel.js +619 -630
  18. package/dist/host/runtime-config.d.ts +9 -6
  19. package/dist/host/runtime.d.ts +3 -0
  20. package/dist/host/to-vercel-tools.d.ts +3 -33
  21. package/dist/host/transports/openai-realtime-transport.d.ts +43 -0
  22. package/dist/host/unstorage-kv.d.ts +0 -26
  23. package/dist/index.js +3 -3
  24. package/dist/openai-realtime-cjPAHMMx.js +10 -0
  25. package/dist/sdk/_internal-types.d.ts +6 -55
  26. package/dist/sdk/allowed-hosts.d.ts +4 -3
  27. package/dist/sdk/constants.d.ts +4 -29
  28. package/dist/sdk/define.d.ts +7 -4
  29. package/dist/sdk/kv.d.ts +13 -37
  30. package/dist/sdk/manifest-barrel.js +1 -1
  31. package/dist/sdk/manifest.d.ts +8 -2
  32. package/dist/sdk/protocol.js +1 -1
  33. package/dist/sdk/providers/s2s/openai-realtime.d.ts +17 -0
  34. package/dist/sdk/providers/s2s-barrel.d.ts +9 -0
  35. package/dist/sdk/providers/s2s-barrel.js +2 -0
  36. package/dist/sdk/providers/tts/rime.d.ts +1 -1
  37. package/dist/sdk/providers.d.ts +6 -2
  38. package/dist/sdk/types.d.ts +7 -1
  39. package/dist/{types-KUgezM6u.js → types-DOWVZhb9.js} +1 -7
  40. package/dist/{ws-upgrade-BeOQ7fXL.js → ws-upgrade-CG8-by1n.js} +2 -3
  41. package/host/_base64.ts +9 -0
  42. package/host/_mock-ws.ts +0 -65
  43. package/host/_pipeline-test-fakes.ts +19 -31
  44. package/host/_run-code.ts +10 -53
  45. package/host/_runtime-conformance.ts +3 -44
  46. package/host/_test-utils.ts +20 -42
  47. package/host/builtin-tools.test.ts +127 -222
  48. package/host/builtin-tools.ts +6 -10
  49. package/host/cleanup.test.ts +30 -73
  50. package/host/integration/pipeline-reference.integration.test.ts +12 -17
  51. package/host/integration.test.ts +0 -7
  52. package/host/memory-vector.test.ts +3 -1
  53. package/host/memory-vector.ts +16 -21
  54. package/host/pinecone-vector.test.ts +14 -17
  55. package/host/pinecone-vector.ts +10 -19
  56. package/host/providers/providers.test-d.ts +5 -3
  57. package/host/providers/resolve-kv.ts +23 -41
  58. package/host/providers/resolve-vector.ts +3 -12
  59. package/host/providers/resolve.test.ts +15 -28
  60. package/host/providers/resolve.ts +24 -24
  61. package/host/providers/stt/assemblyai.test.ts +2 -14
  62. package/host/providers/stt/assemblyai.ts +12 -35
  63. package/host/providers/stt/deepgram.test.ts +23 -83
  64. package/host/providers/stt/deepgram.ts +15 -40
  65. package/host/providers/stt/elevenlabs.test.ts +26 -38
  66. package/host/providers/stt/elevenlabs.ts +10 -9
  67. package/host/providers/stt/soniox.test.ts +35 -85
  68. package/host/providers/stt/soniox.ts +8 -53
  69. package/host/providers/tts/cartesia.test.ts +19 -58
  70. package/host/providers/tts/cartesia.ts +36 -66
  71. package/host/providers/tts/rime.test.ts +12 -38
  72. package/host/providers/tts/rime.ts +23 -86
  73. package/host/runtime-config.test.ts +9 -9
  74. package/host/runtime-config.ts +16 -22
  75. package/host/runtime.test.ts +111 -73
  76. package/host/runtime.ts +138 -86
  77. package/host/s2s.test.ts +92 -191
  78. package/host/s2s.ts +55 -49
  79. package/host/server-shutdown.test.ts +9 -30
  80. package/host/server.test.ts +2 -13
  81. package/host/server.ts +85 -100
  82. package/host/session-core.test.ts +15 -30
  83. package/host/session-core.ts +10 -13
  84. package/host/session-prompt.test.ts +1 -5
  85. package/host/to-vercel-tools.test.ts +53 -72
  86. package/host/to-vercel-tools.ts +9 -39
  87. package/host/tool-executor.test.ts +25 -51
  88. package/host/tool-executor.ts +18 -12
  89. package/host/transports/openai-realtime-transport.test.ts +371 -0
  90. package/host/transports/openai-realtime-transport.ts +319 -0
  91. package/host/transports/pipeline-transport.test.ts +125 -298
  92. package/host/transports/pipeline-transport.ts +20 -68
  93. package/host/transports/s2s-transport-fixtures.test.ts +31 -92
  94. package/host/transports/s2s-transport.test.ts +65 -134
  95. package/host/transports/s2s-transport.ts +15 -43
  96. package/host/transports/types.test.ts +4 -8
  97. package/host/unstorage-kv.test.ts +3 -2
  98. package/host/unstorage-kv.ts +5 -35
  99. package/host/ws-handler.test.ts +72 -176
  100. package/host/ws-handler.ts +6 -12
  101. package/package.json +6 -1
  102. package/sdk/__snapshots__/exports.test.ts.snap +7 -0
  103. package/sdk/__snapshots__/schema-shapes.test.ts.snap +1 -0
  104. package/sdk/_internal-types.test.ts +6 -9
  105. package/sdk/_internal-types.ts +16 -57
  106. package/sdk/_test-matchers.ts +25 -15
  107. package/sdk/allowed-hosts.test.ts +50 -114
  108. package/sdk/allowed-hosts.ts +8 -14
  109. package/sdk/constants.ts +5 -52
  110. package/sdk/define.test.ts +7 -6
  111. package/sdk/define.ts +7 -3
  112. package/sdk/exports.test.ts +6 -1
  113. package/sdk/kv.ts +13 -37
  114. package/sdk/manifest.test-d.ts +5 -0
  115. package/sdk/manifest.test.ts +61 -9
  116. package/sdk/manifest.ts +11 -11
  117. package/sdk/protocol-compat.test.ts +66 -98
  118. package/sdk/protocol-snapshot.test.ts +2 -16
  119. package/sdk/protocol.test.ts +13 -22
  120. package/sdk/providers/s2s/openai-realtime.ts +36 -0
  121. package/sdk/providers/s2s-barrel.ts +12 -0
  122. package/sdk/providers/tts/rime.ts +1 -1
  123. package/sdk/providers.ts +24 -5
  124. package/sdk/schema-alignment.test.ts +25 -73
  125. package/sdk/schema-shapes.test.ts +1 -29
  126. package/sdk/system-prompt.test.ts +0 -1
  127. package/sdk/system-prompt.ts +17 -19
  128. package/sdk/types-inference.test.ts +10 -36
  129. package/sdk/types.ts +7 -0
  130. package/sdk/ws-upgrade.test.ts +24 -23
  131. package/sdk/ws-upgrade.ts +2 -3
  132. package/tsdown.config.ts +8 -11
  133. 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 { defs } = resolveAllBuiltins(["run_code"]);
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 { defs } = resolveAllBuiltins(["run_code"]);
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 { defs } = resolveAllBuiltins(["run_code"]);
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 { defs } = resolveAllBuiltins(["run_code"]);
72
- const ctx = createMockToolContext();
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 { defs } = resolveAllBuiltins(["run_code"]);
86
- const ctx = createMockToolContext();
87
- const result = await defs.run_code?.execute(
88
- {
89
- code: `
90
- try {
91
- const res = await fetch("https://example.com");
92
- console.log("ESCAPED:" + res.status);
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 { defs } = resolveAllBuiltins(["run_code"]);
106
- const ctx = createMockToolContext();
107
- const result = await defs.run_code?.execute(
108
- {
109
- code: `
110
- try {
111
- const fs = await import("node:fs");
112
- fs.writeFileSync("/tmp/pwned.txt", "owned");
113
- console.log("ESCAPED");
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 { defs } = resolveAllBuiltins(["run_code"]);
127
- const ctx = createMockToolContext();
128
- const result = await defs.run_code?.execute(
129
- {
130
- code: `
131
- try {
132
- const cp = await import("node:child_process");
133
- const out = cp.execSync("id").toString();
134
- console.log("ESCAPED:" + out);
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 { defs } = resolveAllBuiltins(["run_code"]);
148
- const ctx = createMockToolContext();
149
- const result = await defs.run_code?.execute(
150
- {
151
- code: `
152
- try {
153
- const keys = process.env ? Object.keys(process.env) : [];
154
- const hasPath = keys.includes("PATH");
155
- const hasHome = keys.includes("HOME");
156
- console.log(hasPath || hasHome ? "LEAKED_ENV" : "SAFE:" + keys.length);
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 defs.run_code?.execute(
174
- {
175
- code: `
176
- const c = "con" + "stru" + "ctor";
177
- const F = ""[c][c];
178
- try {
179
- const p = F("return process")();
180
- const keys = p && p.env ? Object.keys(p.env) : [];
181
- const hasPath = keys.includes("PATH");
182
- console.log(hasPath ? "LEAKED_ENV" : "SAFE:" + keys.length);
183
- } catch(e) {
184
- console.log("SAFE:" + e.message);
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 { defs } = resolveAllBuiltins(["run_code"]);
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 { defs } = resolveAllBuiltins(["run_code"]);
206
- const ctx = createMockToolContext();
207
- const result = await defs.run_code?.execute(
208
- {
209
- code: `
210
- try {
211
- const fn = console.log.constructor('return 1')();
212
- console.log("ESCAPED:" + fn);
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 { defs } = resolveAllBuiltins(["run_code"]);
227
- const ctx = createMockToolContext();
228
- const result = await defs.run_code?.execute(
229
- {
230
- code: `
231
- try {
232
- const fn = URL.constructor.constructor('return 1')();
233
- console.log("ESCAPED:" + fn);
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 { defs } = resolveAllBuiltins(["run_code"]);
248
- const ctx = createMockToolContext();
249
- const result = await defs.run_code?.execute(
250
- {
251
- code: `
252
- const c = \`\${"con"}\${"stru"}\${"ctor"}\`;
253
- const F = ""[c][c];
254
- try {
255
- const p = F("return process")();
256
- const keys = p && p.env ? Object.keys(p.env) : [];
257
- const hasPath = keys.includes("PATH");
258
- console.log(hasPath ? "LEAKED_ENV" : "SAFE:" + keys.length + " keys");
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 { defs } = resolveAllBuiltins(["run_code"]);
272
- const ctx = createMockToolContext();
273
- const result = await defs.run_code?.execute(
274
- {
275
- code: `
276
- const c = ["con","stru","ctor"].join("");
277
- const F = ""[c][c];
278
- try {
279
- const p = F("return process")();
280
- const keys = p && p.env ? Object.keys(p.env) : [];
281
- const hasPath = keys.includes("PATH");
282
- console.log(hasPath ? "LEAKED_ENV" : "SAFE:" + keys.length + " keys");
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 { defs } = resolveAllBuiltins(["run_code"]);
296
- const ctx = createMockToolContext();
297
- const result = await defs.run_code?.execute(
298
- {
299
- code: `
300
- const s = String.fromCharCode(99,111,110,115,116,114,117,99,116,111,114);
301
- const F = ""[s][s];
302
- try {
303
- const p = F("return process")();
304
- const keys = p && p.env ? Object.keys(p.env) : [];
305
- const hasPath = keys.includes("PATH");
306
- console.log(hasPath ? "LEAKED_ENV" : "SAFE:" + keys.length + " keys");
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 { defs } = resolveAllBuiltins(["run_code"]);
320
- const ctx = createMockToolContext();
321
- const result = await defs.run_code?.execute(
322
- {
323
- code: `
324
- try {
325
- const m = await import("node:os");
326
- console.log("ESCAPED: " + m.hostname());
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 { defs } = resolveAllBuiltins(["run_code"]);
341
- const ctx = createMockToolContext();
342
- const result = await defs.run_code?.execute(
343
- {
344
- code: `
345
- try {
346
- const res = await fetch("http://169.254.169.254/latest/meta-data/");
347
- console.log("ESCAPED:" + res.status);
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
  });
@@ -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 htmlContent = await resp.text();
117
- const trimmedHtml =
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
- /** Resolve a builtin name to an array of [toolName, ToolDef] pairs. */
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
- const g = (def as { guidance?: string }).guidance;
251
- if (g) guidance.push(g);
247
+ if (def.guidance) guidance.push(def.guidance);
252
248
  }
253
249
  }
254
250
 
@@ -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
- // ─── wireSessionSocket cleanup tests ─────────────────────────────────────────
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 = new MockWebSocket("ws://test");
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 ws = new MockWebSocket("ws://test");
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 sessions = new Map<string, SessionCore>();
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 sessions = new Map<string, SessionCore>();
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 = new Map<string, SessionCore>();
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 = new MockWebSocket("ws://test");
132
- ws.readyState = MockWebSocket.OPEN;
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
  });