@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 +34 -0
- package/dist/aai.js +3 -0
- package/dist/cli.js +53 -33
- package/package.json +2 -3
- package/sdk/session.ts +4 -11
- package/sdk/ws_handler.ts +12 -5
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
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
}
|
|
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="
|
|
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
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
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
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"aai": "dist/
|
|
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
|
|
254
|
-
// the client
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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,
|