@alexkroman1/aai 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # aai
2
+
3
+ Voice agent development kit. Define agents in TypeScript, deploy anywhere.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install -g @alexkroman1/aai
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```sh
14
+ # Create a new agent
15
+ aai init my-agent
16
+ cd my-agent
17
+
18
+ # Start local dev server
19
+ aai dev
20
+
21
+ # Deploy to production
22
+ aai deploy
23
+ ```
24
+
25
+ ## Commands
26
+
27
+ | Command | Description |
28
+ |---------|-------------|
29
+ | `aai init [dir]` | Scaffold a new agent project |
30
+ | `aai dev` | Start a local development server |
31
+ | `aai deploy` | Bundle and deploy to production |
32
+ | `aai start` | Start production server from build |
33
+ | `aai secret <cmd>` | Manage secrets |
34
+ | `aai rag <url>` | Ingest a site into the vector store |
package/dist/aai.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ if(!process.env.FORCE_COLOR&&!process.env.NO_COLOR&&process.stdout.isTTY)process.env.FORCE_COLOR='1';
3
+ await import("./cli.js");
package/dist/cli.js CHANGED
@@ -966,7 +966,8 @@ function createS2sSession(opts) {
966
966
  }
967
967
  handle.updateSession({
968
968
  system_prompt: systemPrompt,
969
- tools: s2sTools
969
+ tools: s2sTools,
970
+ ...agentConfig.greeting ? { greeting: agentConfig.greeting } : {}
970
971
  });
971
972
  handle.addEventListener("ready", ((e) => {
972
973
  s2sSessionId = e.detail.session_id;
@@ -1087,13 +1088,6 @@ function createS2sSession(opts) {
1087
1088
  onAudioReady() {
1088
1089
  if (audioReady) return;
1089
1090
  audioReady = true;
1090
- if (agentConfig.greeting && s2s) {
1091
- s2s.updateSession({
1092
- system_prompt: systemPrompt,
1093
- tools: s2sTools,
1094
- greeting: agentConfig.greeting
1095
- });
1096
- }
1097
1091
  },
1098
1092
  onCancel() {
1099
1093
  client.event({ type: "cancelled" });
@@ -1344,7 +1338,7 @@ function wireSessionSocket(ws, opts) {
1344
1338
  const sid = sessionId.slice(0, 8);
1345
1339
  const ctx = opts.logContext ?? {};
1346
1340
  let session = null;
1347
- ws.addEventListener("open", () => {
1341
+ function onOpen() {
1348
1342
  opts.onOpen?.();
1349
1343
  log.info("Session connected", { ...ctx, sid });
1350
1344
  const client = createClientSink(ws);
@@ -1353,13 +1347,18 @@ function wireSessionSocket(ws, opts) {
1353
1347
  ws.send(JSON.stringify({ type: "config", ...opts.readyConfig }));
1354
1348
  void session.start();
1355
1349
  log.info("Session ready", { ...ctx, sid });
1356
- });
1350
+ }
1351
+ if (ws.readyState === 1) {
1352
+ onOpen();
1353
+ } else {
1354
+ ws.addEventListener("open", onOpen);
1355
+ }
1357
1356
  ws.addEventListener("message", (event) => {
1358
1357
  if (!session) return;
1359
1358
  const msgEvent = event;
1360
1359
  const { data } = msgEvent;
1361
- if (data instanceof ArrayBuffer) {
1362
- const chunk = new Uint8Array(data);
1360
+ if (Buffer.isBuffer(data)) {
1361
+ const chunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
1363
1362
  if (!isValidAudioChunk(chunk)) {
1364
1363
  log.warn("Invalid audio chunk, dropping", {
1365
1364
  ...ctx,
@@ -1794,6 +1793,7 @@ async function askSelect(message, choices) {
1794
1793
  Select,
1795
1794
  {
1796
1795
  options: choices,
1796
+ visibleOptionCount: choices.length,
1797
1797
  onChange: (value) => {
1798
1798
  resolve(value);
1799
1799
  app.unmount();
@@ -1807,7 +1807,8 @@ async function askSelect(message, choices) {
1807
1807
 
1808
1808
  // cli/_discover.ts
1809
1809
  function isDevMode() {
1810
- return path.basename(process.argv[0] ?? "") !== "aai";
1810
+ const script = process.argv[1] ?? "";
1811
+ return script.endsWith(".ts") || script.endsWith(".tsx");
1811
1812
  }
1812
1813
  function generateSlug() {
1813
1814
  return humanId({ separator: "-", capitalize: false });
@@ -2072,7 +2073,10 @@ function CommandRunner({
2072
2073
  const { items, log } = useStepLog();
2073
2074
  const [spinning, setSpinning] = useState(true);
2074
2075
  const [err, setErr] = useState(null);
2076
+ const started = useRef(false);
2075
2077
  React.useEffect(() => {
2078
+ if (started.current) return;
2079
+ started.current = true;
2076
2080
  (async () => {
2077
2081
  try {
2078
2082
  await run(log);
@@ -2084,7 +2088,7 @@ function CommandRunner({
2084
2088
  setSpinning(false);
2085
2089
  exit();
2086
2090
  })();
2087
- }, [exit, log, onError, run]);
2091
+ });
2088
2092
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
2089
2093
  /* @__PURE__ */ jsx2(StepLog, { items }),
2090
2094
  err && /* @__PURE__ */ jsx2(ErrorLine, { msg: err }),
@@ -2485,6 +2489,7 @@ var DEFAULT_STYLES_CSS = `@import url("https://fonts.googleapis.com/css2?family=
2485
2489
  @import "tailwindcss";
2486
2490
  @source "./";
2487
2491
  @source "./components/";
2492
+ @source "./node_modules/@alexkroman1/aai/ui/";
2488
2493
 
2489
2494
  @theme {
2490
2495
  --color-aai-bg: #101010;
@@ -2562,7 +2567,7 @@ var INDEX_HTML = `<!DOCTYPE html>
2562
2567
  content="width=device-width, initial-scale=1.0, viewport-fit=cover"
2563
2568
  />
2564
2569
  <title>aai</title>
2565
- <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
2570
+ <link rel="icon" href="data:," />
2566
2571
  <link rel="stylesheet" href="../styles.css" />
2567
2572
  </head>
2568
2573
  <body>
@@ -2863,15 +2868,25 @@ init_output();
2863
2868
 
2864
2869
  // cli/_server_common.ts
2865
2870
  import path7 from "node:path";
2871
+ import { createServer as createViteServer } from "vite";
2866
2872
  init_output();
2867
2873
  async function loadAgentDef(cwd) {
2868
2874
  const agentPath = path7.resolve(cwd, "agent.ts");
2869
- const agentModule = await import(agentPath);
2870
- const agentDef = agentModule.default;
2871
- if (!agentDef || typeof agentDef !== "object" || !agentDef.name) {
2872
- throw new Error("agent.ts must export a default agent definition (from defineAgent())");
2875
+ const vite = await createViteServer({
2876
+ root: cwd,
2877
+ logLevel: "silent",
2878
+ server: { middlewareMode: true }
2879
+ });
2880
+ try {
2881
+ const agentModule = await vite.ssrLoadModule(agentPath);
2882
+ const agentDef = agentModule.default;
2883
+ if (!agentDef || typeof agentDef !== "object" || !agentDef.name) {
2884
+ throw new Error("agent.ts must export a default agent definition (from defineAgent())");
2885
+ }
2886
+ return agentDef;
2887
+ } finally {
2888
+ await vite.close();
2873
2889
  }
2874
- return agentDef;
2875
2890
  }
2876
2891
  async function resolveServerEnv() {
2877
2892
  const env = { ...process.env };
@@ -2885,13 +2900,21 @@ async function resolveServerEnv() {
2885
2900
  }
2886
2901
  return env;
2887
2902
  }
2888
- async function bootServer(agentDef, html, env, port) {
2903
+ async function loadWsFromProject(cwd) {
2904
+ const wsPath = path7.resolve(cwd, "node_modules", "ws", "index.js");
2905
+ const mod = await import(wsPath);
2906
+ const WS = mod.default ?? mod;
2907
+ return (url, opts) => new WS(url, { headers: opts.headers });
2908
+ }
2909
+ async function bootServer(agentDef, html, env, port, cwd) {
2889
2910
  step("Start", `http://localhost:${port}`);
2911
+ const createWebSocket = await loadWsFromProject(cwd);
2890
2912
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
2891
2913
  const server = createServer2({
2892
2914
  agent: agentDef,
2893
2915
  clientHtml: html,
2894
- env
2916
+ env,
2917
+ createWebSocket
2895
2918
  });
2896
2919
  await server.listen(port);
2897
2920
  step("Ready", `http://localhost:${port}`);
@@ -2901,7 +2924,7 @@ async function bootServer(agentDef, html, env, port) {
2901
2924
  async function _startDevServer(cwd, port) {
2902
2925
  const agent = await loadAgent(cwd);
2903
2926
  if (!agent) {
2904
- throw new Error("No agent found \u2014 run `aai new` first");
2927
+ throw new Error("No agent found \u2014 run `aai init` first");
2905
2928
  }
2906
2929
  step("Bundle", agent.slug);
2907
2930
  let html;
@@ -2918,7 +2941,7 @@ async function _startDevServer(cwd, port) {
2918
2941
  step("Load", "agent.ts");
2919
2942
  const agentDef = await loadAgentDef(cwd);
2920
2943
  const env = await resolveServerEnv();
2921
- await bootServer(agentDef, html, env, port);
2944
+ await bootServer(agentDef, html, env, port, cwd);
2922
2945
  }
2923
2946
 
2924
2947
  // cli/dev.tsx
@@ -3332,7 +3355,7 @@ async function _startProductionServer(cwd, port) {
3332
3355
  step("Load", "agent");
3333
3356
  const agentDef = await loadAgentDef(cwd);
3334
3357
  const env = await resolveServerEnv();
3335
- await bootServer(agentDef, html, env, port);
3358
+ await bootServer(agentDef, html, env, port, cwd);
3336
3359
  }
3337
3360
 
3338
3361
  // cli/start.tsx
@@ -3422,14 +3445,11 @@ async function main(args) {
3422
3445
  process.exit(1);
3423
3446
  }
3424
3447
  }
3425
- var isMain = process.argv[1] === fileURLToPath3(import.meta.url);
3426
- if (isMain) {
3427
- try {
3428
- await main(process.argv.slice(2));
3429
- } catch (err) {
3430
- error2(err instanceof Error ? err.message : String(err));
3431
- process.exit(1);
3432
- }
3448
+ try {
3449
+ await main(process.argv.slice(2));
3450
+ } catch (err) {
3451
+ error2(err instanceof Error ? err.message : String(err));
3452
+ process.exit(1);
3433
3453
  }
3434
3454
  export {
3435
3455
  main
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "bin": {
6
- "aai": "dist/cli.js"
6
+ "aai": "dist/aai.js"
7
7
  },
8
8
  "exports": {
9
9
  ".": "./sdk/mod.ts",
@@ -69,7 +69,6 @@
69
69
  },
70
70
  "scripts": {
71
71
  "setup": "git config core.hooksPath .githooks",
72
- "build:cli": "node scripts/build-cli.mjs",
73
72
  "test": "vitest run",
74
73
  "lint": "biome check sdk/ ui/ cli/",
75
74
  "lint:fix": "biome check --write sdk/ ui/ cli/",
package/sdk/session.ts CHANGED
@@ -250,12 +250,12 @@ export function createS2sSession(opts: SessionOptions): Session {
250
250
  });
251
251
  handle.resumeSession(s2sSessionId);
252
252
  }
253
- // Send config without greeting first greeting is deferred until
254
- // the client's audio is ready (onAudioReady) to avoid a race where
255
- // greeting audio arrives before the browser can play it.
253
+ // Send config with greeting on initial connect.
254
+ // Audio may arrive before the client is ready the client buffers it.
256
255
  handle.updateSession({
257
256
  system_prompt: systemPrompt,
258
257
  tools: s2sTools,
258
+ ...(agentConfig.greeting ? { greeting: agentConfig.greeting } : {}),
259
259
  });
260
260
 
261
261
  handle.addEventListener("ready", ((e: CustomEvent<{ session_id: string }>) => {
@@ -400,14 +400,7 @@ export function createS2sSession(opts: SessionOptions): Session {
400
400
  onAudioReady(): void {
401
401
  if (audioReady) return;
402
402
  audioReady = true;
403
- // Now that the client can play audio, send greeting via session.update.
404
- if (agentConfig.greeting && s2s) {
405
- s2s.updateSession({
406
- system_prompt: systemPrompt,
407
- tools: s2sTools,
408
- greeting: agentConfig.greeting,
409
- });
410
- }
403
+ // Greeting audio + transcript come from S2S automatically.
411
404
  },
412
405
 
413
406
  onCancel(): void {
package/sdk/ws_handler.ts CHANGED
@@ -100,7 +100,7 @@ export function wireSessionSocket(ws: SessionWebSocket, opts: WsSessionOptions):
100
100
 
101
101
  let session: Session | null = null;
102
102
 
103
- ws.addEventListener("open", () => {
103
+ function onOpen(): void {
104
104
  opts.onOpen?.();
105
105
  log.info("Session connected", { ...ctx, sid });
106
106
 
@@ -113,16 +113,23 @@ export function wireSessionSocket(ws: SessionWebSocket, opts: WsSessionOptions):
113
113
 
114
114
  void session.start();
115
115
  log.info("Session ready", { ...ctx, sid });
116
- });
116
+ }
117
+
118
+ // readyState 1 = OPEN — socket already open (e.g. from ws handleUpgrade)
119
+ if (ws.readyState === 1) {
120
+ onOpen();
121
+ } else {
122
+ ws.addEventListener("open", onOpen);
123
+ }
117
124
 
118
125
  ws.addEventListener("message", (event: Event) => {
119
126
  if (!session) return;
120
127
  const msgEvent = event as MessageEvent;
121
128
  const { data } = msgEvent;
122
129
 
123
- // Binary frame → raw PCM16 audio
124
- if (data instanceof ArrayBuffer) {
125
- const chunk = new Uint8Array(data);
130
+ // Binary frame → raw PCM16 audio (ws package delivers Buffer)
131
+ if (Buffer.isBuffer(data)) {
132
+ const chunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
126
133
  if (!isValidAudioChunk(chunk)) {
127
134
  log.warn("Invalid audio chunk, dropping", {
128
135
  ...ctx,