@alexkroman1/aai-cli 13.3.0 → 14.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.
- package/dist/{_agent-Clbq3a4h.mjs → _agent-BzUeqOdj.mjs} +32 -9
- package/dist/_agent.d.ts +23 -0
- package/dist/{_bundler-DEkjO1d8.mjs → _bundler-DolUCMxu.mjs} +8 -7
- package/dist/_cli-common.d.ts +45 -6
- package/dist/{_config-ZY2euhdh.mjs → _config-DYzC6WMD.mjs} +2 -2
- package/dist/_dev-env.d.ts +1 -1
- package/dist/{_dev-server-C13xuGB1.mjs → _dev-server-CSMqF8PN.mjs} +20 -22
- package/dist/_dev-vite-config.d.ts +3 -3
- package/dist/{_init-Ci4QGjp8.mjs → _init-CQ8idAwo.mjs} +9 -23
- package/dist/{_output-DbJt9gsl.mjs → _output-DBP9Op_d.mjs} +22 -13
- package/dist/_output.d.ts +10 -9
- package/dist/{_preflight-B2laWPdU.mjs → _preflight-CcAR577V.mjs} +2 -2
- package/dist/_resource-commands.d.ts +9 -9
- package/dist/{_server-common-BV4_CrP3.mjs → _server-common-vILJp3it.mjs} +1 -1
- package/dist/{_slug-api-DlKpj91X.mjs → _slug-api-CGvc2YSQ.mjs} +18 -9
- package/dist/_slug-api.d.ts +19 -0
- package/dist/{_studio-DTi3EdXQ.mjs → _studio-DnR_BqFp.mjs} +7 -3
- package/dist/_studio-commands.d.ts +13 -13
- package/dist/{_templates-V4l5sZJy.mjs → _templates-CK4oKoeX.mjs} +3 -3
- package/dist/{_typecheck-gate-sV4N5F93.mjs → _typecheck-gate-U3jcil8L.mjs} +3 -2
- package/dist/_typecheck-gate.d.ts +3 -1
- package/dist/{_utils-Dhd058D9.mjs → _utils-D5JGcjiW.mjs} +43 -1
- package/dist/_utils.d.ts +35 -0
- package/dist/{_workflow-determinism-C9l8uC2R.mjs → _workflow-determinism-CvpqMpdh.mjs} +18 -26
- package/dist/_workflow-determinism.d.ts +3 -3
- package/dist/{build-CWyFGe-W.mjs → build-Mxk8gWvX.mjs} +46 -7
- package/dist/build.d.ts +5 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +117 -135
- package/dist/{client-bundler-CT-_26c5.mjs → client-bundler-BJgREAh6.mjs} +1 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-RqlQZ6Y_.mjs → delete-BdFnbnVR.mjs} +4 -4
- package/dist/{deploy-B7f3CK7v.mjs → deploy-uAJ4NukN.mjs} +11 -11
- package/dist/{dev-DamXieW_.mjs → dev-DApPSaE_.mjs} +3 -3
- package/dist/{eval-h3DTVfRw.mjs → eval-BK47A_K5.mjs} +3 -3
- package/dist/{init-D-vFrV8h.mjs → init-DukDxECd.mjs} +11 -19
- package/dist/{login-CDiemNmC.mjs → login-DaLrPOFg.mjs} +4 -6
- package/dist/{logs-DGwPNCzB.mjs → logs-7BRwq0cG.mjs} +21 -14
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +84 -56
- package/dist/scaffold/package.json +4 -4
- package/dist/scaffold/server.mjs +129 -25
- package/dist/{secret-V1w7y9-j.mjs → secret-Cjp912yF.mjs} +5 -5
- package/dist/secret.d.ts +8 -6
- package/dist/{studio-BpMCfUNx.mjs → studio-CpHlNHUZ.mjs} +13 -15
- package/dist/templates/call-audit/agent.test.ts +11 -6
- package/dist/templates/call-audit/agent.ts +2 -2
- package/dist/templates/call-audit/client.tsx +27 -42
- package/dist/templates/call-audit/workflows/audit.ts +7 -5
- package/dist/templates/call-audit/workflows/ingest.ts +7 -7
- package/dist/templates/call-audit/workflows/media.ts +12 -37
- package/dist/templates/call-audit/workflows/summarize.ts +8 -8
- package/dist/templates/call-audit/workflows/sync-api.ts +3 -3
- package/dist/templates/dispatch-center/agent.test.ts +7 -7
- package/dist/templates/dispatch-center/client.tsx +53 -61
- package/dist/templates/dispatch-center/shared.ts +0 -8
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +4 -1
- package/dist/templates/embedded-assets/agent.eval.test.ts +4 -2
- package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
- package/dist/templates/infocom-adventure/client.tsx +40 -36
- package/dist/templates/link-digest/agent.test.ts +3 -3
- package/dist/templates/link-digest/agent.ts +2 -2
- package/dist/templates/link-digest/client.tsx +27 -17
- package/dist/templates/link-digest/workflows/digest.ts +13 -16
- package/dist/templates/night-owl/agent.eval.test.ts +4 -2
- package/dist/templates/night-owl/client.tsx +8 -2
- package/dist/templates/pipeline-simple/agent.test.ts +2 -2
- package/dist/templates/pizza-ordering/client.tsx +5 -4
- package/dist/templates/pizza-ordering/shared.ts +3 -8
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/place_order.ts +3 -2
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/view_order.ts +3 -2
- package/dist/templates/plan-and-execute/agent.test.ts +10 -5
- package/dist/templates/plan-and-execute/client.tsx +2 -2
- package/dist/templates/podcast-digest/agent.test.ts +20 -11
- package/dist/templates/podcast-digest/client.tsx +16 -22
- package/dist/templates/podcast-digest/workflows/digest.ts +21 -19
- package/dist/templates/podcast-digest/workflows/feeds.ts +61 -33
- package/dist/templates/podcast-digest/workflows/slack.ts +7 -7
- package/dist/templates/recap-workflow/agent.eval.test.ts +21 -13
- package/dist/templates/recap-workflow/agent.test.ts +27 -23
- package/dist/templates/recap-workflow/workflows/recap.ts +24 -28
- package/dist/templates/redline/agent.eval.test.ts +5 -8
- package/dist/templates/redline/agent.test.ts +1 -1
- package/dist/templates/redline/client.tsx +36 -34
- package/dist/templates/redline/workflows/redline.ts +17 -17
- package/dist/templates/research-workflow/agent.eval.test.ts +5 -5
- package/dist/templates/research-workflow/agent.test.ts +10 -7
- package/dist/templates/research-workflow/workflows/research.ts +28 -30
- package/dist/templates/retail/agent.test.ts +17 -15
- package/dist/templates/retail/cancel.ts +7 -6
- package/dist/templates/retail/client.tsx +39 -58
- package/dist/templates/retail/payment.ts +4 -3
- package/dist/templates/retail/pending.test.ts +19 -19
- package/dist/templates/retail/store.test.ts +5 -5
- package/dist/templates/retail/swap.ts +8 -11
- package/dist/templates/solo-rpg/agent.eval.test.ts +2 -16
- package/dist/templates/solo-rpg/agent.test.ts +12 -12
- package/dist/templates/solo-rpg/client.tsx +3 -3
- package/dist/templates/spoken-summary/agent.test.ts +9 -9
- package/dist/templates/spoken-summary/agent.ts +3 -3
- package/dist/templates/spoken-summary/client.tsx +23 -30
- package/dist/templates/spoken-summary/workflows/summarize.ts +11 -11
- package/dist/templates/spoken-summary/workflows/transcribe.ts +14 -14
- package/dist/templates/support-line/client.tsx +4 -3
- package/dist/templates/support-line/prompts.ts +0 -2
- package/dist/templates/transcription-workflow/agent.test.ts +20 -12
- package/dist/templates/transcription-workflow/agent.ts +2 -2
- package/dist/templates/transcription-workflow/client.tsx +15 -14
- package/dist/templates/transcription-workflow/recover.ts +1 -1
- package/dist/templates/transcription-workflow/run-panel.tsx +34 -14
- package/dist/templates/transcription-workflow/total-latency.tsx +46 -20
- package/dist/templates/transcription-workflow/workflows/batch.ts +15 -15
- package/dist/templates/transcription-workflow/workflows/downsample.ts +1 -1
- package/dist/templates/transcription-workflow/workflows/normalize.ts +15 -19
- package/dist/templates/transcription-workflow/workflows/stream.ts +12 -12
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +3 -3
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +23 -34
- package/dist/templates/travel-concierge/agent.eval.test.ts +2 -2
- package/dist/templates/travel-concierge/agent.test.ts +15 -7
- package/dist/templates/travel-concierge/client.tsx +2 -2
- package/dist/templates/travel-concierge/shared.ts +10 -14
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +4 -3
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_excursions.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_flights.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_hotels.ts +3 -2
- package/dist/{test-guenJUxi.mjs → test-D032V2cA.mjs} +4 -17
- package/dist/typecheck.mjs +1 -1
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-BZyKLkhV.mjs → workflow-CVioyO3V.mjs} +6 -5
- package/package.json +4 -4
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as ok, t as CliError } from "./_output-
|
|
2
|
+
import { a as ok, t as CliError } from "./_output-DBP9Op_d.mjs";
|
|
3
3
|
import { n as log } from "./_ui-DCt4qZrk.mjs";
|
|
4
|
-
import { o as updateGlobalConfig
|
|
5
|
-
import {
|
|
4
|
+
import { o as updateGlobalConfig } from "./_config-DYzC6WMD.mjs";
|
|
5
|
+
import { a as resolveApprovedServer } from "./_agent-BzUeqOdj.mjs";
|
|
6
6
|
import { linkConfirmationCode, sleep } from "@alexkroman1/aai/internal";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
@@ -133,9 +133,7 @@ function defaultOpenBrowser(url) {
|
|
|
133
133
|
async function executeLogin(opts, deps = {}) {
|
|
134
134
|
const fetchFn = deps.fetchFn ?? globalThis.fetch;
|
|
135
135
|
requireTty();
|
|
136
|
-
const
|
|
137
|
-
const serverUrl = resolveServerUrl(opts.server, void 0, globalConfig.approvedServers ?? []);
|
|
138
|
-
if (opts.server) await approveServer(serverUrl);
|
|
136
|
+
const { serverUrl } = await resolveApprovedServer(null, opts.server);
|
|
139
137
|
if ((await jsonBody(await reachable(fetchFn, `${serverUrl}/studio/auth`, serverUrl), "Reading the server's login configuration")).mode === "none") throw new CliError("login_unavailable", "This server has no browser login configured, so there is no account to link.", "Point `--server` at a platform with browser login configured — linking an account there is the only way to authenticate.");
|
|
140
138
|
const code = randomBytes(32).toString("base64url");
|
|
141
139
|
const linkUrl = `${serverUrl}/?cli-link=${code}`;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as ok } from "./_output-
|
|
2
|
+
import { a as ok } from "./_output-DBP9Op_d.mjs";
|
|
3
3
|
import { n as log } from "./_ui-DCt4qZrk.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { n as getServerInfo } from "./_agent-BzUeqOdj.mjs";
|
|
5
|
+
import { f as checkedResponse } from "./_studio-DnR_BqFp.mjs";
|
|
6
|
+
import { n as slugRequestOn } from "./_slug-api-CGvc2YSQ.mjs";
|
|
6
7
|
import { isRecord, omitUndefined } from "@alexkroman1/aai/utils";
|
|
7
8
|
import { sleep } from "@alexkroman1/aai/internal";
|
|
8
9
|
function isLogsPage(value) {
|
|
@@ -19,12 +20,16 @@ function formatLine(line) {
|
|
|
19
20
|
const mark = line.stream === "stderr" ? " ERR" : "";
|
|
20
21
|
return `${formatTime(line.at)}${mark} ${line.text}`;
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
/**
|
|
24
|
+
* One page, against a target resolved ONCE by the caller.
|
|
25
|
+
*
|
|
26
|
+
* `slugRequestOn` rather than `slugRequest`: under `--follow` this runs every
|
|
27
|
+
* second for the life of the command, and the target is immutable — see that
|
|
28
|
+
* function's doc for what re-resolving it cost.
|
|
29
|
+
*/
|
|
30
|
+
async function readPage(target, after) {
|
|
31
|
+
const data = await slugRequestOn(target, `/logs?after=${after}`, { action: "logs" });
|
|
32
|
+
return checkedResponse(data, isLogsPage, `the logs route for ${target.slug}`);
|
|
28
33
|
}
|
|
29
34
|
/**
|
|
30
35
|
* Print the agent's buffered output, once or continuously.
|
|
@@ -34,7 +39,9 @@ async function readPage(cwd, after, server) {
|
|
|
34
39
|
* into a result object would duplicate what it just printed.
|
|
35
40
|
*/
|
|
36
41
|
async function executeLogs(cwd, opts = {}) {
|
|
37
|
-
const
|
|
42
|
+
const target = await getServerInfo(cwd, opts.server);
|
|
43
|
+
const { slug } = target;
|
|
44
|
+
const page = await readPage(target, -1);
|
|
38
45
|
let cursor = page.cursor;
|
|
39
46
|
let printed = printPage(page);
|
|
40
47
|
if (!opts.follow) {
|
|
@@ -52,11 +59,11 @@ async function executeLogs(cwd, opts = {}) {
|
|
|
52
59
|
while (!opts.signal?.aborted) {
|
|
53
60
|
await sleep(pollMs, omitUndefined({ signal: opts.signal }));
|
|
54
61
|
if (opts.signal?.aborted) break;
|
|
55
|
-
const next = await readPage(
|
|
62
|
+
const next = await readPage(target, cursor).catch(() => void 0);
|
|
56
63
|
if (!next) continue;
|
|
57
|
-
cursor = next.
|
|
58
|
-
running = next.
|
|
59
|
-
printed += printPage(next
|
|
64
|
+
cursor = next.cursor;
|
|
65
|
+
running = next.running;
|
|
66
|
+
printed += printPage(next);
|
|
60
67
|
}
|
|
61
68
|
return ok({
|
|
62
69
|
slug,
|
package/dist/project-config.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as writeGlobalConfig, s as updateProjectConfig } from "./_config-
|
|
2
|
+
import { c as writeGlobalConfig, s as updateProjectConfig } from "./_config-DYzC6WMD.mjs";
|
|
3
3
|
export { updateProjectConfig, writeGlobalConfig as writeConfigHome };
|
package/dist/scaffold/CLAUDE.md
CHANGED
|
@@ -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,
|
|
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,6 +124,13 @@ 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
136
|
`server.mjs` serves this agent from a plain Node process — no platform
|
|
@@ -416,14 +425,14 @@ below.
|
|
|
416
425
|
|
|
417
426
|
### Workflow bodies live in `workflows/`
|
|
418
427
|
|
|
419
|
-
A body is an ordinary exported async function of its input and a `
|
|
428
|
+
A body is an ordinary exported async function of its input and a `WorkflowContext`.
|
|
420
429
|
There is no directive and no compile step of its own — the agent bundle compiles
|
|
421
430
|
`workflows/` like any other source file — and durability is a method call:
|
|
422
431
|
|
|
423
432
|
```ts
|
|
424
|
-
import type {
|
|
433
|
+
import type { WorkflowContext } from "@alexkroman1/aai";
|
|
425
434
|
|
|
426
|
-
export async function digestFlow(input: { url: string }, ctx:
|
|
435
|
+
export async function digestFlow(input: { url: string }, ctx: WorkflowContext) {
|
|
427
436
|
const digest = await ctx.step("summarize", () => summarize(input.url));
|
|
428
437
|
|
|
429
438
|
// Suspended, not blocked: the container is free to exit here and the run
|
|
@@ -515,11 +524,11 @@ The three undurable reads a body most often wants, each journaled — read once
|
|
|
515
524
|
the first reach, and the same value on every later walk:
|
|
516
525
|
|
|
517
526
|
```ts
|
|
518
|
-
import type {
|
|
527
|
+
import type { WorkflowContext } from "@alexkroman1/aai";
|
|
519
528
|
|
|
520
529
|
declare function charge(amount: number, idempotencyKey: string, jitter: number): Promise<void>;
|
|
521
530
|
|
|
522
|
-
export async function chargeFlow(input: { amount: number }, ctx:
|
|
531
|
+
export async function chargeFlow(input: { amount: number }, ctx: WorkflowContext) {
|
|
523
532
|
const startedAt = await ctx.now(); // epoch ms, decided once
|
|
524
533
|
const idempotencyKey = await ctx.uuid(); // still the same id after a crash
|
|
525
534
|
const jitter = await ctx.random(); // one float in [0, 1), journaled per call
|
|
@@ -601,6 +610,22 @@ Five things worth knowing:
|
|
|
601
610
|
which is how a "send it now" tool cuts a scheduled wait short. Naming no ids
|
|
602
611
|
wakes every outstanding SLEEP and deliberately not a `waitFor` deadline, so
|
|
603
612
|
cutting a schedule short cannot also close an approval window.
|
|
613
|
+
- **A SUSPEND is not free, so `ctx.sleep` is not a `setTimeout`.** A wait costs
|
|
614
|
+
a journal write to record it, a queued delivery to bring the run back, and a
|
|
615
|
+
fresh WALK of the body — measured on a deployed agent at roughly a second of
|
|
616
|
+
overhead around the sleep itself, on top of whatever you asked for. There is a
|
|
617
|
+
cliff at the bottom of the range worth knowing about: a sleep shorter
|
|
618
|
+
than the round trip that records it never suspends AT ALL — `ctx.sleep("beat",
|
|
619
|
+
100)` and `ctx.sleep("beat", 0)` are both simply free — while anything longer
|
|
620
|
+
pays the whole cost. Measured, `nap(100)` and `nap(0)` came back within 30 ms
|
|
621
|
+
of each other on a run whose total was 2.1 s.
|
|
622
|
+
|
|
623
|
+
So a sub-second pause is not what this is for. For a short backoff inside a
|
|
624
|
+
step, use an ordinary timer (`sleep` from `@alexkroman1/aai/internal`) — a step
|
|
625
|
+
body may not call `ctx.sleep` anyway, and the engine refuses one that does. Use
|
|
626
|
+
`ctx.sleep` for a wait you want to SURVIVE the process, which is the thing a
|
|
627
|
+
timer cannot do. A body that polls in a loop pays the suspend per iteration,
|
|
628
|
+
which is the strongest argument for the next section: park on the callback.
|
|
604
629
|
|
|
605
630
|
#### A third-party callback is an OPTIMIZATION over a reconciling read
|
|
606
631
|
|
|
@@ -656,15 +681,15 @@ trap somebody has already paid for:
|
|
|
656
681
|
Steps are ordinary exported functions, so a spec imports and calls them. The
|
|
657
682
|
BODY needs an engine, and there are two, for two different questions.
|
|
658
683
|
|
|
659
|
-
**"What did the body ask for?"** — `
|
|
684
|
+
**"What did the body ask for?"** — `createWorkflowContext` from
|
|
660
685
|
`@alexkroman1/aai/testing`. It runs the steps and records the names, the retry
|
|
661
686
|
policies and the sleeps, over one walk with no journal. Nothing replays, so a
|
|
662
687
|
spec built on it must not claim to test durability.
|
|
663
688
|
|
|
664
689
|
```ts no-check
|
|
665
|
-
import {
|
|
690
|
+
import { createWorkflowContext } from "@alexkroman1/aai/testing";
|
|
666
691
|
|
|
667
|
-
const ctx =
|
|
692
|
+
const ctx = createWorkflowContext({ runSteps: false });
|
|
668
693
|
await digestFlow({ url: "https://example.com/a" }, ctx);
|
|
669
694
|
|
|
670
695
|
expect(ctx.steps.map((s) => s.name)).toEqual(["fetchArticle", "summarize", "file"]);
|
|
@@ -729,7 +754,7 @@ root barrel would drag the whole SDK into that bundle.
|
|
|
729
754
|
|
|
730
755
|
```ts
|
|
731
756
|
import { stepEnv } from "@alexkroman1/aai/step";
|
|
732
|
-
import {
|
|
757
|
+
import { stepGenerateOrFail } from "@alexkroman1/aai/step-errors";
|
|
733
758
|
|
|
734
759
|
async function summarize(text: string) {
|
|
735
760
|
// The agent's env by name — the same values a tool reads from `ctx.env`.
|
|
@@ -737,7 +762,7 @@ async function summarize(text: string) {
|
|
|
737
762
|
const style = stepEnv("DIGEST_STYLE") ?? "plain";
|
|
738
763
|
|
|
739
764
|
// One model call, on the agent's own ASSEMBLYAI_API_KEY and default model.
|
|
740
|
-
return await
|
|
765
|
+
return await stepGenerateOrFail(`${style} summary of:\n\n${text}`, {
|
|
741
766
|
system: "Reply with two sentences and nothing else.",
|
|
742
767
|
});
|
|
743
768
|
}
|
|
@@ -749,9 +774,9 @@ before and after a deploy. List what you read in `requiredEnv` and a deploy
|
|
|
749
774
|
checks it for you. And **`stepGenerate` is not `ctx.generate`**: it is one
|
|
750
775
|
request to the AssemblyAI LLM Gateway, with no tools and no structured output,
|
|
751
776
|
because bundling the AI SDK into a step artifact costs megabytes on every
|
|
752
|
-
deploy. Use `
|
|
777
|
+
deploy. Use `stepGenerateJsonOrFail` with a Zod `schema` if you need a shape.
|
|
753
778
|
|
|
754
|
-
### From a step, reach for the `
|
|
779
|
+
### From a step, reach for the `OrFail` call
|
|
755
780
|
|
|
756
781
|
`@alexkroman1/aai/step-errors` publishes a wrapper for every `/step` call that
|
|
757
782
|
can fail against a remote service, and **inside a step the wrapper is the one to
|
|
@@ -759,14 +784,14 @@ use**:
|
|
|
759
784
|
|
|
760
785
|
| Raw, on `@alexkroman1/aai/step` | Use this instead, on `@alexkroman1/aai/step-errors` |
|
|
761
786
|
| --- | --- |
|
|
762
|
-
| `stepGenerate` | `
|
|
763
|
-
| `stepGenerateJson` | `
|
|
764
|
-
| `stepFetch` | `
|
|
765
|
-
| `stepTranscribeSync` | `
|
|
766
|
-
| `stepTranscribeUpload` / `Submit` / `Poll` | the matching `*
|
|
767
|
-
| `sendToChannel` (`/channels`) | `
|
|
768
|
-
|
|
769
|
-
`
|
|
787
|
+
| `stepGenerate` | `stepGenerateOrFail` |
|
|
788
|
+
| `stepGenerateJson` | `stepGenerateJsonOrFail` |
|
|
789
|
+
| `stepFetch` | `stepFetchOrFail` |
|
|
790
|
+
| `stepTranscribeSync` | `stepTranscribeSyncOrFail` |
|
|
791
|
+
| `stepTranscribeUpload` / `Submit` / `Poll` | the matching `*OrFail` |
|
|
792
|
+
| `sendToChannel` (`/channels`) | `sendToChannelOrFail` |
|
|
793
|
+
|
|
794
|
+
`stepFetchOrFail` is the one that is not spelled `*OrFail`, and the name is the
|
|
770
795
|
difference: the others turn an already-thrown failure into a classified one,
|
|
771
796
|
while this also turns a NON-2XX RESPONSE into a throw — `stepFetch` resolves
|
|
772
797
|
with a `404` rather than raising it. Two changes, so two names.
|
|
@@ -804,7 +829,7 @@ bundling rule as `/step` — import them there, never through the root barrel:
|
|
|
804
829
|
recording, or `stepTranscribeUpload` → `stepTranscribeSubmit` →
|
|
805
830
|
`stepTranscribePoll` for a long one, plus `Transcript`, `TranscribeError` and
|
|
806
831
|
the `TRANSCRIBE_*` limits. (There is no `/transcribe` subpath; transcription
|
|
807
|
-
lives on `/step` with the other step primitives.) Use the `
|
|
832
|
+
lives on `/step` with the other step primitives.) Use the `OrFail`
|
|
808
833
|
wrappers above: a provider refusal — a container it will not read, a
|
|
809
834
|
recording with no speech — arrives
|
|
810
835
|
with `retryable: false`, and unclassified a step re-uploads the same bytes
|
|
@@ -841,19 +866,19 @@ export async function measure(uploadId: string) {
|
|
|
841
866
|
|
|
842
867
|
A run that finishes while nobody is on the line needs somewhere to put the
|
|
843
868
|
result. `slackChannel({ webhookUrl })` names a destination and
|
|
844
|
-
`
|
|
869
|
+
`sendToChannelOrFail(channel, message)` posts to it:
|
|
845
870
|
|
|
846
871
|
```ts no-check
|
|
847
872
|
import { type ChannelMessage, slackChannel } from "@alexkroman1/aai/channels";
|
|
848
873
|
import { requireStepEnv } from "@alexkroman1/aai/step";
|
|
849
|
-
import {
|
|
874
|
+
import { sendToChannelOrFail } from "@alexkroman1/aai/step-errors";
|
|
850
875
|
|
|
851
876
|
export async function announce(headline: string, points: string[]) {
|
|
852
877
|
const message: ChannelMessage = {
|
|
853
878
|
text: headline,
|
|
854
879
|
sections: points.map((point) => ({ text: point })),
|
|
855
880
|
};
|
|
856
|
-
return await
|
|
881
|
+
return await sendToChannelOrFail(slackChannel({ webhookUrl: requireStepEnv("SLACK_WEBHOOK_URL") }), message);
|
|
857
882
|
}
|
|
858
883
|
```
|
|
859
884
|
|
|
@@ -862,7 +887,7 @@ it with `aai secret put`. A channel's credential is its DESTINATION and is
|
|
|
862
887
|
passed in, which is why no channel reads an env var of its own. `ChannelMessage`
|
|
863
888
|
is rendered per platform, so the same message is legal on a channel kind added
|
|
864
889
|
later; `isSlackWebhookUrl` / `isSlackWorkflowTriggerUrl` validate a pasted URL
|
|
865
|
-
before a run depends on it, and `
|
|
890
|
+
before a run depends on it, and `explainChannelFailure` turns a refusal into a sentence
|
|
866
891
|
a person can act on. `podcast-digest` is the worked example.
|
|
867
892
|
|
|
868
893
|
### A step's HTTP: use `stepFetch`, not `fetch`
|
|
@@ -941,11 +966,11 @@ Both are on `@alexkroman1/aai/step`, and `spoken-summary` is the template that
|
|
|
941
966
|
shows the whole round trip.
|
|
942
967
|
|
|
943
968
|
```ts
|
|
944
|
-
import { stepSpeak,
|
|
969
|
+
import { stepSpeak, stepWriteUpload } from "@alexkroman1/aai/step";
|
|
945
970
|
|
|
946
971
|
export async function narrate(script: string) {
|
|
947
972
|
const spoken = await stepSpeak(script, { voice: "jane" });
|
|
948
|
-
const stored = await
|
|
973
|
+
const stored = await stepWriteUpload(spoken.audio, { name: "summary.wav", type: "audio/wav" });
|
|
949
974
|
return { audio: stored.id, durationMs: spoken.durationMs };
|
|
950
975
|
}
|
|
951
976
|
```
|
|
@@ -962,11 +987,13 @@ opens and produces silence rather than an error. The `AssemblyAITtsVoice` type
|
|
|
962
987
|
gives you autocomplete over it and nothing more: it accepts any string, so that
|
|
963
988
|
a voice the service adds after this release still compiles.
|
|
964
989
|
|
|
965
|
-
**`
|
|
966
|
-
output is read back as JSON, so audio cannot travel in one — the same rule
|
|
967
|
-
keeps an uploaded recording's bytes out of a run's INPUT, arriving at the
|
|
968
|
-
end of the run. Store the bytes, return the **id**, and let the page fetch
|
|
969
|
-
with `api.download(id)`.
|
|
990
|
+
**`stepWriteUpload` is `stepReadUpload`'s other direction, and you need it.** A
|
|
991
|
+
run's output is read back as JSON, so audio cannot travel in one — the same rule
|
|
992
|
+
that keeps an uploaded recording's bytes out of a run's INPUT, arriving at the
|
|
993
|
+
other end of the run. Store the bytes, return the **id**, and let the page fetch
|
|
994
|
+
it with `api.download(id)`. A step that wants the record rather than the bytes
|
|
995
|
+
reads it with `stepUploadInfo(id)`, which answers an `UploadInfo` — the name,
|
|
996
|
+
the size stored so far, and whether that is all of it.
|
|
970
997
|
|
|
971
998
|
Three rules come with it:
|
|
972
999
|
|
|
@@ -1020,12 +1047,13 @@ naming a shape is what asks the compiler to make you handle the failure.
|
|
|
1020
1047
|
|
|
1021
1048
|
### The page
|
|
1022
1049
|
|
|
1023
|
-
A workflow app's `client.tsx` mounts with `
|
|
1024
|
-
there is no session to build, so no socket, no audio graph
|
|
1025
|
-
request. Everything else is the same file, React and
|
|
1050
|
+
A workflow app's `client.tsx` mounts with `mountPage()` rather than
|
|
1051
|
+
`mountClient()` — there is no session to build, so no socket, no audio graph
|
|
1052
|
+
and no microphone request. Everything else is the same file, React and
|
|
1053
|
+
Tailwind included.
|
|
1026
1054
|
|
|
1027
1055
|
```tsx no-check
|
|
1028
|
-
import { createWorkflowApi,
|
|
1056
|
+
import { createWorkflowApi, mountPage, useWorkflowRun } from "@alexkroman1/aai-ui";
|
|
1029
1057
|
import "@alexkroman1/aai-ui/styles.css";
|
|
1030
1058
|
import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
|
|
1031
1059
|
import { useState } from "react";
|
|
@@ -1053,7 +1081,7 @@ export function App() {
|
|
|
1053
1081
|
);
|
|
1054
1082
|
}
|
|
1055
1083
|
|
|
1056
|
-
|
|
1084
|
+
mountPage({ name: "Link Digest", component: App });
|
|
1057
1085
|
```
|
|
1058
1086
|
|
|
1059
1087
|
`api.start()` resolves as soon as the RUN EXISTS, not when it finishes — that
|
|
@@ -1172,7 +1200,7 @@ statement about pipeline mode, not about the SDK.
|
|
|
1172
1200
|
### Answering a phone call
|
|
1173
1201
|
|
|
1174
1202
|
A deployed voice agent already serves carrier media streams — there is nothing
|
|
1175
|
-
to switch on. `
|
|
1203
|
+
to switch on. `createRuntimeServer` mounts `WS /phone` whenever the agent is a voice
|
|
1176
1204
|
agent (`telephony` defaults to `true`, and to `false` for a `page: "static"`
|
|
1177
1205
|
workflow app, which has no stages to put on a call). Point the carrier at it
|
|
1178
1206
|
with a `carrier` query parameter naming who is dialling:
|
|
@@ -1188,7 +1216,7 @@ the bridge transcodes in both directions, so the agent, its tools and its slots
|
|
|
1188
1216
|
behave exactly as they do in the browser — a phone call is a transport, not a
|
|
1189
1217
|
mode. Nothing about `agent.ts` changes to support one.
|
|
1190
1218
|
|
|
1191
|
-
Turn the route off with `telephony: false` on `
|
|
1219
|
+
Turn the route off with `telephony: false` on `createRuntimeServer`. If you are
|
|
1192
1220
|
embedding the runtime yourself rather than deploying, the pieces are
|
|
1193
1221
|
`createTelephonyBridge`, `startTelephonySession`, `TELEPHONY_PATH` and
|
|
1194
1222
|
`carrierByName`, all on `@alexkroman1/aai-runtime`.
|
|
@@ -1268,12 +1296,12 @@ API keys require it; the US endpoints reject them. Example:
|
|
|
1268
1296
|
| Factory | SDK package | Env var |
|
|
1269
1297
|
| --------------- | ------------------- | ------------------------------ |
|
|
1270
1298
|
| `anthropicLlm` | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
|
|
1271
|
-
| `
|
|
1299
|
+
| `openAILlm` | `@ai-sdk/openai` | `OPENAI_API_KEY` |
|
|
1272
1300
|
| `googleLlm` | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
|
|
1273
1301
|
| `mistralLlm` | `@ai-sdk/mistral` | `MISTRAL_API_KEY` |
|
|
1274
|
-
| `
|
|
1302
|
+
| `xAILlm` | `@ai-sdk/xai` | `XAI_API_KEY` |
|
|
1275
1303
|
| `groqLlm` | `@ai-sdk/groq` | `GROQ_API_KEY` |
|
|
1276
|
-
| `
|
|
1304
|
+
| `openRouterLlm` | `@ai-sdk/openai` | `OPENROUTER_API_KEY` |
|
|
1277
1305
|
| `gatewayLlm` | `ai` (built in) | `AI_GATEWAY_API_KEY` |
|
|
1278
1306
|
| `assemblyAILlm` | `@ai-sdk/openai` | `ASSEMBLYAI_API_KEY` |
|
|
1279
1307
|
|
|
@@ -1283,10 +1311,10 @@ shared by all of them except `assemblyAILlm`. Example:
|
|
|
1283
1311
|
because a third-party vendor's catalog is not this SDK's to default from;
|
|
1284
1312
|
`assemblyAILlm()` is the one bare call, since it has a default model.
|
|
1285
1313
|
|
|
1286
|
-
`
|
|
1314
|
+
`openRouterLlm` routes through [OpenRouter](https://openrouter.ai) — an
|
|
1287
1315
|
OpenAI-compatible endpoint fronting hundreds of models addressed as
|
|
1288
1316
|
`"creator/model"`, e.g.
|
|
1289
|
-
`
|
|
1317
|
+
`openRouterLlm({ model: "meta-llama/llama-3.3-70b-instruct" })`. It needs
|
|
1290
1318
|
no extra SDK install (it reuses the `@ai-sdk/openai` client).
|
|
1291
1319
|
|
|
1292
1320
|
`gatewayLlm` routes through the [Vercel AI
|
|
@@ -1947,7 +1975,7 @@ Those three cover almost everything an agent wants. A database is for data that
|
|
|
1947
1975
|
must outlive a session AND be queryable: a ledger, filed records, cross-session
|
|
1948
1976
|
saves.
|
|
1949
1977
|
|
|
1950
|
-
## Custom UI — `
|
|
1978
|
+
## Custom UI — `mountClient()`
|
|
1951
1979
|
|
|
1952
1980
|
File: `client.tsx` alongside `agent.ts`. Uses **React** (not Preact).
|
|
1953
1981
|
Always import `"@alexkroman1/aai-ui/styles.css"` first.
|
|
@@ -1957,9 +1985,9 @@ Always import `"@alexkroman1/aai-ui/styles.css"` first.
|
|
|
1957
1985
|
```tsx
|
|
1958
1986
|
/// <reference types="vite/client" />
|
|
1959
1987
|
import "@alexkroman1/aai-ui/styles.css";
|
|
1960
|
-
import {
|
|
1988
|
+
import { mountClient } from "@alexkroman1/aai-ui";
|
|
1961
1989
|
|
|
1962
|
-
|
|
1990
|
+
mountClient({ name: "My Agent" });
|
|
1963
1991
|
```
|
|
1964
1992
|
|
|
1965
1993
|
### Tier 1 with sidebar
|
|
@@ -1967,7 +1995,7 @@ client({ name: "My Agent" });
|
|
|
1967
1995
|
```tsx
|
|
1968
1996
|
/// <reference types="vite/client" />
|
|
1969
1997
|
import "@alexkroman1/aai-ui/styles.css";
|
|
1970
|
-
import {
|
|
1998
|
+
import { mountClient, useEvent } from "@alexkroman1/aai-ui";
|
|
1971
1999
|
import { useState } from "react";
|
|
1972
2000
|
|
|
1973
2001
|
function Sidebar() {
|
|
@@ -1982,7 +2010,7 @@ function Sidebar() {
|
|
|
1982
2010
|
);
|
|
1983
2011
|
}
|
|
1984
2012
|
|
|
1985
|
-
|
|
2013
|
+
mountClient({ name: "My Agent", sidebar: Sidebar });
|
|
1986
2014
|
```
|
|
1987
2015
|
|
|
1988
2016
|
### Tier 2 — full custom component
|
|
@@ -1990,7 +2018,7 @@ client({ name: "My Agent", sidebar: Sidebar });
|
|
|
1990
2018
|
```tsx
|
|
1991
2019
|
/// <reference types="vite/client" />
|
|
1992
2020
|
import "@alexkroman1/aai-ui/styles.css";
|
|
1993
|
-
import {
|
|
2021
|
+
import { mountClient, useSession } from "@alexkroman1/aai-ui";
|
|
1994
2022
|
|
|
1995
2023
|
function MyApp() {
|
|
1996
2024
|
const { messages, userTranscript, started, running, start, toggle, end } =
|
|
@@ -2011,10 +2039,10 @@ function MyApp() {
|
|
|
2011
2039
|
);
|
|
2012
2040
|
}
|
|
2013
2041
|
|
|
2014
|
-
|
|
2042
|
+
mountClient({ component: MyApp });
|
|
2015
2043
|
```
|
|
2016
2044
|
|
|
2017
|
-
### `
|
|
2045
|
+
### `mountClient()` config
|
|
2018
2046
|
|
|
2019
2047
|
| Field | Type | Default | Description |
|
|
2020
2048
|
| --- | --- | --- | --- |
|
|
@@ -2172,7 +2200,7 @@ Property 'children' is missing` build error:
|
|
|
2172
2200
|
```tsx
|
|
2173
2201
|
/// <reference types="vite/client" />
|
|
2174
2202
|
import "@alexkroman1/aai-ui/styles.css";
|
|
2175
|
-
import { ChatView,
|
|
2203
|
+
import { ChatView, mountClient, StartScreen } from "@alexkroman1/aai-ui";
|
|
2176
2204
|
|
|
2177
2205
|
function PizzaApp() {
|
|
2178
2206
|
return (
|
|
@@ -2182,7 +2210,7 @@ function PizzaApp() {
|
|
|
2182
2210
|
);
|
|
2183
2211
|
}
|
|
2184
2212
|
|
|
2185
|
-
|
|
2213
|
+
mountClient({ component: PizzaApp });
|
|
2186
2214
|
```
|
|
2187
2215
|
|
|
2188
2216
|
## Styling
|
|
@@ -2190,7 +2218,7 @@ client({ component: PizzaApp });
|
|
|
2190
2218
|
- **Tailwind CSS v4** — compiled at bundle time, configured via CSS.
|
|
2191
2219
|
Do NOT create `tailwind.config.js` — it will be ignored.
|
|
2192
2220
|
- Use Tailwind classes for layout, `useTheme()` for dynamic colors.
|
|
2193
|
-
- Set theme: `
|
|
2221
|
+
- Set theme: `mountClient({ theme: { bg, primary, text, surface, border } })`.
|
|
2194
2222
|
- Override CSS custom properties for extra tokens:
|
|
2195
2223
|
`--color-aai-*`, `--radius-aai`, `--font-aai`.
|
|
2196
2224
|
- Always import `"@alexkroman1/aai-ui/styles.css"` at the top of `client.tsx`.
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"publish:agent": "aai publish"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@alexkroman1/aai": "^
|
|
19
|
-
"@alexkroman1/aai-runtime": "^
|
|
20
|
-
"@alexkroman1/aai-ui": "^
|
|
18
|
+
"@alexkroman1/aai": "^14.0.0",
|
|
19
|
+
"@alexkroman1/aai-runtime": "^14.0.0",
|
|
20
|
+
"@alexkroman1/aai-ui": "^14.0.0",
|
|
21
21
|
"react": "^19.2.8",
|
|
22
22
|
"react-dom": "^19.2.8",
|
|
23
23
|
"tailwindcss": "^4.0.0",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"zod": "^4.5.4"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@alexkroman1/aai-cli": "^
|
|
28
|
+
"@alexkroman1/aai-cli": "^14.0.0",
|
|
29
29
|
"@tailwindcss/vite": "^4.3.3",
|
|
30
30
|
"@types/node": "^26.4.1",
|
|
31
31
|
"@types/react": "^19.2.18",
|