@alexkroman1/aai 0.8.7 → 0.9.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 (288) hide show
  1. package/dist/{sdk/_internal_types.d.ts → _internal-types.d.ts} +0 -3
  2. package/dist/_internal-types.js +19 -0
  3. package/dist/{sdk/_mock_ws.d.ts → _mock-ws.d.ts} +16 -5
  4. package/dist/_mock-ws.js +158 -0
  5. package/dist/{sdk/_utils.d.ts → _utils.d.ts} +1 -2
  6. package/dist/_utils.js +8 -0
  7. package/dist/{sdk/builtin_tools.d.ts → builtin-tools.d.ts} +5 -8
  8. package/dist/builtin-tools.js +270 -0
  9. package/dist/{sdk/direct_executor.d.ts → direct-executor.d.ts} +3 -7
  10. package/dist/direct-executor.js +125 -0
  11. package/dist/{sdk/mod.d.ts → index.d.ts} +0 -4
  12. package/dist/index.js +2 -0
  13. package/dist/{sdk/kv.d.ts → kv.d.ts} +23 -20
  14. package/dist/kv.js +99 -0
  15. package/dist/{sdk/protocol.d.ts → protocol.d.ts} +67 -31
  16. package/dist/protocol.js +142 -0
  17. package/dist/runtime.d.ts +18 -0
  18. package/dist/runtime.js +16 -0
  19. package/dist/s2s.d.ts +110 -0
  20. package/dist/s2s.js +242 -0
  21. package/dist/{sdk/server.d.ts → server.d.ts} +3 -23
  22. package/dist/server.js +105 -0
  23. package/dist/{sdk/session.d.ts → session.d.ts} +4 -11
  24. package/dist/session.js +312 -0
  25. package/dist/tsdown.config.d.ts +2 -0
  26. package/dist/{sdk/types.d.ts → types.d.ts} +41 -25
  27. package/dist/types.js +139 -0
  28. package/dist/{sdk/vector.d.ts → vector.d.ts} +14 -15
  29. package/dist/vector.js +56 -0
  30. package/dist/{sdk/worker_entry.d.ts → worker-entry.d.ts} +2 -5
  31. package/dist/worker-entry.js +59 -0
  32. package/dist/{sdk/ws_handler.d.ts → ws-handler.d.ts} +10 -8
  33. package/dist/ws-handler.js +155 -0
  34. package/package.json +66 -149
  35. package/README.md +0 -34
  36. package/dist/aai.js +0 -3
  37. package/dist/cli/tsconfig.tsbuildinfo +0 -1
  38. package/dist/cli.js +0 -2811
  39. package/dist/sdk/_internal_types.d.ts.map +0 -1
  40. package/dist/sdk/_internal_types.js +0 -25
  41. package/dist/sdk/_internal_types.js.map +0 -1
  42. package/dist/sdk/_mock_ws.d.ts.map +0 -1
  43. package/dist/sdk/_mock_ws.js +0 -154
  44. package/dist/sdk/_mock_ws.js.map +0 -1
  45. package/dist/sdk/_render_check.d.ts +0 -10
  46. package/dist/sdk/_render_check.d.ts.map +0 -1
  47. package/dist/sdk/_render_check.js +0 -72
  48. package/dist/sdk/_render_check.js.map +0 -1
  49. package/dist/sdk/_utils.d.ts.map +0 -1
  50. package/dist/sdk/_utils.js +0 -7
  51. package/dist/sdk/_utils.js.map +0 -1
  52. package/dist/sdk/builtin_tools.d.ts.map +0 -1
  53. package/dist/sdk/builtin_tools.js +0 -309
  54. package/dist/sdk/builtin_tools.js.map +0 -1
  55. package/dist/sdk/capnweb.d.ts +0 -102
  56. package/dist/sdk/capnweb.d.ts.map +0 -1
  57. package/dist/sdk/capnweb.js +0 -219
  58. package/dist/sdk/capnweb.js.map +0 -1
  59. package/dist/sdk/define_agent.d.ts +0 -36
  60. package/dist/sdk/define_agent.d.ts.map +0 -1
  61. package/dist/sdk/define_agent.js +0 -71
  62. package/dist/sdk/define_agent.js.map +0 -1
  63. package/dist/sdk/direct_executor.d.ts.map +0 -1
  64. package/dist/sdk/direct_executor.js +0 -145
  65. package/dist/sdk/direct_executor.js.map +0 -1
  66. package/dist/sdk/host.d.ts +0 -59
  67. package/dist/sdk/host.d.ts.map +0 -1
  68. package/dist/sdk/host.js +0 -131
  69. package/dist/sdk/host.js.map +0 -1
  70. package/dist/sdk/kv.d.ts.map +0 -1
  71. package/dist/sdk/kv.js +0 -94
  72. package/dist/sdk/kv.js.map +0 -1
  73. package/dist/sdk/memory_tools.d.ts +0 -38
  74. package/dist/sdk/memory_tools.d.ts.map +0 -1
  75. package/dist/sdk/memory_tools.js +0 -77
  76. package/dist/sdk/memory_tools.js.map +0 -1
  77. package/dist/sdk/mod.d.ts.map +0 -1
  78. package/dist/sdk/mod.js +0 -27
  79. package/dist/sdk/mod.js.map +0 -1
  80. package/dist/sdk/protocol.d.ts.map +0 -1
  81. package/dist/sdk/protocol.js +0 -133
  82. package/dist/sdk/protocol.js.map +0 -1
  83. package/dist/sdk/runtime.d.ts +0 -36
  84. package/dist/sdk/runtime.d.ts.map +0 -1
  85. package/dist/sdk/runtime.js +0 -27
  86. package/dist/sdk/runtime.js.map +0 -1
  87. package/dist/sdk/s2s.d.ts +0 -74
  88. package/dist/sdk/s2s.d.ts.map +0 -1
  89. package/dist/sdk/s2s.js +0 -218
  90. package/dist/sdk/s2s.js.map +0 -1
  91. package/dist/sdk/server.d.ts.map +0 -1
  92. package/dist/sdk/server.js +0 -144
  93. package/dist/sdk/server.js.map +0 -1
  94. package/dist/sdk/session.d.ts.map +0 -1
  95. package/dist/sdk/session.js +0 -303
  96. package/dist/sdk/session.js.map +0 -1
  97. package/dist/sdk/system_prompt.d.ts +0 -6
  98. package/dist/sdk/system_prompt.d.ts.map +0 -1
  99. package/dist/sdk/system_prompt.js +0 -35
  100. package/dist/sdk/system_prompt.js.map +0 -1
  101. package/dist/sdk/tsconfig.tsbuildinfo +0 -1
  102. package/dist/sdk/types.d.ts.map +0 -1
  103. package/dist/sdk/types.js +0 -96
  104. package/dist/sdk/types.js.map +0 -1
  105. package/dist/sdk/vector.d.ts.map +0 -1
  106. package/dist/sdk/vector.js +0 -63
  107. package/dist/sdk/vector.js.map +0 -1
  108. package/dist/sdk/winterc_server.d.ts +0 -56
  109. package/dist/sdk/winterc_server.d.ts.map +0 -1
  110. package/dist/sdk/winterc_server.js +0 -77
  111. package/dist/sdk/winterc_server.js.map +0 -1
  112. package/dist/sdk/worker_entry.d.ts.map +0 -1
  113. package/dist/sdk/worker_entry.js +0 -68
  114. package/dist/sdk/worker_entry.js.map +0 -1
  115. package/dist/sdk/worker_shim.d.ts +0 -19
  116. package/dist/sdk/worker_shim.d.ts.map +0 -1
  117. package/dist/sdk/worker_shim.js +0 -141
  118. package/dist/sdk/worker_shim.js.map +0 -1
  119. package/dist/sdk/ws_handler.d.ts.map +0 -1
  120. package/dist/sdk/ws_handler.js +0 -158
  121. package/dist/sdk/ws_handler.js.map +0 -1
  122. package/dist/ui/_cn.d.ts +0 -5
  123. package/dist/ui/_cn.d.ts.map +0 -1
  124. package/dist/ui/_cn.js +0 -22
  125. package/dist/ui/_cn.js.map +0 -1
  126. package/dist/ui/_components/app.d.ts +0 -5
  127. package/dist/ui/_components/app.d.ts.map +0 -1
  128. package/dist/ui/_components/app.js +0 -12
  129. package/dist/ui/_components/app.js.map +0 -1
  130. package/dist/ui/_components/button.d.ts +0 -11
  131. package/dist/ui/_components/button.d.ts.map +0 -1
  132. package/dist/ui/_components/button.js +0 -17
  133. package/dist/ui/_components/button.js.map +0 -1
  134. package/dist/ui/_components/chat_view.d.ts +0 -5
  135. package/dist/ui/_components/chat_view.d.ts.map +0 -1
  136. package/dist/ui/_components/chat_view.js +0 -15
  137. package/dist/ui/_components/chat_view.js.map +0 -1
  138. package/dist/ui/_components/controls.d.ts +0 -4
  139. package/dist/ui/_components/controls.d.ts.map +0 -1
  140. package/dist/ui/_components/controls.js +0 -10
  141. package/dist/ui/_components/controls.js.map +0 -1
  142. package/dist/ui/_components/error_banner.d.ts +0 -8
  143. package/dist/ui/_components/error_banner.d.ts.map +0 -1
  144. package/dist/ui/_components/error_banner.js +0 -8
  145. package/dist/ui/_components/error_banner.js.map +0 -1
  146. package/dist/ui/_components/message_bubble.d.ts +0 -7
  147. package/dist/ui/_components/message_bubble.d.ts.map +0 -1
  148. package/dist/ui/_components/message_bubble.js +0 -11
  149. package/dist/ui/_components/message_bubble.js.map +0 -1
  150. package/dist/ui/_components/message_list.d.ts +0 -4
  151. package/dist/ui/_components/message_list.d.ts.map +0 -1
  152. package/dist/ui/_components/message_list.js +0 -45
  153. package/dist/ui/_components/message_list.js.map +0 -1
  154. package/dist/ui/_components/sidebar_layout.d.ts +0 -20
  155. package/dist/ui/_components/sidebar_layout.d.ts.map +0 -1
  156. package/dist/ui/_components/sidebar_layout.js +0 -19
  157. package/dist/ui/_components/sidebar_layout.js.map +0 -1
  158. package/dist/ui/_components/start_screen.d.ts +0 -25
  159. package/dist/ui/_components/start_screen.d.ts.map +0 -1
  160. package/dist/ui/_components/start_screen.js +0 -28
  161. package/dist/ui/_components/start_screen.js.map +0 -1
  162. package/dist/ui/_components/state_indicator.d.ts +0 -8
  163. package/dist/ui/_components/state_indicator.d.ts.map +0 -1
  164. package/dist/ui/_components/state_indicator.js +0 -6
  165. package/dist/ui/_components/state_indicator.js.map +0 -1
  166. package/dist/ui/_components/thinking_indicator.d.ts +0 -5
  167. package/dist/ui/_components/thinking_indicator.d.ts.map +0 -1
  168. package/dist/ui/_components/thinking_indicator.js +0 -10
  169. package/dist/ui/_components/thinking_indicator.js.map +0 -1
  170. package/dist/ui/_components/tool_call_block.d.ts +0 -7
  171. package/dist/ui/_components/tool_call_block.d.ts.map +0 -1
  172. package/dist/ui/_components/tool_call_block.js +0 -46
  173. package/dist/ui/_components/tool_call_block.js.map +0 -1
  174. package/dist/ui/_components/tool_icons.d.ts +0 -18
  175. package/dist/ui/_components/tool_icons.d.ts.map +0 -1
  176. package/dist/ui/_components/tool_icons.js +0 -26
  177. package/dist/ui/_components/tool_icons.js.map +0 -1
  178. package/dist/ui/_components/transcript.d.ts +0 -7
  179. package/dist/ui/_components/transcript.d.ts.map +0 -1
  180. package/dist/ui/_components/transcript.js +0 -9
  181. package/dist/ui/_components/transcript.js.map +0 -1
  182. package/dist/ui/_dom_shim.d.ts +0 -7
  183. package/dist/ui/_dom_shim.d.ts.map +0 -1
  184. package/dist/ui/_dom_shim.js +0 -21
  185. package/dist/ui/_dom_shim.js.map +0 -1
  186. package/dist/ui/_hooks.d.ts +0 -21
  187. package/dist/ui/_hooks.d.ts.map +0 -1
  188. package/dist/ui/_hooks.js +0 -35
  189. package/dist/ui/_hooks.js.map +0 -1
  190. package/dist/ui/_jsdom_setup.d.ts +0 -1
  191. package/dist/ui/_jsdom_setup.d.ts.map +0 -1
  192. package/dist/ui/_jsdom_setup.js +0 -6
  193. package/dist/ui/_jsdom_setup.js.map +0 -1
  194. package/dist/ui/_test_utils.js +0 -272
  195. package/dist/ui/_test_utils.js.map +0 -1
  196. package/dist/ui/audio.d.ts +0 -46
  197. package/dist/ui/audio.d.ts.map +0 -1
  198. package/dist/ui/audio.js +0 -130
  199. package/dist/ui/audio.js.map +0 -1
  200. package/dist/ui/components.d.ts +0 -14
  201. package/dist/ui/components.d.ts.map +0 -1
  202. package/dist/ui/components.js +0 -15
  203. package/dist/ui/components.js.map +0 -1
  204. package/dist/ui/components_mod.d.ts +0 -34
  205. package/dist/ui/components_mod.d.ts.map +0 -1
  206. package/dist/ui/components_mod.js +0 -32
  207. package/dist/ui/components_mod.js.map +0 -1
  208. package/dist/ui/mod.d.ts +0 -23
  209. package/dist/ui/mod.d.ts.map +0 -1
  210. package/dist/ui/mod.js +0 -22
  211. package/dist/ui/mod.js.map +0 -1
  212. package/dist/ui/mount.d.ts +0 -44
  213. package/dist/ui/mount.d.ts.map +0 -1
  214. package/dist/ui/mount.js +0 -61
  215. package/dist/ui/mount.js.map +0 -1
  216. package/dist/ui/mount_context.d.ts +0 -22
  217. package/dist/ui/mount_context.d.ts.map +0 -1
  218. package/dist/ui/mount_context.js +0 -10
  219. package/dist/ui/mount_context.js.map +0 -1
  220. package/dist/ui/session.d.ts +0 -96
  221. package/dist/ui/session.d.ts.map +0 -1
  222. package/dist/ui/session.js +0 -379
  223. package/dist/ui/session.js.map +0 -1
  224. package/dist/ui/session_mod.d.ts +0 -19
  225. package/dist/ui/session_mod.d.ts.map +0 -1
  226. package/dist/ui/session_mod.js +0 -18
  227. package/dist/ui/session_mod.js.map +0 -1
  228. package/dist/ui/signals.d.ts +0 -80
  229. package/dist/ui/signals.d.ts.map +0 -1
  230. package/dist/ui/signals.js +0 -137
  231. package/dist/ui/signals.js.map +0 -1
  232. package/dist/ui/tsconfig.tsbuildinfo +0 -1
  233. package/dist/ui/types.d.ts +0 -36
  234. package/dist/ui/types.d.ts.map +0 -1
  235. package/dist/ui/types.js +0 -4
  236. package/dist/ui/types.js.map +0 -1
  237. package/dist/ui/worklets/capture-processor.d.ts +0 -3
  238. package/dist/ui/worklets/capture-processor.d.ts.map +0 -1
  239. package/dist/ui/worklets/capture-processor.js +0 -61
  240. package/dist/ui/worklets/capture-processor.js.map +0 -1
  241. package/dist/ui/worklets/playback-processor.d.ts +0 -3
  242. package/dist/ui/worklets/playback-processor.d.ts.map +0 -1
  243. package/dist/ui/worklets/playback-processor.js +0 -109
  244. package/dist/ui/worklets/playback-processor.js.map +0 -1
  245. package/templates/.env +0 -1
  246. package/templates/_shared/.env.example +0 -5
  247. package/templates/_shared/CLAUDE.md +0 -1073
  248. package/templates/_shared/biome.json +0 -32
  249. package/templates/_shared/global.d.ts +0 -1
  250. package/templates/_shared/index.html +0 -16
  251. package/templates/_shared/package.json +0 -22
  252. package/templates/_shared/tsconfig.json +0 -16
  253. package/templates/code-interpreter/agent.ts +0 -27
  254. package/templates/code-interpreter/client.tsx +0 -3
  255. package/templates/dispatch-center/agent.ts +0 -1223
  256. package/templates/dispatch-center/client.tsx +0 -505
  257. package/templates/embedded-assets/agent.ts +0 -48
  258. package/templates/embedded-assets/client.tsx +0 -3
  259. package/templates/embedded-assets/knowledge.json +0 -20
  260. package/templates/health-assistant/agent.ts +0 -160
  261. package/templates/health-assistant/client.tsx +0 -3
  262. package/templates/infocom-adventure/agent.ts +0 -164
  263. package/templates/infocom-adventure/client.tsx +0 -300
  264. package/templates/math-buddy/agent.ts +0 -21
  265. package/templates/math-buddy/client.tsx +0 -3
  266. package/templates/memory-agent/agent.ts +0 -20
  267. package/templates/memory-agent/client.tsx +0 -3
  268. package/templates/night-owl/agent.ts +0 -98
  269. package/templates/night-owl/client.tsx +0 -12
  270. package/templates/personal-finance/agent.ts +0 -26
  271. package/templates/personal-finance/client.tsx +0 -3
  272. package/templates/pizza-ordering/agent.ts +0 -214
  273. package/templates/pizza-ordering/client.tsx +0 -264
  274. package/templates/simple/agent.ts +0 -6
  275. package/templates/simple/client.tsx +0 -3
  276. package/templates/smart-research/agent.ts +0 -164
  277. package/templates/smart-research/client.tsx +0 -3
  278. package/templates/solo-rpg/agent.ts +0 -1240
  279. package/templates/solo-rpg/client.tsx +0 -698
  280. package/templates/support/README.md +0 -62
  281. package/templates/support/agent.ts +0 -19
  282. package/templates/support/client.tsx +0 -3
  283. package/templates/travel-concierge/agent.ts +0 -29
  284. package/templates/travel-concierge/client.tsx +0 -3
  285. package/templates/tsconfig.json +0 -1
  286. package/templates/web-researcher/agent.ts +0 -17
  287. package/templates/web-researcher/client.tsx +0 -3
  288. package/ui/styles.css +0 -74
@@ -1,1073 +0,0 @@
1
- # aai Voice Agent Project
2
-
3
- You are helping a user build a voice agent using the **aai** framework.
4
-
5
- ## Workflow
6
-
7
- 1. **Understand** — Restate what the user wants to build. If the request is
8
- vague, ask a clarifying question before writing code.
9
- 2. **Check existing work** — Look for a template or built-in tool that already
10
- does what the user needs before writing custom code.
11
- 3. **Start minimal** — Scaffold from the closest template, then layer on
12
- customizations. Don't over-engineer the first version.
13
- 4. **Verify** — After every change, run `aai build` to validate the bundle and
14
- catch errors. Fix all errors before presenting work to the user.
15
- 5. **Iterate** — Make small, focused changes. Verify each change works before
16
- moving on.
17
-
18
- ## Key rules
19
-
20
- - Every agent lives in `agent.ts` and exports a default `defineAgent()` call
21
- - Custom UI goes in `client.tsx` alongside `agent.ts`
22
- - Optimize `instructions` for spoken conversation — short sentences, no visual
23
- formatting, no exclamation points
24
- - Never hardcode secrets — use `aai secret put` and access via `ctx.env`
25
- - Tool `execute` return values go into LLM context — filter and truncate large
26
- API responses
27
- - Agent code runs in a sandboxed worker — use `fetch` (proxied) for HTTP,
28
- `ctx.env` for secrets
29
-
30
- ## CLI commands
31
-
32
- ```sh
33
- aai init # Scaffold a new agent (uses simple template)
34
- aai init -t <template> # Scaffold from a specific template
35
- aai dev # Start local dev server
36
- aai build # Bundle and validate (no server or deploy)
37
- aai deploy # Bundle and deploy to production
38
- aai deploy -y # Deploy without prompts
39
- aai deploy --dry-run # Validate and bundle without deploying
40
- aai secret put <NAME> # Set a secret on the server (prompts for value)
41
- aai secret delete <NAME> # Remove a secret
42
- aai secret list # List secret names
43
- aai secret pull # Pull secret names into .env for local dev
44
- aai rag <url> # Ingest a site's llms-full.txt into the vector store
45
- ```
46
-
47
- ## Templates
48
-
49
- Before writing an agent from scratch, choose the closest template and scaffold
50
- with `aai init -t <template_name>`.
51
-
52
- | Template | Description |
53
- | ------------------- | ---------------------------------------------------------------------------------- |
54
- | `simple` | Minimal starter with web_search, visit_webpage, fetch_json, run_code. **Default.** |
55
- | `web-researcher` | Research assistant — web search + page visits for detailed answers |
56
- | `smart-research` | Phase-based research (gather → analyze → respond) with dynamic tool filtering |
57
- | `memory-agent` | Persistent KV storage — remembers facts and preferences across conversations |
58
- | `code-interpreter` | Writes and runs JavaScript for math, calculations, data processing |
59
- | `math-buddy` | Calculations, unit conversions, dice rolls via run_code |
60
- | `health-assistant` | Medication lookup, drug interactions, BMI, symptom guidance |
61
- | `personal-finance` | Currency conversion, crypto prices, loan calculations, savings projections |
62
- | `travel-concierge` | Trip planning, weather, flights, hotels, currency conversion |
63
- | `night-owl` | Movie/music/book recs by mood, sleep calculator. **Has custom UI.** |
64
- | `pizza-ordering` | Pizza order-taker with dynamic cart sidebar. **Has custom UI.** |
65
- | `dispatch-center` | 911 dispatch with incident triage and resource assignment. **Has custom UI.** |
66
- | `infocom-adventure` | Zork-style text adventure with state, puzzles, inventory. **Has custom UI.** |
67
- | `solo-rpg` | Solo dark-fantasy RPG with dice, oaths, combat, save/load. **Has custom UI.** |
68
- | `embedded-assets` | FAQ bot using embedded JSON knowledge (no web search) |
69
- | `support` | RAG-powered support agent using vector_search (AssemblyAI docs example) |
70
-
71
- ## Minimal agent
72
-
73
- Every agent lives in `agent.ts` and exports a default `defineAgent()` call:
74
-
75
- ```ts
76
- import { defineAgent } from "aai";
77
-
78
- export default defineAgent({
79
- name: "My Agent",
80
- instructions: "You are a helpful assistant that...",
81
- greeting: "Hey there. What can I help you with?",
82
- });
83
- ```
84
-
85
- ### Imports
86
-
87
- ```ts
88
- import { defineAgent, memoryTools, tool } from "aai"; // defineAgent + helpers
89
- import type { BeforeStepResult, BuiltinTool, HookContext, StepInfo, ToolContext } from "aai";
90
- import { z } from "zod"; // Tools with typed params (included in package.json)
91
- ```
92
-
93
- ## Agent configuration
94
-
95
- ```ts
96
- defineAgent({
97
- // Core
98
- name: string; // Required: display name
99
- instructions?: string; // System prompt (default: general voice assistant)
100
- greeting?: string; // Spoken on connect (default: "Hey, how can I help you?")
101
- // Speech
102
- sttPrompt?: string; // STT guidance for jargon, names, acronyms
103
-
104
- // Tools
105
- builtinTools?: BuiltinTool[];
106
- tools?: Record<string, ToolDef>;
107
- toolChoice?: ToolChoice; // "auto" | "required" | "none" | { type: "tool", toolName }
108
- activeTools?: string[]; // Default active tools per turn (subset of all tools)
109
- maxSteps?: number | ((ctx: HookContext) => number);
110
-
111
- // State
112
- state?: () => S; // Factory for per-session state
113
-
114
- // Lifecycle hooks
115
- onConnect?: (ctx: HookContext) => void | Promise<void>;
116
- onDisconnect?: (ctx: HookContext) => void | Promise<void>;
117
- onError?: (error: Error, ctx?: HookContext) => void;
118
- onTurn?: (text: string, ctx: HookContext) => void | Promise<void>;
119
- onStep?: (step: StepInfo, ctx: HookContext) => void | Promise<void>;
120
- onBeforeStep?: (stepNumber: number, ctx: HookContext) =>
121
- BeforeStepResult | Promise<BeforeStepResult>;
122
- });
123
- ```
124
-
125
- Use `sttPrompt` for domain-specific vocabulary:
126
-
127
- ```ts
128
- export default defineAgent({
129
- name: "Tech Support",
130
- sttPrompt: "Transcribe technical terms: Kubernetes, gRPC, PostgreSQL",
131
- });
132
- ```
133
-
134
- ### Writing good `instructions`
135
-
136
- Optimize for spoken conversation:
137
-
138
- - Short, punchy sentences — optimize for speech, not text
139
- - Never mention "search results" or "sources" — speak as if knowledge is your
140
- own
141
- - No visual formatting ("bullet point", "bold") — use "First", "Next", "Finally"
142
- - Lead with the most important information
143
- - Be concise and confident — no hedging ("It seems that", "I believe")
144
- - No exclamation points — calm, conversational tone
145
- - Define personality, tone, and specialty
146
- - Include when and how to use each tool
147
-
148
- ### Environment variables
149
-
150
- Secrets are managed on the server via the CLI, like `vercel env`. They are
151
- injected into agent workers at runtime and available as `ctx.env`. Secrets are
152
- **never** embedded in the bundled code.
153
-
154
- ```sh
155
- # Set secrets on the server (prompts for value)
156
- aai secret put ASSEMBLYAI_API_KEY
157
- aai secret put MY_API_KEY
158
-
159
- # List what's set
160
- aai secret list
161
-
162
- # Pull secret names into .env for local dev reference
163
- aai secret pull
164
-
165
- # Remove a secret
166
- aai secret delete MY_API_KEY
167
- ```
168
-
169
- Access secrets in tool code via `ctx.env`:
170
-
171
- ```ts
172
- import { defineAgent } from "aai";
173
- import { z } from "zod";
174
-
175
- export default defineAgent({
176
- name: "API Agent",
177
- tools: {
178
- call_api: {
179
- description: "Call an external API",
180
- parameters: z.object({ query: z.string() }),
181
- execute: async (args, ctx) => {
182
- const res = await fetch(`https://api.example.com?q=${args.query}`, {
183
- headers: { Authorization: `Bearer ${ctx.env.MY_API_KEY}` },
184
- });
185
- return res.json();
186
- },
187
- },
188
- },
189
- });
190
- ```
191
-
192
- ## Tools
193
-
194
- ### Custom tools
195
-
196
- Define tools as plain objects in the `tools` record. The `parameters` field
197
- takes a Zod schema for type-safe argument inference:
198
-
199
- ```ts
200
- import { defineAgent, tool } from "aai";
201
- import { z } from "zod";
202
-
203
- export default defineAgent({
204
- name: "Weather Agent",
205
- tools: {
206
- get_weather: tool({
207
- description: "Get current weather for a city",
208
- parameters: z.object({
209
- city: z.string().describe("City name"),
210
- }),
211
- execute: async ({ city }, ctx) => {
212
- // city is typed as string (inferred from Zod schema)
213
- const data = await fetch(
214
- `https://api.example.com/weather?q=${city}`,
215
- );
216
- return data.json();
217
- },
218
- }),
219
-
220
- // No-parameter tools — omit `parameters` and `tool()` wrapper
221
- list_items: {
222
- description: "List all items",
223
- execute: () => items,
224
- },
225
- },
226
- });
227
- ```
228
-
229
- **Important:** Wrap tool definitions in `tool()` to get typed `args` inferred
230
- from the Zod `parameters` schema. Without `tool()`, args are untyped.
231
-
232
- Zod schema patterns:
233
-
234
- ```ts
235
- parameters: z.object({
236
- query: z.string().describe("Search query"),
237
- category: z.enum(["a", "b", "c"]),
238
- count: z.number().describe("How many"),
239
- label: z.string().describe("Optional label").optional(),
240
- }),
241
- ```
242
-
243
- ### Built-in tools
244
-
245
- Enable via `builtinTools`.
246
-
247
- | Tool | Description | Params |
248
- | --------------- | ---------------------------------------------- | ----------------------------------- |
249
- | `web_search` | Search the web (Brave Search) | `query`, `max_results?` (default 5) |
250
- | `visit_webpage` | Fetch URL → Markdown | `url` |
251
- | `fetch_json` | HTTP GET a JSON API | `url`, `headers?` |
252
- | `run_code` | Execute JS in sandbox (no net/fs, 5s timeout) | `code` |
253
- | `vector_search` | Search the agent's RAG knowledge base | `query`, `topK?` (default 5) |
254
- | `memory` | Persistent KV memory (4 tools, see below) | — |
255
-
256
- The agentic loop runs up to `maxSteps` iterations (default 5) and stops when the
257
- LLM produces a text response.
258
-
259
- ### Tool context
260
-
261
- Every `execute` function and lifecycle hook receives a context object:
262
-
263
- ```ts
264
- ctx.env; // Record<string, string> — secrets from `aai secret put`
265
- ctx.abortSignal; // AbortSignal — cancelled on interruption (tools only)
266
- ctx.state; // per-session state
267
- ctx.kv; // persistent KV store
268
- ctx.vector; // VectorStore — vector store for RAG (tools only)
269
- ctx.messages; // readonly Message[] — conversation history (tools only)
270
- ```
271
-
272
- Hooks get `HookContext` (same but without `abortSignal` and `messages`).
273
-
274
- **Timeouts:** Tool execution times out after **30 seconds** (`abortSignal`
275
- fires). Lifecycle hooks (`onConnect`, `onTurn`, etc.) time out after **5
276
- seconds**. Long-running tools should pass `ctx.abortSignal` to `fetch` and
277
- check `ctx.abortSignal.aborted` in loops.
278
-
279
- ### Fetching external APIs
280
-
281
- Use `fetch` directly in tool execute functions:
282
-
283
- ```ts
284
- execute: async (args, ctx) => {
285
- const resp = await fetch(url, {
286
- headers: { Authorization: `Bearer ${ctx.env.API_KEY}` },
287
- signal: ctx.abortSignal, // Respect interruptions
288
- });
289
- if (!resp.ok) return { error: `${resp.status} ${resp.statusText}` };
290
- return resp.json();
291
- },
292
- ```
293
-
294
- `fetch` is proxied through the host process (the worker has no direct network
295
- access). All URLs are validated against SSRF rules — only public addresses are
296
- allowed.
297
-
298
- ## State and storage
299
-
300
- ### Per-session state
301
-
302
- For data that lasts only one connection (games, workflows, multi-step
303
- processes). Fresh state is created per session and cleaned up on disconnect:
304
-
305
- ```ts
306
- export default defineAgent({
307
- state: () => ({ score: 0, question: 0 }),
308
- tools: {
309
- answer: {
310
- description: "Submit an answer",
311
- parameters: z.object({ answer: z.string() }),
312
- execute: (args, ctx) => {
313
- const state = ctx.state as { score: number; question: number };
314
- state.question++;
315
- return state;
316
- },
317
- },
318
- },
319
- });
320
- ```
321
-
322
- ### Persisting state across reconnects
323
-
324
- Use the KV store to auto-save and auto-load state:
325
-
326
- ```ts
327
- export default defineAgent({
328
- state: () => ({ score: 0, initialized: false }),
329
- onConnect: async (ctx) => {
330
- const saved = await ctx.kv.get("save:game");
331
- if (saved) Object.assign(ctx.state, saved);
332
- },
333
- onTurn: async (_text, ctx) => {
334
- await ctx.kv.set("save:game", ctx.state);
335
- },
336
- });
337
- ```
338
-
339
- This works for games, workflows,
340
- or any agent where users expect to resume where they left off.
341
-
342
- ### Persistent storage (KV)
343
-
344
- `ctx.kv` is a persistent key-value store scoped per agent. Values are
345
- auto-serialized as JSON.
346
-
347
- ```ts
348
- await ctx.kv.set("user:123", { name: "Alice" }); // save
349
- await ctx.kv.set("temp:x", value, { expireIn: 60_000 }); // save with TTL (ms)
350
- const user = await ctx.kv.get<User>("user:123"); // read (or null)
351
- const notes = await ctx.kv.list("note:", { limit: 10, reverse: true }); // list by prefix
352
- await ctx.kv.delete("user:123"); // delete
353
- ```
354
-
355
- Keys are strings; use colon-separated prefixes (`"user:123"`). Max value: 64 KB.
356
-
357
- `kv.list()` returns `KvEntry[]` where each entry has
358
- `{ key: string, value: T }`.
359
-
360
- ### Memory tools (pre-built KV tools)
361
-
362
- Add `"memory"` to `builtinTools` to give the agent four persistent KV tools:
363
- `save_memory`, `recall_memory`, `list_memories`, and `forget_memory`.
364
-
365
- ```ts
366
- import { defineAgent } from "aai";
367
-
368
- export default defineAgent({
369
- name: "My Agent",
370
- builtinTools: ["memory"],
371
- });
372
- ```
373
-
374
- Keys use colon-separated prefixes (`"user:name"`, `"preference:color"`).
375
-
376
- You can also spread `memoryTools()` into `tools` if you want to combine them
377
- with custom tools or override individual tools:
378
-
379
- ```ts
380
- import { defineAgent, memoryTools } from "aai";
381
-
382
- export default defineAgent({
383
- name: "My Agent",
384
- tools: {
385
- ...memoryTools(),
386
- // your other tools...
387
- },
388
- });
389
- ```
390
-
391
- ## Advanced patterns
392
-
393
- ### Step hooks
394
-
395
- `onStep` — called after each LLM step (logging, analytics):
396
-
397
- ```ts
398
- onStep: (step, ctx) => {
399
- console.log(`Step ${step.stepNumber}: ${step.toolCalls.length} tool calls`);
400
- },
401
- ```
402
-
403
- `onBeforeStep` — return `{ activeTools: [...] }` to filter tools per step:
404
-
405
- ```ts
406
- state: () => ({ phase: "gather" }),
407
- onBeforeStep: (stepNumber, ctx) => {
408
- const state = ctx.state as { phase: string };
409
- if (state.phase === "gather") {
410
- return { activeTools: ["search", "lookup"] };
411
- }
412
- return { activeTools: ["summarize"] };
413
- },
414
- ```
415
-
416
- ### Tool choice
417
-
418
- Control when the LLM uses tools:
419
-
420
- ```ts
421
- toolChoice: "auto", // Default — LLM decides when to use tools
422
- toolChoice: "required", // Force a tool call every step (useful for research pipelines)
423
- toolChoice: "none", // Disable all tool use
424
- toolChoice: { type: "tool", toolName: "search" }, // Force a specific tool
425
- ```
426
-
427
- ### Phase-based tool filtering
428
-
429
- Combine `state`, `onBeforeStep`, and `activeTools` for multi-phase workflows:
430
-
431
- ```ts
432
- state: () => ({ phase: "gather" as "gather" | "analyze" | "respond" }),
433
- onBeforeStep: (_step, ctx) => {
434
- const state = ctx.state as { phase: string };
435
- if (state.phase === "gather") return { activeTools: ["web_search", "advance"] };
436
- if (state.phase === "analyze") return { activeTools: ["summarize", "advance"] };
437
- return { activeTools: [] }; // respond phase — LLM speaks freely
438
- },
439
- tools: {
440
- advance: {
441
- description: "Move to the next phase",
442
- execute: (_args, ctx) => {
443
- const state = ctx.state as { phase: string };
444
- if (state.phase === "gather") state.phase = "analyze";
445
- else if (state.phase === "analyze") state.phase = "respond";
446
- return { phase: state.phase };
447
- },
448
- },
449
- },
450
- ```
451
-
452
- ### Static `activeTools`
453
-
454
- Restrict which tools the LLM can use by default, without writing a hook:
455
-
456
- ```ts
457
- export default defineAgent({
458
- builtinTools: ["web_search", "visit_webpage", "run_code"],
459
- tools: { summarize: {/* ... */} },
460
- activeTools: ["web_search", "summarize"], // Only these two are available
461
- });
462
- ```
463
-
464
- Use `onBeforeStep` to override `activeTools` dynamically per step.
465
-
466
- ### Dynamic `maxSteps`
467
-
468
- ```ts
469
- maxSteps: (ctx) => {
470
- const state = ctx.state as { complexity: string };
471
- return state.complexity === "complex" ? 10 : 5;
472
- },
473
- ```
474
-
475
- ### Conversation history in tools
476
-
477
- ```ts
478
- execute: (args, ctx) => {
479
- const userMessages = ctx.messages.filter(m => m.role === "user");
480
- return { turns: userMessages.length };
481
- },
482
- ```
483
-
484
- ### Embedded knowledge
485
-
486
- ```ts
487
- import knowledge from "./knowledge.json" with { type: "json" };
488
-
489
- export default defineAgent({
490
- tools: {
491
- search_faq: {
492
- description: "Search the knowledge base",
493
- parameters: z.object({ query: z.string() }),
494
- execute: (args) =>
495
- knowledge.faqs.filter((f: { question: string }) =>
496
- f.question.toLowerCase().includes(args.query.toLowerCase())
497
- ),
498
- },
499
- },
500
- });
501
- ```
502
-
503
- ### Using npm packages
504
-
505
- Add packages to `package.json` dependencies:
506
-
507
- ```sh
508
- npm install some-package
509
- ```
510
-
511
- ## Custom UI (`client.tsx`)
512
-
513
- Add `client.tsx` alongside `agent.ts`. Define a Preact component and call
514
- `mount()` to render it. Use JSX syntax:
515
-
516
- ```tsx
517
- import "aai/ui/styles.css";
518
- import { mount, useSession } from "aai/ui";
519
-
520
- function App() {
521
- const { session, started, running, start, toggle, reset } = useSession();
522
- const msgs = session.messages.value;
523
- const tx = session.userUtterance.value;
524
- return (
525
- <div>
526
- {msgs.map((m, i) => <p key={i}>{m.text}</p>)}
527
- {tx !== null && <p>{tx || "..."}</p>}
528
- {!started.value ? <button onClick={start}>Start</button> : (
529
- <>
530
- <button onClick={toggle}>{running.value ? "Stop" : "Resume"}</button>
531
- <button onClick={reset}>Reset</button>
532
- </>
533
- )}
534
- </div>
535
- );
536
- }
537
-
538
- mount(App);
539
- ```
540
-
541
- **Rules:**
542
-
543
- - Always import `"aai/ui/styles.css"` at the top — without it, default styles
544
- won't load
545
- - Call `mount(YourComponent)` at the end of the file
546
- - Use `.tsx` file extension for JSX syntax
547
- - Import hooks from `preact/hooks` (`useEffect`, `useRef`, `useState`, etc.)
548
- - Style with `style={{ color: "red" }}` or inject `<style>` for selectors,
549
- keyframes, media queries
550
-
551
- ### `mount()` options
552
-
553
- ```ts
554
- mount(App, {
555
- target: "#app", // CSS selector or DOM element (default: "#app")
556
- platformUrl: "...", // Server URL (auto-derived from location.href)
557
- title: "My Agent", // Shown in header and start screen
558
- theme: { // CSS custom property overrides
559
- bg: "#101010", // Background color
560
- primary: "#fab283", // Accent color
561
- text: "#ffffff", // Text color
562
- surface: "#1a1a1a", // Card/surface color
563
- border: "#333333", // Border color
564
- },
565
- });
566
- ```
567
-
568
- `mount()` returns a `MountHandle` with `session`, `signals`, and `dispose()`.
569
-
570
- ### Built-in components
571
-
572
- Import from `aai/ui`:
573
-
574
- **Layout components:**
575
-
576
- | Component | Description |
577
- | --------------- | ---------------------------------------------------- |
578
- | `App` | Default full UI (StartScreen + ChatView) |
579
- | `StartScreen` | Centered start card; renders children after start |
580
- | `ChatView` | Chat interface (header + messages + controls) |
581
- | `SidebarLayout` | Two-column layout with sidebar + main area |
582
- | `Controls` | Stop/Resume + New Conversation buttons |
583
- | `MessageList` | Messages with auto-scroll, tool calls, transcript |
584
-
585
- `StartScreen` props: `{ children, icon?, title?, subtitle?, buttonText? }`
586
- `SidebarLayout` props: `{ sidebar, children, width?, side? }`
587
-
588
- **Atomic components:**
589
-
590
- | Component | Props | Description |
591
- | ------------------- | --------------------------------------- | ------------------------------- |
592
- | `MessageBubble` | `{ message: Message }` | Single message bubble |
593
- | `Transcript` | `{ userUtterance: Signal<str\|null> }` | Live STT text display |
594
- | `StateIndicator` | `{ state: Signal<AgentState> }` | Colored dot + state label |
595
- | `ErrorBanner` | `{ error: Signal<SessionError\|null> }` | Red error box with message |
596
- | `ThinkingIndicator` | none | Animated dots during processing |
597
- | `ToolCallBlock` | `{ toolCall: ToolCallInfo }` | Collapsible tool call display |
598
-
599
- **Hooks:**
600
-
601
- - `useAutoScroll()` — returns a `RefObject<HTMLDivElement>` to attach to a
602
- sentinel div. Auto-scrolls when messages or utterances change.
603
- - `useMountConfig()` — returns the `title` and `theme` passed to `mount()`.
604
-
605
- **Important:** Components that accept `Signal<T>` props (like `StateIndicator`,
606
- `Transcript`, `ErrorBanner`) expect the Signal object itself, NOT `.value`. Pass
607
- `session.state`, not `session.state.value`. Passing `.value` compiles but breaks
608
- reactivity silently.
609
-
610
- ### Session signals (`useSession()`)
611
-
612
- `useSession()` returns
613
- `{ session, started, running, start, toggle, reset, dispose }`. Reactive agent
614
- data lives on `session` (a `VoiceSession`); UI-only controls are top-level.
615
-
616
- | Signal / field | Type | Description |
617
- | ------------------------------ | ---------------------- | --------------------------------------------------------------- |
618
- | `session.state.value` | `AgentState` | "disconnected", "connecting", "ready", "listening", etc. |
619
- | `session.messages.value` | `Message[]` | `{ role, text }` objects |
620
- | `session.toolCalls.value` | `ToolCallInfo[]` | `{ toolCallId, toolName, args, status, result? }` — tool calls |
621
- | `session.userUtterance.value` | `string \| null` | `null` = not speaking, `""` = speech detected, string = text |
622
- | `session.agentUtterance.value` | `string \| null` | `null` = not speaking, string = streaming agent response text |
623
- | `session.error.value` | `SessionError \| null` | `{ code, message }` |
624
- | `session.disconnected.value` | `object \| null` | `{ intentional: boolean }` when disconnected, `null` otherwise |
625
- | `started.value` | `boolean` | Whether session has been started |
626
- | `running.value` | `boolean` | Whether session is active |
627
-
628
- **Methods:** `start()`, `toggle()`, `reset()`, `dispose()`
629
-
630
- **Hooks:**
631
-
632
- | Hook | Description |
633
- | --------------------------------------------- | -------------------------------------------------------------------------------------------- |
634
- | `useToolResult((toolName, result, tc) => {})` | Fires once per completed tool call with parsed JSON result. Use for carts, scoreboards, etc. |
635
-
636
- **Signal semantics for utterances:**
637
-
638
- - `userUtterance`: `null` = user is not speaking, `""` = speech detected but
639
- no text yet (show "..."), non-empty string = partial/final transcript
640
- - `agentUtterance`: `null` = agent is not speaking, non-empty string =
641
- streaming response text (cleared when final `chat` message arrives)
642
- - `disconnected`: `null` = connected, `{ intentional: true }` = user
643
- disconnected, `{ intentional: false }` = unexpected disconnect (show
644
- reconnect UI)
645
-
646
- **UI Message type:** `{ role: "user" | "assistant"; text: string }`. Note: UI
647
- messages use `text` (not `content`). The SDK `Message` type uses `content` — do
648
- not mix them up in custom UIs.
649
-
650
- ### Showing tool calls in custom UI
651
-
652
- ```tsx
653
- import "aai/ui/styles.css";
654
- import { mount, ToolCallBlock, useSession } from "aai/ui";
655
-
656
- function App() {
657
- const { session, started, start } = useSession();
658
- if (!started.value) return <button onClick={start}>Start</button>;
659
-
660
- const msgs = session.messages.value;
661
- const toolCalls = session.toolCalls.value;
662
-
663
- return (
664
- <div>
665
- {msgs.map((m, i) => (
666
- <div key={i}>
667
- <p>{m.text}</p>
668
- {toolCalls
669
- .filter((tc) => tc.afterMessageIndex === i)
670
- .map((tc) => <ToolCallBlock key={tc.toolCallId} toolCall={tc} />)}
671
- </div>
672
- ))}
673
- </div>
674
- );
675
- }
676
-
677
- mount(App);
678
- ```
679
-
680
- ### Building dynamic UI from tool results
681
-
682
- Use `useToolResult` to update local state (carts, scoreboards, dashboards)
683
- whenever a tool completes. It fires exactly once per completed tool call with
684
- the parsed JSON result, handling deduplication internally.
685
-
686
- ```tsx
687
- import "aai/ui/styles.css";
688
- import { useState } from "preact/hooks";
689
- import { ChatView, SidebarLayout, StartScreen, mount, useToolResult } from "aai/ui";
690
-
691
- interface CartItem { id: number; name: string; price: number }
692
-
693
- function ShopAgent() {
694
- const [cart, setCart] = useState<CartItem[]>([]);
695
-
696
- useToolResult((toolName, result: any) => {
697
- switch (toolName) {
698
- case "add_item":
699
- setCart((prev) => [...prev, result.item]);
700
- break;
701
- case "remove_item":
702
- setCart((prev) => prev.filter((i) => i.id !== result.removedId));
703
- break;
704
- case "clear_cart":
705
- setCart([]);
706
- break;
707
- }
708
- });
709
-
710
- const sidebar = (
711
- <div class="p-4">
712
- <h3 class="text-aai-text font-bold">Cart ({cart.length})</h3>
713
- {cart.map((i) => <p key={i.id} class="text-aai-text text-sm">{i.name} — ${i.price}</p>)}
714
- </div>
715
- );
716
-
717
- return (
718
- <StartScreen title="Shop" buttonText="Start Shopping">
719
- <SidebarLayout sidebar={sidebar}>
720
- <ChatView />
721
- </SidebarLayout>
722
- </StartScreen>
723
- );
724
- }
725
-
726
- mount(ShopAgent);
727
- ```
728
-
729
- **Do NOT use `useEffect` + `session.toolCalls.value` to build derived state.**
730
- That pattern re-processes every tool call on every signal change, causing
731
- duplicates (e.g. items added to the cart multiple times). `useToolResult`
732
- handles this correctly.
733
-
734
- ### Reacting to agent state
735
-
736
- ```tsx
737
- import "aai/ui/styles.css";
738
- import { useEffect } from "preact/hooks";
739
- import { mount, StateIndicator, useSession } from "aai/ui";
740
-
741
- function App() {
742
- const { session, started, start } = useSession();
743
-
744
- useEffect(() => {
745
- // Run side effects when state changes
746
- if (session.state.value === "speaking") {
747
- // Agent is speaking — e.g., show animation
748
- }
749
- }, [session.state.value]);
750
-
751
- return (
752
- <div>
753
- <StateIndicator />
754
- {!started.value && <button onClick={start}>Start</button>}
755
- </div>
756
- );
757
- }
758
-
759
- mount(App);
760
- ```
761
-
762
- ### Styling custom UIs
763
-
764
- The framework uses **Tailwind CSS v4** (compiled at bundle time). Prefer
765
- Tailwind classes over inline styles — all design tokens work as classes:
766
- `bg-aai-surface` not `style={{ background: "var(--color-aai-surface)" }}`,
767
- `border-t border-aai-border` not `style={{ borderTop: "1px solid var(--)" }}`.
768
-
769
- Three approaches:
770
-
771
- 1. **Tailwind classes** — `class="flex items-center gap-2 bg-aai-surface"`
772
- 2. **Inline styles** — only for dynamic values (`style={{ width: pixels }}`)
773
- 3. **Injected `<style>` tags** — for keyframes, selectors, media queries:
774
-
775
- ```tsx
776
- function App() {
777
- return (
778
- <>
779
- <style>
780
- {`
781
- @keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: 0.5 } }
782
- .pulse { animation: pulse 2s ease-in-out infinite; }
783
- @media (max-width: 640px) { .sidebar { display: none; } }
784
- `}
785
- </style>
786
- <div class="pulse">Content</div>
787
- </>
788
- );
789
- }
790
- ```
791
-
792
- **Design tokens** — available as CSS custom properties and Tailwind classes
793
- (e.g. `bg-aai-bg`, `text-aai-text-muted`, `rounded-aai`, `font-aai`):
794
-
795
- | Token | Tailwind class | Default |
796
- | ---------------------------- | ------------------------- | ------------------------- |
797
- | `--color-aai-bg` | `bg-aai-bg` | `#101010` |
798
- | `--color-aai-surface` | `bg-aai-surface` | `#151515` |
799
- | `--color-aai-surface-faint` | `bg-aai-surface-faint` | `rgba(255,255,255,0.031)` |
800
- | `--color-aai-surface-hover` | `bg-aai-surface-hover` | `rgba(255,255,255,0.059)` |
801
- | `--color-aai-border` | `border-aai-border` | `#282828` |
802
- | `--color-aai-primary` | `text-aai-primary` | `#fab283` |
803
- | `--color-aai-text` | `text-aai-text` | `rgba(255,255,255,0.936)` |
804
- | `--color-aai-text-secondary` | `text-aai-text-secondary` | `rgba(255,255,255,0.618)` |
805
- | `--color-aai-text-muted` | `text-aai-text-muted` | `rgba(255,255,255,0.284)` |
806
- | `--color-aai-text-dim` | `text-aai-text-dim` | `rgba(255,255,255,0.422)` |
807
- | `--color-aai-error` | `text-aai-error` | `#e06c75` |
808
- | `--color-aai-ring` | `ring-aai-ring` | `#56b6c2` |
809
- | `--color-aai-state-{state}` | `text-aai-state-{state}` | per-state colors |
810
- | `--radius-aai` | `rounded-aai` | `6px` |
811
- | `--font-aai` | `font-aai` | Inter, sans-serif |
812
- | `--font-aai-mono` | `font-aai-mono` | IBM Plex Mono, mono |
813
-
814
- The 5 core colors (`bg`, `primary`, `text`, `surface`, `border`) can be
815
- overridden via `mount()` theme options. All other tokens use fixed defaults.
816
-
817
- ### Common UI patterns
818
-
819
- **Auto-scrolling messages** — use `useAutoScroll` for custom message lists:
820
-
821
- ```tsx
822
- import { useAutoScroll, useSession } from "aai/ui";
823
-
824
- function MyChat() {
825
- const { session } = useSession();
826
- const bottomRef = useAutoScroll();
827
-
828
- return (
829
- <div class="overflow-y-auto">
830
- {session.messages.value.map((m, i) => <p key={i}>{m.text}</p>)}
831
- <div ref={bottomRef} />
832
- </div>
833
- );
834
- }
835
- ```
836
-
837
- Note: `MessageList` and `ChatView` already include auto-scroll. Only use
838
- `useAutoScroll` when building a fully custom message list.
839
-
840
- **Reading signal values in render:** Extract `.value` once at the top of the
841
- component to avoid redundant signal subscriptions:
842
-
843
- ```tsx
844
- function MyComponent() {
845
- const { session } = useSession();
846
- const state = session.state.value;
847
- const msgs = session.messages.value;
848
- // Use `state` and `msgs` as plain values throughout the render
849
- }
850
- ```
851
-
852
- ## Self-hosting with `createServer()`
853
-
854
- Agents can run anywhere (Node, Deno, Docker) without the managed platform:
855
-
856
- ```ts
857
- import { defineAgent } from "aai";
858
- import { createServer } from "aai/server";
859
-
860
- const agent = defineAgent({
861
- name: "My Agent",
862
- instructions: "You are a helpful assistant.",
863
- });
864
-
865
- const server = createServer({
866
- agent,
867
- clientDir: "public", // optional: serve static files
868
- });
869
-
870
- await server.listen(3000);
871
- ```
872
-
873
- Run with `node --experimental-strip-types server.ts` or bundle with your
874
- preferred tool. The server handles WebSocket connections, STT/TTS, and the
875
- agentic loop. Set `ASSEMBLYAI_API_KEY` as an environment variable.
876
-
877
- ## Useful free API endpoints
878
-
879
- These public APIs require no auth and work well in voice agents:
880
-
881
- ```text
882
- Weather (Open-Meteo):
883
- Geocode: https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1&language=en
884
- Forecast: https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,weathercode&timezone=auto&forecast_days=7
885
-
886
- Currency (ExchangeRate):
887
- Rates: https://open.er-api.com/v6/latest/{CODE} → { rates: { USD: 1.0, EUR: 0.85, ... } }
888
-
889
- Crypto (CoinGecko):
890
- Price: https://api.coingecko.com/api/v3/simple/price?ids={coin}&vs_currencies={cur}&include_24hr_change=true
891
-
892
- Drug info (FDA):
893
- Label: https://api.fda.gov/drug/label.json?search=openfda.generic_name:"{name}"&limit=1
894
-
895
- Drug interactions (RxNorm):
896
- RxCUI: https://rxnav.nlm.nih.gov/REST/rxcui.json?name={name}
897
- Interactions: https://rxnav.nlm.nih.gov/REST/interaction/list.json?rxcuis={id1}+{id2}
898
- ```
899
-
900
- Use `fetch_json` builtin tool or `fetch` in custom tools to call these.
901
-
902
- ## Custom start screen with `StartScreen`
903
-
904
- Use `StartScreen` for a branded start card that transitions to `ChatView`:
905
-
906
- ```tsx
907
- import "aai/ui/styles.css";
908
- import { ChatView, StartScreen, mount } from "aai/ui";
909
-
910
- function MyAgent() {
911
- return (
912
- <StartScreen icon={<span>&#x1F3A4;</span>} title="My Agent" subtitle="Ask me anything">
913
- <ChatView />
914
- </StartScreen>
915
- );
916
- }
917
-
918
- mount(MyAgent);
919
- ```
920
-
921
- `StartScreen` handles the started/not-started transition automatically. Pass
922
- `icon`, `title`, `subtitle`, and `buttonText` to customize the card.
923
-
924
- ## Sidebar layout with `SidebarLayout`
925
-
926
- Use `SidebarLayout` for apps with a persistent side panel (cart, dashboard):
927
-
928
- ```tsx
929
- import "aai/ui/styles.css";
930
- import { useState } from "preact/hooks";
931
- import { ChatView, SidebarLayout, StartScreen, mount, useToolResult } from "aai/ui";
932
-
933
- function ShopAgent() {
934
- const [cart, setCart] = useState<{ id: number; name: string }[]>([]);
935
-
936
- useToolResult((toolName, result: any) => {
937
- if (toolName === "add_item") setCart((prev) => [...prev, result.item]);
938
- });
939
-
940
- const sidebar = (
941
- <div class="p-4">
942
- <h3 class="text-aai-text font-bold">Cart ({cart.length})</h3>
943
- {cart.map((i) => <p key={i.id} class="text-aai-text text-sm">{i.name}</p>)}
944
- </div>
945
- );
946
-
947
- return (
948
- <StartScreen title="Shop" buttonText="Start Shopping">
949
- <SidebarLayout sidebar={sidebar}>
950
- <ChatView />
951
- </SidebarLayout>
952
- </StartScreen>
953
- );
954
- }
955
-
956
- mount(ShopAgent);
957
- ```
958
-
959
- `SidebarLayout` accepts `width` (default `"20rem"`) and `side` (`"left"` or
960
- `"right"`).
961
-
962
- ## Project structure
963
-
964
- After scaffolding, your project directory looks like:
965
-
966
- ```text
967
- my-agent/
968
- agent.ts # Agent definition
969
- client.tsx # UI component (calls mount() to render into #app)
970
- styles.css # Tailwind CSS entry point
971
- package.json # Dependencies, scripts, and config
972
- tsconfig.json # TypeScript configuration
973
- .env.example # Reference for env var names
974
- .env # Local dev secrets (gitignored)
975
- .gitignore # Ignores node_modules/, .aai/, .env, etc.
976
- README.md # Getting started guide
977
- CLAUDE.md # Agent API reference (always loaded by Claude Code)
978
- .aai/ # Build output (managed by CLI, gitignored)
979
- project.json # Deploy target (slug, server URL)
980
- build/ # Bundle output
981
- ```
982
-
983
- ## Instructions patterns from templates
984
-
985
- Good instructions tell the LLM what it is, how to behave, and when to use each
986
- tool. Study these patterns:
987
-
988
- **Code execution agent** — force tool use for anything computational:
989
-
990
- ```text
991
- You MUST use the run_code tool for ANY question involving math, counting,
992
- string manipulation, or data processing. NEVER do mental math or estimate.
993
- Use console.log() to output intermediate steps.
994
- ```
995
-
996
- **Research agent** — search before answering:
997
-
998
- ```text
999
- Search first. Never guess or rely on memory for factual questions.
1000
- Use visit_webpage when search snippets aren't detailed enough.
1001
- For complex questions, search multiple times with different queries.
1002
- ```
1003
-
1004
- **FAQ/support agent** — stay grounded in knowledge:
1005
-
1006
- ```text
1007
- Always use vector_search to find relevant documentation before answering.
1008
- Base your answers strictly on the retrieved documentation — don't guess.
1009
- If search results aren't relevant, say the docs don't cover that topic.
1010
- ```
1011
-
1012
- **API-calling agent** — tell the LLM which endpoints to use:
1013
-
1014
- ```text
1015
- API endpoints (use fetch_json):
1016
- - Currency rates: https://open.er-api.com/v6/latest/{CODE}
1017
- Returns { rates: { USD: 1.0, EUR: 0.85, ... } }
1018
- - Weather: https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}...
1019
- ```
1020
-
1021
- **Game/interactive agent** — establish world rules and voice style:
1022
-
1023
- ```text
1024
- You ARE the game. Maintain world state, describe rooms, handle puzzles.
1025
- Keep descriptions to two to four sentences. No visual formatting.
1026
- Use directional words naturally: "To the north you see..." not "N: forest"
1027
- ```
1028
-
1029
- ## Common pitfalls
1030
-
1031
- - **Using `useEffect` to build state from tool calls** — Iterating
1032
- `session.toolCalls.value` in a `useEffect` re-processes every tool call on
1033
- every signal change, causing duplicates (e.g. cart items added multiple
1034
- times). Use the `useToolResult` hook instead — it fires exactly once per
1035
- completed tool call with proper deduplication.
1036
- - **Writing `instructions` with visual formatting** — Bullets, bold, numbered
1037
- lists sound terrible when spoken. Use natural transitions: "First", "Next",
1038
- "Finally". Write instructions as if you're coaching a human phone operator.
1039
- - **Returning huge payloads from tools** — Everything a tool returns goes into
1040
- the LLM context. Filter, summarize, or truncate API responses before
1041
- returning. Return only what the agent needs to formulate a spoken answer.
1042
- - **Forgetting sandbox constraints** — Agent code runs in a sandboxed Worker
1043
- with no direct network or filesystem access. Use `fetch` (proxied through the
1044
- host) for HTTP. Use `ctx.env` for secrets. Direct network access will fail.
1045
- - **Ignoring `ctx.abortSignal`** — When the user interrupts, in-flight tool
1046
- calls are cancelled via `ctx.abortSignal`. Long-running tools (polling,
1047
- multi-step fetches) should check `ctx.abortSignal.aborted` or pass the signal
1048
- to `fetch`.
1049
- - **Hardcoding secrets** — Never put API keys in `agent.ts`. Use
1050
- `aai secret put MY_KEY` to store them on the server, then access via
1051
- `ctx.env.MY_KEY`.
1052
- - **Telling the agent to be verbose** — Voice responses should be 1-3 sentences.
1053
- If your `instructions` say "provide detailed explanations", the agent will
1054
- monologue. Instruct it to be brief and let the user ask follow-ups.
1055
- - **Not setting secrets before deploying** — If your agent needs custom
1056
- secrets, set them with `aai secret put MY_KEY` before deploying.
1057
- - **Forgetting SSRF restrictions on `fetch`** — The host validates all proxied
1058
- fetch URLs. Requests to private/internal IP addresses (localhost, 10.x,
1059
- 192.168.x, etc.) are blocked.
1060
-
1061
- ## Troubleshooting
1062
-
1063
- - **"no agent found"** — Ensure `agent.ts` exists in the current directory
1064
- - **"bundle failed"** — TypeScript syntax error — check imports, brackets
1065
- - **"No .aai/project.json found"** — Run `aai deploy` first before using
1066
- `aai secret`
1067
- - **Tool returns `undefined`** — Make sure `execute` returns a value. Even
1068
- `return { ok: true }` is better than an implicit void return.
1069
- - **Agent doesn't use a tool** — Check `description` is clear about when to use
1070
- it. The LLM relies on the description to decide. Also check `activeTools`
1071
- isn't filtering it out.
1072
- - **KV reads return `null`** — Keys are scoped per agent deployment. A
1073
- redeployment with a new slug creates a fresh KV namespace.