@alexkroman1/aai 0.12.3 → 1.0.2

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 (135) hide show
  1. package/.turbo/turbo-build.log +20 -0
  2. package/CHANGELOG.md +174 -0
  3. package/dist/constants-VTFoymJ-.js +47 -0
  4. package/dist/host/_run-code.d.ts +1 -1
  5. package/dist/host/_runtime-conformance.d.ts +4 -5
  6. package/dist/host/builtin-tools.d.ts +11 -9
  7. package/dist/host/runtime-barrel.d.ts +15 -0
  8. package/dist/{direct-executor-DRRrZUp0.js → host/runtime-barrel.js} +453 -348
  9. package/dist/host/runtime-config.d.ts +42 -0
  10. package/dist/host/runtime.d.ts +119 -35
  11. package/dist/host/s2s.d.ts +14 -38
  12. package/dist/host/server.d.ts +16 -8
  13. package/dist/host/session-ctx.d.ts +55 -0
  14. package/dist/host/session.d.ts +20 -70
  15. package/dist/host/tool-executor.d.ts +20 -0
  16. package/dist/host/unstorage-kv.d.ts +1 -1
  17. package/dist/host/ws-handler.d.ts +4 -2
  18. package/dist/index.d.ts +9 -20
  19. package/dist/index.js +63 -2
  20. package/dist/{isolate → sdk}/_internal-types.d.ts +5 -9
  21. package/dist/{isolate → sdk}/constants.d.ts +6 -4
  22. package/dist/sdk/define.d.ts +66 -0
  23. package/dist/{isolate → sdk}/kv.d.ts +1 -49
  24. package/dist/sdk/manifest-barrel.d.ts +8 -0
  25. package/dist/sdk/manifest-barrel.js +52 -0
  26. package/dist/sdk/manifest.d.ts +50 -0
  27. package/dist/{isolate → sdk}/protocol.d.ts +59 -36
  28. package/dist/sdk/protocol.js +163 -0
  29. package/dist/{isolate → sdk}/system-prompt.d.ts +2 -2
  30. package/dist/sdk/types.d.ts +201 -0
  31. package/dist/sdk/ws-upgrade.d.ts +5 -0
  32. package/dist/{system-prompt-DYAYFW99.js → system-prompt-nik_iavo.js} +10 -10
  33. package/dist/types-Cfx_4QDK.js +39 -0
  34. package/dist/ws-upgrade-BeOQ7fXL.js +30 -0
  35. package/exports-no-dev-deps.test.ts +62 -0
  36. package/host/_mock-ws.ts +185 -0
  37. package/host/_run-code.ts +217 -0
  38. package/host/_runtime-conformance.ts +143 -0
  39. package/host/_test-utils.ts +276 -0
  40. package/host/builtin-tools.test.ts +774 -0
  41. package/host/builtin-tools.ts +255 -0
  42. package/host/cleanup.test.ts +422 -0
  43. package/host/fixture-replay.test.ts +463 -0
  44. package/host/fixtures/README.md +40 -0
  45. package/host/fixtures/greeting-session-sequence.json +40 -0
  46. package/host/fixtures/reply-audio-samples.json +42 -0
  47. package/host/fixtures/reply-lifecycle.json +21 -0
  48. package/host/fixtures/session-ready.json +48 -0
  49. package/host/fixtures/session-updated.json +45 -0
  50. package/host/fixtures/simple-question-sequence.json +73 -0
  51. package/host/fixtures/tool-call-sequence.json +114 -0
  52. package/host/fixtures/tool-calls.json +11 -0
  53. package/host/fixtures/tool-config-session-sequence.json +51 -0
  54. package/host/fixtures/user-speech-recognition.json +30 -0
  55. package/host/fixtures/web-search-sequence.json +122 -0
  56. package/host/integration.test.ts +222 -0
  57. package/host/runtime-barrel.ts +25 -0
  58. package/host/runtime-config.test.ts +71 -0
  59. package/host/runtime-config.ts +99 -0
  60. package/host/runtime.test.ts +641 -0
  61. package/host/runtime.ts +308 -0
  62. package/host/s2s-fixtures.test.ts +237 -0
  63. package/host/s2s.test.ts +562 -0
  64. package/host/s2s.ts +310 -0
  65. package/host/server-shutdown.test.ts +76 -0
  66. package/host/server.test.ts +116 -0
  67. package/host/server.ts +223 -0
  68. package/host/session-ctx.ts +107 -0
  69. package/host/session-fixture-replay.test.ts +136 -0
  70. package/host/session-prompt.test.ts +77 -0
  71. package/host/session.test.ts +590 -0
  72. package/host/session.ts +370 -0
  73. package/host/tool-executor.test.ts +124 -0
  74. package/host/tool-executor.ts +80 -0
  75. package/host/unstorage-kv.test.ts +99 -0
  76. package/host/unstorage-kv.ts +69 -0
  77. package/host/ws-handler.test.ts +739 -0
  78. package/host/ws-handler.ts +255 -0
  79. package/index.ts +16 -0
  80. package/package.json +24 -72
  81. package/sdk/_internal-types.test.ts +34 -0
  82. package/sdk/_internal-types.ts +115 -0
  83. package/sdk/compat-fixtures/README.md +26 -0
  84. package/sdk/compat-fixtures/v1.json +68 -0
  85. package/sdk/constants.ts +77 -0
  86. package/sdk/define.test.ts +57 -0
  87. package/sdk/define.ts +88 -0
  88. package/sdk/kv.ts +60 -0
  89. package/sdk/manifest-barrel.ts +12 -0
  90. package/sdk/manifest.test.ts +56 -0
  91. package/sdk/manifest.ts +89 -0
  92. package/sdk/protocol-compat.test.ts +187 -0
  93. package/sdk/protocol-snapshot.test.ts +199 -0
  94. package/sdk/protocol.test.ts +170 -0
  95. package/sdk/protocol.ts +223 -0
  96. package/sdk/schema-alignment.test.ts +191 -0
  97. package/sdk/system-prompt.test.ts +111 -0
  98. package/sdk/system-prompt.ts +74 -0
  99. package/sdk/tsconfig.json +12 -0
  100. package/sdk/types-inference.test.ts +122 -0
  101. package/sdk/types.test.ts +14 -0
  102. package/sdk/types.ts +226 -0
  103. package/sdk/utils.test.ts +52 -0
  104. package/sdk/utils.ts +20 -0
  105. package/sdk/ws-upgrade.test.ts +48 -0
  106. package/sdk/ws-upgrade.ts +13 -0
  107. package/tsconfig.build.json +14 -0
  108. package/tsconfig.json +10 -0
  109. package/tsdown.config.ts +26 -0
  110. package/vitest.config.ts +17 -0
  111. package/dist/host/_test-utils.d.ts +0 -73
  112. package/dist/host/direct-executor.d.ts +0 -130
  113. package/dist/host/index.d.ts +0 -19
  114. package/dist/host/index.js +0 -165
  115. package/dist/host/matchers.d.ts +0 -20
  116. package/dist/host/matchers.js +0 -41
  117. package/dist/host/server.js +0 -164
  118. package/dist/host/testing.d.ts +0 -294
  119. package/dist/host/testing.js +0 -2
  120. package/dist/host/vite-plugin.d.ts +0 -15
  121. package/dist/host/vite-plugin.js +0 -83
  122. package/dist/isolate/_kv-utils.d.ts +0 -10
  123. package/dist/isolate/_utils.js +0 -17
  124. package/dist/isolate/hooks.d.ts +0 -44
  125. package/dist/isolate/hooks.js +0 -58
  126. package/dist/isolate/index.d.ts +0 -18
  127. package/dist/isolate/index.js +0 -6
  128. package/dist/isolate/kv.js +0 -1
  129. package/dist/isolate/protocol.js +0 -2
  130. package/dist/isolate/types.d.ts +0 -418
  131. package/dist/isolate/types.js +0 -175
  132. package/dist/protocol-rcOrz7T3.js +0 -183
  133. package/dist/testing-BreLdpq-.js +0 -513
  134. package/dist/types.test-d.d.ts +0 -7
  135. /package/dist/{isolate/_utils.d.ts → sdk/utils.d.ts} +0 -0
@@ -1,165 +0,0 @@
1
- import { BuiltinToolSchema, DEFAULT_GREETING, DEFAULT_INSTRUCTIONS, ToolChoiceSchema, defineAgent, defineTool, defineToolFactory } from "../isolate/types.js";
2
- import { a as agentToolsToSchemas, i as ToolSchemaSchema, n as AgentConfigSchema, o as toAgentConfig, r as EMPTY_PARAMS, t as buildSystemPrompt } from "../system-prompt-DYAYFW99.js";
3
- import { errorDetail, errorMessage, toolError } from "../isolate/_utils.js";
4
- import { C as MAX_VALUE_SIZE, S as MAX_TOOL_RESULT_CHARS, T as TOOL_EXECUTION_TIMEOUT_MS, _ as FETCH_TIMEOUT_MS, a as ReadyConfigSchema, b as MAX_HTML_BYTES, c as TurnConfigSchema, d as DEFAULT_IDLE_TIMEOUT_MS, f as DEFAULT_MAX_HISTORY, g as DEFAULT_TTS_SAMPLE_RATE, h as DEFAULT_STT_SAMPLE_RATE, i as KvRequestSchema, l as buildReadyConfig, m as DEFAULT_SHUTDOWN_TIMEOUT_MS, n as ClientEventSchema, o as ServerMessageSchema, p as DEFAULT_SESSION_START_TIMEOUT_MS, r as ClientMessageSchema, s as SessionErrorCodeSchema, t as AUDIO_FORMAT, u as AGENT_CSP, v as HOOK_TIMEOUT_MS, w as RUN_CODE_TIMEOUT_MS, x as MAX_PAGE_CHARS, y as MAX_GLOB_PATTERN_LENGTH } from "../protocol-rcOrz7T3.js";
5
- import { callResolveTurnConfig, createAgentHooks } from "../isolate/hooks.js";
6
- import "../isolate/index.js";
7
- import { a as _internals, c as connectS2s, d as consoleLogger, f as jsonLogger, g as executeInIsolate, h as getBuiltinToolSchemas, i as createUnstorageKv, l as defaultCreateS2sWebSocket, m as getBuiltinToolGuidance, n as executeToolCall, o as buildCtx, p as getBuiltinToolDefs, r as wireSessionSocket, s as createS2sSession, t as createRuntime, u as DEFAULT_S2S_CONFIG } from "../direct-executor-DRRrZUp0.js";
8
- import { z } from "zod";
9
- import { describe, expect, test } from "vitest";
10
- //#region host/_runtime-conformance.ts
11
- /**
12
- * Shared runtime conformance tests.
13
- *
14
- * Both the self-hosted direct executor and the platform sandbox must satisfy
15
- * the same behavioral contract. This module defines that contract as a
16
- * reusable test suite that can be wired to either runtime.
17
- *
18
- * Inspired by Nitro's `testNitro()` pattern: one test fixture, many runtimes.
19
- *
20
- * @example Direct executor (unit test)
21
- * ```ts
22
- * import { testRuntime } from "./_runtime-conformance.ts";
23
- *
24
- * testRuntime("direct", () => {
25
- * const exec = createRuntime({ agent: CONFORMANCE_AGENT, env: { MY_VAR: "test-value" } });
26
- * return { executeTool: exec.executeTool, hooks: exec.hooks };
27
- * });
28
- * ```
29
- *
30
- * @example Sandbox (integration test in aai-server)
31
- * ```ts
32
- * import { testRuntime } from "@alexkroman1/aai/host";
33
- *
34
- * testRuntime("sandbox", async () => {
35
- * // ... start isolate with a bundled agent
36
- * return { executeTool: buildExecuteTool(...), hooks: buildHookInvoker(...) };
37
- * });
38
- * ```
39
- */
40
- /** Agent definition used by the conformance suite (direct executor path). */
41
- const CONFORMANCE_AGENT = {
42
- name: "conformance-test",
43
- instructions: "Conformance test agent.",
44
- greeting: "Hello!",
45
- maxSteps: 5,
46
- state: () => ({
47
- count: 0,
48
- lastTurn: ""
49
- }),
50
- tools: {
51
- echo: defineTool({
52
- description: "Echo input",
53
- parameters: z.object({ text: z.string() }),
54
- execute: ({ text }) => `echo:${text}`
55
- }),
56
- get_env: {
57
- description: "Get MY_VAR from env",
58
- execute: (_args, ctx) => ctx.env.MY_VAR ?? "missing"
59
- },
60
- get_state: {
61
- description: "Get session state",
62
- execute: (_args, ctx) => JSON.stringify(ctx.state)
63
- },
64
- echo_messages: {
65
- description: "Return messages as JSON",
66
- execute: (_args, ctx) => JSON.stringify(ctx.messages)
67
- },
68
- kv_roundtrip: defineTool({
69
- description: "KV set then get",
70
- parameters: z.object({ value: z.string() }),
71
- execute: async ({ value }, ctx) => {
72
- await ctx.kv.set("test-key", value);
73
- const result = await ctx.kv.get("test-key");
74
- return `stored:${JSON.stringify(result)}`;
75
- }
76
- })
77
- },
78
- onConnect: (ctx) => {
79
- ctx.state.count = 1;
80
- },
81
- onTurn: (text, ctx) => {
82
- ctx.state.lastTurn = text;
83
- }
84
- };
85
- /**
86
- * Run the runtime conformance test suite against a given runtime context.
87
- *
88
- * The `getContext` callback is invoked once per test to retrieve the
89
- * current {@link RuntimeTestContext}. This allows the caller to set up
90
- * the runtime in a `beforeAll` and return it lazily.
91
- *
92
- * All tests assume the runtime was created with {@link CONFORMANCE_AGENT}
93
- * (or its bundle equivalent) and `env: { MY_VAR: "test-value" }`.
94
- */
95
- function testRuntime(label, getContext) {
96
- describe(`runtime conformance: ${label}`, () => {
97
- test("executes tool and returns result", async () => {
98
- const { executeTool } = getContext();
99
- expect(await executeTool("echo", { text: "hello" }, "s1", [])).toBe("echo:hello");
100
- });
101
- test("tool receives env variables", async () => {
102
- const { executeTool } = getContext();
103
- expect(await executeTool("get_env", {}, "s1", [])).toBe("test-value");
104
- });
105
- test("tool receives conversation messages", async () => {
106
- const { executeTool } = getContext();
107
- const msgs = [{
108
- role: "user",
109
- content: "hi"
110
- }, {
111
- role: "assistant",
112
- content: "hello"
113
- }];
114
- const result = await executeTool("echo_messages", {}, "s1", msgs);
115
- expect(JSON.parse(result)).toEqual(msgs);
116
- });
117
- test("KV round-trip through tool context", async () => {
118
- const { executeTool } = getContext();
119
- expect(await executeTool("kv_roundtrip", { value: "abc" }, "s1", [])).toBe("stored:\"abc\"");
120
- });
121
- test("session state is initialized from factory", async () => {
122
- const { executeTool } = getContext();
123
- const result = await executeTool("get_state", {}, "state-init", []);
124
- const state = JSON.parse(result);
125
- expect(state).toHaveProperty("count", 0);
126
- expect(state).toHaveProperty("lastTurn", "");
127
- });
128
- test("onConnect hook updates session state", async () => {
129
- const { executeTool, hooks } = getContext();
130
- const sid = "state-connect";
131
- await hooks.callHook("connect", sid);
132
- const result = await executeTool("get_state", {}, sid, []);
133
- expect(JSON.parse(result).count).toBe(1);
134
- });
135
- test("onTurn hook updates session state", async () => {
136
- const { executeTool, hooks } = getContext();
137
- const sid = "state-turn";
138
- await hooks.callHook("turn", sid, "user said something");
139
- const result = await executeTool("get_state", {}, sid, []);
140
- expect(JSON.parse(result).lastTurn).toBe("user said something");
141
- });
142
- test("onConnect resolves without error", async () => {
143
- const { hooks } = getContext();
144
- await expect(hooks.callHook("connect", "hook-1")).resolves.toBeUndefined();
145
- });
146
- test("onDisconnect resolves without error", async () => {
147
- const { hooks } = getContext();
148
- await expect(hooks.callHook("disconnect", "hook-2")).resolves.toBeUndefined();
149
- });
150
- test("onTurn resolves without error", async () => {
151
- const { hooks } = getContext();
152
- await expect(hooks.callHook("turn", "hook-3", "test")).resolves.toBeUndefined();
153
- });
154
- test("onError resolves without error", async () => {
155
- const { hooks } = getContext();
156
- await expect(hooks.callHook("error", "hook-4", { message: "boom" })).resolves.toBeUndefined();
157
- });
158
- test("resolveTurnConfig returns null for static maxSteps", async () => {
159
- const { hooks } = getContext();
160
- expect(await callResolveTurnConfig(hooks, "hook-5")).toBeNull();
161
- });
162
- });
163
- }
164
- //#endregion
165
- export { AGENT_CSP, AUDIO_FORMAT, AgentConfigSchema, BuiltinToolSchema, CONFORMANCE_AGENT, ClientEventSchema, ClientMessageSchema, DEFAULT_GREETING, DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_INSTRUCTIONS, DEFAULT_MAX_HISTORY, DEFAULT_S2S_CONFIG, DEFAULT_SESSION_START_TIMEOUT_MS, DEFAULT_SHUTDOWN_TIMEOUT_MS, DEFAULT_STT_SAMPLE_RATE, DEFAULT_TTS_SAMPLE_RATE, EMPTY_PARAMS, FETCH_TIMEOUT_MS, HOOK_TIMEOUT_MS, KvRequestSchema, MAX_GLOB_PATTERN_LENGTH, MAX_HTML_BYTES, MAX_PAGE_CHARS, MAX_TOOL_RESULT_CHARS, MAX_VALUE_SIZE, RUN_CODE_TIMEOUT_MS, ReadyConfigSchema, ServerMessageSchema, SessionErrorCodeSchema, TOOL_EXECUTION_TIMEOUT_MS, ToolChoiceSchema, ToolSchemaSchema, TurnConfigSchema, _internals, agentToolsToSchemas, buildCtx, buildReadyConfig, buildSystemPrompt, callResolveTurnConfig, connectS2s, consoleLogger, createAgentHooks, createRuntime, createS2sSession, createUnstorageKv, defaultCreateS2sWebSocket, defineAgent, defineTool, defineTool as tool, defineToolFactory, errorDetail, errorMessage, executeInIsolate, executeToolCall, getBuiltinToolDefs, getBuiltinToolGuidance, getBuiltinToolSchemas, jsonLogger, testRuntime, toAgentConfig, toolError, wireSessionSocket };
@@ -1,20 +0,0 @@
1
- /**
2
- * Vitest custom matchers for AAI testing.
3
- *
4
- * Add this to your Vitest setup file to enable `expect(turn).toHaveCalledTool()`:
5
- *
6
- * ```ts
7
- * // vitest.config.ts
8
- * export default defineConfig({
9
- * test: { setupFiles: ["@alexkroman1/aai/testing/matchers"] },
10
- * });
11
- * ```
12
- *
13
- * Or import directly in your test file:
14
- * ```ts
15
- * import "@alexkroman1/aai/testing/matchers";
16
- * ```
17
- *
18
- * @packageDocumentation
19
- */
20
- export {};
@@ -1,41 +0,0 @@
1
- import { n as TurnResult } from "../testing-BreLdpq-.js";
2
- import { expect } from "vitest";
3
- //#region host/matchers.ts
4
- /**
5
- * Vitest custom matchers for AAI testing.
6
- *
7
- * Add this to your Vitest setup file to enable `expect(turn).toHaveCalledTool()`:
8
- *
9
- * ```ts
10
- * // vitest.config.ts
11
- * export default defineConfig({
12
- * test: { setupFiles: ["@alexkroman1/aai/testing/matchers"] },
13
- * });
14
- * ```
15
- *
16
- * Or import directly in your test file:
17
- * ```ts
18
- * import "@alexkroman1/aai/testing/matchers";
19
- * ```
20
- *
21
- * @packageDocumentation
22
- */
23
- expect.extend({ toHaveCalledTool(received, toolName, args) {
24
- if (!(received instanceof TurnResult)) return {
25
- pass: false,
26
- message: () => `expected a TurnResult, got ${typeof received}`,
27
- actual: received,
28
- expected: "TurnResult"
29
- };
30
- const pass = received.toHaveCalledTool(toolName, args);
31
- const calledTools = received.toolCalls.map((tc) => tc.toolName);
32
- const argsHint = args ? ` with args ${JSON.stringify(args)}` : "";
33
- return {
34
- pass,
35
- message: () => pass ? `expected turn NOT to have called tool "${toolName}"${argsHint}, but it was called.\nCalled tools: ${JSON.stringify(calledTools)}` : `expected turn to have called tool "${toolName}"${argsHint}, but it was not.\nCalled tools: ${JSON.stringify(calledTools)}`,
36
- actual: calledTools,
37
- expected: toolName
38
- };
39
- } });
40
- //#endregion
41
- export {};
@@ -1,164 +0,0 @@
1
- import { u as AGENT_CSP } from "../protocol-rcOrz7T3.js";
2
- import { d as consoleLogger, t as createRuntime } from "../direct-executor-DRRrZUp0.js";
3
- import { WebSocketServer } from "ws";
4
- import fs from "node:fs";
5
- import http from "node:http";
6
- import path from "node:path";
7
- //#region host/server.ts
8
- /**
9
- * Self-hostable agent server.
10
- *
11
- * {@link createServer} wraps a {@link Runtime} with an HTTP + WebSocket
12
- * server using only `node:http` and `ws` (no framework dependencies).
13
- */
14
- const MIME_TYPES = {
15
- ".html": "text/html",
16
- ".js": "application/javascript",
17
- ".mjs": "application/javascript",
18
- ".css": "text/css",
19
- ".json": "application/json",
20
- ".svg": "image/svg+xml",
21
- ".png": "image/png",
22
- ".jpg": "image/jpeg",
23
- ".ico": "image/x-icon",
24
- ".woff2": "font/woff2",
25
- ".woff": "font/woff",
26
- ".map": "application/json"
27
- };
28
- function serveStatic(dir, req, res) {
29
- const url = req.url?.split("?")[0] ?? "/";
30
- const filePath = path.join(dir, url === "/" ? "index.html" : url);
31
- if (!filePath.startsWith(dir)) return false;
32
- try {
33
- const stat = fs.statSync(filePath);
34
- if (!stat.isFile()) return false;
35
- const mime = MIME_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
36
- res.writeHead(200, {
37
- "Content-Type": mime,
38
- "Content-Length": stat.size
39
- });
40
- fs.createReadStream(filePath).pipe(res);
41
- return true;
42
- } catch {
43
- return false;
44
- }
45
- }
46
- /**
47
- * Create an HTTP + WebSocket server for self-hosted agent deployments.
48
- *
49
- * @example
50
- * ```ts
51
- * import { defineAgent } from "@alexkroman1/aai";
52
- * import { createRuntime, createServer } from "@alexkroman1/aai/server";
53
- *
54
- * const agent = defineAgent({ name: "my-agent" });
55
- * const runtime = createRuntime({ agent, env: process.env });
56
- * const server = createServer({ runtime, name: agent.name });
57
- * await server.listen(3000);
58
- * ```
59
- *
60
- * @public
61
- */
62
- function handleKvGet(kv, req, res) {
63
- const key = new URL(req.url ?? "/", "http://localhost").searchParams.get("key");
64
- if (!key) {
65
- res.writeHead(400, { "Content-Type": "application/json" });
66
- res.end(JSON.stringify({ error: "Missing key query parameter" }));
67
- return;
68
- }
69
- kv.get(key).then((value) => {
70
- if (value === null) {
71
- res.writeHead(404, { "Content-Type": "application/json" });
72
- res.end("null");
73
- } else {
74
- res.writeHead(200, { "Content-Type": "application/json" });
75
- res.end(JSON.stringify(value));
76
- }
77
- }).catch(() => {
78
- res.writeHead(500, { "Content-Type": "application/json" });
79
- res.end(JSON.stringify({ error: "KV error" }));
80
- });
81
- }
82
- function createServer(options) {
83
- const { runtime, clientHtml, clientDir, logger = consoleLogger, kv } = options;
84
- const name = options.name ?? "agent";
85
- if (clientHtml && clientDir) throw new Error("clientHtml and clientDir are mutually exclusive");
86
- const httpServer = http.createServer((req, res) => {
87
- const url = req.url?.split("?")[0] ?? "/";
88
- const method = req.method ?? "GET";
89
- res.setHeader("Content-Security-Policy", AGENT_CSP);
90
- res.setHeader("X-Content-Type-Options", "nosniff");
91
- res.setHeader("X-Frame-Options", "SAMEORIGIN");
92
- if (method === "GET" && url === "/health") {
93
- res.writeHead(200, { "Content-Type": "application/json" });
94
- res.end(JSON.stringify({
95
- status: "ok",
96
- name
97
- }));
98
- return;
99
- }
100
- if (kv && method === "GET" && url === "/kv") {
101
- handleKvGet(kv, req, res);
102
- return;
103
- }
104
- if (clientDir && serveStatic(clientDir, req, res)) return;
105
- if (method === "GET" && url === "/") {
106
- const escaped = name.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
107
- const body = clientHtml ?? `<!DOCTYPE html><html><body><h1>${escaped}</h1><p>Agent server running.</p></body></html>`;
108
- res.writeHead(200, { "Content-Type": "text/html" });
109
- res.end(body);
110
- return;
111
- }
112
- logger.error(`${method} ${url} 404`);
113
- res.writeHead(404, { "Content-Type": "application/json" });
114
- res.end(JSON.stringify({ error: "Not found" }));
115
- });
116
- const wss = new WebSocketServer({ noServer: true });
117
- httpServer.on("upgrade", (req, socket, head) => {
118
- const url = req.url?.split("?")[0] ?? "";
119
- if (!url.startsWith("/websocket")) return;
120
- wss.handleUpgrade(req, socket, head, (ws) => {
121
- const search = req.url?.includes("?") ? req.url.split("?")[1] ?? "" : "";
122
- const params = new URLSearchParams(search);
123
- const resumeFrom = params.get("sessionId") ?? void 0;
124
- const skipGreeting = params.has("resume") || resumeFrom !== void 0;
125
- logger.info(`WS upgrade ${url}${skipGreeting ? " (resume)" : ""}`);
126
- runtime.startSession(ws, {
127
- skipGreeting,
128
- ...resumeFrom ? { resumeFrom } : {}
129
- });
130
- });
131
- });
132
- let listenPort;
133
- return {
134
- get port() {
135
- return listenPort;
136
- },
137
- async listen(port = 3e3) {
138
- await new Promise((resolve, reject) => {
139
- httpServer.on("error", reject);
140
- httpServer.listen(port, () => {
141
- const addr = httpServer.address();
142
- listenPort = typeof addr === "object" && addr ? addr.port : port;
143
- resolve();
144
- });
145
- });
146
- },
147
- async close() {
148
- try {
149
- await runtime.shutdown();
150
- } finally {
151
- try {
152
- wss.close();
153
- } finally {
154
- if (listenPort !== void 0) await new Promise((resolve, reject) => {
155
- httpServer.close((err) => err ? reject(err) : resolve());
156
- });
157
- listenPort = void 0;
158
- }
159
- }
160
- }
161
- };
162
- }
163
- //#endregion
164
- export { createRuntime, createServer };
@@ -1,294 +0,0 @@
1
- /**
2
- * Testing utilities for AAI agents.
3
- *
4
- * Provides a test harness for unit-testing agents without audio, network,
5
- * or an LLM. Use {@link createTestHarness} to create a harness from a
6
- * `defineAgent()` result, then drive tool calls and multi-turn conversations.
7
- *
8
- * @example
9
- * ```ts
10
- * import { describe, expect, test } from "vitest";
11
- * import { createTestHarness } from "@alexkroman1/aai/testing";
12
- * import agent from "./agent.ts";
13
- *
14
- * describe("my agent", () => {
15
- * test("greet tool returns greeting", async () => {
16
- * const t = createTestHarness(agent);
17
- * const result = await t.executeTool("greet", { name: "Alice" });
18
- * expect(result).toBe("Hello, Alice!");
19
- * });
20
- *
21
- * test("multi-turn conversation", async () => {
22
- * const t = createTestHarness(agent);
23
- * const turn1 = await t.turn("Add a pizza", [
24
- * { tool: "add_pizza", args: { size: "large", crust: "regular", toppings: ["pepperoni"], quantity: 1 } },
25
- * ]);
26
- * expect(turn1).toHaveCalledTool("add_pizza");
27
- *
28
- * const turn2 = await t.turn("View my order", [
29
- * { tool: "view_order", args: {} },
30
- * ]);
31
- * expect(turn2).toHaveCalledTool("view_order");
32
- * });
33
- * });
34
- * ```
35
- *
36
- * @packageDocumentation
37
- */
38
- import type { Kv } from "../isolate/kv.ts";
39
- import type { AgentDef, Message } from "../isolate/types.ts";
40
- import { type Runtime } from "./direct-executor.ts";
41
- export { installMockWebSocket, MockWebSocket } from "./_mock-ws.ts";
42
- export { flush, makeStubSession } from "./_test-utils.ts";
43
- /**
44
- * A single tool call recorded during a turn.
45
- *
46
- * @public
47
- */
48
- export type RecordedToolCall = {
49
- /** The name of the tool that was called. */
50
- toolName: string;
51
- /** The arguments passed to the tool. */
52
- args: Readonly<Record<string, unknown>>;
53
- /** The string result returned by the tool. */
54
- result: string;
55
- };
56
- /**
57
- * Result of a simulated turn via {@link TestHarness.turn}.
58
- *
59
- * Contains all tool calls that were executed and provides assertion helpers
60
- * for verifying agent behavior in tests.
61
- *
62
- * @example
63
- * ```ts
64
- * const result = await t.turn("search for flights", [
65
- * { tool: "search_flights", args: { destination: "NYC" } },
66
- * ]);
67
- *
68
- * // Check if a tool was called
69
- * expect(result).toHaveCalledTool("search_flights");
70
- *
71
- * // Check tool was called with specific args
72
- * expect(result).toHaveCalledTool("search_flights", { destination: "NYC" });
73
- *
74
- * // Access raw tool call data
75
- * expect(result.toolCalls[0].result).toContain("JFK");
76
- * ```
77
- *
78
- * @public
79
- */
80
- export declare class TurnResult {
81
- /** The user text that initiated this turn. */
82
- readonly text: string;
83
- /** All tool calls executed during this turn, in order. */
84
- readonly toolCalls: readonly RecordedToolCall[];
85
- /** Convenience accessor: just the result strings from each tool call. */
86
- readonly toolResults: readonly string[];
87
- /** @internal */
88
- constructor(text: string, toolCalls: RecordedToolCall[]);
89
- /**
90
- * Check whether a tool was called during this turn.
91
- *
92
- * When `args` is provided, checks that at least one call to the named tool
93
- * contains all specified key-value pairs (partial match).
94
- *
95
- * @param toolName - The tool name to look for.
96
- * @param args - Optional partial args to match against.
97
- * @returns `true` if a matching tool call was found.
98
- *
99
- * @example
100
- * ```ts
101
- * result.toHaveCalledTool("add_pizza"); // any call
102
- * result.toHaveCalledTool("add_pizza", { size: "large" }); // partial match
103
- * ```
104
- */
105
- toHaveCalledTool(toolName: string, args?: Record<string, unknown>): boolean;
106
- /**
107
- * Get all calls to a specific tool during this turn.
108
- *
109
- * @param toolName - The tool name to filter by.
110
- * @returns Array of matching tool calls (may be empty).
111
- */
112
- getToolCalls(toolName: string): readonly RecordedToolCall[];
113
- /**
114
- * Get the parsed JSON result of the first call to a specific tool.
115
- *
116
- * Throws if the tool was not called during this turn.
117
- *
118
- * @typeParam T - The expected shape of the parsed result.
119
- * @param toolName - The tool name to look up.
120
- * @returns The parsed result, cast to `T`.
121
- *
122
- * @example
123
- * ```ts
124
- * const order = turn.toolResult<{ pizzas: Pizza[]; total: string }>("view_order");
125
- * expect(order.pizzas).toHaveLength(2);
126
- * ```
127
- */
128
- toolResult<T = unknown>(toolName: string): T;
129
- }
130
- /**
131
- * Options for creating a {@link TestHarness}.
132
- *
133
- * @public
134
- */
135
- export type TestHarnessOptions = {
136
- /** Environment variables available to tools via `ctx.env`. */
137
- env?: Record<string, string>;
138
- /** KV store instance. Defaults to an in-memory SQLite store. */
139
- kv?: Kv;
140
- };
141
- /**
142
- * A tool call to execute during a simulated turn.
143
- *
144
- * @public
145
- */
146
- export type TurnToolCall = {
147
- /** The tool name to invoke. */
148
- tool: string;
149
- /** Arguments to pass to the tool. */
150
- args: Record<string, unknown>;
151
- };
152
- /**
153
- * Test harness for unit-testing AAI agents without audio, network, or LLM.
154
- *
155
- * Created via {@link createTestHarness}. Maintains conversation state across
156
- * turns, executes tools against the real agent code, and records all tool
157
- * calls for assertions.
158
- *
159
- * @example
160
- * ```ts
161
- * import { createTestHarness } from "@alexkroman1/aai/testing";
162
- * import agent from "./agent.ts";
163
- *
164
- * const t = createTestHarness(agent);
165
- *
166
- * // Execute a single tool
167
- * const result = await t.executeTool("greet", { name: "Alice" });
168
- *
169
- * // Simulate a full turn with tool calls
170
- * const turn = await t.turn("hello", [
171
- * { tool: "greet", args: { name: "Alice" } },
172
- * ]);
173
- * expect(turn).toHaveCalledTool("greet");
174
- * ```
175
- *
176
- * @public
177
- */
178
- export declare class TestHarness {
179
- /** @internal */
180
- readonly _executor: Runtime;
181
- /** @internal */
182
- readonly _sessionId: string;
183
- private _messages;
184
- private _onTurnCalls;
185
- private _connected;
186
- /** @internal */
187
- constructor(executor: Runtime, sessionId: string);
188
- /** Conversation messages accumulated across turns. */
189
- get messages(): readonly Message[];
190
- /** All `onTurn` hook invocations (the text argument) recorded so far. */
191
- get turns(): readonly string[];
192
- /**
193
- * Fire the `onConnect` lifecycle hook.
194
- *
195
- * Called automatically on the first {@link turn} call if not called manually.
196
- */
197
- connect(): Promise<void>;
198
- /**
199
- * Fire the `onDisconnect` lifecycle hook and clean up session state.
200
- */
201
- disconnect(): Promise<void>;
202
- /**
203
- * Execute a single tool by name with the given arguments.
204
- *
205
- * The tool runs with full agent context (env, state, kv, messages).
206
- * The call is **not** recorded in conversation history — use {@link turn}
207
- * for that.
208
- *
209
- * @param toolName - The tool to execute.
210
- * @param args - Arguments to pass to the tool.
211
- * @returns The tool's string result.
212
- *
213
- * @example
214
- * ```ts
215
- * const result = await t.executeTool("get_weather", { city: "London" });
216
- * const data = JSON.parse(result);
217
- * expect(data.temp).toBeDefined();
218
- * ```
219
- */
220
- executeTool(toolName: string, args?: Record<string, unknown>): Promise<string>;
221
- /**
222
- * Simulate a user turn: add the user message, execute the given tool calls
223
- * in sequence, and record everything.
224
- *
225
- * This is the primary method for testing agent behavior. It:
226
- * 1. Fires `onConnect` if this is the first turn
227
- * 2. Adds the user message to conversation history
228
- * 3. Fires the `onTurn` hook
229
- * 4. Executes each tool call in order
230
- * 5. Returns a {@link TurnResult} with assertion helpers
231
- *
232
- * @param text - The user's spoken/typed input.
233
- * @param toolCalls - Tool calls to execute (simulating what the LLM would invoke).
234
- * @returns A {@link TurnResult} with recorded tool calls and assertion methods.
235
- *
236
- * @example
237
- * ```ts
238
- * const turn = await t.turn("Add pepperoni pizza", [
239
- * { tool: "add_pizza", args: { size: "large", crust: "regular", toppings: ["pepperoni"], quantity: 1 } },
240
- * ]);
241
- * expect(turn).toHaveCalledTool("add_pizza", { size: "large" });
242
- * expect(turn.toolCalls[0].result).toContain("$14.99");
243
- * ```
244
- */
245
- turn(text: string, toolCalls?: TurnToolCall[]): Promise<TurnResult>;
246
- /**
247
- * Add a user message to conversation history without executing tools.
248
- *
249
- * Useful for setting up conversation context before a turn.
250
- */
251
- addUserMessage(text: string): void;
252
- /**
253
- * Add an assistant message to conversation history.
254
- *
255
- * Useful for simulating prior assistant responses in multi-turn tests.
256
- */
257
- addAssistantMessage(text: string): void;
258
- /**
259
- * Reset conversation state: clears messages, step/turn history.
260
- *
261
- * Does **not** reset KV store — create a new harness for that.
262
- */
263
- reset(): void;
264
- }
265
- /**
266
- * Create a test harness for unit-testing an agent.
267
- *
268
- * The harness wraps the agent's tool definitions and lifecycle hooks,
269
- * providing a simple API for executing tools and simulating multi-turn
270
- * conversations — all without audio, network, or an LLM.
271
- *
272
- * @param agent - The agent definition returned by `defineAgent()`.
273
- * @param options - Optional environment and KV store overrides.
274
- * @returns A {@link TestHarness} instance.
275
- *
276
- * @example
277
- * ```ts
278
- * import { createTestHarness } from "@alexkroman1/aai/testing";
279
- * import agent from "./agent.ts";
280
- *
281
- * const t = createTestHarness(agent);
282
- * const result = await t.executeTool("my_tool", { key: "value" });
283
- * ```
284
- *
285
- * @example With environment variables
286
- * ```ts
287
- * const t = createTestHarness(agent, {
288
- * env: { API_KEY: "test-key" },
289
- * });
290
- * ```
291
- *
292
- * @public
293
- */
294
- export declare function createTestHarness(agent: AgentDef<any>, options?: TestHarnessOptions): TestHarness;
@@ -1,2 +0,0 @@
1
- import { a as makeStubSession, i as flush, n as TurnResult, o as MockWebSocket, r as createTestHarness, s as installMockWebSocket, t as TestHarness } from "../testing-BreLdpq-.js";
2
- export { MockWebSocket, TestHarness, TurnResult, createTestHarness, flush, installMockWebSocket, makeStubSession };