@alexkroman1/aai-cli 13.3.0 → 15.0.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 (158) hide show
  1. package/dist/{_agent-Clbq3a4h.mjs → _agent-BzUeqOdj.mjs} +32 -9
  2. package/dist/_agent.d.ts +23 -0
  3. package/dist/_artifacts-BJOYGQPp.mjs +21 -0
  4. package/dist/_artifacts.d.ts +16 -0
  5. package/dist/_build-target.d.ts +172 -0
  6. package/dist/{_bundler-DEkjO1d8.mjs → _bundler-DM0d0M7m.mjs} +8 -7
  7. package/dist/_cli-common.d.ts +45 -6
  8. package/dist/{_config-ZY2euhdh.mjs → _config-DYzC6WMD.mjs} +2 -2
  9. package/dist/_dev-env.d.ts +1 -1
  10. package/dist/{_dev-server-C13xuGB1.mjs → _dev-server-BzWB6-4y.mjs} +25 -24
  11. package/dist/_dev-vite-config.d.ts +3 -3
  12. package/dist/_e2e-test-utils.d.ts +1 -1
  13. package/dist/{_init-Ci4QGjp8.mjs → _init-Bsi3DZNJ.mjs} +9 -23
  14. package/dist/{_output-DbJt9gsl.mjs → _output-DBP9Op_d.mjs} +22 -13
  15. package/dist/_output.d.ts +10 -9
  16. package/dist/{_preflight-B2laWPdU.mjs → _preflight-CcAR577V.mjs} +2 -2
  17. package/dist/_resource-commands.d.ts +9 -9
  18. package/dist/_server-common-De0haHr9.mjs +70 -0
  19. package/dist/_server-common.d.ts +20 -1
  20. package/dist/{_slug-api-DlKpj91X.mjs → _slug-api-CGvc2YSQ.mjs} +18 -9
  21. package/dist/_slug-api.d.ts +19 -0
  22. package/dist/{_studio-DTi3EdXQ.mjs → _studio-DnR_BqFp.mjs} +7 -3
  23. package/dist/_studio-commands.d.ts +13 -13
  24. package/dist/{_templates-V4l5sZJy.mjs → _templates-CIlJ3Vay.mjs} +4 -4
  25. package/dist/_templates.d.ts +1 -1
  26. package/dist/{_typecheck-gate-sV4N5F93.mjs → _typecheck-gate-U3jcil8L.mjs} +3 -2
  27. package/dist/_typecheck-gate.d.ts +3 -1
  28. package/dist/{_utils-Dhd058D9.mjs → _utils-D5JGcjiW.mjs} +43 -1
  29. package/dist/_utils.d.ts +35 -0
  30. package/dist/_vercel-output.d.ts +63 -0
  31. package/dist/{_workflow-determinism-C9l8uC2R.mjs → _workflow-determinism-CvpqMpdh.mjs} +18 -26
  32. package/dist/_workflow-determinism.d.ts +3 -3
  33. package/dist/build-BhEaxBPu.mjs +481 -0
  34. package/dist/build.d.ts +23 -10
  35. package/dist/cli.d.ts +1 -2
  36. package/dist/cli.mjs +155 -138
  37. package/dist/{client-bundler-CT-_26c5.mjs → client-bundler-6mTLs6ny.mjs} +5 -5
  38. package/dist/client-bundler.d.ts +1 -1
  39. package/dist/client-bundler.mjs +1 -1
  40. package/dist/{delete-RqlQZ6Y_.mjs → delete-BdFnbnVR.mjs} +4 -4
  41. package/dist/{deploy-B7f3CK7v.mjs → deploy-CGqPU5U-.mjs} +11 -11
  42. package/dist/{dev-DamXieW_.mjs → dev-Bx9gYBHM.mjs} +3 -3
  43. package/dist/{eval-h3DTVfRw.mjs → eval-B3I7FqN9.mjs} +3 -3
  44. package/dist/{init-D-vFrV8h.mjs → init-CFyusRbq.mjs} +48 -20
  45. package/dist/init.d.ts +14 -0
  46. package/dist/{login-CDiemNmC.mjs → login-DaLrPOFg.mjs} +4 -6
  47. package/dist/{logs-DGwPNCzB.mjs → logs-7BRwq0cG.mjs} +21 -14
  48. package/dist/project-config.mjs +1 -1
  49. package/dist/scaffold/CLAUDE.md +155 -76
  50. package/dist/scaffold/package.json +6 -6
  51. package/dist/{secret-V1w7y9-j.mjs → secret-Cjp912yF.mjs} +5 -5
  52. package/dist/secret.d.ts +8 -6
  53. package/dist/start.d.ts +112 -0
  54. package/dist/start.mjs +156 -0
  55. package/dist/{studio-BpMCfUNx.mjs → studio-C_zuRC_z.mjs} +13 -15
  56. package/dist/templates/briefing-desk/agent.eval.test.ts +156 -0
  57. package/dist/templates/call-audit/agent.test.ts +11 -6
  58. package/dist/templates/call-audit/agent.ts +2 -2
  59. package/dist/templates/call-audit/client.tsx +27 -42
  60. package/dist/templates/call-audit/workflows/audit.ts +7 -5
  61. package/dist/templates/call-audit/workflows/ingest.ts +7 -7
  62. package/dist/templates/call-audit/workflows/media.ts +12 -37
  63. package/dist/templates/call-audit/workflows/summarize.ts +8 -8
  64. package/dist/templates/call-audit/workflows/sync-api.ts +3 -3
  65. package/dist/templates/code-interpreter/agent.test.ts +103 -0
  66. package/dist/templates/dispatch-center/agent.test.ts +7 -7
  67. package/dist/templates/dispatch-center/client.tsx +53 -61
  68. package/dist/templates/dispatch-center/shared.ts +0 -8
  69. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +4 -1
  70. package/dist/templates/embedded-assets/agent.eval.test.ts +4 -2
  71. package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
  72. package/dist/templates/infocom-adventure/client.tsx +40 -36
  73. package/dist/templates/link-digest/agent.test.ts +3 -3
  74. package/dist/templates/link-digest/agent.ts +2 -2
  75. package/dist/templates/link-digest/client.tsx +81 -19
  76. package/dist/templates/link-digest/workflows/digest.ts +13 -16
  77. package/dist/templates/math-buddy/agent.test.ts +126 -0
  78. package/dist/templates/night-owl/agent.eval.test.ts +4 -2
  79. package/dist/templates/night-owl/client.tsx +8 -2
  80. package/dist/templates/personal-finance/agent.test.ts +127 -0
  81. package/dist/templates/pipeline-simple/agent.test.ts +2 -2
  82. package/dist/templates/pizza-ordering/client.tsx +5 -4
  83. package/dist/templates/pizza-ordering/shared.ts +3 -8
  84. package/dist/templates/pizza-ordering/tools/add_pizza.ts +3 -2
  85. package/dist/templates/pizza-ordering/tools/place_order.ts +3 -2
  86. package/dist/templates/pizza-ordering/tools/remove_pizza.ts +3 -2
  87. package/dist/templates/pizza-ordering/tools/update_pizza.ts +3 -2
  88. package/dist/templates/pizza-ordering/tools/view_order.ts +3 -2
  89. package/dist/templates/plan-and-execute/agent.test.ts +10 -5
  90. package/dist/templates/plan-and-execute/client.tsx +2 -2
  91. package/dist/templates/podcast-digest/agent.test.ts +20 -11
  92. package/dist/templates/podcast-digest/client.tsx +16 -22
  93. package/dist/templates/podcast-digest/workflows/digest.ts +21 -19
  94. package/dist/templates/podcast-digest/workflows/feeds.ts +61 -33
  95. package/dist/templates/podcast-digest/workflows/slack.ts +7 -7
  96. package/dist/templates/recap-workflow/agent.eval.test.ts +21 -13
  97. package/dist/templates/recap-workflow/agent.test.ts +27 -23
  98. package/dist/templates/recap-workflow/workflows/recap.ts +24 -28
  99. package/dist/templates/redline/agent.eval.test.ts +5 -8
  100. package/dist/templates/redline/agent.test.ts +1 -1
  101. package/dist/templates/redline/client.tsx +36 -34
  102. package/dist/templates/redline/workflows/redline.ts +17 -17
  103. package/dist/templates/research-workflow/agent.eval.test.ts +5 -5
  104. package/dist/templates/research-workflow/agent.test.ts +10 -7
  105. package/dist/templates/research-workflow/workflows/research.ts +28 -30
  106. package/dist/templates/retail/agent.test.ts +17 -15
  107. package/dist/templates/retail/cancel.ts +7 -6
  108. package/dist/templates/retail/client.tsx +39 -58
  109. package/dist/templates/retail/payment.ts +4 -3
  110. package/dist/templates/retail/pending.test.ts +19 -19
  111. package/dist/templates/retail/store.test.ts +5 -5
  112. package/dist/templates/retail/swap.ts +8 -11
  113. package/dist/templates/solo-rpg/agent.eval.test.ts +2 -16
  114. package/dist/templates/solo-rpg/agent.test.ts +12 -12
  115. package/dist/templates/solo-rpg/client.tsx +3 -3
  116. package/dist/templates/spoken-summary/agent.test.ts +9 -9
  117. package/dist/templates/spoken-summary/agent.ts +3 -3
  118. package/dist/templates/spoken-summary/client.tsx +23 -30
  119. package/dist/templates/spoken-summary/workflows/summarize.ts +11 -11
  120. package/dist/templates/spoken-summary/workflows/transcribe.ts +14 -14
  121. package/dist/templates/support-line/agent.ts +8 -0
  122. package/dist/templates/support-line/client.tsx +4 -3
  123. package/dist/templates/support-line/prompts.ts +0 -2
  124. package/dist/templates/transcription-workflow/agent.test.ts +20 -12
  125. package/dist/templates/transcription-workflow/agent.ts +2 -2
  126. package/dist/templates/transcription-workflow/client.tsx +15 -14
  127. package/dist/templates/transcription-workflow/recover.ts +1 -1
  128. package/dist/templates/transcription-workflow/run-panel.tsx +34 -14
  129. package/dist/templates/transcription-workflow/total-latency.tsx +46 -20
  130. package/dist/templates/transcription-workflow/workflows/batch.ts +15 -15
  131. package/dist/templates/transcription-workflow/workflows/downsample.ts +1 -1
  132. package/dist/templates/transcription-workflow/workflows/normalize.ts +15 -19
  133. package/dist/templates/transcription-workflow/workflows/stream.ts +12 -12
  134. package/dist/templates/transcription-workflow/workflows/sync-api.ts +3 -3
  135. package/dist/templates/transcription-workflow/workflows/transcribe.ts +23 -34
  136. package/dist/templates/travel-concierge/agent.eval.test.ts +2 -2
  137. package/dist/templates/travel-concierge/agent.test.ts +15 -7
  138. package/dist/templates/travel-concierge/client.tsx +2 -2
  139. package/dist/templates/travel-concierge/routing.ts +64 -55
  140. package/dist/templates/travel-concierge/shared.ts +10 -14
  141. package/dist/templates/travel-concierge/tools/cancel_action.ts +3 -1
  142. package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +3 -1
  143. package/dist/templates/travel-concierge/tools/confirm_action.ts +3 -1
  144. package/dist/templates/travel-concierge/tools/lookup_booking.ts +4 -3
  145. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +3 -2
  146. package/dist/templates/travel-concierge/tools/search_excursions.ts +3 -2
  147. package/dist/templates/travel-concierge/tools/search_flights.ts +3 -2
  148. package/dist/templates/travel-concierge/tools/search_hotels.ts +3 -2
  149. package/dist/templates/web-researcher/agent.test.ts +130 -0
  150. package/dist/{test-guenJUxi.mjs → test-D032V2cA.mjs} +4 -17
  151. package/dist/typecheck.mjs +1 -1
  152. package/dist/worker-bundler.d.ts +1 -1
  153. package/dist/worker-bundler.mjs +8 -8
  154. package/dist/{workflow-BZyKLkhV.mjs → workflow-CVioyO3V.mjs} +6 -5
  155. package/package.json +9 -4
  156. package/dist/_server-common-BV4_CrP3.mjs +0 -43
  157. package/dist/build-CWyFGe-W.mjs +0 -69
  158. package/dist/scaffold/server.mjs +0 -204
@@ -77,7 +77,8 @@ The fast loop: edit → `pnpm dev` (browser, talk to it) →
77
77
  you stopped talking, barge-in, two sentences merging into one turn. Those
78
78
  need `pnpm dev` and your own voice.
79
79
  4. **Run `pnpm build` before declaring done** — bundles `agent.ts`,
80
- type-checks, and validates the manifest. Catches issues `dev` won't.
80
+ type-checks, validates the manifest, and runs the WHOLE spec suite first.
81
+ Catches issues `dev` won't.
81
82
  5. **Make small, focused changes** — verify each one before stacking the
82
83
  next.
83
84
  6. **Look at templates before writing custom code** — the CLI ships working
@@ -109,6 +110,7 @@ npx @alexkroman1/aai-cli init # Scaffold a new agent
109
110
  npx @alexkroman1/aai-cli templates # List available templates
110
111
  npx @alexkroman1/aai-cli dev # Start local dev server
111
112
  npx @alexkroman1/aai-cli test # Run agent.test.ts via vitest
113
+ npx @alexkroman1/aai-cli test --all # ...or every spec in the project
112
114
  npx @alexkroman1/aai-cli eval # Run agent.eval.test.ts against a model
113
115
  npx @alexkroman1/aai-cli build # Bundle and validate
114
116
  npx @alexkroman1/aai-cli deploy # Deploy to production
@@ -122,10 +124,17 @@ The scaffold's `package.json` exposes `dev`, `build`, `test`, `eval` and
122
124
  `deploy` as `pnpm <name>` shortcuts. Other commands (`init`, `templates`,
123
125
  `delete`, `secret`) are CLI-only.
124
126
 
127
+ **`aai test` targets `agent.test.ts` and nothing else**, which `--all` widens.
128
+ What matters is that a narrowed run does not report itself as a pass: when the
129
+ project holds spec files the run did not cover, it FAILS and names them rather
130
+ than printing a green line that says nothing about `tools/*.test.ts`. `pnpm
131
+ test` (the scaffold's own script) already runs the whole suite, and so does the
132
+ gate in front of `aai build`.
133
+
125
134
  ## Running it yourself (`npm start`)
126
135
 
127
- `server.mjs` serves this agent from a plain Node process — no platform
128
- account, nothing managed. It is the deployment counterpart of `aai dev`:
136
+ `aai start` serves this agent from a plain Node process — no platform account,
137
+ nothing managed. It is the deployment counterpart of `aai dev`:
129
138
 
130
139
  ```sh
131
140
  npm start # http://127.0.0.1:3000
@@ -133,11 +142,18 @@ PORT=8080 HOST=0.0.0.0 npm start # bind every interface, e.g. in a container
133
142
  ```
134
143
 
135
144
  `npm start` **builds first** (that is the `prestart` script) and then serves
136
- the result: `server.mjs` boots `.aai/worker.mjs`, the same artifact
145
+ the result: `aai start` boots `.aai/worker.mjs`, the same artifact
137
146
  `aai publish` uploads. It serves your own `client.tsx` build when there is one
138
- and falls back to `defaultClientDir()` (`@alexkroman1/aai-ui/client-dir`), the
139
- prebuilt default UI shipped inside the package — the only export of `aai-ui`
140
- that runs on Node rather than in the browser.
147
+ and falls back to the prebuilt default UI shipped inside `@alexkroman1/aai-ui`.
148
+
149
+ There is no server file in your project, and that is deliberate — the boot
150
+ belongs to the framework, so it improves when you update rather than being
151
+ frozen at the moment you scaffolded. When you need to own it, import
152
+ `createProjectServer` from `@alexkroman1/aai-cli/start`: it builds the server
153
+ and binds nothing, so you decide how it is served. Building one from scratch
154
+ instead, `defaultClientDir()` (`@alexkroman1/aai-ui/client-dir`) is where that
155
+ prebuilt UI lives — the only export of `aai-ui` that runs on Node rather than
156
+ in the browser.
141
157
 
142
158
  The build is what makes `tools/` work — a tool is registered by existing, and
143
159
  the enumeration happens where the bundle is assembled, so a server that loaded
@@ -152,9 +168,31 @@ One thing to know: it binds **loopback by default**, because this server has
152
168
  no request authentication of its own; set `HOST=0.0.0.0` only behind your own
153
169
  proxy or auth.
154
170
 
155
- Deleting `server.mjs` costs nothing: `aai dev`, `aai publish` and the managed
156
- platform never read it. `run_code` is the one feature that does not follow —
157
- it needs the platform's sandbox and refuses outside one.
171
+ `run_code` is the one feature that does not follow — it needs the platform's
172
+ sandbox and refuses outside one.
173
+
174
+ ### Deploying to a host that wants its own entry file
175
+
176
+ `aai build --target <host>` writes the deployment that host expects into the
177
+ build output. Nothing host-specific lives in your project: the files are
178
+ generated, gitignored, and rewritten by the host's own build.
179
+
180
+ ```sh
181
+ aai build --target vercel # writes .vercel/output/ (Build Output API v3)
182
+ ```
183
+
184
+ You rarely type it. The target is detected from the host's own build
185
+ environment, so `vercel deploy` (or a git push) picks it up with nothing
186
+ configured; `--target node`, the default everywhere else, emits nothing extra
187
+ and is what `npm start` runs.
188
+
189
+ One thing to know before deploying a VOICE agent to a serverless host: the
190
+ session is a WebSocket, so the host has to support one. Vercel does — it hands
191
+ the function the raw upgrade, and the emitted entry passes it to the same
192
+ server `aai dev` runs. A host that serves only request/response still runs the
193
+ HTTP surface — `/health`, `/client-config`,
194
+ `/workflows/*` and your static assets — which is everything a workflow app
195
+ needs and none of what a voice agent needs.
158
196
 
159
197
  ## Project structure
160
198
 
@@ -416,14 +454,14 @@ below.
416
454
 
417
455
  ### Workflow bodies live in `workflows/`
418
456
 
419
- A body is an ordinary exported async function of its input and a `WorkflowCtx`.
457
+ A body is an ordinary exported async function of its input and a `WorkflowContext`.
420
458
  There is no directive and no compile step of its own — the agent bundle compiles
421
459
  `workflows/` like any other source file — and durability is a method call:
422
460
 
423
461
  ```ts
424
- import type { WorkflowCtx } from "@alexkroman1/aai";
462
+ import type { WorkflowContext } from "@alexkroman1/aai";
425
463
 
426
- export async function digestFlow(input: { url: string }, ctx: WorkflowCtx) {
464
+ export async function digestFlow(input: { url: string }, ctx: WorkflowContext) {
427
465
  const digest = await ctx.step("summarize", () => summarize(input.url));
428
466
 
429
467
  // Suspended, not blocked: the container is free to exit here and the run
@@ -515,11 +553,11 @@ The three undurable reads a body most often wants, each journaled — read once
515
553
  the first reach, and the same value on every later walk:
516
554
 
517
555
  ```ts
518
- import type { WorkflowCtx } from "@alexkroman1/aai";
556
+ import type { WorkflowContext } from "@alexkroman1/aai";
519
557
 
520
558
  declare function charge(amount: number, idempotencyKey: string, jitter: number): Promise<void>;
521
559
 
522
- export async function chargeFlow(input: { amount: number }, ctx: WorkflowCtx) {
560
+ export async function chargeFlow(input: { amount: number }, ctx: WorkflowContext) {
523
561
  const startedAt = await ctx.now(); // epoch ms, decided once
524
562
  const idempotencyKey = await ctx.uuid(); // still the same id after a crash
525
563
  const jitter = await ctx.random(); // one float in [0, 1), journaled per call
@@ -601,6 +639,22 @@ Five things worth knowing:
601
639
  which is how a "send it now" tool cuts a scheduled wait short. Naming no ids
602
640
  wakes every outstanding SLEEP and deliberately not a `waitFor` deadline, so
603
641
  cutting a schedule short cannot also close an approval window.
642
+ - **A SUSPEND is not free, so `ctx.sleep` is not a `setTimeout`.** A wait costs
643
+ a journal write to record it, a queued delivery to bring the run back, and a
644
+ fresh WALK of the body — measured on a deployed agent at roughly a second of
645
+ overhead around the sleep itself, on top of whatever you asked for. There is a
646
+ cliff at the bottom of the range worth knowing about: a sleep shorter
647
+ than the round trip that records it never suspends AT ALL — `ctx.sleep("beat",
648
+ 100)` and `ctx.sleep("beat", 0)` are both simply free — while anything longer
649
+ pays the whole cost. Measured, `nap(100)` and `nap(0)` came back within 30 ms
650
+ of each other on a run whose total was 2.1 s.
651
+
652
+ So a sub-second pause is not what this is for. For a short backoff inside a
653
+ step, use an ordinary timer (`sleep` from `@alexkroman1/aai/internal`) — a step
654
+ body may not call `ctx.sleep` anyway, and the engine refuses one that does. Use
655
+ `ctx.sleep` for a wait you want to SURVIVE the process, which is the thing a
656
+ timer cannot do. A body that polls in a loop pays the suspend per iteration,
657
+ which is the strongest argument for the next section: park on the callback.
604
658
 
605
659
  #### A third-party callback is an OPTIMIZATION over a reconciling read
606
660
 
@@ -656,15 +710,15 @@ trap somebody has already paid for:
656
710
  Steps are ordinary exported functions, so a spec imports and calls them. The
657
711
  BODY needs an engine, and there are two, for two different questions.
658
712
 
659
- **"What did the body ask for?"** — `createWorkflowCtx` from
713
+ **"What did the body ask for?"** — `createWorkflowContext` from
660
714
  `@alexkroman1/aai/testing`. It runs the steps and records the names, the retry
661
715
  policies and the sleeps, over one walk with no journal. Nothing replays, so a
662
716
  spec built on it must not claim to test durability.
663
717
 
664
718
  ```ts no-check
665
- import { createWorkflowCtx } from "@alexkroman1/aai/testing";
719
+ import { createWorkflowContext } from "@alexkroman1/aai/testing";
666
720
 
667
- const ctx = createWorkflowCtx({ runSteps: false });
721
+ const ctx = createWorkflowContext({ runSteps: false });
668
722
  await digestFlow({ url: "https://example.com/a" }, ctx);
669
723
 
670
724
  expect(ctx.steps.map((s) => s.name)).toEqual(["fetchArticle", "summarize", "file"]);
@@ -729,7 +783,7 @@ root barrel would drag the whole SDK into that bundle.
729
783
 
730
784
  ```ts
731
785
  import { stepEnv } from "@alexkroman1/aai/step";
732
- import { stepGenerateClassified } from "@alexkroman1/aai/step-errors";
786
+ import { stepGenerateOrFail } from "@alexkroman1/aai/step-errors";
733
787
 
734
788
  async function summarize(text: string) {
735
789
  // The agent's env by name — the same values a tool reads from `ctx.env`.
@@ -737,7 +791,7 @@ async function summarize(text: string) {
737
791
  const style = stepEnv("DIGEST_STYLE") ?? "plain";
738
792
 
739
793
  // One model call, on the agent's own ASSEMBLYAI_API_KEY and default model.
740
- return await stepGenerateClassified(`${style} summary of:\n\n${text}`, {
794
+ return await stepGenerateOrFail(`${style} summary of:\n\n${text}`, {
741
795
  system: "Reply with two sentences and nothing else.",
742
796
  });
743
797
  }
@@ -749,9 +803,9 @@ before and after a deploy. List what you read in `requiredEnv` and a deploy
749
803
  checks it for you. And **`stepGenerate` is not `ctx.generate`**: it is one
750
804
  request to the AssemblyAI LLM Gateway, with no tools and no structured output,
751
805
  because bundling the AI SDK into a step artifact costs megabytes on every
752
- deploy. Use `stepGenerateJsonClassified` with a Zod `schema` if you need a shape.
806
+ deploy. Use `stepGenerateJsonOrFail` with a Zod `schema` if you need a shape.
753
807
 
754
- ### From a step, reach for the `Classified` call
808
+ ### From a step, reach for the `OrFail` call
755
809
 
756
810
  `@alexkroman1/aai/step-errors` publishes a wrapper for every `/step` call that
757
811
  can fail against a remote service, and **inside a step the wrapper is the one to
@@ -759,14 +813,14 @@ use**:
759
813
 
760
814
  | Raw, on `@alexkroman1/aai/step` | Use this instead, on `@alexkroman1/aai/step-errors` |
761
815
  | --- | --- |
762
- | `stepGenerate` | `stepGenerateClassified` |
763
- | `stepGenerateJson` | `stepGenerateJsonClassified` |
764
- | `stepFetch` | `stepFetchOk` |
765
- | `stepTranscribeSync` | `stepTranscribeSyncClassified` |
766
- | `stepTranscribeUpload` / `Submit` / `Poll` | the matching `*Classified` |
767
- | `sendToChannel` (`/channels`) | `sendToChannelClassified` |
768
-
769
- `stepFetchOk` is the one that is not spelled `*Classified`, and the name is the
816
+ | `stepGenerate` | `stepGenerateOrFail` |
817
+ | `stepGenerateJson` | `stepGenerateJsonOrFail` |
818
+ | `stepFetch` | `stepFetchOrFail` |
819
+ | `stepTranscribeSync` | `stepTranscribeSyncOrFail` |
820
+ | `stepTranscribeUpload` / `Submit` / `Poll` | the matching `*OrFail` |
821
+ | `sendToChannel` (`/channels`) | `sendToChannelOrFail` |
822
+
823
+ `stepFetchOrFail` is the one that is not spelled `*OrFail`, and the name is the
770
824
  difference: the others turn an already-thrown failure into a classified one,
771
825
  while this also turns a NON-2XX RESPONSE into a throw — `stepFetch` resolves
772
826
  with a `404` rather than raising it. Two changes, so two names.
@@ -804,7 +858,7 @@ bundling rule as `/step` — import them there, never through the root barrel:
804
858
  recording, or `stepTranscribeUpload` → `stepTranscribeSubmit` →
805
859
  `stepTranscribePoll` for a long one, plus `Transcript`, `TranscribeError` and
806
860
  the `TRANSCRIBE_*` limits. (There is no `/transcribe` subpath; transcription
807
- lives on `/step` with the other step primitives.) Use the `Classified`
861
+ lives on `/step` with the other step primitives.) Use the `OrFail`
808
862
  wrappers above: a provider refusal — a container it will not read, a
809
863
  recording with no speech — arrives
810
864
  with `retryable: false`, and unclassified a step re-uploads the same bytes
@@ -841,19 +895,19 @@ export async function measure(uploadId: string) {
841
895
 
842
896
  A run that finishes while nobody is on the line needs somewhere to put the
843
897
  result. `slackChannel({ webhookUrl })` names a destination and
844
- `sendToChannelClassified(channel, message)` posts to it:
898
+ `sendToChannelOrFail(channel, message)` posts to it:
845
899
 
846
900
  ```ts no-check
847
901
  import { type ChannelMessage, slackChannel } from "@alexkroman1/aai/channels";
848
902
  import { requireStepEnv } from "@alexkroman1/aai/step";
849
- import { sendToChannelClassified } from "@alexkroman1/aai/step-errors";
903
+ import { sendToChannelOrFail } from "@alexkroman1/aai/step-errors";
850
904
 
851
905
  export async function announce(headline: string, points: string[]) {
852
906
  const message: ChannelMessage = {
853
907
  text: headline,
854
908
  sections: points.map((point) => ({ text: point })),
855
909
  };
856
- return await sendToChannelClassified(slackChannel({ webhookUrl: requireStepEnv("SLACK_WEBHOOK_URL") }), message);
910
+ return await sendToChannelOrFail(slackChannel({ webhookUrl: requireStepEnv("SLACK_WEBHOOK_URL") }), message);
857
911
  }
858
912
  ```
859
913
 
@@ -862,7 +916,7 @@ it with `aai secret put`. A channel's credential is its DESTINATION and is
862
916
  passed in, which is why no channel reads an env var of its own. `ChannelMessage`
863
917
  is rendered per platform, so the same message is legal on a channel kind added
864
918
  later; `isSlackWebhookUrl` / `isSlackWorkflowTriggerUrl` validate a pasted URL
865
- before a run depends on it, and `channelAdvice` turns a refusal into a sentence
919
+ before a run depends on it, and `explainChannelFailure` turns a refusal into a sentence
866
920
  a person can act on. `podcast-digest` is the worked example.
867
921
 
868
922
  ### A step's HTTP: use `stepFetch`, not `fetch`
@@ -941,11 +995,11 @@ Both are on `@alexkroman1/aai/step`, and `spoken-summary` is the template that
941
995
  shows the whole round trip.
942
996
 
943
997
  ```ts
944
- import { stepSpeak, writeUpload } from "@alexkroman1/aai/step";
998
+ import { stepSpeak, stepWriteUpload } from "@alexkroman1/aai/step";
945
999
 
946
1000
  export async function narrate(script: string) {
947
1001
  const spoken = await stepSpeak(script, { voice: "jane" });
948
- const stored = await writeUpload(spoken.audio, { name: "summary.wav", type: "audio/wav" });
1002
+ const stored = await stepWriteUpload(spoken.audio, { name: "summary.wav", type: "audio/wav" });
949
1003
  return { audio: stored.id, durationMs: spoken.durationMs };
950
1004
  }
951
1005
  ```
@@ -962,11 +1016,13 @@ opens and produces silence rather than an error. The `AssemblyAITtsVoice` type
962
1016
  gives you autocomplete over it and nothing more: it accepts any string, so that
963
1017
  a voice the service adds after this release still compiles.
964
1018
 
965
- **`writeUpload` is `readUpload`'s other direction, and you need it.** A run's
966
- output is read back as JSON, so audio cannot travel in one — the same rule that
967
- keeps an uploaded recording's bytes out of a run's INPUT, arriving at the other
968
- end of the run. Store the bytes, return the **id**, and let the page fetch it
969
- with `api.download(id)`.
1019
+ **`stepWriteUpload` is `stepReadUpload`'s other direction, and you need it.** A
1020
+ run's output is read back as JSON, so audio cannot travel in one — the same rule
1021
+ that keeps an uploaded recording's bytes out of a run's INPUT, arriving at the
1022
+ other end of the run. Store the bytes, return the **id**, and let the page fetch
1023
+ it with `api.download(id)`. A step that wants the record rather than the bytes
1024
+ reads it with `stepUploadInfo(id)`, which answers an `UploadInfo` — the name,
1025
+ the size stored so far, and whether that is all of it.
970
1026
 
971
1027
  Three rules come with it:
972
1028
 
@@ -1020,12 +1076,13 @@ naming a shape is what asks the compiler to make you handle the failure.
1020
1076
 
1021
1077
  ### The page
1022
1078
 
1023
- A workflow app's `client.tsx` mounts with `page()` rather than `client()` —
1024
- there is no session to build, so no socket, no audio graph and no microphone
1025
- request. Everything else is the same file, React and Tailwind included.
1079
+ A workflow app's `client.tsx` mounts with `mountPage()` rather than
1080
+ `mountClient()` — there is no session to build, so no socket, no audio graph
1081
+ and no microphone request. Everything else is the same file, React and
1082
+ Tailwind included.
1026
1083
 
1027
1084
  ```tsx no-check
1028
- import { createWorkflowApi, page, useWorkflowRun } from "@alexkroman1/aai-ui";
1085
+ import { createWorkflowApi, mountPage, useWorkflowRun } from "@alexkroman1/aai-ui";
1029
1086
  import "@alexkroman1/aai-ui/styles.css";
1030
1087
  import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
1031
1088
  import { useState } from "react";
@@ -1053,7 +1110,7 @@ export function App() {
1053
1110
  );
1054
1111
  }
1055
1112
 
1056
- page({ name: "Link Digest", component: App });
1113
+ mountPage({ name: "Link Digest", component: App });
1057
1114
  ```
1058
1115
 
1059
1116
  `api.start()` resolves as soon as the RUN EXISTS, not when it finishes — that
@@ -1171,11 +1228,23 @@ statement about pipeline mode, not about the SDK.
1171
1228
 
1172
1229
  ### Answering a phone call
1173
1230
 
1174
- A deployed voice agent already serves carrier media streams there is nothing
1175
- to switch on. `createServer` mounts `WS /phone` whenever the agent is a voice
1176
- agent (`telephony` defaults to `true`, and to `false` for a `page: "static"`
1177
- workflow app, which has no stages to put on a call). Point the carrier at it
1178
- with a `carrier` query parameter naming who is dialling:
1231
+ **Say which carriers may call, and `WS /phone` is served for exactly those.**
1232
+ `telephony` is an allow-list on `agent()`, and it is the whole of the wiring:
1233
+
1234
+ ```ts
1235
+ import { agent } from "@alexkroman1/aai";
1236
+
1237
+ export default agent({
1238
+ name: "Support",
1239
+ greeting: "Support line — what's happened?",
1240
+ // The carrier this agent's number is with. `true` admits every carrier this
1241
+ // build decodes; omit the field and `/phone` is not served at all.
1242
+ telephony: ["twilio"],
1243
+ });
1244
+ ```
1245
+
1246
+ Point the carrier at the deployed agent with a `carrier` query parameter naming
1247
+ who is dialling:
1179
1248
 
1180
1249
  ```text
1181
1250
  wss://<your-agent-url>/phone?carrier=twilio
@@ -1183,15 +1252,25 @@ wss://<your-agent-url>/phone?carrier=telnyx
1183
1252
  ```
1184
1253
 
1185
1254
  Twilio and Telnyx are the two carriers this build decodes (`CARRIER_CODECS`);
1186
- an unknown `carrier` is declined at the upgrade. Both speak 8 kHz mu-law, which
1187
- the bridge transcodes in both directions, so the agent, its tools and its slots
1255
+ an unknown `carrier` is declined at the upgrade with a `400`, and a real one
1256
+ this agent did not declare with a `404`. Both speak 8 kHz mu-law, which the
1257
+ bridge transcodes in both directions, so the agent, its tools and its slots
1188
1258
  behave exactly as they do in the browser — a phone call is a transport, not a
1189
- mode. Nothing about `agent.ts` changes to support one.
1190
-
1191
- Turn the route off with `telephony: false` on `createServer`. If you are
1192
- embedding the runtime yourself rather than deploying, the pieces are
1193
- `createTelephonyBridge`, `startTelephonySession`, `TELEPHONY_PATH` and
1194
- `carrierByName`, all on `@alexkroman1/aai-runtime`.
1259
+ mode. Nothing else about `agent.ts` changes to support one.
1260
+
1261
+ **An agent that declares nothing answers no carrier**, which is a change from
1262
+ earlier releases: every voice agent used to serve both carriers' framing from
1263
+ the moment it booted, whether or not it had a phone number. `/phone` is the one
1264
+ door dialled from OUTSIDE your deployment, by a carrier following a number, so
1265
+ it is opened by a sentence in `agent.ts` rather than inherited. `aai dev` and a
1266
+ deployed sandbox honour the same declaration, so a call refused after a deploy
1267
+ is refused on your laptop too.
1268
+
1269
+ `telephony: false` is the same refusal stated out loud, and an operator can pass
1270
+ `telephony` to `createAgentServer` to override one deployment of an agent that
1271
+ does declare a carrier. If you are embedding the runtime yourself rather than
1272
+ deploying, the pieces are `createTelephonyBridge`, `startTelephonySession`,
1273
+ `TELEPHONY_PATH` and `carrierByName`, all on `@alexkroman1/aai-runtime`.
1195
1274
 
1196
1275
  **Silence nudge (pipeline only):** set `silenceTimeoutMs` to make the
1197
1276
  assistant proactively take a turn after that much user silence (e.g.
@@ -1268,12 +1347,12 @@ API keys require it; the US endpoints reject them. Example:
1268
1347
  | Factory | SDK package | Env var |
1269
1348
  | --------------- | ------------------- | ------------------------------ |
1270
1349
  | `anthropicLlm` | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
1271
- | `openaiLlm` | `@ai-sdk/openai` | `OPENAI_API_KEY` |
1350
+ | `openAILlm` | `@ai-sdk/openai` | `OPENAI_API_KEY` |
1272
1351
  | `googleLlm` | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
1273
1352
  | `mistralLlm` | `@ai-sdk/mistral` | `MISTRAL_API_KEY` |
1274
- | `xaiLlm` | `@ai-sdk/xai` | `XAI_API_KEY` |
1353
+ | `xAILlm` | `@ai-sdk/xai` | `XAI_API_KEY` |
1275
1354
  | `groqLlm` | `@ai-sdk/groq` | `GROQ_API_KEY` |
1276
- | `openrouterLlm` | `@ai-sdk/openai` | `OPENROUTER_API_KEY` |
1355
+ | `openRouterLlm` | `@ai-sdk/openai` | `OPENROUTER_API_KEY` |
1277
1356
  | `gatewayLlm` | `ai` (built in) | `AI_GATEWAY_API_KEY` |
1278
1357
  | `assemblyAILlm` | `@ai-sdk/openai` | `ASSEMBLYAI_API_KEY` |
1279
1358
 
@@ -1283,10 +1362,10 @@ shared by all of them except `assemblyAILlm`. Example:
1283
1362
  because a third-party vendor's catalog is not this SDK's to default from;
1284
1363
  `assemblyAILlm()` is the one bare call, since it has a default model.
1285
1364
 
1286
- `openrouterLlm` routes through [OpenRouter](https://openrouter.ai) — an
1365
+ `openRouterLlm` routes through [OpenRouter](https://openrouter.ai) — an
1287
1366
  OpenAI-compatible endpoint fronting hundreds of models addressed as
1288
1367
  `"creator/model"`, e.g.
1289
- `openrouterLlm({ model: "meta-llama/llama-3.3-70b-instruct" })`. It needs
1368
+ `openRouterLlm({ model: "meta-llama/llama-3.3-70b-instruct" })`. It needs
1290
1369
  no extra SDK install (it reuses the `@ai-sdk/openai` client).
1291
1370
 
1292
1371
  `gatewayLlm` routes through the [Vercel AI
@@ -1947,7 +2026,7 @@ Those three cover almost everything an agent wants. A database is for data that
1947
2026
  must outlive a session AND be queryable: a ledger, filed records, cross-session
1948
2027
  saves.
1949
2028
 
1950
- ## Custom UI — `client()`
2029
+ ## Custom UI — `mountClient()`
1951
2030
 
1952
2031
  File: `client.tsx` alongside `agent.ts`. Uses **React** (not Preact).
1953
2032
  Always import `"@alexkroman1/aai-ui/styles.css"` first.
@@ -1957,9 +2036,9 @@ Always import `"@alexkroman1/aai-ui/styles.css"` first.
1957
2036
  ```tsx
1958
2037
  /// <reference types="vite/client" />
1959
2038
  import "@alexkroman1/aai-ui/styles.css";
1960
- import { client } from "@alexkroman1/aai-ui";
2039
+ import { mountClient } from "@alexkroman1/aai-ui";
1961
2040
 
1962
- client({ name: "My Agent" });
2041
+ mountClient({ name: "My Agent" });
1963
2042
  ```
1964
2043
 
1965
2044
  ### Tier 1 with sidebar
@@ -1967,7 +2046,7 @@ client({ name: "My Agent" });
1967
2046
  ```tsx
1968
2047
  /// <reference types="vite/client" />
1969
2048
  import "@alexkroman1/aai-ui/styles.css";
1970
- import { client, useEvent } from "@alexkroman1/aai-ui";
2049
+ import { mountClient, useEvent } from "@alexkroman1/aai-ui";
1971
2050
  import { useState } from "react";
1972
2051
 
1973
2052
  function Sidebar() {
@@ -1982,7 +2061,7 @@ function Sidebar() {
1982
2061
  );
1983
2062
  }
1984
2063
 
1985
- client({ name: "My Agent", sidebar: Sidebar });
2064
+ mountClient({ name: "My Agent", sidebar: Sidebar });
1986
2065
  ```
1987
2066
 
1988
2067
  ### Tier 2 — full custom component
@@ -1990,7 +2069,7 @@ client({ name: "My Agent", sidebar: Sidebar });
1990
2069
  ```tsx
1991
2070
  /// <reference types="vite/client" />
1992
2071
  import "@alexkroman1/aai-ui/styles.css";
1993
- import { client, useSession } from "@alexkroman1/aai-ui";
2072
+ import { mountClient, useSession } from "@alexkroman1/aai-ui";
1994
2073
 
1995
2074
  function MyApp() {
1996
2075
  const { messages, userTranscript, started, running, start, toggle, end } =
@@ -2011,10 +2090,10 @@ function MyApp() {
2011
2090
  );
2012
2091
  }
2013
2092
 
2014
- client({ component: MyApp });
2093
+ mountClient({ component: MyApp });
2015
2094
  ```
2016
2095
 
2017
- ### `client()` config
2096
+ ### `mountClient()` config
2018
2097
 
2019
2098
  | Field | Type | Default | Description |
2020
2099
  | --- | --- | --- | --- |
@@ -2172,7 +2251,7 @@ Property 'children' is missing` build error:
2172
2251
  ```tsx
2173
2252
  /// <reference types="vite/client" />
2174
2253
  import "@alexkroman1/aai-ui/styles.css";
2175
- import { ChatView, client, StartScreen } from "@alexkroman1/aai-ui";
2254
+ import { ChatView, mountClient, StartScreen } from "@alexkroman1/aai-ui";
2176
2255
 
2177
2256
  function PizzaApp() {
2178
2257
  return (
@@ -2182,7 +2261,7 @@ function PizzaApp() {
2182
2261
  );
2183
2262
  }
2184
2263
 
2185
- client({ component: PizzaApp });
2264
+ mountClient({ component: PizzaApp });
2186
2265
  ```
2187
2266
 
2188
2267
  ## Styling
@@ -2190,7 +2269,7 @@ client({ component: PizzaApp });
2190
2269
  - **Tailwind CSS v4** — compiled at bundle time, configured via CSS.
2191
2270
  Do NOT create `tailwind.config.js` — it will be ignored.
2192
2271
  - Use Tailwind classes for layout, `useTheme()` for dynamic colors.
2193
- - Set theme: `client({ theme: { bg, primary, text, surface, border } })`.
2272
+ - Set theme: `mountClient({ theme: { bg, primary, text, surface, border } })`.
2194
2273
  - Override CSS custom properties for extra tokens:
2195
2274
  `--color-aai-*`, `--radius-aai`, `--font-aai`.
2196
2275
  - Always import `"@alexkroman1/aai-ui/styles.css"` at the top of `client.tsx`.
@@ -7,7 +7,7 @@
7
7
  "scripts": {
8
8
  "dev": "aai dev",
9
9
  "prestart": "aai build --skip-tests",
10
- "start": "node server.mjs",
10
+ "start": "aai start",
11
11
  "build": "aai build",
12
12
  "test": "vitest run --exclude \"**/*.eval.test.*\"",
13
13
  "test:agent": "aai test",
@@ -15,9 +15,10 @@
15
15
  "publish:agent": "aai publish"
16
16
  },
17
17
  "dependencies": {
18
- "@alexkroman1/aai": "^13.3.0",
19
- "@alexkroman1/aai-runtime": "^13.3.0",
20
- "@alexkroman1/aai-ui": "^13.3.0",
18
+ "@alexkroman1/aai": "^15.0.0",
19
+ "@alexkroman1/aai-cli": "^15.0.0",
20
+ "@alexkroman1/aai-runtime": "^15.0.0",
21
+ "@alexkroman1/aai-ui": "^15.0.0",
21
22
  "react": "^19.2.8",
22
23
  "react-dom": "^19.2.8",
23
24
  "tailwindcss": "^4.0.0",
@@ -25,9 +26,8 @@
25
26
  "zod": "^4.5.4"
26
27
  },
27
28
  "devDependencies": {
28
- "@alexkroman1/aai-cli": "^13.3.0",
29
29
  "@tailwindcss/vite": "^4.3.3",
30
- "@types/node": "^26.4.1",
30
+ "@types/node": "^24.10.1",
31
31
  "@types/react": "^19.2.18",
32
32
  "@types/react-dom": "^19.2.5",
33
33
  "@vitejs/plugin-react": "^6.1.1",
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { a as ok, n as fail, t as CliError } from "./_output-DbJt9gsl.mjs";
2
+ import { a as ok, n as fail, t as CliError } from "./_output-DBP9Op_d.mjs";
3
3
  import { n as log, s as unwrapCancel } from "./_ui-DCt4qZrk.mjs";
4
- import { f as checkedResponse, p as isStringArray } from "./_studio-DTi3EdXQ.mjs";
5
- import { t as secretRequest } from "./_slug-api-DlKpj91X.mjs";
6
- import { isRecord } from "@alexkroman1/aai/utils";
4
+ import { f as checkedResponse, p as isStringArray } from "./_studio-DnR_BqFp.mjs";
5
+ import { t as secretRequest } from "./_slug-api-CGvc2YSQ.mjs";
6
+ import { isRecord, plural } from "@alexkroman1/aai/utils";
7
7
  import * as p from "@clack/prompts";
8
8
  import pTimeout from "p-timeout";
9
9
  //#region src/secret.ts
@@ -153,7 +153,7 @@ async function executeSecretList(cwd, server) {
153
153
  const { vars } = checkedResponse(data, (value) => isRecord(value) && isStringArray(value.vars), `the secret list for ${target}`);
154
154
  if (vars.length === 0) log.info("No secrets set. Use `aai secret put <name>` to add one.");
155
155
  else {
156
- log.message(`${vars.length} secret${vars.length === 1 ? "" : "s"}:`);
156
+ log.message(`${vars.length} ${plural(vars.length, "secret")}:`);
157
157
  for (const v of vars) log.message(` ${v}`);
158
158
  }
159
159
  return ok({ secrets: vars });
package/dist/secret.d.ts CHANGED
@@ -38,10 +38,12 @@ export type SecretValueIo = {
38
38
  * sitting at a terminal has no reason to send.
39
39
  */
40
40
  export declare function resolveSecretValue(name: string, mode: OutputMode, io?: SecretValueIo): Promise<string | undefined>;
41
- type SecretPutData = {
42
- name: string;
43
- };
44
- type SecretDeleteData = {
41
+ /**
42
+ * Both the put and the delete result — one shape, so one name. They were two
43
+ * distinct aliases for `{ name: string }`, which implied a difference that does
44
+ * not exist and left "which alias meant the secret result" to be re-decided.
45
+ */
46
+ type SecretNameData = {
45
47
  name: string;
46
48
  };
47
49
  type SecretListData = {
@@ -52,7 +54,7 @@ type SecretListData = {
52
54
  * path). If not, prompt for it — masked — which is what
53
55
  * {@link resolveSecretValue} returning `undefined` asks for.
54
56
  */
55
- export declare function executeSecretPut(cwd: string, name: string, value: string | undefined, server: string | undefined): Promise<CommandResult<SecretPutData>>;
56
- export declare function executeSecretDelete(cwd: string, name: string, server: string | undefined): Promise<CommandResult<SecretDeleteData>>;
57
+ export declare function executeSecretPut(cwd: string, name: string, value: string | undefined, server: string | undefined): Promise<CommandResult<SecretNameData>>;
58
+ export declare function executeSecretDelete(cwd: string, name: string, server: string | undefined): Promise<CommandResult<SecretNameData>>;
57
59
  export declare function executeSecretList(cwd: string, server: string | undefined): Promise<CommandResult<SecretListData>>;
58
60
  export {};