@alexkroman1/aai 1.7.0 → 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 +16 -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 +628 -642
  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 +56 -53
  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
@@ -17,10 +17,9 @@ function toolError(message) {
17
17
  //#region sdk/ws-upgrade.ts
18
18
  /** Parse WebSocket upgrade query params into session start options. */
19
19
  function parseWsUpgradeParams(rawUrl) {
20
- const search = rawUrl.includes("?") ? rawUrl.split("?")[1] ?? "" : "";
21
- const params = new URLSearchParams(search);
20
+ const params = new URLSearchParams(rawUrl.split("?")[1] ?? "");
22
21
  const resumeFrom = params.get("sessionId") ?? void 0;
23
- const skipGreeting = params.has("resume") || resumeFrom !== void 0;
22
+ const skipGreeting = resumeFrom !== void 0 || params.has("resume");
24
23
  return resumeFrom !== void 0 ? {
25
24
  resumeFrom,
26
25
  skipGreeting
@@ -0,0 +1,9 @@
1
+ // Copyright 2025 the AAI authors. MIT license.
2
+
3
+ export function uint8ToBase64(bytes: Uint8Array): string {
4
+ return Buffer.from(bytes).toString("base64");
5
+ }
6
+
7
+ export function base64ToUint8(base64: string): Uint8Array {
8
+ return new Uint8Array(Buffer.from(base64, "base64"));
9
+ }
package/host/_mock-ws.ts CHANGED
@@ -1,46 +1,16 @@
1
1
  // Copyright 2025 the AAI authors. MIT license.
2
2
 
3
- /**
4
- * A mock WebSocket implementation for testing.
5
- *
6
- * Extends `EventTarget` to simulate WebSocket behavior without a real
7
- * network connection. Records all sent messages in the {@link sent}
8
- * array and provides helper methods to simulate incoming messages,
9
- * connection events, and errors.
10
- *
11
- * @example
12
- * ```ts
13
- * const ws = new MockWebSocket("wss://example.com");
14
- * ws.send(JSON.stringify({ type: "ping" }));
15
- * ws.simulateMessage(JSON.stringify({ type: "pong" }));
16
- * assertEquals(ws.sentJson(), [{ type: "ping" }]);
17
- * ```
18
- */
19
3
  export class MockWebSocket extends EventTarget {
20
- // mirrors the WebSocket API
21
4
  static readonly CONNECTING = 0;
22
- // mirrors the WebSocket API
23
5
  static readonly OPEN = 1;
24
- // mirrors the WebSocket API
25
6
  static readonly CLOSING = 2;
26
- // mirrors the WebSocket API
27
7
  static readonly CLOSED = 3;
28
8
 
29
9
  readyState = MockWebSocket.CONNECTING;
30
10
  binaryType = "arraybuffer";
31
- /** All messages passed to {@link send}, in order. */
32
11
  sent: (string | ArrayBuffer | Uint8Array)[] = [];
33
12
  url: string;
34
13
 
35
- /**
36
- * Create a new MockWebSocket.
37
- *
38
- * Automatically transitions to `OPEN` state on the next microtask,
39
- * dispatching an `"open"` event.
40
- *
41
- * @param url - The WebSocket URL.
42
- * @param _protocols - Ignored; accepted for API compatibility.
43
- */
44
14
  constructor(url: string | URL, _protocols?: string | string[] | Record<string, unknown>) {
45
15
  super();
46
16
  this.url = typeof url === "string" ? url : url.toString();
@@ -64,71 +34,36 @@ export class MockWebSocket extends EventTarget {
64
34
  super.addEventListener(type, listener);
65
35
  }
66
36
 
67
- /**
68
- * Record a sent message without transmitting it.
69
- *
70
- * @param data - The message data to record.
71
- */
72
37
  send(data: string | ArrayBuffer | Uint8Array) {
73
38
  this.sent.push(data);
74
39
  }
75
40
 
76
- /**
77
- * Transition to `CLOSED` state and dispatch a `"close"` event.
78
- *
79
- * @param code - The close code (defaults to 1000).
80
- * @param _reason - Ignored; accepted for API compatibility.
81
- */
82
41
  close(code?: number, _reason?: string) {
83
42
  this.readyState = MockWebSocket.CLOSED;
84
43
  this.dispatchEvent(Object.assign(new Event("close"), { code: code ?? 1000 }));
85
44
  }
86
45
 
87
- /**
88
- * Simulate receiving a message from the server.
89
- *
90
- * @param data - The message data (string or binary).
91
- */
92
46
  simulateMessage(data: string | ArrayBuffer) {
93
47
  this.dispatchEvent(new MessageEvent("message", { data }));
94
48
  }
95
49
 
96
- /** Transition to `OPEN` state and dispatch an `"open"` event. */
97
50
  open() {
98
51
  this.readyState = MockWebSocket.OPEN;
99
52
  this.dispatchEvent(new Event("open"));
100
53
  }
101
54
 
102
- /**
103
- * Shorthand for {@link simulateMessage}.
104
- *
105
- * @param data - The message data to dispatch.
106
- */
107
55
  msg(data: string | ArrayBuffer) {
108
56
  this.simulateMessage(data);
109
57
  }
110
58
 
111
- /**
112
- * Simulate a connection close from the server.
113
- *
114
- * @param code - The close code (defaults to 1000).
115
- */
116
59
  disconnect(code = 1000) {
117
60
  this.dispatchEvent(Object.assign(new Event("close"), { code }));
118
61
  }
119
62
 
120
- /** Dispatch an `"error"` event on this socket. */
121
63
  error() {
122
64
  this.dispatchEvent(new Event("error"));
123
65
  }
124
66
 
125
- /**
126
- * Return all sent string messages parsed as JSON objects.
127
- *
128
- * Binary messages are filtered out.
129
- *
130
- * @returns An array of parsed JSON objects from sent string messages.
131
- */
132
67
  sentJson(): Record<string, unknown>[] {
133
68
  return this.sent.filter((d): d is string => typeof d === "string").map((s) => JSON.parse(s));
134
69
  }
@@ -28,8 +28,14 @@ import type {
28
28
  TtsSession,
29
29
  } from "../sdk/providers.ts";
30
30
 
31
+ function makeCodedError<C extends string>(code: C, message: string): Error & { code: C } {
32
+ return Object.assign(new Error(message), { code });
33
+ }
34
+
31
35
  // ─── Fake STT ───────────────────────────────────────────────────────────────
32
36
 
37
+ type SttErrorCode = "stt_stream_error" | "stt_connect_failed" | "stt_auth_failed";
38
+
33
39
  export type FakeSttSession = SttSession & {
34
40
  readonly emitter: Emitter<SttEvents>;
35
41
  readonly opts: SttOpenOptions;
@@ -37,10 +43,7 @@ export type FakeSttSession = SttSession & {
37
43
  readonly closed: { value: boolean };
38
44
  firePartial(text: string): void;
39
45
  fireFinal(text: string): void;
40
- fireError(
41
- code: "stt_stream_error" | "stt_connect_failed" | "stt_auth_failed",
42
- message: string,
43
- ): void;
46
+ fireError(code: SttErrorCode, message: string): void;
44
47
  };
45
48
 
46
49
  export type FakeSttProvider = SttOpener & {
@@ -71,17 +74,14 @@ export function createFakeSttProvider(): FakeSttProvider {
71
74
  close: vi.fn(async () => {
72
75
  closed.value = true;
73
76
  }),
74
- firePartial(text: string) {
77
+ firePartial(text) {
75
78
  emitter.emit("partial", text);
76
79
  },
77
- fireFinal(text: string) {
80
+ fireFinal(text) {
78
81
  emitter.emit("final", text);
79
82
  },
80
83
  fireError(code, message) {
81
- const err = Object.assign(new Error(message), { code }) as Parameters<
82
- SttEvents["error"]
83
- >[0];
84
- emitter.emit("error", err);
84
+ emitter.emit("error", makeCodedError(code, message) as Parameters<SttEvents["error"]>[0]);
85
85
  },
86
86
  };
87
87
  sessions.push(session);
@@ -92,6 +92,8 @@ export function createFakeSttProvider(): FakeSttProvider {
92
92
 
93
93
  // ─── Fake TTS ───────────────────────────────────────────────────────────────
94
94
 
95
+ type TtsErrorCode = "tts_stream_error" | "tts_connect_failed" | "tts_auth_failed";
96
+
95
97
  export type FakeTtsSession = TtsSession & {
96
98
  readonly emitter: Emitter<TtsEvents>;
97
99
  readonly opts: TtsOpenOptions;
@@ -101,10 +103,7 @@ export type FakeTtsSession = TtsSession & {
101
103
  readonly flush: ReturnType<typeof vi.fn<() => void>>;
102
104
  readonly cancel: ReturnType<typeof vi.fn<() => void>>;
103
105
  fireAudio(pcm: Int16Array): void;
104
- fireError(
105
- code: "tts_stream_error" | "tts_connect_failed" | "tts_auth_failed",
106
- message: string,
107
- ): void;
106
+ fireError(code: TtsErrorCode, message: string): void;
108
107
  };
109
108
 
110
109
  export type FakeTtsProvider = TtsOpener & {
@@ -152,14 +151,11 @@ export function createFakeTtsProvider(
152
151
  close: vi.fn(async () => {
153
152
  closed.value = true;
154
153
  }),
155
- fireAudio(pcm: Int16Array) {
154
+ fireAudio(pcm) {
156
155
  emitter.emit("audio", pcm);
157
156
  },
158
157
  fireError(code, message) {
159
- const err = Object.assign(new Error(message), { code }) as Parameters<
160
- TtsEvents["error"]
161
- >[0];
162
- emitter.emit("error", err);
158
+ emitter.emit("error", makeCodedError(code, message) as Parameters<TtsEvents["error"]>[0]);
163
159
  },
164
160
  };
165
161
  sessions.push(session);
@@ -172,15 +168,11 @@ export function createFakeTtsProvider(
172
168
  * Fake STT provider that throws on `open()` with a given error code. Used to
173
169
  * test atomic provider open — TTS should not be opened at all when STT fails.
174
170
  */
175
- export function createFailingSttProvider(
176
- code: "stt_connect_failed" | "stt_auth_failed" | "stt_stream_error",
177
- message: string,
178
- ): SttOpener {
171
+ export function createFailingSttProvider(code: SttErrorCode, message: string): SttOpener {
179
172
  return {
180
173
  name: "failing-stt",
181
174
  async open(): Promise<SttSession> {
182
- const err = Object.assign(new Error(message), { code }) as Error & { code: typeof code };
183
- throw err;
175
+ throw makeCodedError(code, message);
184
176
  },
185
177
  };
186
178
  }
@@ -189,15 +181,11 @@ export function createFailingSttProvider(
189
181
  * Fake TTS provider that throws on `open()` with a given error code. Used to
190
182
  * test atomic provider open — STT should be closed when TTS fails.
191
183
  */
192
- export function createFailingTtsProvider(
193
- code: "tts_connect_failed" | "tts_auth_failed" | "tts_stream_error",
194
- message: string,
195
- ): TtsOpener {
184
+ export function createFailingTtsProvider(code: TtsErrorCode, message: string): TtsOpener {
196
185
  return {
197
186
  name: "failing-tts",
198
187
  async open(): Promise<TtsSession> {
199
- const err = Object.assign(new Error(message), { code }) as Error & { code: typeof code };
200
- throw err;
188
+ throw makeCodedError(code, message);
201
189
  },
202
190
  };
203
191
  }
package/host/_run-code.ts CHANGED
@@ -1,8 +1,4 @@
1
1
  // Copyright 2025 the AAI authors. MIT license.
2
- /**
3
- * run_code built-in tool — executes user JavaScript in a fresh `node:vm`
4
- * context with no network, filesystem, or process access.
5
- */
6
2
 
7
3
  import vm from "node:vm";
8
4
  import { z } from "zod";
@@ -12,10 +8,6 @@ import { errorMessage } from "../sdk/utils.ts";
12
8
 
13
9
  const SKIPPED_CLASS_KEYS = new Set(["constructor", "prototype", "length", "name"]);
14
10
 
15
- /**
16
- * Copy static members from a class constructor to a wrapper function,
17
- * skipping built-in keys that must not be forwarded.
18
- */
19
11
  // biome-ignore lint/complexity/noBannedTypes: copying descriptors from arbitrary class constructors
20
12
  function copyStaticMembers(src: Function, dst: Function): void {
21
13
  for (const key of Object.getOwnPropertyNames(src)) {
@@ -24,22 +16,16 @@ function copyStaticMembers(src: Function, dst: Function): void {
24
16
  const desc = Object.getOwnPropertyDescriptor(src, key);
25
17
  if (desc) Object.defineProperty(dst, key, desc);
26
18
  } catch {
27
- // Skip non-configurable properties
19
+ // Skip non-configurable properties.
28
20
  }
29
21
  }
30
22
  }
31
23
 
32
24
  /**
33
- * Neuter the `.constructor` chain on a host function or class constructor.
34
- *
35
- * For plain functions: wraps the function so calling `.constructor` or
36
- * `.constructor.constructor` no longer exposes the host `Function`.
37
- *
38
- * For class constructors: additionally copies static methods and neutralizes
39
- * `prototype.constructor` so instances created via `new` also cannot escape.
40
- *
41
- * This prevents sandbox code from reaching the host `Function` constructor
42
- * via patterns like `fn.constructor.constructor('return process')()`.
25
+ * Prevents sandbox code from reaching the host `Function` constructor via
26
+ * `fn.constructor.constructor('return process')()`. For class constructors
27
+ * we also copy static members and neuter `prototype.constructor` so
28
+ * instances created via `new` cannot escape either.
43
29
  */
44
30
  // biome-ignore lint/complexity/noBannedTypes: wrapping arbitrary functions and class constructors
45
31
  function neutralizeConstructor<T extends Function>(fn: T): T {
@@ -54,7 +40,6 @@ function neutralizeConstructor<T extends Function>(fn: T): T {
54
40
 
55
41
  if (hasPrototype) {
56
42
  copyStaticMembers(fn, Wrapper);
57
- // Neuter prototype.constructor so instances can't escape either.
58
43
  if (Wrapper.prototype) {
59
44
  Object.defineProperty(Wrapper.prototype, "constructor", {
60
45
  value: undefined,
@@ -77,19 +62,6 @@ const runCodeParams = z.object({
77
62
  code: z.string().describe("JavaScript code to execute. Use console.log() for output."),
78
63
  });
79
64
 
80
- /**
81
- * Execute JavaScript code inside a fresh `node:vm` context.
82
- *
83
- * Each invocation creates a disposable VM context with:
84
- * - No filesystem access (`node:fs` and other built-ins unavailable)
85
- * - No network access (`fetch`, `http` unavailable)
86
- * - No child process spawning
87
- * - No environment variable access (`process` unavailable)
88
- * - Execution timeout (default 5 s)
89
- *
90
- * The context is discarded after execution, so no state leaks between
91
- * invocations or across sessions.
92
- */
93
65
  export function createRunCode(): ToolDef<typeof runCodeParams> & { guidance: string } {
94
66
  return {
95
67
  guidance:
@@ -105,19 +77,11 @@ export function createRunCode(): ToolDef<typeof runCodeParams> & { guidance: str
105
77
  };
106
78
  }
107
79
 
108
- /**
109
- * Execute user code in a fresh `node:vm` context.
110
- *
111
- * @remarks
112
- * The VM context only exposes standard ECMAScript globals and a console
113
- * object that captures output. Node.js APIs (`process`, `require`,
114
- * `import()`) are not available inside the sandbox.
115
- */
116
80
  export async function executeInIsolate(code: string): Promise<string | { error: string }> {
117
81
  const output: string[] = [];
118
82
  const capture = (...args: unknown[]) => output.push(args.map(String).join(" "));
119
83
 
120
- // Prevent timer callbacks from leaking into host event loop after execution.
84
+ // Tracked so timers can't fire into the host loop after execution ends.
121
85
  const activeTimers = new Set<ReturnType<typeof setTimeout>>();
122
86
 
123
87
  const sandboxSetTimeout = (
@@ -161,9 +125,10 @@ export async function executeInIsolate(code: string): Promise<string | { error:
161
125
  }
162
126
  };
163
127
 
128
+ // Every host function/class is wrapped with neutralizeConstructor to block
129
+ // the `fn.constructor.constructor('return process')()` escape to host Function.
164
130
  const context = vm.createContext(
165
131
  {
166
- // Console methods wrapped to prevent .constructor escape to host Function.
167
132
  console: Object.freeze({
168
133
  log: neutralizeConstructor(capture),
169
134
  info: neutralizeConstructor(capture),
@@ -171,12 +136,10 @@ export async function executeInIsolate(code: string): Promise<string | { error:
171
136
  error: neutralizeConstructor(capture),
172
137
  debug: neutralizeConstructor(capture),
173
138
  }),
174
- // Wrapped timers — neutralized to prevent .constructor escape.
175
139
  setTimeout: neutralizeConstructor(sandboxSetTimeout),
176
140
  clearTimeout: neutralizeConstructor(sandboxClearTimeout),
177
141
  setInterval: neutralizeConstructor(sandboxSetInterval),
178
142
  clearInterval: neutralizeConstructor(sandboxClearInterval),
179
- // Standard web-compat globals — constructor chain neutered.
180
143
  URL: neutralizeConstructor(URL),
181
144
  URLSearchParams: neutralizeConstructor(URLSearchParams),
182
145
  TextEncoder: neutralizeConstructor(TextEncoder),
@@ -186,28 +149,22 @@ export async function executeInIsolate(code: string): Promise<string | { error:
186
149
  structuredClone: neutralizeConstructor(structuredClone),
187
150
  },
188
151
  {
189
- // Block string-based code generation within the sandbox realm.
190
152
  codeGeneration: { strings: false, wasm: false },
191
153
  },
192
154
  );
193
155
 
194
156
  try {
195
- // Wrap user code in an async IIFE so top-level `await` works.
157
+ // Async IIFE so user code can use top-level `await`.
196
158
  const wrapped = `(async () => {\n${code}\n})()`;
197
159
  const script = new vm.Script(wrapped, { filename: "run_code.js" });
198
160
 
199
- // runInContext's `timeout` enforces the execution limit.
200
- const result = await script.runInContext(context, { timeout: RUN_CODE_TIMEOUT_MS });
201
- void result;
161
+ await script.runInContext(context, { timeout: RUN_CODE_TIMEOUT_MS });
202
162
 
203
163
  const text = output.join("\n").trim();
204
164
  return text || "Code ran successfully (no output)";
205
165
  } catch (err: unknown) {
206
166
  return { error: errorMessage(err) };
207
167
  } finally {
208
- // Cancel all sandbox timers that are still pending. This prevents
209
- // setInterval/setTimeout callbacks from running in the host event loop
210
- // after the sandbox execution has completed or timed out.
211
168
  for (const id of activeTimers) {
212
169
  clearTimeout(id);
213
170
  clearInterval(id);
@@ -7,27 +7,6 @@
7
7
  * reusable test suite that can be wired to either runtime.
8
8
  *
9
9
  * Inspired by Nitro's `testNitro()` pattern: one test fixture, many runtimes.
10
- *
11
- * @example Direct executor (unit test)
12
- * ```ts
13
- * import { testRuntime } from "./_runtime-conformance.ts";
14
- *
15
- * testRuntime("direct", () => {
16
- * const exec = createRuntime({ agent: CONFORMANCE_AGENT, env: { MY_VAR: "test-value" } });
17
- * return { executeTool: exec.executeTool, hooks: exec.hooks };
18
- * });
19
- * ```
20
- *
21
- * @example Sandbox (integration test in aai-server)
22
- * ```ts
23
- * // Internal module — import the .ts source directly from this package.
24
- * import { testRuntime } from "../../aai/host/_runtime-conformance.ts";
25
- *
26
- * testRuntime("sandbox", async () => {
27
- * // ... start isolate with a bundled agent
28
- * return { executeTool: buildExecuteTool(...), hooks: buildHookInvoker(...) };
29
- * });
30
- * ```
31
10
  */
32
11
 
33
12
  import { describe, expect, test } from "vitest";
@@ -35,21 +14,10 @@ import { z } from "zod";
35
14
  import type { ExecuteTool } from "../sdk/_internal-types.ts";
36
15
  import type { AgentDef } from "../sdk/types.ts";
37
16
 
38
- // ── Shared context type ────────────────────────────────────────────────────
39
-
40
- /**
41
- * Minimal runtime surface needed for conformance tests.
42
- *
43
- * Both `Runtime` and `buildExecuteTool`/`buildHookInvoker` from the
44
- * sandbox produce objects that satisfy this interface.
45
- */
46
17
  export type RuntimeTestContext = {
47
18
  executeTool: ExecuteTool;
48
19
  };
49
20
 
50
- // ── Conformance agent ──────────────────────────────────────────────────────
51
-
52
- /** Agent definition used by the conformance suite (direct executor path). */
53
21
  export const CONFORMANCE_AGENT: AgentDef = {
54
22
  name: "conformance-test",
55
23
  systemPrompt: "Conformance test agent.",
@@ -96,22 +64,15 @@ export const CONFORMANCE_AGENT: AgentDef = {
96
64
  },
97
65
  };
98
66
 
99
- // ── Shared conformance suite ───────────────────────────────────────────────
100
-
101
67
  /**
102
68
  * Run the runtime conformance test suite against a given runtime context.
103
69
  *
104
- * The `getContext` callback is invoked once per test to retrieve the
105
- * current {@link RuntimeTestContext}. This allows the caller to set up
106
- * the runtime in a `beforeAll` and return it lazily.
107
- *
108
- * All tests assume the runtime was created with {@link CONFORMANCE_AGENT}
109
- * (or its bundle equivalent) and `env: { MY_VAR: "test-value" }`.
70
+ * `getContext` is invoked once per test so callers can lazily set up the
71
+ * runtime in a `beforeAll`. All tests assume the runtime was created with
72
+ * {@link CONFORMANCE_AGENT} and `env: { MY_VAR: "test-value" }`.
110
73
  */
111
74
  export function testRuntime(label: string, getContext: () => RuntimeTestContext): void {
112
75
  describe(`runtime conformance: ${label}`, () => {
113
- // ── Tool execution ───────────────────────────────────────────────
114
-
115
76
  test("executes tool and returns result", async () => {
116
77
  const { executeTool } = getContext();
117
78
  const result = await executeTool("echo", { text: "hello" }, "s1", []);
@@ -151,8 +112,6 @@ export function testRuntime(label: string, getContext: () => RuntimeTestContext)
151
112
  expect(vectorResult).toBe("conformance-input");
152
113
  });
153
114
 
154
- // ── Session state ────────────────────────────────────────────────
155
-
156
115
  test("session state is initialized from factory", async () => {
157
116
  const { executeTool } = getContext();
158
117
  const result = await executeTool("get_state", {}, "state-init", []);
@@ -4,7 +4,7 @@ import { readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
5
  import { vi } from "vitest";
6
6
  import type { AgentConfig } from "../sdk/_internal-types.ts";
7
- import type { ClientSink } from "../sdk/protocol.ts";
7
+ import type { ClientEvent, ClientSink } from "../sdk/protocol.ts";
8
8
  import type { AgentDef, ToolContext, ToolDef } from "../sdk/types.ts";
9
9
  import { DEFAULT_SYSTEM_PROMPT } from "../sdk/types.ts";
10
10
  import { createRuntime } from "./runtime.ts";
@@ -111,12 +111,7 @@ export function makeClientSink(overrides?: Partial<ClientSink>): ClientSink {
111
111
  };
112
112
  }
113
113
 
114
- export const silentLogger: {
115
- info: (...args: unknown[]) => void;
116
- warn: (...args: unknown[]) => void;
117
- error: (...args: unknown[]) => void;
118
- debug: (...args: unknown[]) => void;
119
- } = {
114
+ export const silentLogger = {
120
115
  info: vi.fn(),
121
116
  warn: vi.fn(),
122
117
  error: vi.fn(),
@@ -153,11 +148,11 @@ export type TrackingClientSink = ClientSink & {
153
148
  userTranscripts: string[];
154
149
  toolCallEvents: { callId: string; name: string; args: unknown }[];
155
150
  audioChunks: Uint8Array[];
156
- replyDoneCount: number;
157
- cancelledCount: number;
158
- speechStartedCount: number;
159
- speechStoppedCount: number;
160
- events: import("../sdk/protocol.ts").ClientEvent[];
151
+ readonly replyDoneCount: number;
152
+ readonly cancelledCount: number;
153
+ readonly speechStartedCount: number;
154
+ readonly speechStoppedCount: number;
155
+ events: ClientEvent[];
161
156
  };
162
157
 
163
158
  export function makeTrackingClient(): TrackingClientSink {
@@ -165,11 +160,13 @@ export function makeTrackingClient(): TrackingClientSink {
165
160
  const userTranscripts: string[] = [];
166
161
  const toolCallEvents: { callId: string; name: string; args: unknown }[] = [];
167
162
  const audioChunks: Uint8Array[] = [];
168
- const events: import("../sdk/protocol.ts").ClientEvent[] = [];
169
- let replyDoneCount = 0;
170
- let cancelledCount = 0;
171
- let speechStartedCount = 0;
172
- let speechStoppedCount = 0;
163
+ const events: ClientEvent[] = [];
164
+
165
+ function countByType(type: ClientEvent["type"]): number {
166
+ let n = 0;
167
+ for (const e of events) if (e.type === type) n++;
168
+ return n;
169
+ }
173
170
 
174
171
  return {
175
172
  open: true,
@@ -179,18 +176,18 @@ export function makeTrackingClient(): TrackingClientSink {
179
176
  audioChunks,
180
177
  events,
181
178
  get replyDoneCount() {
182
- return replyDoneCount;
179
+ return countByType("reply_done");
183
180
  },
184
181
  get cancelledCount() {
185
- return cancelledCount;
182
+ return countByType("cancelled");
186
183
  },
187
184
  get speechStartedCount() {
188
- return speechStartedCount;
185
+ return countByType("speech_started");
189
186
  },
190
187
  get speechStoppedCount() {
191
- return speechStoppedCount;
188
+ return countByType("speech_stopped");
192
189
  },
193
- event: vi.fn((e: import("../sdk/protocol.ts").ClientEvent) => {
190
+ event: vi.fn((e: ClientEvent) => {
194
191
  events.push(e);
195
192
  switch (e.type) {
196
193
  case "agent_transcript":
@@ -202,18 +199,6 @@ export function makeTrackingClient(): TrackingClientSink {
202
199
  case "tool_call":
203
200
  toolCallEvents.push({ callId: e.toolCallId, name: e.toolName, args: e.args });
204
201
  break;
205
- case "reply_done":
206
- replyDoneCount++;
207
- break;
208
- case "cancelled":
209
- cancelledCount++;
210
- break;
211
- case "speech_started":
212
- speechStartedCount++;
213
- break;
214
- case "speech_stopped":
215
- speechStoppedCount++;
216
- break;
217
202
  default:
218
203
  break;
219
204
  }
@@ -297,14 +282,7 @@ export function createFixtureSession(
297
282
  opts?: { env?: Record<string, string> },
298
283
  ) {
299
284
  let capturedCallbacks: S2sCallbacks | null = null;
300
- const fakeHandle: S2sHandle = {
301
- sendAudio: vi.fn(),
302
- sendAudioRaw: vi.fn(),
303
- sendToolResult: vi.fn(),
304
- updateSession: vi.fn(),
305
- resumeSession: vi.fn(),
306
- close: vi.fn(),
307
- };
285
+ const fakeHandle = makeMockHandle();
308
286
 
309
287
  const connectSpy = vi
310
288
  .spyOn(s2sTransportInternals, "connectS2s")