@alexkroman1/aai-cli 5.2.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +51 -0
  2. package/dist/{_bundler-DFS4xxqE.mjs → _bundler-Cjaxa2wi.mjs} +1 -1
  3. package/dist/{_dev-server-BBUWo0sb.mjs → _dev-server-cigzayJe.mjs} +2 -2
  4. package/dist/{_init-DU-sXH6S.mjs → _init-_G8l4lh2.mjs} +2 -55
  5. package/dist/_templates-DW9CvuDN.mjs +69 -0
  6. package/dist/_templates.d.ts +6 -0
  7. package/dist/{build-Xq1xxoSg.mjs → build-D_PgQOD4.mjs} +1 -1
  8. package/dist/cli.mjs +35 -10
  9. package/dist/{client-bundler-BWDkUeEP.mjs → client-bundler-yiWoXrgb.mjs} +3 -0
  10. package/dist/client-bundler.d.ts +9 -0
  11. package/dist/client-bundler.mjs +1 -1
  12. package/dist/{deploy-Ci0X1gp2.mjs → deploy-C0VLnnJe.mjs} +1 -1
  13. package/dist/{dev-g7sGt8qX.mjs → dev-2oUsvgRX.mjs} +1 -1
  14. package/dist/{init-BjeK8crW.mjs → init-BkW9BuZF.mjs} +2 -2
  15. package/dist/scaffold/CLAUDE.md +174 -101
  16. package/dist/scaffold/package.json +3 -3
  17. package/dist/templates/dispatch-center/agent.test.ts +3 -3
  18. package/dist/templates/dispatch-center/agent.ts +2 -2
  19. package/dist/templates/dispatch-center/tools/incident_add_note.ts +1 -1
  20. package/dist/templates/dispatch-center/tools/incident_create.ts +1 -1
  21. package/dist/templates/dispatch-center/tools/incident_escalate.ts +1 -1
  22. package/dist/templates/dispatch-center/tools/incident_get.ts +1 -1
  23. package/dist/templates/dispatch-center/tools/incident_triage.ts +1 -1
  24. package/dist/templates/dispatch-center/tools/incident_update_status.ts +1 -1
  25. package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
  26. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +1 -1
  27. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +1 -1
  28. package/dist/templates/dispatch-center/tools/resources_get_available.ts +1 -1
  29. package/dist/templates/dispatch-center/tools/resources_update_status.ts +1 -1
  30. package/dist/templates/embedded-assets/agent.ts +1 -1
  31. package/dist/templates/health-assistant/agent.ts +2 -2
  32. package/dist/templates/infocom-adventure/agent.ts +7 -7
  33. package/dist/templates/math-buddy/agent.ts +1 -1
  34. package/dist/templates/night-owl/agent.ts +1 -1
  35. package/dist/templates/pizza-ordering/agent.test.ts +13 -11
  36. package/dist/templates/pizza-ordering/agent.ts +4 -4
  37. package/dist/templates/simple/agent.test.ts +18 -0
  38. package/dist/templates/simple/agent.ts +2 -0
  39. package/dist/templates/solo-rpg/agent.test.ts +2 -2
  40. package/dist/templates/solo-rpg/tools/action_roll.ts +1 -1
  41. package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
  42. package/dist/templates/solo-rpg/tools/oracle.ts +1 -1
  43. package/dist/templates/solo-rpg/tools/save_game.ts +1 -1
  44. package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
  45. package/dist/templates/solo-rpg/tools/update_state.ts +1 -1
  46. package/dist/typecheck.d.ts +18 -0
  47. package/dist/typecheck.mjs +8 -0
  48. package/dist/worker-bundler.d.ts +9 -1
  49. package/dist/worker-bundler.mjs +8 -0
  50. package/package.json +3 -3
@@ -11,8 +11,9 @@ The fast loop: edit → `pnpm dev` (browser, talk to it) →
11
11
  agent to verify behavior end-to-end. This is the primary feedback loop.
12
12
  2. **Run `pnpm test` after logic changes** — vitest. Co-locate tests as
13
13
  `agent.test.ts` (see `pipeline-simple` template for a reference).
14
- **The project starts with an `agent.test.ts`, and it is yours to
15
- maintain.** It asserts the agent's shape name, providers, tool names
14
+ **When the project has an `agent.test.ts` (the default `simple`
15
+ template and several others ship one), it is yours to maintain.** It
16
+ asserts the agent's shape — name, providers, tool names —
16
17
  so rewriting the agent without updating it leaves a test asserting an
17
18
  agent that no longer exists. When a test fails after your change, decide
18
19
  which side is stale: updating the test to match the new agent is a normal
@@ -26,17 +27,21 @@ The fast loop: edit → `pnpm dev` (browser, talk to it) →
26
27
  `node_modules/@alexkroman1/aai-cli/dist/templates/`. Read them directly;
27
28
  `aai init --template <name>` scaffolds a fresh project from one. Closest
28
29
  matches: `simple`, `pipeline-simple`, `web-researcher`, `solo-rpg`,
29
- `pizza-ordering`.
30
+ `pizza-ordering`. When reading SDK types under
31
+ `node_modules/@alexkroman1/aai*/dist/`, note the built entry points
32
+ re-export with source specifiers (`"./sdk/constants.ts"`,
33
+ `"./components/button.tsx"`) — rewrite `.ts`/`.tsx` to `.d.ts` to find
34
+ the shipped file.
30
35
 
31
36
  ## CLI
32
37
 
33
38
  ```sh
34
39
  npx @alexkroman1/aai-cli init # Scaffold a new agent
40
+ npx @alexkroman1/aai-cli templates # List available templates
35
41
  npx @alexkroman1/aai-cli dev # Start local dev server
36
42
  npx @alexkroman1/aai-cli test # Run agent.test.ts via vitest
37
43
  npx @alexkroman1/aai-cli build # Bundle and validate
38
44
  npx @alexkroman1/aai-cli deploy # Deploy to production
39
- npx @alexkroman1/aai-cli deploy -y # Deploy without prompts
40
45
  npx @alexkroman1/aai-cli delete # Remove deployed agent
41
46
  npx @alexkroman1/aai-cli secret put NAME # Set a secret
42
47
  npx @alexkroman1/aai-cli secret delete NAME
@@ -44,7 +49,7 @@ npx @alexkroman1/aai-cli secret list
44
49
  ```
45
50
 
46
51
  The scaffold's `package.json` exposes `dev`, `build`, `test`, and `deploy`
47
- as `pnpm <name>` shortcuts. Other commands (`init`, `delete`, `secret`)
52
+ as `pnpm <name>` shortcuts. Other commands (`init`, `templates`, `delete`, `secret`)
48
53
  are CLI-only.
49
54
 
50
55
  ## Project structure
@@ -64,23 +69,25 @@ my-agent/
64
69
 
65
70
  ## `agent()` API
66
71
 
67
- ```ts
72
+ ```ts no-check
68
73
  import { agent } from "@alexkroman1/aai";
69
74
 
70
75
  export default agent({
71
76
  name: string; // required — display name
72
77
  systemPrompt?: string; // default: general voice assistant
78
+ // (`system` is an accepted alias)
73
79
  greeting?: string; // default: "Hey there..."
74
80
  sttPrompt?: string; // STT guidance for jargon/acronyms
75
81
  builtinTools?: BuiltinTool[]; // see built-in tools table
76
82
  tools?: Record<string, ToolDef>;
77
83
  maxSteps?: number; // default: 10 — max tool calls per turn
78
- toolChoice?: "auto" | "required"; // default: "auto"
84
+ toolChoice?: ToolChoice; // "auto" (default) | "required" | "none"
85
+ // | { type: "tool", toolName }
79
86
  idleTimeoutMs?: number; // disconnect after inactivity (ms)
80
87
  silenceTimeoutMs?: number; // pipeline only — assistant speaks up after this much user silence (ms)
81
88
  silencePrompt?: string; // instruction injected on silence timeout (requires silenceTimeoutMs)
82
89
  minBargeInWords?: number; // pipeline only — words before user speech interrupts the reply (default 2)
83
- interruptionMinDurationMs?: number; // pipeline only — sustained speech (ms) before an interim barge-in interrupts (default 0 = off)
90
+ interruptionMinDurationMs?: number; // pipeline only — sustained speech (ms) before an interim barge-in interrupts (default 500; 0 disables)
84
91
  holdPhrase?: string; // pipeline only — spoken before a silent tool-call turn (default "One moment."; "" disables)
85
92
  falseInterruptionTimeoutMs?: number; // pipeline only — resume an interrupted reply if no user turn commits (default 2000; 0 disables)
86
93
  state?: () => S; // per-session mutable state, exposed as ctx.state
@@ -91,7 +98,10 @@ export default agent({
91
98
  ```
92
99
 
93
100
  > When `stt`, `llm`, and `tts` are all provided, the agent runs in
94
- > **Pipeline mode** — see the section below.
101
+ > **Pipeline mode** — see the section below. `llm` also accepts a model-id
102
+ > string: `"creator/model"` routes through the Vercel AI Gateway
103
+ > (`AI_GATEWAY_API_KEY`), a bare id through the AssemblyAI LLM Gateway
104
+ > (`ASSEMBLYAI_API_KEY`).
95
105
 
96
106
  Minimal agent — a cascaded pipeline, which is what you should build unless
97
107
  the user asks for the speech-to-speech API:
@@ -111,7 +121,7 @@ by setting it after the spread — everything else stays as the preset put it:
111
121
 
112
122
  ```ts
113
123
  import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
114
- import { assemblyAI as assemblyAITts } from "@alexkroman1/aai/tts";
124
+ import { assemblyAITts } from "@alexkroman1/aai/tts";
115
125
 
116
126
  export default agent({
117
127
  name: "My Agent",
@@ -120,11 +130,27 @@ export default agent({
120
130
  });
121
131
  ```
122
132
 
123
- `agent({ name })` alone is legal and gives you S2S mode instead see below.
133
+ The same pattern changes the gateway LLM model `llm` accepts the model id
134
+ as a plain string:
135
+
136
+ ```ts
137
+ import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
138
+
139
+ export default agent({
140
+ name: "My Agent",
141
+ ...assemblyAIPipeline(),
142
+ llm: "claude-sonnet-4-6",
143
+ });
144
+ ```
145
+
146
+ `agent({ name })` alone is legal and equivalent — an agent that declares no
147
+ providers gets `assemblyAIPipeline()` injected as the default. Speech-to-speech
148
+ (S2S) mode is an explicit opt-in via the `s2s` field — see below.
124
149
 
125
150
  System prompt from file:
126
151
 
127
152
  ```ts
153
+ /// <reference types="vite/client" />
128
154
  import { agent } from "@alexkroman1/aai";
129
155
  import systemPrompt from "./system-prompt.md?raw";
130
156
  export default agent({ name: "My Agent", systemPrompt });
@@ -138,14 +164,32 @@ explicit the modern spelling is `with { type: "json" }`, but plain is fine.
138
164
 
139
165
  ## Pipeline mode
140
166
 
141
- Omitting `stt`/`llm`/`tts` gives **S2S mode**: AssemblyAI's speech-to-speech
142
- service handles STT, the LLM loop, and TTS in one socket. Fewer moving
143
- parts, but you cannot choose the model, swap a provider, or tune a stage.
167
+ Pipeline mode is the default: omitting `stt`/`llm`/`tts` (and `s2s`) gives
168
+ you the all-AssemblyAI pipeline, exactly as if you had spread
169
+ `assemblyAIPipeline()` yourself.
144
170
 
145
- **Prefer pipeline mode** declare all three unless the user specifically
171
+ **S2S mode is an explicit opt-in.** Setting `s2s: assemblyAIS2s()` (imported
172
+ from `@alexkroman1/aai`, next to `agent()`) selects AssemblyAI's
173
+ speech-to-speech Voice Agent API: STT, the LLM loop, and TTS run
174
+ service-side in one socket. Fewer moving parts, but you cannot choose the
175
+ model, swap a provider, or tune a stage. There is no way to reach S2S by
176
+ omission — only the `s2s` field selects it, and it is mutually exclusive
177
+ with the `stt`/`llm`/`tts` triple.
178
+
179
+ ```ts
180
+ import { agent, assemblyAIS2s } from "@alexkroman1/aai";
181
+
182
+ export default agent({
183
+ name: "My Agent",
184
+ s2s: assemblyAIS2s(),
185
+ });
186
+ ```
187
+
188
+ **Prefer pipeline mode** — the default — unless the user specifically
146
189
  asks for the speech-to-speech API. Nearly every template ships this way, and
147
190
  it is what the App Builder defaults to. The host runs the LLM loop locally
148
- (Vercel AI SDK) with your chosen STT, LLM, and TTS. You need it when:
191
+ (Vercel AI SDK) with your chosen STT, LLM, and TTS. You want explicit
192
+ providers when:
149
193
 
150
194
  - you want a specific LLM (Anthropic, OpenAI, Gemini, Mistral, xAI, Groq,
151
195
  hundreds of models via OpenRouter, or 25+ models via the AssemblyAI
@@ -153,18 +197,21 @@ it is what the App Builder defaults to. The host runs the LLM loop locally
153
197
  - you want a specific STT model or TTS voice
154
198
  - you need to swap providers without changing agent code
155
199
 
156
- **The rule:** set all three of `stt`, `llm`, `tts` together, or none. A
157
- partial config is rejected at parse time.
200
+ **The rule:** set all three of `stt`, `llm`, `tts` together, or none.
201
+ `agent()`'s parameter type enforces this — a partial triple is a compile
202
+ error ("missing: llm, tts"), as is combining `s2s` with any pipeline
203
+ provider or pipeline-only tuning field. A raw config that skips `agent()`
204
+ is still rejected at parse time.
158
205
 
159
206
  ```ts
160
207
  import { agent } from "@alexkroman1/aai";
161
- import { assemblyAI } from "@alexkroman1/aai/stt";
208
+ import { assemblyAIStt } from "@alexkroman1/aai/stt";
162
209
  import { anthropic } from "@alexkroman1/aai/llm";
163
210
  import { cartesia } from "@alexkroman1/aai/tts";
164
211
 
165
212
  export default agent({
166
213
  name: "My Agent",
167
- stt: assemblyAI({ model: "universal-3-5-pro" }),
214
+ stt: assemblyAIStt({ model: "universal-3-5-pro" }),
168
215
  llm: anthropic({ model: "claude-haiku-4-5" }),
169
216
  tts: cartesia(),
170
217
  });
@@ -186,11 +233,12 @@ user speaks again.
186
233
  **Voice-UX tuning (pipeline only):** `minBargeInWords` controls how many
187
234
  words of user speech interrupt the assistant mid-reply (default 2, so
188
235
  one-word backchannels like "yeah" don't cut it off);
189
- `interruptionMinDurationMs` adds an optional sustained-speech gate on top
190
- (interim transcripts only — committed turns always land). End-of-turn
191
- detection (how long a pause ends the user's turn) belongs to the STT
192
- provider: `assemblyAI({ minTurnSilenceMs })` / `deepgram({ endpointing })`,
193
- both defaulting to 1500 ms so mid-utterance pauses don't split a request.
236
+ `interruptionMinDurationMs` adds a sustained-speech gate on top (default
237
+ 500 ms; `0` disables; interim transcripts only — committed turns always
238
+ land). End-of-turn detection (how long a pause ends the user's turn)
239
+ belongs to the STT provider: `assemblyAIStt({ minTurnSilenceMs })` (default
240
+ 2000 ms) / `deepgram({ endpointing })` (default 1500 ms), so mid-utterance
241
+ pauses don't split a request.
194
242
  `holdPhrase` is spoken when a turn opens with a tool call and no speech.
195
243
  `falseInterruptionTimeoutMs` resumes an interrupted reply when a barge-in
196
244
  turns out to be noise (no user turn commits within the window).
@@ -202,34 +250,34 @@ for the providers you actually use.
202
250
 
203
251
  ### STT — `@alexkroman1/aai/stt`
204
252
 
205
- | Factory | Default model | Env var |
206
- | ------------- | ------------------------ | -------------------- |
207
- | `assemblyAI` | `"universal-3-5-pro"` | `ASSEMBLYAI_API_KEY` |
208
- | `deepgram` | `"nova-3"` | `DEEPGRAM_API_KEY` |
209
- | `elevenlabs` | `"scribe_v2_realtime"` | `ELEVENLABS_API_KEY` |
210
- | `soniox` | `"stt-rt-v3"` | `SONIOX_API_KEY` |
253
+ | Factory | Default model | Env var |
254
+ | --------------- | ---------------------- | -------------------- |
255
+ | `assemblyAIStt` | `"universal-3-5-pro"` | `ASSEMBLYAI_API_KEY` |
256
+ | `deepgram` | `"nova-3"` | `DEEPGRAM_API_KEY` |
257
+ | `elevenlabs` | `"scribe_v2_realtime"` | `ELEVENLABS_API_KEY` |
258
+ | `soniox` | `"stt-rt-v3"` | `SONIOX_API_KEY` |
211
259
 
212
260
  All STT factories accept `{ model?: string, ... }`. Bare calls
213
261
  (`deepgram()`, `soniox()`, etc.) use the default model.
214
262
 
215
- `assemblyAI` accepts an optional `region: "eu"` for EU data residency —
263
+ `assemblyAIStt` accepts an optional `region: "eu"` for EU data residency —
216
264
  it routes streaming transcription to AssemblyAI's EU endpoints. EU-region
217
265
  API keys require it; the US endpoints reject them. Example:
218
- `assemblyAI({ model: "universal-3-5-pro", region: "eu" })`.
266
+ `assemblyAIStt({ model: "universal-3-5-pro", region: "eu" })`.
219
267
 
220
268
  ### LLM — `@alexkroman1/aai/llm`
221
269
 
222
- | Factory | SDK package | Env var |
223
- | ----------- | --------------------- | -------------------------------- |
224
- | `anthropic` | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
225
- | `openai` | `@ai-sdk/openai` | `OPENAI_API_KEY` |
226
- | `google` | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
227
- | `mistral` | `@ai-sdk/mistral` | `MISTRAL_API_KEY` |
228
- | `xai` | `@ai-sdk/xai` | `XAI_API_KEY` |
229
- | `groq` | `@ai-sdk/groq` | `GROQ_API_KEY` |
230
- | `openrouter`| `@ai-sdk/openai` | `OPENROUTER_API_KEY` |
231
- | `gateway` | `ai` (built in) | `AI_GATEWAY_API_KEY` |
232
- | `assemblyAI`| `@ai-sdk/openai` | `ASSEMBLYAI_API_KEY` |
270
+ | Factory | SDK package | Env var |
271
+ | --------------- | ------------------- | ------------------------------ |
272
+ | `anthropic` | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
273
+ | `openai` | `@ai-sdk/openai` | `OPENAI_API_KEY` |
274
+ | `google` | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
275
+ | `mistral` | `@ai-sdk/mistral` | `MISTRAL_API_KEY` |
276
+ | `xai` | `@ai-sdk/xai` | `XAI_API_KEY` |
277
+ | `groq` | `@ai-sdk/groq` | `GROQ_API_KEY` |
278
+ | `openrouter` | `@ai-sdk/openai` | `OPENROUTER_API_KEY` |
279
+ | `gateway` | `ai` (built in) | `AI_GATEWAY_API_KEY` |
280
+ | `assemblyAILlm` | `@ai-sdk/openai` | `ASSEMBLYAI_API_KEY` |
233
281
 
234
282
  LLM factories require `{ model: string }`. Example:
235
283
  `anthropic({ model: "claude-haiku-4-5" })`.
@@ -246,50 +294,52 @@ hundreds of models addressed as `"creator/model"`, e.g.
246
294
  `gateway({ model: "zai/glm-4.6" })`. It needs no extra SDK install
247
295
  (the gateway client ships inside the `ai` package).
248
296
 
249
- `assemblyAI` routes through the [AssemblyAI LLM
297
+ `assemblyAILlm` routes through the [AssemblyAI LLM
250
298
  Gateway](https://www.assemblyai.com/docs/llm-gateway) — an
251
299
  OpenAI-compatible endpoint fronting 25+ models (Claude, GPT, Gemini,
252
- etc.) with the same API key as AssemblyAI STT. It accepts an optional
253
- `region: "eu"` for EU data residency. It shares its name with the STT
254
- factory, so alias one when using both:
300
+ etc.) with the same API key as AssemblyAI STT:
255
301
 
256
302
  ```ts
257
- import { assemblyAI } from "@alexkroman1/aai/stt";
258
- import { assemblyAI as assemblyAILlm } from "@alexkroman1/aai/llm";
303
+ import { agent } from "@alexkroman1/aai";
304
+ import { assemblyAILlm } from "@alexkroman1/aai/llm";
305
+ import { assemblyAIStt } from "@alexkroman1/aai/stt";
306
+ import { cartesia } from "@alexkroman1/aai/tts";
259
307
 
260
308
  export default agent({
261
309
  name: "My Agent",
262
- stt: assemblyAI({ model: "universal-3-5-pro" }),
310
+ stt: assemblyAIStt({ model: "universal-3-5-pro" }),
263
311
  llm: assemblyAILlm({ model: "claude-sonnet-4-6" }),
264
312
  tts: cartesia(),
265
313
  });
266
314
  ```
267
315
 
316
+ It accepts an optional `region: "eu"` for EU data residency.
317
+
268
318
  An all-AssemblyAI pipeline — one provider, one key:
269
319
 
270
320
  ```ts
271
321
  import { agent } from "@alexkroman1/aai";
272
- import { assemblyAI } from "@alexkroman1/aai/stt";
273
- import { assemblyAI as assemblyAILlm } from "@alexkroman1/aai/llm";
274
- import { assemblyAI as assemblyAITts } from "@alexkroman1/aai/tts";
322
+ import { assemblyAILlm } from "@alexkroman1/aai/llm";
323
+ import { assemblyAIStt } from "@alexkroman1/aai/stt";
324
+ import { assemblyAITts } from "@alexkroman1/aai/tts";
275
325
 
276
326
  export default agent({
277
327
  name: "My Agent",
278
- stt: assemblyAI({ model: "universal-3-5-pro" }),
328
+ stt: assemblyAIStt({ model: "universal-3-5-pro" }),
279
329
  llm: assemblyAILlm({ model: "gemini-2.5-flash-lite" }),
280
- tts: assemblyAITts({ voice: "vera" }),
330
+ tts: assemblyAITts({ voice: "jane" }),
281
331
  });
282
332
  ```
283
333
 
284
334
  ### TTS — `@alexkroman1/aai/tts`
285
335
 
286
- | Factory | Default voice | Env var |
287
- | ------------ | ---------------------------------------- | -------------------- |
288
- | `assemblyAI` | `"vera"` | `ASSEMBLYAI_API_KEY` |
289
- | `cartesia` | `"f786b574-daa5-4673-aa0c-cbe3e8534c02"` | `CARTESIA_API_KEY` |
290
- | `rime` | `"cove"` (model `mistv2`) | `RIME_API_KEY` |
336
+ | Factory | Default voice | Env var |
337
+ | --------------- | ---------------------------------------- | -------------------- |
338
+ | `assemblyAITts` | `"jane"` | `ASSEMBLYAI_API_KEY` |
339
+ | `cartesia` | `"f786b574-daa5-4673-aa0c-cbe3e8534c02"` | `CARTESIA_API_KEY` |
340
+ | `rime` | `"cove"` (model `mistv2`) | `RIME_API_KEY` |
291
341
 
292
- Bare calls (`assemblyAI()`, `cartesia()`, `rime()`) use the defaults.
342
+ Bare calls (`assemblyAITts()`, `cartesia()`, `rime()`) use the defaults.
293
343
  Override with `{ voice, model, language }`.
294
344
 
295
345
  **AssemblyAI TTS** shares `ASSEMBLYAI_API_KEY` with AssemblyAI STT and the
@@ -300,8 +350,8 @@ connected, "ready", and permanently silent**, so pick one from here rather
300
350
  than guessing a plausible name:
301
351
 
302
352
  - **English, US accent**: `alba`, `anna`, `charles`, `eve`, `george`,
303
- `jane`, `jean`, `mary`, `michael`
304
- - **English, UK accent**: `paul`, `vera` (the default)
353
+ `jane` (the default), `jean`, `mary`, `michael`
354
+ - **English, UK accent**: `paul`, `vera`
305
355
  - **Native accent, code-switches with English**: `estelle` (fr),
306
356
  `giovanni` (it), `juergen` (de), `lola` (es), `rafael` (pt)
307
357
 
@@ -312,8 +362,7 @@ Set
312
362
  `language` only alongside a voice that speaks it, as an ISO 639-1 code —
313
363
  `"en"`, `"fr"`, `"de"`, `"it"`, `"pt"`, `"es"` are the six the catalog
314
364
  covers, and the SDK translates each to the full name the service wants.
315
- Anything else fails at session start. Because the factory is named
316
- `assemblyAI` in `/stt`, `/llm`, and `/tts`, alias on import.
365
+ Anything else fails at session start.
317
366
 
318
367
  **Rime quirk:** language uses ISO 639-3 three-letter codes (e.g. `"eng"`
319
368
  not `"en"`).
@@ -323,13 +372,13 @@ Set provider keys the same way as any secret: `.env` for local dev,
323
372
 
324
373
  ## `tool()` API
325
374
 
326
- ```ts
375
+ ```ts no-check
327
376
  import { tool } from "@alexkroman1/aai";
328
377
  import { z } from "zod";
329
378
 
330
379
  const myTool = tool({
331
380
  description: string; // shown to LLM — decides when to call
332
- parameters?: z.ZodObject; // Zod schema (omit for no-arg tools)
381
+ inputSchema?: z.ZodObject; // Zod schema (omit for no-arg tools)
333
382
  execute(args, ctx): unknown; // sync or async
334
383
  });
335
384
  ```
@@ -339,13 +388,13 @@ same way in `aai dev` and deployed.
339
388
 
340
389
  ### `ctx` (ToolContext)
341
390
 
342
- ```ts
391
+ ```ts no-check
343
392
  ctx.env: Readonly<Record<string, string>> // secrets from .env / aai secret put
344
393
  ctx.state: S // per-session mutable state (agent's `state` factory)
345
394
  ctx.db: Db // SQL database, needs storage enabled (see Database section)
346
395
  ctx.messages: readonly Message[] // conversation history [{role, content}]
347
396
  ctx.sessionId: string // unique session ID
348
- ctx.send(event: string, data: unknown): void // push custom event to browser client
397
+ ctx.send(event: string, data: unknown): void // push custom event to browser client (silently dropped over 64 KB JSON)
349
398
  ctx.generate(opts): Promise<{ text, object? }> // one-shot LLM call (host-side)
350
399
  ```
351
400
 
@@ -353,7 +402,7 @@ ctx.generate(opts): Promise<{ text, object? }> // one-shot LLM call (host-side)
353
402
  the project's tsconfig turns off `noImplicitAny`, so both of these compile
354
403
  with no annotations and no errors:
355
404
 
356
- ```ts
405
+ ```ts no-check
357
406
  ctx.state.count++;
358
407
  ctx.state.incidents.filter((i) => i.status === "open");
359
408
  ```
@@ -366,7 +415,7 @@ declare empty.** With `noImplicitAny` off, TypeScript does not widen an empty
366
415
  initializer from what you later assign, so `[]` stays `never[]` and `null`
367
416
  stays `null` — forever, whether or not a callback is involved:
368
417
 
369
- ```ts
418
+ ```ts no-check
370
419
  const items = []; // never[] → items.push(x) is an error
371
420
  let best = null; // null → best = {...} is an error
372
421
  const [picks, set] = useState([]); // never[] in a client, same thing
@@ -418,10 +467,11 @@ compile error, which is the point.
418
467
 
419
468
  `ctx.generate({ prompt, system?, llm?, schema?, temperature?, maxOutputTokens? })`
420
469
  runs one LLM generation on the host. It defaults to the agent's pipeline
421
- `llm`; pass an `llm` descriptor (from `@alexkroman1/aai/llm`) to use another
422
- provider whose API key is in the agent's secrets — that's also how S2S
423
- agents use it. `schema` must be a **plain JSON Schema object** (use
424
- `z.toJSONSchema(...)`), never a Zod schema.
470
+ `llm`; pass an `llm` descriptor (from `@alexkroman1/aai/llm`) or a model-id
471
+ string to use another provider whose API key is in the agent's secrets —
472
+ that's also how S2S agents use it. Pass a Zod schema as `schema` for typed
473
+ structured output (`generateObject`-style): the result's `object` carries
474
+ the parsed, typed value. A plain JSON Schema object also works.
425
475
 
426
476
  ### Inline tool example
427
477
 
@@ -434,7 +484,7 @@ export default agent({
434
484
  tools: {
435
485
  get_weather: tool({
436
486
  description: "Get current weather for a city",
437
- parameters: z.object({
487
+ inputSchema: z.object({
438
488
  city: z.string().describe("City name"),
439
489
  }),
440
490
  async execute({ city }, ctx) {
@@ -453,7 +503,7 @@ export default agent({
453
503
  them, and they are not on `ctx`. When your own `execute` needs one, import
454
504
  it:
455
505
 
456
- ```ts
506
+ ```ts no-check
457
507
  import { fetchJson, visitWebpage, webSearch } from "@alexkroman1/aai/tools";
458
508
 
459
509
  execute: async ({ city }) => await fetchJson(`https://api.example.com/${city}`),
@@ -482,18 +532,18 @@ Wrapping `webSearch` in a single custom tool is the mistake to avoid — it
482
532
  replaces "the model searches as needed" with one fixed query-and-summarize
483
533
  pipeline, and no amount of prompting gets the flexibility back.
484
534
 
485
- **`parameters` is a Zod object, or absent.** The field itself is optional,
486
- but its VALUE must be a plain `z.object(...)` — so all of these are type
487
- errors:
535
+ **`inputSchema` is a Zod object, or absent.** The field itself is
536
+ optional, but its VALUE must be a plain `z.object(...)` — so all of these
537
+ are type errors:
488
538
 
489
- ```ts
490
- parameters: z.undefined(), // ✗ ZodUndefined
491
- parameters: z.void(), // ✗
492
- parameters: z.object({ q: z.string() }).optional(), // ✗ ZodOptional
539
+ ```ts no-check
540
+ inputSchema: z.undefined(), // ✗ ZodUndefined
541
+ inputSchema: z.void(), // ✗
542
+ inputSchema: z.object({ q: z.string() }).optional(), // ✗ ZodOptional
493
543
  ```
494
544
 
495
545
  For a tool with no arguments write `tool({ description, execute })`, or
496
- `parameters: z.object({})` if you prefer it explicit. To make an individual
546
+ `inputSchema: z.object({})` if you prefer it explicit. To make an individual
497
547
  argument optional, put `.optional()` on the FIELD, never on the object:
498
548
  `z.object({ notes: z.string().optional() })`.
499
549
 
@@ -514,14 +564,14 @@ import { z } from "zod";
514
564
 
515
565
  export const rollDice = tool({
516
566
  description: "Roll dice",
517
- parameters: z.object({ sides: z.number() }),
567
+ inputSchema: z.object({ sides: z.number() }),
518
568
  execute({ sides }) {
519
569
  return Math.floor(Math.random() * sides) + 1;
520
570
  },
521
571
  });
522
572
  ```
523
573
 
524
- ```ts
574
+ ```ts no-check
525
575
  // agent.ts
526
576
  import { agent } from "@alexkroman1/aai";
527
577
  import { rollDice } from "./tools/roll_dice.ts";
@@ -584,7 +634,7 @@ call it from `execute` — see the builtin table above.
584
634
  Persistent SQL storage scoped per app, backed by the app's own Postgres
585
635
  schema. Access via `ctx.db`:
586
636
 
587
- ```ts
637
+ ```ts no-check
588
638
  ctx.db.query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>
589
639
  ```
590
640
 
@@ -602,7 +652,7 @@ A query returning more than 1000 rows throws — always bound reads with
602
652
 
603
653
  Create tables lazily from tool code and upsert with `on conflict`:
604
654
 
605
- ```ts
655
+ ```ts no-check
606
656
  await ctx.db.query(`create table if not exists app_state (
607
657
  key text primary key,
608
658
  value jsonb not null,
@@ -633,6 +683,7 @@ Always import `"@alexkroman1/aai-ui/styles.css"` first.
633
683
  ### Tier 1 — config only (default UI)
634
684
 
635
685
  ```tsx
686
+ /// <reference types="vite/client" />
636
687
  import "@alexkroman1/aai-ui/styles.css";
637
688
  import { client } from "@alexkroman1/aai-ui";
638
689
 
@@ -642,6 +693,7 @@ client({ name: "My Agent" });
642
693
  ### Tier 1 with sidebar
643
694
 
644
695
  ```tsx
696
+ /// <reference types="vite/client" />
645
697
  import "@alexkroman1/aai-ui/styles.css";
646
698
  import { client, useEvent } from "@alexkroman1/aai-ui";
647
699
  import { useState } from "react";
@@ -664,6 +716,7 @@ client({ name: "My Agent", sidebar: Sidebar });
664
716
  ### Tier 2 — full custom component
665
717
 
666
718
  ```tsx
719
+ /// <reference types="vite/client" />
667
720
  import "@alexkroman1/aai-ui/styles.css";
668
721
  import { client, useSession } from "@alexkroman1/aai-ui";
669
722
 
@@ -714,7 +767,7 @@ to put it in.
714
767
  | `state` | `AgentState` | `"disconnected"` `"connecting"` `"ready"` `"listening"` `"thinking"` `"speaking"` `"error"` |
715
768
  | `messages` | `ChatMessage[]` | `{ role, content }` |
716
769
  | `toolCalls` | `ToolCallInfo[]` | `{ callId, name, args, status, result? }` |
717
- | `customEvents` | `CustomEvent[]` | `{ id, event, data }` from `ctx.send()` |
770
+ | `customEvents` | `AgentCustomEvent[]` | `{ id, event, data }` from `ctx.send()` |
718
771
  | `userTranscript` | `string \| null` | `null` = not speaking, `""` = speech detected, string = text |
719
772
  | `agentTranscript` | `string \| null` | `null` = not speaking, string = streaming response |
720
773
  | `error` | `SessionError \| null` | `{ code, message }` |
@@ -729,7 +782,7 @@ Methods: `start()`, `toggle()`, `reset()`, `cancel()`, `disconnect()`,
729
782
  **`useToolResult`** — fires once per completed tool call (deduplicates by
730
783
  callId):
731
784
 
732
- ```ts
785
+ ```ts no-check
733
786
  useToolResult("tool_name", (result, toolCall) => { ... }) // one tool
734
787
  useToolResult((toolName, result, toolCall) => { ... }) // all tools
735
788
  useToolResult<ResultType>("tool_name", (result) => { ... }) // typed (optional)
@@ -746,7 +799,7 @@ also what you want for anything that can be a string, an array, or null.
746
799
 
747
800
  **`useAgentState`** — the agent's session state, pushed automatically:
748
801
 
749
- ```ts
802
+ ```ts no-check
750
803
  // agent.ts
751
804
  export default agent({
752
805
  state: () => ({ cart: [] as Item[], staffPin: "" }),
@@ -772,7 +825,7 @@ after every tool call and is sent only when the result changed.
772
825
 
773
826
  **`useEvent`** — fires for custom events from `ctx.send()`:
774
827
 
775
- ```ts
828
+ ```ts no-check
776
829
  useEvent<DataType>("event_name", (data) => { ... })
777
830
  ```
778
831
 
@@ -793,13 +846,33 @@ Available from `@alexkroman1/aai-ui`:
793
846
 
794
847
  | Component | Props | Description |
795
848
  | --- | --- | --- |
796
- | `StartScreen` | `children, icon?, title?, subtitle?, buttonText?` | Centered start card; renders children after start |
849
+ | `StartScreen` | `children` (**required**)`, icon?, title?, subtitle?, buttonText?` | **Wrapper, never self-closing.** Shows the start card, then renders `children` your whole app — once the session starts |
797
850
  | `ChatView` | `icon?, title?` | Chat interface (header + messages + controls) |
798
851
  | `SidebarLayout` | `sidebar, children, sidebarWidth?, sidebarPosition?` | Two-column layout |
799
852
  | `MessageList` | — | Messages with auto-scroll, tool calls, transcript |
800
853
  | `Controls` | — | Stop/Resume + New Conversation buttons |
801
854
  | `Button` | — | Styled button |
802
855
 
856
+ The usual shape — note `StartScreen` **wraps** the app rather than sitting
857
+ beside it; writing `<StartScreen ... />` self-closing is a `TS2741:
858
+ Property 'children' is missing` build error:
859
+
860
+ ```tsx
861
+ /// <reference types="vite/client" />
862
+ import "@alexkroman1/aai-ui/styles.css";
863
+ import { ChatView, client, StartScreen } from "@alexkroman1/aai-ui";
864
+
865
+ function PizzaApp() {
866
+ return (
867
+ <StartScreen title="Pizza Palace" subtitle="Voice-powered ordering">
868
+ <ChatView />
869
+ </StartScreen>
870
+ );
871
+ }
872
+
873
+ client({ component: PizzaApp });
874
+ ```
875
+
803
876
  ## Styling
804
877
 
805
878
  - **Tailwind CSS v4** — compiled at bundle time, configured via CSS.
@@ -877,8 +950,8 @@ Common mistakes when working in aai projects:
877
950
  values are injected into LLM context. Truncate, summarize, or extract
878
951
  only what the model needs.
879
952
  - **Pipeline mode requires all three of `stt` / `llm` / `tts`.** Partial
880
- configs are rejected at parse time. Use S2S (omit all three) if you
881
- don't need provider control.
953
+ configs are rejected at parse time. Omit all three for the default
954
+ AssemblyAI pipeline; S2S needs an explicit `s2s: assemblyAIS2s()`.
882
955
  - **Never hardcode secrets.** Use `ctx.env.MY_KEY`. `.env` for local dev,
883
956
  `aai secret put` for production.
884
957
  - **Don't use `useEffect` + `toolCalls` to derive state.** Use
@@ -906,10 +979,10 @@ Common mistakes when working in aai projects:
906
979
 
907
980
  ## Constraints
908
981
 
909
- - Tool `execute` return values go into LLM context filter and truncate
910
- large API responses
911
- - `fetch` is proxied through the host; private/internal IPs are blocked
912
- (SSRF protection)
982
+ - Tool `execute` return values go into LLM context, capped at 4000 chars
983
+ (a truncation marker replaces the tail) — filter large API responses
984
+ - Tool code uses plain `fetch` with open egress; the keyless web builtins
985
+ screen private/internal IPs (SSRF) when running outside a sandbox
913
986
  - Agent code runs in a sandboxed worker — use `fetch` for HTTP, `ctx.env`
914
987
  for secrets
915
988
  - Tool execution timeout: 30 seconds
@@ -11,15 +11,15 @@
11
11
  "deploy": "aai deploy"
12
12
  },
13
13
  "dependencies": {
14
- "@alexkroman1/aai": "^5.2.0",
15
- "@alexkroman1/aai-ui": "^5.2.0",
14
+ "@alexkroman1/aai": "^5.4.0",
15
+ "@alexkroman1/aai-ui": "^5.4.0",
16
16
  "react": "^19.2.8",
17
17
  "react-dom": "^19.2.8",
18
18
  "tailwindcss": "^4.0.0",
19
19
  "zod": "^4.4.3"
20
20
  },
21
21
  "devDependencies": {
22
- "@alexkroman1/aai-cli": "^5.2.0",
22
+ "@alexkroman1/aai-cli": "^5.4.0",
23
23
  "@tailwindcss/vite": "^4.3.3",
24
24
  "@types/node": "^26.1.1",
25
25
  "@types/react": "^19.2.17",
@@ -90,17 +90,17 @@ describe("dispatch-center template", () => {
90
90
 
91
91
  test("negative casualty counts are rejected by the parameter schemas", () => {
92
92
  expect(
93
- incidentTriage.parameters?.safeParse({ incidentId: "INC-0001", casualtyUpdate: -5 }).success,
93
+ incidentTriage.inputSchema?.safeParse({ incidentId: "INC-0001", casualtyUpdate: -5 }).success,
94
94
  ).toBe(false);
95
95
  expect(
96
- incidentCreate.parameters?.safeParse({
96
+ incidentCreate.inputSchema?.safeParse({
97
97
  location: "1 First St",
98
98
  description: "fire",
99
99
  estimatedCasualties: -1,
100
100
  }).success,
101
101
  ).toBe(false);
102
102
  expect(
103
- incidentUpdateStatus.parameters?.safeParse({
103
+ incidentUpdateStatus.inputSchema?.safeParse({
104
104
  incidentId: "INC-0001",
105
105
  status: "on_scene",
106
106
  casualtyUpdate: { confirmed: -2 },