@alexkroman1/aai 0.12.3 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +20 -0
- package/CHANGELOG.md +176 -0
- package/dist/constants-VTFoymJ-.js +47 -0
- package/dist/host/_run-code.d.ts +1 -1
- package/dist/host/_runtime-conformance.d.ts +4 -5
- package/dist/host/builtin-tools.d.ts +11 -9
- package/dist/host/runtime-barrel.d.ts +15 -0
- package/dist/{direct-executor-DRRrZUp0.js → host/runtime-barrel.js} +453 -348
- package/dist/host/runtime-config.d.ts +42 -0
- package/dist/host/runtime.d.ts +119 -35
- package/dist/host/s2s.d.ts +14 -38
- package/dist/host/server.d.ts +16 -8
- package/dist/host/session-ctx.d.ts +55 -0
- package/dist/host/session.d.ts +20 -70
- package/dist/host/tool-executor.d.ts +20 -0
- package/dist/host/unstorage-kv.d.ts +1 -1
- package/dist/host/ws-handler.d.ts +4 -2
- package/dist/index.d.ts +9 -20
- package/dist/index.js +63 -2
- package/dist/{isolate → sdk}/_internal-types.d.ts +5 -9
- package/dist/{isolate → sdk}/constants.d.ts +6 -4
- package/dist/sdk/define.d.ts +66 -0
- package/dist/{isolate → sdk}/kv.d.ts +1 -49
- package/dist/sdk/manifest-barrel.d.ts +8 -0
- package/dist/sdk/manifest-barrel.js +52 -0
- package/dist/sdk/manifest.d.ts +50 -0
- package/dist/{isolate → sdk}/protocol.d.ts +59 -36
- package/dist/sdk/protocol.js +163 -0
- package/dist/{isolate → sdk}/system-prompt.d.ts +2 -2
- package/dist/sdk/types.d.ts +201 -0
- package/dist/sdk/ws-upgrade.d.ts +5 -0
- package/dist/{system-prompt-DYAYFW99.js → system-prompt-nik_iavo.js} +10 -10
- package/dist/types-Cfx_4QDK.js +39 -0
- package/dist/ws-upgrade-BeOQ7fXL.js +30 -0
- package/exports-no-dev-deps.test.ts +62 -0
- package/host/_mock-ws.ts +185 -0
- package/host/_run-code.ts +217 -0
- package/host/_runtime-conformance.ts +143 -0
- package/host/_test-utils.ts +276 -0
- package/host/builtin-tools.test.ts +774 -0
- package/host/builtin-tools.ts +255 -0
- package/host/cleanup.test.ts +422 -0
- package/host/fixture-replay.test.ts +463 -0
- package/host/fixtures/README.md +40 -0
- package/host/fixtures/greeting-session-sequence.json +40 -0
- package/host/fixtures/reply-audio-samples.json +42 -0
- package/host/fixtures/reply-lifecycle.json +21 -0
- package/host/fixtures/session-ready.json +48 -0
- package/host/fixtures/session-updated.json +45 -0
- package/host/fixtures/simple-question-sequence.json +73 -0
- package/host/fixtures/tool-call-sequence.json +114 -0
- package/host/fixtures/tool-calls.json +11 -0
- package/host/fixtures/tool-config-session-sequence.json +51 -0
- package/host/fixtures/user-speech-recognition.json +30 -0
- package/host/fixtures/web-search-sequence.json +122 -0
- package/host/integration.test.ts +222 -0
- package/host/runtime-barrel.ts +25 -0
- package/host/runtime-config.test.ts +71 -0
- package/host/runtime-config.ts +99 -0
- package/host/runtime.test.ts +641 -0
- package/host/runtime.ts +308 -0
- package/host/s2s-fixtures.test.ts +237 -0
- package/host/s2s.test.ts +562 -0
- package/host/s2s.ts +310 -0
- package/host/server-shutdown.test.ts +76 -0
- package/host/server.test.ts +116 -0
- package/host/server.ts +223 -0
- package/host/session-ctx.ts +107 -0
- package/host/session-fixture-replay.test.ts +136 -0
- package/host/session-prompt.test.ts +77 -0
- package/host/session.test.ts +590 -0
- package/host/session.ts +370 -0
- package/host/tool-executor.test.ts +124 -0
- package/host/tool-executor.ts +80 -0
- package/host/unstorage-kv.test.ts +99 -0
- package/host/unstorage-kv.ts +69 -0
- package/host/ws-handler.test.ts +739 -0
- package/host/ws-handler.ts +255 -0
- package/index.ts +16 -0
- package/package.json +24 -72
- package/sdk/_internal-types.test.ts +34 -0
- package/sdk/_internal-types.ts +115 -0
- package/sdk/compat-fixtures/README.md +26 -0
- package/sdk/compat-fixtures/v1.json +68 -0
- package/sdk/constants.ts +77 -0
- package/sdk/define.test.ts +57 -0
- package/sdk/define.ts +88 -0
- package/sdk/kv.ts +60 -0
- package/sdk/manifest-barrel.ts +12 -0
- package/sdk/manifest.test.ts +56 -0
- package/sdk/manifest.ts +89 -0
- package/sdk/protocol-compat.test.ts +187 -0
- package/sdk/protocol-snapshot.test.ts +199 -0
- package/sdk/protocol.test.ts +170 -0
- package/sdk/protocol.ts +223 -0
- package/sdk/schema-alignment.test.ts +191 -0
- package/sdk/system-prompt.test.ts +111 -0
- package/sdk/system-prompt.ts +74 -0
- package/sdk/tsconfig.json +12 -0
- package/sdk/types-inference.test.ts +122 -0
- package/sdk/types.test.ts +14 -0
- package/sdk/types.ts +226 -0
- package/sdk/utils.test.ts +52 -0
- package/sdk/utils.ts +20 -0
- package/sdk/ws-upgrade.test.ts +48 -0
- package/sdk/ws-upgrade.ts +13 -0
- package/tsconfig.build.json +14 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +26 -0
- package/vitest.config.ts +17 -0
- package/dist/host/_test-utils.d.ts +0 -73
- package/dist/host/direct-executor.d.ts +0 -130
- package/dist/host/index.d.ts +0 -19
- package/dist/host/index.js +0 -165
- package/dist/host/matchers.d.ts +0 -20
- package/dist/host/matchers.js +0 -41
- package/dist/host/server.js +0 -164
- package/dist/host/testing.d.ts +0 -294
- package/dist/host/testing.js +0 -2
- package/dist/host/vite-plugin.d.ts +0 -15
- package/dist/host/vite-plugin.js +0 -83
- package/dist/isolate/_kv-utils.d.ts +0 -10
- package/dist/isolate/_utils.js +0 -17
- package/dist/isolate/hooks.d.ts +0 -44
- package/dist/isolate/hooks.js +0 -58
- package/dist/isolate/index.d.ts +0 -18
- package/dist/isolate/index.js +0 -6
- package/dist/isolate/kv.js +0 -1
- package/dist/isolate/protocol.js +0 -2
- package/dist/isolate/types.d.ts +0 -418
- package/dist/isolate/types.js +0 -175
- package/dist/protocol-rcOrz7T3.js +0 -183
- package/dist/testing-BreLdpq-.js +0 -513
- package/dist/types.test-d.d.ts +0 -7
- /package/dist/{isolate/_utils.d.ts → sdk/utils.d.ts} +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the AAI agent SDK.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import type { Kv } from "./kv.ts";
|
|
6
|
+
/**
|
|
7
|
+
* Identifier for a built-in server-side tool.
|
|
8
|
+
*
|
|
9
|
+
* Built-in tools run on the host process (not inside the sandboxed worker)
|
|
10
|
+
* and provide capabilities like web search, code execution, and API access.
|
|
11
|
+
*
|
|
12
|
+
* - `"web_search"` — Search the web for current information, facts, or news.
|
|
13
|
+
* - `"visit_webpage"` — Fetch a URL and return its content as clean text.
|
|
14
|
+
* - `"fetch_json"` — Call a REST API endpoint and return the JSON response.
|
|
15
|
+
* - `"run_code"` — Execute JavaScript in a sandbox for calculations and data processing.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export type BuiltinTool = "web_search" | "visit_webpage" | "fetch_json" | "run_code";
|
|
20
|
+
/**
|
|
21
|
+
* How the LLM should select tools during a turn.
|
|
22
|
+
*
|
|
23
|
+
* - `"auto"` — The model decides whether to call a tool (default).
|
|
24
|
+
* - `"required"` — The model must call at least one tool each step.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export type ToolChoice = "auto" | "required";
|
|
29
|
+
/**
|
|
30
|
+
* A single message in the conversation history.
|
|
31
|
+
*
|
|
32
|
+
* Messages are passed to tool `execute` functions via
|
|
33
|
+
* {@link ToolContext.messages} to provide conversation context.
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export type Message = {
|
|
38
|
+
/** The role of the message sender. */
|
|
39
|
+
role: "user" | "assistant" | "tool";
|
|
40
|
+
/** The text content of the message. */
|
|
41
|
+
content: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Context passed to tool `execute` functions.
|
|
45
|
+
*
|
|
46
|
+
* Provides access to the session environment, state, KV store, and
|
|
47
|
+
* conversation history from within a tool's execute handler.
|
|
48
|
+
*
|
|
49
|
+
* @typeParam S - The shape of per-session state created by the agent's
|
|
50
|
+
* `state` factory. Defaults to `Record<string, unknown>`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { type ToolDef } from "@alexkroman1/aai";
|
|
55
|
+
* import { z } from "zod";
|
|
56
|
+
*
|
|
57
|
+
* const myTool: ToolDef = {
|
|
58
|
+
* description: "Look up a value from the KV store",
|
|
59
|
+
* parameters: z.object({ key: z.string() }),
|
|
60
|
+
* execute: async ({ key }, ctx) => {
|
|
61
|
+
* const value = await ctx.kv.get(key);
|
|
62
|
+
* return { key, value };
|
|
63
|
+
* },
|
|
64
|
+
* };
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export type ToolContext<S = Record<string, unknown>> = {
|
|
70
|
+
/** Environment variables declared in the agent config. */
|
|
71
|
+
env: Readonly<Record<string, string>>;
|
|
72
|
+
/** Mutable per-session state created by the agent's `state` factory. */
|
|
73
|
+
state: S;
|
|
74
|
+
/** Key-value store scoped to this agent deployment. */
|
|
75
|
+
kv: Kv;
|
|
76
|
+
/** Read-only snapshot of conversation messages so far. */
|
|
77
|
+
messages: readonly Message[];
|
|
78
|
+
/** Unique identifier for the current session. Useful for correlating logs across concurrent sessions. */
|
|
79
|
+
sessionId: string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Definition of a custom tool that the agent can invoke.
|
|
83
|
+
*
|
|
84
|
+
* Tools are the primary way to extend agent capabilities. Each tool has a
|
|
85
|
+
* description (shown to the LLM), optional Zod parameters schema, and an
|
|
86
|
+
* `execute` function that runs inside the sandboxed worker.
|
|
87
|
+
*
|
|
88
|
+
* @typeParam P - A Zod object schema describing the tool's parameters.
|
|
89
|
+
* Defaults to `ZodObject<ZodRawShape>` so tools without parameters don't need an explicit
|
|
90
|
+
* type argument.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { type ToolDef } from "@alexkroman1/aai";
|
|
95
|
+
* import { z } from "zod";
|
|
96
|
+
*
|
|
97
|
+
* const weatherTool: ToolDef<typeof params> = {
|
|
98
|
+
* description: "Get current weather for a city",
|
|
99
|
+
* parameters: z.object({
|
|
100
|
+
* city: z.string().describe("City name"),
|
|
101
|
+
* }),
|
|
102
|
+
* execute: async ({ city }) => {
|
|
103
|
+
* const res = await fetch(`https://wttr.in/${city}?format=j1`);
|
|
104
|
+
* return await res.json();
|
|
105
|
+
* },
|
|
106
|
+
* };
|
|
107
|
+
*
|
|
108
|
+
* const params = z.object({ city: z.string() });
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* @public
|
|
112
|
+
*/
|
|
113
|
+
export type ToolDef<P extends z.ZodObject<z.ZodRawShape> = z.ZodObject<z.ZodRawShape>, S = Record<string, unknown>> = {
|
|
114
|
+
/** Human-readable description shown to the LLM. */
|
|
115
|
+
description: string;
|
|
116
|
+
/** Zod schema for the tool's parameters. */
|
|
117
|
+
parameters?: P;
|
|
118
|
+
/** Function that executes the tool and returns a result. */
|
|
119
|
+
execute(args: z.infer<P>, ctx: ToolContext<S>): Promise<unknown> | unknown;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* A mapping of tool names to their result types.
|
|
123
|
+
*
|
|
124
|
+
* Define this in a shared file (e.g. `shared.ts`) that both `agent.ts` and
|
|
125
|
+
* `client.tsx` can import, so tool result types stay in sync without
|
|
126
|
+
* duplication.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* // shared.ts
|
|
131
|
+
* import type { ToolResultMap } from "@alexkroman1/aai-cli/types";
|
|
132
|
+
*
|
|
133
|
+
* export interface Pizza {
|
|
134
|
+
* id: number;
|
|
135
|
+
* size: "small" | "medium" | "large";
|
|
136
|
+
* toppings: string[];
|
|
137
|
+
* }
|
|
138
|
+
*
|
|
139
|
+
* export type MyToolResults = ToolResultMap<{
|
|
140
|
+
* add_pizza: { added: Pizza; orderTotal: string };
|
|
141
|
+
* place_order: { orderNumber: number; total: string };
|
|
142
|
+
* }>;
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* Then use with {@link aai-ui#useToolResult | useToolResult}:
|
|
146
|
+
*
|
|
147
|
+
* ```tsx
|
|
148
|
+
* // client.tsx
|
|
149
|
+
* import type { MyToolResults } from "./shared.ts";
|
|
150
|
+
*
|
|
151
|
+
* useToolResult<MyToolResults["add_pizza"]>("add_pizza", (result) => {
|
|
152
|
+
* console.log(result.added); // fully typed
|
|
153
|
+
* });
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* @public
|
|
157
|
+
*/
|
|
158
|
+
export type ToolResultMap<T extends Record<string, unknown> = Record<string, unknown>> = T;
|
|
159
|
+
/**
|
|
160
|
+
* Default system prompt used when `systemPrompt` is not provided.
|
|
161
|
+
*
|
|
162
|
+
* Optimized for voice-first interactions: short sentences, no visual
|
|
163
|
+
* formatting, confident tone, and concise answers.
|
|
164
|
+
*/
|
|
165
|
+
export declare const DEFAULT_SYSTEM_PROMPT: string;
|
|
166
|
+
/** Default greeting spoken when a session starts. */
|
|
167
|
+
export declare const DEFAULT_GREETING: string;
|
|
168
|
+
/**
|
|
169
|
+
* Fully resolved agent definition.
|
|
170
|
+
*
|
|
171
|
+
* Core fields (`name`, `systemPrompt`, `greeting`, `maxSteps`, `tools`)
|
|
172
|
+
* are resolved to their final values with defaults applied. Optional
|
|
173
|
+
* behavioral fields (hooks, `sttPrompt`, etc.) remain optional —
|
|
174
|
+
* `undefined` means "not configured."
|
|
175
|
+
*
|
|
176
|
+
* @public
|
|
177
|
+
*/
|
|
178
|
+
export type AgentDef<S = Record<string, unknown>> = {
|
|
179
|
+
name: string;
|
|
180
|
+
systemPrompt: string;
|
|
181
|
+
greeting: string;
|
|
182
|
+
sttPrompt?: string;
|
|
183
|
+
maxSteps: number;
|
|
184
|
+
toolChoice?: ToolChoice;
|
|
185
|
+
builtinTools?: readonly BuiltinTool[];
|
|
186
|
+
tools: Readonly<Record<string, ToolDef<z.ZodObject<z.ZodRawShape>, S>>>;
|
|
187
|
+
state?: () => S;
|
|
188
|
+
idleTimeoutMs?: number;
|
|
189
|
+
};
|
|
190
|
+
/** @internal Zod schema for {@link BuiltinTool}. Exported for reuse in internal schemas. */
|
|
191
|
+
export declare const BuiltinToolSchema: z.ZodEnum<{
|
|
192
|
+
web_search: "web_search";
|
|
193
|
+
visit_webpage: "visit_webpage";
|
|
194
|
+
fetch_json: "fetch_json";
|
|
195
|
+
run_code: "run_code";
|
|
196
|
+
}>;
|
|
197
|
+
/** @internal Zod schema for {@link ToolChoice}. Exported for reuse in internal schemas. */
|
|
198
|
+
export declare const ToolChoiceSchema: z.ZodEnum<{
|
|
199
|
+
auto: "auto";
|
|
200
|
+
required: "required";
|
|
201
|
+
}>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as ToolChoiceSchema, r as DEFAULT_SYSTEM_PROMPT, t as BuiltinToolSchema } from "./types-Cfx_4QDK.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
//#region
|
|
3
|
+
//#region sdk/_internal-types.ts
|
|
4
4
|
/**
|
|
5
5
|
* Zod schema for serializable agent configuration sent over the wire.
|
|
6
6
|
*
|
|
@@ -9,7 +9,7 @@ import { z } from "zod";
|
|
|
9
9
|
*/
|
|
10
10
|
const AgentConfigSchema = z.object({
|
|
11
11
|
name: z.string().min(1),
|
|
12
|
-
|
|
12
|
+
systemPrompt: z.string(),
|
|
13
13
|
greeting: z.string(),
|
|
14
14
|
sttPrompt: z.string().optional(),
|
|
15
15
|
maxSteps: z.number().int().positive().optional(),
|
|
@@ -21,11 +21,11 @@ const AgentConfigSchema = z.object({
|
|
|
21
21
|
function toAgentConfig(src) {
|
|
22
22
|
const config = {
|
|
23
23
|
name: src.name,
|
|
24
|
-
|
|
24
|
+
systemPrompt: src.systemPrompt,
|
|
25
25
|
greeting: src.greeting
|
|
26
26
|
};
|
|
27
27
|
if (src.sttPrompt !== void 0) config.sttPrompt = src.sttPrompt;
|
|
28
|
-
if (
|
|
28
|
+
if (src.maxSteps !== void 0) config.maxSteps = src.maxSteps;
|
|
29
29
|
if (src.toolChoice !== void 0) config.toolChoice = src.toolChoice;
|
|
30
30
|
if (src.builtinTools) config.builtinTools = [...src.builtinTools];
|
|
31
31
|
if (src.idleTimeoutMs !== void 0) config.idleTimeoutMs = src.idleTimeoutMs;
|
|
@@ -58,7 +58,7 @@ function agentToolsToSchemas(tools) {
|
|
|
58
58
|
}));
|
|
59
59
|
}
|
|
60
60
|
//#endregion
|
|
61
|
-
//#region
|
|
61
|
+
//#region sdk/system-prompt.ts
|
|
62
62
|
function getFormattedDate() {
|
|
63
63
|
return (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
|
|
64
64
|
weekday: "long",
|
|
@@ -71,10 +71,10 @@ const VOICE_RULES = "\n\nCRITICAL OUTPUT RULES — you MUST follow these for EVE
|
|
|
71
71
|
/**
|
|
72
72
|
* Build the system prompt sent to the LLM from the agent configuration.
|
|
73
73
|
*
|
|
74
|
-
* Assembles the default
|
|
74
|
+
* Assembles the default system prompt, today's date, agent-specific instructions,
|
|
75
75
|
* and optional sections for tool usage preamble and voice output rules.
|
|
76
76
|
*
|
|
77
|
-
* @param config - The serializable agent configuration (name,
|
|
77
|
+
* @param config - The serializable agent configuration (name, systemPrompt, etc.).
|
|
78
78
|
* @param opts.hasTools - When `true`, appends a preamble instructing the LLM to
|
|
79
79
|
* speak a brief phrase before each tool call to fill silence.
|
|
80
80
|
* @param opts.voice - When `true`, appends strict voice-specific output rules
|
|
@@ -83,10 +83,10 @@ const VOICE_RULES = "\n\nCRITICAL OUTPUT RULES — you MUST follow these for EVE
|
|
|
83
83
|
*/
|
|
84
84
|
function buildSystemPrompt(config, opts) {
|
|
85
85
|
const { hasTools } = opts;
|
|
86
|
-
const agentInstructions = config.
|
|
86
|
+
const agentInstructions = config.systemPrompt && config.systemPrompt !== DEFAULT_SYSTEM_PROMPT ? `\n\nAgent-Specific Instructions:\n${config.systemPrompt}` : "";
|
|
87
87
|
const toolPreamble = hasTools ? "\n\nWhen you decide to use a tool, ALWAYS say a brief natural phrase BEFORE the tool call (e.g. \"Let me look that up\" or \"One moment while I check\"). This fills silence while the tool executes. Keep preambles to one short sentence." : "";
|
|
88
88
|
const guidance = opts.toolGuidance && opts.toolGuidance.length > 0 ? `\n\nBuilt-in Tool Usage:\n${opts.toolGuidance.join("\n")}` : "";
|
|
89
|
-
return
|
|
89
|
+
return DEFAULT_SYSTEM_PROMPT + `\n\nToday's date is ${getFormattedDate()}.` + agentInstructions + toolPreamble + guidance + (opts.voice ? VOICE_RULES : "");
|
|
90
90
|
}
|
|
91
91
|
//#endregion
|
|
92
92
|
export { agentToolsToSchemas as a, ToolSchemaSchema as i, AgentConfigSchema as n, toAgentConfig as o, EMPTY_PARAMS as r, buildSystemPrompt as t };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region sdk/types.ts
|
|
3
|
+
/**
|
|
4
|
+
* Core type definitions for the AAI agent SDK.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Default system prompt used when `systemPrompt` is not provided.
|
|
8
|
+
*
|
|
9
|
+
* Optimized for voice-first interactions: short sentences, no visual
|
|
10
|
+
* formatting, confident tone, and concise answers.
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_SYSTEM_PROMPT = `\
|
|
13
|
+
You are AAI, a helpful AI assistant.
|
|
14
|
+
|
|
15
|
+
Voice-First Rules:
|
|
16
|
+
- Optimize for natural speech. Avoid jargon unless central to the answer. \
|
|
17
|
+
Use short, punchy sentences.
|
|
18
|
+
- Never mention "search results," "sources," or "the provided text." \
|
|
19
|
+
Speak as if the knowledge is your own.
|
|
20
|
+
- No visual formatting. Do not say "bullet point," "bold," or "bracketed one." \
|
|
21
|
+
If you need to list items, say "First," "Next," and "Finally."
|
|
22
|
+
- Start with the most important information. No introductory filler.
|
|
23
|
+
- Be concise. Keep answers to 1-3 sentences. For complex topics, provide a high-level summary.
|
|
24
|
+
- Be confident. Avoid hedging phrases like "It seems that" or "I believe."
|
|
25
|
+
- If you don't have enough information, say so directly rather than guessing.
|
|
26
|
+
- Never use exclamation points. Keep your tone calm and conversational.`;
|
|
27
|
+
/** Default greeting spoken when a session starts. */
|
|
28
|
+
const DEFAULT_GREETING = "Hey there. I'm a voice assistant. What can I help you with?";
|
|
29
|
+
/** @internal Zod schema for {@link BuiltinTool}. Exported for reuse in internal schemas. */
|
|
30
|
+
const BuiltinToolSchema = z.enum([
|
|
31
|
+
"web_search",
|
|
32
|
+
"visit_webpage",
|
|
33
|
+
"fetch_json",
|
|
34
|
+
"run_code"
|
|
35
|
+
]);
|
|
36
|
+
/** @internal Zod schema for {@link ToolChoice}. Exported for reuse in internal schemas. */
|
|
37
|
+
const ToolChoiceSchema = z.enum(["auto", "required"]);
|
|
38
|
+
//#endregion
|
|
39
|
+
export { ToolChoiceSchema as i, DEFAULT_GREETING as n, DEFAULT_SYSTEM_PROMPT as r, BuiltinToolSchema as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region sdk/utils.ts
|
|
2
|
+
/** Shared utility functions. */
|
|
3
|
+
/** Extract an error message from an unknown thrown value. */
|
|
4
|
+
function errorMessage(err) {
|
|
5
|
+
return err instanceof Error ? err.message : String(err);
|
|
6
|
+
}
|
|
7
|
+
/** Extract a detailed error string (message + stack) for diagnostic logging. */
|
|
8
|
+
function errorDetail(err) {
|
|
9
|
+
if (err instanceof Error) return err.stack ?? err.message;
|
|
10
|
+
return String(err);
|
|
11
|
+
}
|
|
12
|
+
/** Return a JSON error string for the LLM: `'{"error":"<message>"}'`. */
|
|
13
|
+
function toolError(message) {
|
|
14
|
+
return JSON.stringify({ error: message });
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region sdk/ws-upgrade.ts
|
|
18
|
+
/** Parse WebSocket upgrade query params into session start options. */
|
|
19
|
+
function parseWsUpgradeParams(rawUrl) {
|
|
20
|
+
const search = rawUrl.includes("?") ? rawUrl.split("?")[1] ?? "" : "";
|
|
21
|
+
const params = new URLSearchParams(search);
|
|
22
|
+
const resumeFrom = params.get("sessionId") ?? void 0;
|
|
23
|
+
const skipGreeting = params.has("resume") || resumeFrom !== void 0;
|
|
24
|
+
return resumeFrom !== void 0 ? {
|
|
25
|
+
resumeFrom,
|
|
26
|
+
skipGreeting
|
|
27
|
+
} : { skipGreeting };
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { toolError as i, errorDetail as n, errorMessage as r, parseWsUpgradeParams as t };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
+
/**
|
|
3
|
+
* Regression guard: the published bundle must not import any devDependency.
|
|
4
|
+
*
|
|
5
|
+
* `tsdown` is configured with `deps.neverBundle: [/^[^./]/]`, meaning every
|
|
6
|
+
* bare npm specifier survives as an `import` in the built output. If a
|
|
7
|
+
* devDependency (e.g. `vitest`) is reachable from any public export, the
|
|
8
|
+
* production server — which only installs `dependencies` — crashes at
|
|
9
|
+
* startup with `ERR_MODULE_NOT_FOUND`.
|
|
10
|
+
*
|
|
11
|
+
* This test reads the built `dist/` files for each public export and fails
|
|
12
|
+
* if any bare import specifier is a devDependency.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { dirname, resolve } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { describe, expect, test } from "vitest";
|
|
19
|
+
|
|
20
|
+
const PKG_DIR = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const pkg = JSON.parse(readFileSync(resolve(PKG_DIR, "package.json"), "utf-8")) as {
|
|
22
|
+
exports: Record<string, { "@dev/source"?: string; import?: string }>;
|
|
23
|
+
devDependencies?: Record<string, string>;
|
|
24
|
+
dependencies?: Record<string, string>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const devDeps = new Set(Object.keys(pkg.devDependencies ?? {}));
|
|
28
|
+
|
|
29
|
+
// Extract bare module specifiers from an ESM source string. Covers:
|
|
30
|
+
// import ... from "x" export ... from "x" import("x")
|
|
31
|
+
const IMPORT_RE =
|
|
32
|
+
/(?:\bimport\s+(?:[^"'`;]+?\s+from\s+)?|\bexport\s+(?:\*|\{[^}]*\}|[\w$,\s]+)\s+from\s+|\bimport\s*\(\s*)["']([^"']+)["']/g;
|
|
33
|
+
|
|
34
|
+
function rootSpecifier(spec: string): string {
|
|
35
|
+
if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
|
|
36
|
+
return spec.split("/")[0] ?? spec;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("built exports do not import devDependencies", () => {
|
|
40
|
+
const entries = Object.entries(pkg.exports)
|
|
41
|
+
.map(([subpath, val]) => ({ subpath, dist: val.import }))
|
|
42
|
+
.filter((e): e is { subpath: string; dist: string } => typeof e.dist === "string");
|
|
43
|
+
|
|
44
|
+
test.each(entries)("$subpath bundle has no devDependency import", ({ dist }) => {
|
|
45
|
+
const file = resolve(PKG_DIR, dist);
|
|
46
|
+
if (!existsSync(file)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Built artifact missing: ${file}. Run \`pnpm --filter @alexkroman1/aai build\` first.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const src = readFileSync(file, "utf-8");
|
|
52
|
+
const leaks = new Set<string>();
|
|
53
|
+
for (const match of src.matchAll(IMPORT_RE)) {
|
|
54
|
+
const spec = match[1];
|
|
55
|
+
if (spec === undefined) continue;
|
|
56
|
+
if (spec.startsWith(".") || spec.startsWith("node:")) continue;
|
|
57
|
+
const root = rootSpecifier(spec);
|
|
58
|
+
if (devDeps.has(root)) leaks.add(root);
|
|
59
|
+
}
|
|
60
|
+
expect([...leaks], `devDependency imports in ${dist}: ${[...leaks].join(", ")}`).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
});
|
package/host/_mock-ws.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Copyright 2025 the AAI authors. MIT license.
|
|
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
|
+
export class MockWebSocket extends EventTarget {
|
|
20
|
+
// mirrors the WebSocket API
|
|
21
|
+
static readonly CONNECTING = 0;
|
|
22
|
+
// mirrors the WebSocket API
|
|
23
|
+
static readonly OPEN = 1;
|
|
24
|
+
// mirrors the WebSocket API
|
|
25
|
+
static readonly CLOSING = 2;
|
|
26
|
+
// mirrors the WebSocket API
|
|
27
|
+
static readonly CLOSED = 3;
|
|
28
|
+
|
|
29
|
+
readyState = MockWebSocket.CONNECTING;
|
|
30
|
+
binaryType = "arraybuffer";
|
|
31
|
+
/** All messages passed to {@link send}, in order. */
|
|
32
|
+
sent: (string | ArrayBuffer | Uint8Array)[] = [];
|
|
33
|
+
url: string;
|
|
34
|
+
|
|
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
|
+
constructor(url: string | URL, _protocols?: string | string[] | Record<string, unknown>) {
|
|
45
|
+
super();
|
|
46
|
+
this.url = typeof url === "string" ? url : url.toString();
|
|
47
|
+
queueMicrotask(() => {
|
|
48
|
+
if (this.readyState === MockWebSocket.CONNECTING) {
|
|
49
|
+
this.readyState = MockWebSocket.OPEN;
|
|
50
|
+
this.dispatchEvent(new Event("open"));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override addEventListener(type: "close" | "open", listener: () => void): void;
|
|
56
|
+
override addEventListener(type: "message", listener: (event: { data: unknown }) => void): void;
|
|
57
|
+
override addEventListener(
|
|
58
|
+
type: "close",
|
|
59
|
+
listener: (event: { code?: number; reason?: string }) => void,
|
|
60
|
+
): void;
|
|
61
|
+
override addEventListener(type: "error", listener: (event: { message?: string }) => void): void;
|
|
62
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypeScript requires `any` for overload implementation signatures when overloads have incompatible parameter types (e.g. `() => void` vs `(event: {data: unknown}) => void`)
|
|
63
|
+
override addEventListener(type: string, listener: any): void {
|
|
64
|
+
super.addEventListener(type, listener);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Record a sent message without transmitting it.
|
|
69
|
+
*
|
|
70
|
+
* @param data - The message data to record.
|
|
71
|
+
*/
|
|
72
|
+
send(data: string | ArrayBuffer | Uint8Array) {
|
|
73
|
+
this.sent.push(data);
|
|
74
|
+
}
|
|
75
|
+
|
|
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
|
+
close(code?: number, _reason?: string) {
|
|
83
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
84
|
+
this.dispatchEvent(Object.assign(new Event("close"), { code: code ?? 1000 }));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Simulate receiving a message from the server.
|
|
89
|
+
*
|
|
90
|
+
* @param data - The message data (string or binary).
|
|
91
|
+
*/
|
|
92
|
+
simulateMessage(data: string | ArrayBuffer) {
|
|
93
|
+
this.dispatchEvent(new MessageEvent("message", { data }));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Transition to `OPEN` state and dispatch an `"open"` event. */
|
|
97
|
+
open() {
|
|
98
|
+
this.readyState = MockWebSocket.OPEN;
|
|
99
|
+
this.dispatchEvent(new Event("open"));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Shorthand for {@link simulateMessage}.
|
|
104
|
+
*
|
|
105
|
+
* @param data - The message data to dispatch.
|
|
106
|
+
*/
|
|
107
|
+
msg(data: string | ArrayBuffer) {
|
|
108
|
+
this.simulateMessage(data);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Simulate a connection close from the server.
|
|
113
|
+
*
|
|
114
|
+
* @param code - The close code (defaults to 1000).
|
|
115
|
+
*/
|
|
116
|
+
disconnect(code = 1000) {
|
|
117
|
+
this.dispatchEvent(Object.assign(new Event("close"), { code }));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Dispatch an `"error"` event on this socket. */
|
|
121
|
+
error() {
|
|
122
|
+
this.dispatchEvent(new Event("error"));
|
|
123
|
+
}
|
|
124
|
+
|
|
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
|
+
sentJson(): Record<string, unknown>[] {
|
|
133
|
+
return this.sent.filter((d): d is string => typeof d === "string").map((s) => JSON.parse(s));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const g: { WebSocket: unknown } = globalThis;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Replace `globalThis.WebSocket` with {@link MockWebSocket} for testing.
|
|
141
|
+
*
|
|
142
|
+
* Returns a handle that tracks all created mock sockets and can restore the
|
|
143
|
+
* original `WebSocket` constructor. Supports the `using` declaration via
|
|
144
|
+
* `Symbol.dispose` for automatic cleanup.
|
|
145
|
+
*
|
|
146
|
+
* @returns An object with `created` array, `lastWs` getter, `restore()`, and `[Symbol.dispose]()`.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* using mock = installMockWebSocket();
|
|
151
|
+
* const session = new Session("wss://example.com");
|
|
152
|
+
* const ws = mock.lastWs!;
|
|
153
|
+
* ws.simulateMessage(JSON.stringify({ type: "ready" }));
|
|
154
|
+
* // mock automatically restores WebSocket when disposed
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export function installMockWebSocket(): {
|
|
158
|
+
restore: () => void;
|
|
159
|
+
created: MockWebSocket[];
|
|
160
|
+
get lastWs(): MockWebSocket | null;
|
|
161
|
+
[Symbol.dispose]: () => void;
|
|
162
|
+
} {
|
|
163
|
+
const saved = globalThis.WebSocket;
|
|
164
|
+
const created: MockWebSocket[] = [];
|
|
165
|
+
|
|
166
|
+
g.WebSocket = class extends MockWebSocket {
|
|
167
|
+
constructor(url: string | URL, protocols?: string | string[] | Record<string, unknown>) {
|
|
168
|
+
super(url, protocols);
|
|
169
|
+
created.push(this);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
created,
|
|
175
|
+
get lastWs() {
|
|
176
|
+
return created.at(-1) ?? null;
|
|
177
|
+
},
|
|
178
|
+
restore() {
|
|
179
|
+
globalThis.WebSocket = saved;
|
|
180
|
+
},
|
|
181
|
+
[Symbol.dispose]() {
|
|
182
|
+
this.restore();
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|