@alexkroman1/aai-cli 5.3.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +51 -0
  2. package/dist/{_agent-D8zBb3M5.mjs → _agent-DMyOab9_.mjs} +2 -2
  3. package/dist/{_bundler-DFS4xxqE.mjs → _bundler-Cjaxa2wi.mjs} +1 -1
  4. package/dist/{_config-D3F9km8X.mjs → _config-5AEqhh-O.mjs} +2 -2
  5. package/dist/{_dev-server-BBUWo0sb.mjs → _dev-server-vV05Fnki.mjs} +3 -3
  6. package/dist/{_init-DU-sXH6S.mjs → _init-BZ9t_Kz-.mjs} +3 -56
  7. package/dist/{_slug-api-19R5kZ0U.mjs → _slug-api-DaqQJHk8.mjs} +1 -1
  8. package/dist/_templates-Bv8CR800.mjs +69 -0
  9. package/dist/_templates.d.ts +6 -0
  10. package/dist/{build-Xq1xxoSg.mjs → build-D_PgQOD4.mjs} +1 -1
  11. package/dist/cli.mjs +59 -17
  12. package/dist/{client-bundler-BWDkUeEP.mjs → client-bundler-yiWoXrgb.mjs} +3 -0
  13. package/dist/client-bundler.d.ts +9 -0
  14. package/dist/client-bundler.mjs +1 -1
  15. package/dist/{delete-kSXFLxek.mjs → delete-DXilFBb1.mjs} +1 -1
  16. package/dist/{deploy-Ci0X1gp2.mjs → deploy-1eaXcfUw.mjs} +3 -3
  17. package/dist/{dev-g7sGt8qX.mjs → dev-gVNdGFYY.mjs} +1 -1
  18. package/dist/{init-BjeK8crW.mjs → init-DoU4_txp.mjs} +3 -3
  19. package/dist/login-C59ZHzuO.mjs +109 -0
  20. package/dist/login.d.ts +34 -0
  21. package/dist/scaffold/CLAUDE.md +190 -120
  22. package/dist/scaffold/package.json +3 -3
  23. package/dist/{secret-DOva9OGk.mjs → secret-CGAIAbUx.mjs} +1 -1
  24. package/dist/{storage-vM6HjDZZ.mjs → storage-CnhOayhm.mjs} +1 -1
  25. package/dist/templates/code-interpreter/agent.ts +1 -2
  26. package/dist/templates/dispatch-center/agent.test.ts +3 -3
  27. package/dist/templates/dispatch-center/agent.ts +7 -7
  28. package/dist/templates/dispatch-center/client.tsx +7 -2
  29. package/dist/templates/dispatch-center/tools/incident_add_note.ts +1 -1
  30. package/dist/templates/dispatch-center/tools/incident_create.ts +1 -1
  31. package/dist/templates/dispatch-center/tools/incident_escalate.ts +1 -1
  32. package/dist/templates/dispatch-center/tools/incident_get.ts +1 -1
  33. package/dist/templates/dispatch-center/tools/incident_triage.ts +1 -1
  34. package/dist/templates/dispatch-center/tools/incident_update_status.ts +1 -1
  35. package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
  36. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +1 -1
  37. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +1 -1
  38. package/dist/templates/dispatch-center/tools/resources_get_available.ts +1 -1
  39. package/dist/templates/dispatch-center/tools/resources_update_status.ts +1 -1
  40. package/dist/templates/embedded-assets/agent.ts +2 -3
  41. package/dist/templates/health-assistant/agent.ts +3 -4
  42. package/dist/templates/infocom-adventure/agent.ts +10 -12
  43. package/dist/templates/infocom-adventure/client.tsx +5 -5
  44. package/dist/templates/math-buddy/agent.ts +7 -6
  45. package/dist/templates/night-owl/agent.ts +2 -3
  46. package/dist/templates/personal-finance/agent.ts +1 -2
  47. package/dist/templates/pipeline-simple/agent.test.ts +13 -13
  48. package/dist/templates/pipeline-simple/agent.ts +5 -6
  49. package/dist/templates/pizza-ordering/agent.test.ts +13 -11
  50. package/dist/templates/pizza-ordering/agent.ts +5 -6
  51. package/dist/templates/retail/address.ts +29 -0
  52. package/dist/templates/retail/agent.test.ts +1030 -0
  53. package/dist/templates/retail/agent.ts +71 -0
  54. package/dist/templates/retail/authenticate.ts +34 -0
  55. package/dist/templates/retail/client.tsx +459 -0
  56. package/dist/templates/retail/refund.ts +19 -0
  57. package/dist/templates/retail/registry.test.ts +182 -0
  58. package/dist/templates/retail/resolve.test.ts +158 -0
  59. package/dist/templates/retail/resolve.ts +158 -0
  60. package/dist/templates/retail/seed.json +4260 -0
  61. package/dist/templates/retail/seed.test.ts +224 -0
  62. package/dist/templates/retail/shared.test.ts +192 -0
  63. package/dist/templates/retail/shared.ts +430 -0
  64. package/dist/templates/retail/store.test.ts +256 -0
  65. package/dist/templates/retail/store.ts +243 -0
  66. package/dist/templates/retail/swap.test.ts +186 -0
  67. package/dist/templates/retail/swap.ts +138 -0
  68. package/dist/templates/retail/system-prompt.md +111 -0
  69. package/dist/templates/retail/tools/cancel_pending_order.ts +82 -0
  70. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +83 -0
  71. package/dist/templates/retail/tools/find_user_id_by_email.ts +32 -0
  72. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +38 -0
  73. package/dist/templates/retail/tools/get_item_details.ts +29 -0
  74. package/dist/templates/retail/tools/get_order_details.ts +41 -0
  75. package/dist/templates/retail/tools/get_product_details.ts +33 -0
  76. package/dist/templates/retail/tools/get_user_details.ts +44 -0
  77. package/dist/templates/retail/tools/list_all_product_types.ts +24 -0
  78. package/dist/templates/retail/tools/modify_pending_order_address.ts +46 -0
  79. package/dist/templates/retail/tools/modify_pending_order_items.ts +102 -0
  80. package/dist/templates/retail/tools/modify_pending_order_payment.ts +103 -0
  81. package/dist/templates/retail/tools/modify_user_address.ts +38 -0
  82. package/dist/templates/retail/tools/return_delivered_order_items.ts +101 -0
  83. package/dist/templates/retail/tools/transfer_to_human_agents.ts +22 -0
  84. package/dist/templates/simple/agent.test.ts +18 -0
  85. package/dist/templates/simple/agent.ts +3 -0
  86. package/dist/templates/solo-rpg/agent.test.ts +2 -2
  87. package/dist/templates/solo-rpg/agent.ts +1 -2
  88. package/dist/templates/solo-rpg/tools/action_roll.ts +1 -1
  89. package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
  90. package/dist/templates/solo-rpg/tools/oracle.ts +1 -1
  91. package/dist/templates/solo-rpg/tools/save_game.ts +1 -1
  92. package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
  93. package/dist/templates/solo-rpg/tools/update_state.ts +1 -1
  94. package/dist/templates/web-researcher/agent.ts +1 -2
  95. package/dist/typecheck.d.ts +18 -0
  96. package/dist/typecheck.mjs +8 -0
  97. package/dist/worker-bundler.d.ts +9 -1
  98. package/dist/worker-bundler.mjs +8 -0
  99. package/package.json +3 -3
@@ -11,15 +11,15 @@
11
11
  "deploy": "aai deploy"
12
12
  },
13
13
  "dependencies": {
14
- "@alexkroman1/aai": "^5.3.0",
15
- "@alexkroman1/aai-ui": "^5.3.0",
14
+ "@alexkroman1/aai": "^5.5.0",
15
+ "@alexkroman1/aai-ui": "^5.5.0",
16
16
  "react": "^19.2.8",
17
17
  "react-dom": "^19.2.8",
18
18
  "tailwindcss": "^4.0.0",
19
19
  "zod": "^4.4.3"
20
20
  },
21
21
  "devDependencies": {
22
- "@alexkroman1/aai-cli": "^5.3.0",
22
+ "@alexkroman1/aai-cli": "^5.5.0",
23
23
  "@tailwindcss/vite": "^4.3.3",
24
24
  "@types/node": "^26.1.1",
25
25
  "@types/react": "^19.2.17",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as unwrapCancel, n as log, s as fail, u as ok } from "./_ui-8kOEB-JH.mjs";
3
- import { t as slugRequest } from "./_slug-api-19R5kZ0U.mjs";
3
+ import { t as slugRequest } from "./_slug-api-DaqQJHk8.mjs";
4
4
  import * as p from "@clack/prompts";
5
5
  import { text } from "node:stream/consumers";
6
6
  //#region secret.ts
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as log, s as fail, u as ok } from "./_ui-8kOEB-JH.mjs";
3
- import { t as slugRequest } from "./_slug-api-19R5kZ0U.mjs";
3
+ import { t as slugRequest } from "./_slug-api-DaqQJHk8.mjs";
4
4
  import * as p from "@clack/prompts";
5
5
  //#region storage.ts
6
6
  async function storageRequest(cwd, init, server) {
@@ -1,9 +1,8 @@
1
- import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
1
+ import { agent } from "@alexkroman1/aai";
2
2
  import systemPrompt from "./system-prompt.md?raw";
3
3
 
4
4
  export default agent({
5
5
  name: "Coda",
6
- ...assemblyAIPipeline(),
7
6
  systemPrompt,
8
7
  greeting:
9
8
  "Hey, I'm Coda. I solve problems by writing and running code. Try asking me something like, what's the 50th fibonacci number, or what day of the week was January 1st 2000.",
@@ -90,17 +90,17 @@ describe("dispatch-center template", () => {
90
90
 
91
91
  test("negative casualty counts are rejected by the parameter schemas", () => {
92
92
  expect(
93
- incidentTriage.parameters?.safeParse({ incidentId: "INC-0001", casualtyUpdate: -5 }).success,
93
+ incidentTriage.inputSchema?.safeParse({ incidentId: "INC-0001", casualtyUpdate: -5 }).success,
94
94
  ).toBe(false);
95
95
  expect(
96
- incidentCreate.parameters?.safeParse({
96
+ incidentCreate.inputSchema?.safeParse({
97
97
  location: "1 First St",
98
98
  description: "fire",
99
99
  estimatedCasualties: -1,
100
100
  }).success,
101
101
  ).toBe(false);
102
102
  expect(
103
- incidentUpdateStatus.parameters?.safeParse({
103
+ incidentUpdateStatus.inputSchema?.safeParse({
104
104
  incidentId: "INC-0001",
105
105
  status: "on_scene",
106
106
  casualtyUpdate: { confirmed: -2 },
@@ -1,5 +1,5 @@
1
- import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
2
- import { assemblyAI } from "@alexkroman1/aai/stt";
1
+ import { agent } from "@alexkroman1/aai";
2
+ import { assemblyAIStt } from "@alexkroman1/aai/stt";
3
3
  import { dashboardView } from "./shared.ts";
4
4
  import systemPrompt from "./system-prompt.md?raw";
5
5
  import { incidentAddNote } from "./tools/incident_add_note.ts";
@@ -17,14 +17,14 @@ import { resourcesUpdateStatus } from "./tools/resources_update_status.ts";
17
17
 
18
18
  export default agent({
19
19
  name: "Dispatch Command Center",
20
- ...assemblyAIPipeline(),
21
20
  // One projection replaces eleven `ctx.send("incidents", ...)` calls, and
22
21
  // is the single place that decides caller PII stays server-side.
23
22
  syncState: dashboardView,
24
- // Overriding the STT stage: a dispatcher reads addresses and unit numbers
25
- // in bursts with pauses inside one message, so end-of-turn silence has to
26
- // be longer than the default or "unit twelve … respond to" splits in two.
27
- stt: assemblyAI({ minTurnSilenceMs: 2200 }),
23
+ // Only the STT stage is declared (LLM and TTS stay on the AssemblyAI
24
+ // defaults): a dispatcher reads addresses and unit numbers in bursts with
25
+ // pauses inside one message, so end-of-turn silence has to be longer than
26
+ // the default or "unit twelve … respond to" splits in two.
27
+ stt: assemblyAIStt({ minTurnSilenceMs: 2200 }),
28
28
  systemPrompt,
29
29
  greeting:
30
30
  "Dispatch Command Center online. Restoring operational state. I'm ready to take incoming calls, manage active incidents, or run dispatch operations. Say 'dashboard' for a full status report. What do we have.",
@@ -300,13 +300,18 @@ function App() {
300
300
  >
301
301
  {session.running ? "Pause" : "Resume"}
302
302
  </button>
303
+ {/* end() hangs up and flips `started` back, so the UI
304
+ returns to "Start Dispatch" and the next start is a
305
+ brand-new shift (fresh incident board, greeting
306
+ included). reset() would keep the call live — the
307
+ buttons never toggle back. */}
303
308
  <button
304
309
  type="button"
305
310
  className="px-4 py-2 border-none rounded-md font-mono text-xs font-semibold uppercase tracking-wider cursor-pointer text-white"
306
311
  style={{ background: "#dc2626" }}
307
- onClick={() => session.reset()}
312
+ onClick={() => session.end()}
308
313
  >
309
- Reset
314
+ End Shift
310
315
  </button>
311
316
  </>
312
317
  )}
@@ -4,7 +4,7 @@ import { findIncident, logEvent, updateState } from "../shared.ts";
4
4
 
5
5
  export const incidentAddNote = tool({
6
6
  description: "Add a situational update note to an incident's timeline.",
7
- parameters: z.object({
7
+ inputSchema: z.object({
8
8
  incidentId: z.string().max(20).describe("The incident ID"),
9
9
  note: z.string().max(1000).describe("The note to add"),
10
10
  source: z.string().max(100).describe("Who reported this — unit callsign or caller").optional(),
@@ -13,7 +13,7 @@ import {
13
13
 
14
14
  export const incidentCreate = tool({
15
15
  description: "Create a new incident from an incoming emergency call.",
16
- parameters: z.object({
16
+ inputSchema: z.object({
17
17
  location: z.string().max(300).describe("Address or location description"),
18
18
  description: z.string().max(2000).describe("Nature of the emergency as described by caller"),
19
19
  callerName: z.string().max(120).describe("Caller's name").optional(),
@@ -11,7 +11,7 @@ import {
11
11
 
12
12
  export const incidentEscalate = tool({
13
13
  description: "Escalate an incident when it exceeds current capacity or severity increases.",
14
- parameters: z.object({
14
+ inputSchema: z.object({
15
15
  incidentId: z.string().max(20).describe("The incident ID"),
16
16
  reason: z.string().max(1000).describe("Reason for escalation"),
17
17
  requestMutualAid: z
@@ -4,7 +4,7 @@ import { findIncident, getApplicableProtocols, getState, incidentAgeMinutes } fr
4
4
 
5
5
  export const incidentGet = tool({
6
6
  description: "Get full details on a specific incident including timeline and assigned resources.",
7
- parameters: z.object({
7
+ inputSchema: z.object({
8
8
  incidentId: z.string().max(20).describe("The incident ID"),
9
9
  }),
10
10
  async execute(args, ctx) {
@@ -15,7 +15,7 @@ import {
15
15
  export const incidentTriage = tool({
16
16
  description:
17
17
  "Triage an incident — confirm or override severity, type, hazards, and casualty count.",
18
- parameters: z.object({
18
+ inputSchema: z.object({
19
19
  incidentId: z.string().max(20).describe("The incident ID"),
20
20
  severity: z.enum(SEVERITIES).describe("Confirmed severity after triage").optional(),
21
21
  type: z.enum(INCIDENT_TYPES).describe("Confirmed incident type").optional(),
@@ -4,7 +4,7 @@ import { assertNotResolved, findIncident, logEvent, updateState } from "../share
4
4
 
5
5
  export const incidentUpdateStatus = tool({
6
6
  description: "Update an incident's status (en_route, on_scene, resolved, escalated).",
7
- parameters: z.object({
7
+ inputSchema: z.object({
8
8
  incidentId: z.string().max(20).describe("The incident ID"),
9
9
  status: z.enum(["en_route", "on_scene", "resolved", "escalated"]).describe("New status"),
10
10
  notes: z.string().max(1000).describe("Status update notes").optional(),
@@ -4,7 +4,7 @@ import { getApplicableProtocols, INCIDENT_TYPES, SEVERITIES } from "../shared.ts
4
4
 
5
5
  export const opsProtocols = tool({
6
6
  description: "Look up step-by-step response protocols for a given incident type and severity.",
7
- parameters: z.object({
7
+ inputSchema: z.object({
8
8
  incidentType: z.enum(INCIDENT_TYPES).describe("Type of incident"),
9
9
  severity: z.enum(SEVERITIES).describe("Severity level"),
10
10
  }),
@@ -119,7 +119,7 @@ const SCENARIO_NAMES = Object.keys(scenarios) as [ScenarioName, ...ScenarioName[
119
119
 
120
120
  export const opsRunScenario = tool({
121
121
  description: "Run a training scenario that creates simulated incidents for dispatch practice.",
122
- parameters: z.object({
122
+ inputSchema: z.object({
123
123
  scenario: z.enum(SCENARIO_NAMES).describe("Scenario type to simulate"),
124
124
  }),
125
125
  async execute(args, ctx) {
@@ -12,7 +12,7 @@ import {
12
12
  export const resourcesDispatch = tool({
13
13
  description:
14
14
  "Dispatch units to an incident. Can auto-dispatch recommended resources or manually specify callsigns.",
15
- parameters: z.object({
15
+ inputSchema: z.object({
16
16
  incidentId: z.string().max(20).describe("The incident ID"),
17
17
  callsigns: z
18
18
  .array(z.string().max(50))
@@ -4,7 +4,7 @@ import { getState, RESOURCE_TYPES } from "../shared.ts";
4
4
 
5
5
  export const resourcesGetAvailable = tool({
6
6
  description: "List available resources, optionally filtered by type.",
7
- parameters: z.object({
7
+ inputSchema: z.object({
8
8
  type: z
9
9
  .enum([...RESOURCE_TYPES, "all"])
10
10
  .describe("Filter by resource type, or 'all'")
@@ -4,7 +4,7 @@ import { logEvent, RESOURCE_STATUSES, updateState } from "../shared.ts";
4
4
 
5
5
  export const resourcesUpdateStatus = tool({
6
6
  description: "Update a resource unit's status when it radios in.",
7
- parameters: z.object({
7
+ inputSchema: z.object({
8
8
  callsign: z.string().max(50).describe("The resource callsign"),
9
9
  status: z.enum(RESOURCE_STATUSES).describe("New status"),
10
10
  notes: z.string().max(1000).describe("Status notes").optional(),
@@ -1,4 +1,4 @@
1
- import { agent, assemblyAIPipeline, tool } from "@alexkroman1/aai";
1
+ import { agent, tool } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import knowledge from "./knowledge.json" with { type: "json" };
4
4
 
@@ -14,7 +14,6 @@ const searchable = faqs.map((entry) => ({
14
14
 
15
15
  export default agent({
16
16
  name: "FAQ Bot",
17
- ...assemblyAIPipeline(),
18
17
  systemPrompt:
19
18
  "You are a friendly FAQ assistant. Answer questions using ONLY the information from your embedded knowledge base. If the user asks something not covered by your knowledge base, say you don't have that information and suggest they check the official documentation.\n\nRules:\n- Keep answers concise and conversational — this is a voice agent\n- Quote the knowledge base accurately, do not embellish\n- If a question is ambiguous, ask the user to clarify\n- Use 'search_knowledge' to find answers to specific questions\n- Use 'list_topics' to see all available FAQ topics\n- Always be helpful and polite",
20
19
  greeting:
@@ -31,7 +30,7 @@ export default agent({
31
30
  search_knowledge: tool({
32
31
  description:
33
32
  "Search the embedded FAQ knowledge base for an answer matching the user's question.",
34
- parameters: z.object({
33
+ inputSchema: z.object({
35
34
  query: z.string().describe("The user's question to search for"),
36
35
  }),
37
36
  async execute(args) {
@@ -1,4 +1,4 @@
1
- import { agent, assemblyAIPipeline, tool } from "@alexkroman1/aai";
1
+ import { agent, tool } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import systemPrompt from "./system-prompt.md?raw";
4
4
 
@@ -76,7 +76,6 @@ function excerptAround(text: string, alias: string): string {
76
76
 
77
77
  export default agent({
78
78
  name: "Dr. Sage",
79
- ...assemblyAIPipeline(),
80
79
  systemPrompt,
81
80
  greeting:
82
81
  "Hey, I'm Dr. Sage. Try asking me something like, what are the side effects of ibuprofen, can I take aspirin and warfarin together, or calculate my BMI. Just remember, I'm not a real doctor, so always check with your healthcare provider.",
@@ -86,7 +85,7 @@ export default agent({
86
85
  check_drug_interaction: tool({
87
86
  description:
88
87
  "Check for interactions between two or more medications using FDA drug label data. Looks up each drug's official label and reports where one drug's Drug Interactions section mentions another. Absence of a mention does not guarantee safety.",
89
- parameters: z.object({
88
+ inputSchema: z.object({
90
89
  drugs: z
91
90
  .array(z.string().min(1))
92
91
  .min(2)
@@ -142,7 +141,7 @@ export default agent({
142
141
  medication_lookup: tool({
143
142
  description:
144
143
  "Look up detailed information about a single medication, including purpose, warnings, dosage, side effects, and manufacturer. Works with both generic and brand names.",
145
- parameters: z.object({
144
+ inputSchema: z.object({
146
145
  name: z
147
146
  .string()
148
147
  .describe("Medication name (generic or brand, e.g. 'ibuprofen' or 'Advil')"),
@@ -1,15 +1,13 @@
1
- import { agent, assemblyAIPipeline, tool } from "@alexkroman1/aai";
2
- import { assemblyAI as assemblyAITts } from "@alexkroman1/aai/tts";
1
+ import { agent, tool } from "@alexkroman1/aai";
3
2
  import { z } from "zod";
4
3
  import { getGameState, resetGameState } from "./shared.ts";
5
4
  import systemPrompt from "./system-prompt.md?raw";
6
5
 
7
6
  export default agent({
8
7
  name: "Cavern Adventure",
9
- ...assemblyAIPipeline(),
10
- // Overriding one stage of the preset: a narrator wants a narrative voice,
11
- // and the other two stages stay as the preset set them.
12
- tts: assemblyAITts({ voice: "paul" }),
8
+ // A narrator wants a narrative voice; everything else stays on the
9
+ // default all-AssemblyAI pipeline.
10
+ voice: "paul",
13
11
  systemPrompt,
14
12
  // The opening scene here must agree with DEFAULT_GAME_STATE.currentRoom
15
13
  // (shared.ts) and the world map in system-prompt.md.
@@ -19,7 +17,7 @@ export default agent({
19
17
  tools: {
20
18
  game_state_drop: tool({
21
19
  description: "Remove an item from the player's inventory.",
22
- parameters: z.object({
20
+ inputSchema: z.object({
23
21
  value: z.string().describe("Item name to drop"),
24
22
  }),
25
23
  async execute(args, ctx) {
@@ -31,7 +29,7 @@ export default agent({
31
29
 
32
30
  game_state_flag: tool({
33
31
  description: "Set a game flag to true, used for tracking puzzle and event state.",
34
- parameters: z.object({
32
+ inputSchema: z.object({
35
33
  value: z.string().describe("Flag name to set"),
36
34
  }),
37
35
  async execute(args, ctx) {
@@ -59,7 +57,7 @@ export default agent({
59
57
 
60
58
  game_state_history: tool({
61
59
  description: "Log a player command to the history and increment the move counter.",
62
- parameters: z.object({
60
+ inputSchema: z.object({
63
61
  value: z.string().describe("Command text to log"),
64
62
  }),
65
63
  async execute(args, ctx) {
@@ -72,7 +70,7 @@ export default agent({
72
70
 
73
71
  game_state_move: tool({
74
72
  description: "Move the player to a new room and increment the move counter.",
75
- parameters: z.object({
73
+ inputSchema: z.object({
76
74
  value: z.string().describe("Room name to move to"),
77
75
  }),
78
76
  async execute(args, ctx) {
@@ -94,7 +92,7 @@ export default agent({
94
92
 
95
93
  game_state_score: tool({
96
94
  description: "Add points to the player's score.",
97
- parameters: z.object({
95
+ inputSchema: z.object({
98
96
  value: z.number().describe("Points to add"),
99
97
  }),
100
98
  async execute(args, ctx) {
@@ -106,7 +104,7 @@ export default agent({
106
104
 
107
105
  game_state_take: tool({
108
106
  description: "Add an item to the player's inventory.",
109
- parameters: z.object({
107
+ inputSchema: z.object({
110
108
  value: z.string().describe("Item name to take"),
111
109
  }),
112
110
  async execute(args, ctx) {
@@ -241,15 +241,15 @@ function InfocomAdventure() {
241
241
  >
242
242
  {session.running ? "[P]ause" : "[R]esume"}
243
243
  </button>
244
- {/* Full reload = new WebSocket = fresh sessionId, so the
245
- session-scoped game state starts over too.
246
- (session.reset() keeps the same sessionId and would
247
- resume the old game.) */}
244
+ {/* end() hangs up and drops the sessionId, so the
245
+ session-scoped game state starts over and the title
246
+ screen returns. (session.reset() keeps the same
247
+ sessionId and would resume the old game.) */}
248
248
  <button
249
249
  type="button"
250
250
  className="px-4 py-1 bg-transparent cursor-pointer uppercase tracking-wider font-mono text-[11px]"
251
251
  style={{ color: GREEN_DIM, border: `1px solid ${GREEN_DARK}` }}
252
- onClick={() => window.location.reload()}
252
+ onClick={() => session.end()}
253
253
  >
254
254
  [N]ew Game
255
255
  </button>
@@ -1,13 +1,14 @@
1
- import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
2
- import { assemblyAI as assemblyAILlm } from "@alexkroman1/aai/llm";
1
+ import { agent } from "@alexkroman1/aai";
2
+ import { assemblyAILlm } from "@alexkroman1/aai/llm";
3
3
  import systemPrompt from "./system-prompt.md?raw";
4
4
 
5
5
  export default agent({
6
6
  name: "Math Buddy",
7
- ...assemblyAIPipeline(),
8
- // Overriding the LLM stage: this tutor delegates every calculation to
9
- // run_code, so it needs turn-taking speed more than reasoning depth
10
- // and Flash-Lite is both quicker and cheaper than the preset's default.
7
+ // Only the LLM stage is declared: this tutor delegates every calculation
8
+ // to run_code, so it needs turn-taking speed more than reasoning depth —
9
+ // and Flash-Lite is both quicker and cheaper than the default model. STT
10
+ // and TTS stay on the AssemblyAI defaults. (The string shorthand
11
+ // `llm: "gemini-2.5-flash-lite"` desugars to this same descriptor.)
11
12
  llm: assemblyAILlm({ model: "gemini-2.5-flash-lite" }),
12
13
  systemPrompt,
13
14
  greeting:
@@ -1,4 +1,4 @@
1
- import { agent, assemblyAIPipeline, tool } from "@alexkroman1/aai";
1
+ import { agent, tool } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import { CATEGORIES, type Category, MOODS, type Mood } from "./shared.ts";
4
4
 
@@ -48,7 +48,6 @@ const PICKS: Record<Category, Record<Mood, string[]>> = {
48
48
 
49
49
  export default agent({
50
50
  name: "Night Owl",
51
- ...assemblyAIPipeline(),
52
51
  systemPrompt:
53
52
  "You are Night Owl, a cozy evening companion. You help people wind down, recommend entertainment, and share interesting facts about the night sky. Keep your tone warm and relaxed. Use short, conversational responses.\n\nUse run_code for sleep calculations:\n- Each sleep cycle is 90 minutes, plus 15 minutes to fall asleep\n- Bedtime = wake_time - (cycles * 90 + 15) minutes\n- If result is negative, add 1440 (24 hours in minutes)\n- Format as HH:MM",
54
53
  greeting:
@@ -58,7 +57,7 @@ export default agent({
58
57
  tools: {
59
58
  recommend: tool({
60
59
  description: "Get recommendations for movies, music, or books based on mood.",
61
- parameters: z.object({
60
+ inputSchema: z.object({
62
61
  category: z.enum(CATEGORIES),
63
62
  mood: z.enum(MOODS),
64
63
  }),
@@ -1,9 +1,8 @@
1
- import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
1
+ import { agent } from "@alexkroman1/aai";
2
2
  import systemPrompt from "./system-prompt.md?raw";
3
3
 
4
4
  export default agent({
5
5
  name: "Penny",
6
- ...assemblyAIPipeline(),
7
6
  systemPrompt,
8
7
  greeting:
9
8
  "Hey, I'm Penny, your personal finance helper. Try asking me something like, what's 100 dollars in euros, what's the price of bitcoin, or help me split a 120 dollar bill four ways with 20 percent tip.",
@@ -4,29 +4,29 @@ import agentDef from "./agent.ts";
4
4
 
5
5
  describe("pipeline-simple template", () => {
6
6
  test("config passes manifest validation", () => {
7
- // Same conversion `aai build`/`aai deploy` run — catches a partial
8
- // provider triple or invalid tuning, not just descriptor presence.
7
+ // Same conversion `aai build`/`aai deploy` run — catches an invalid
8
+ // provider combination or tuning, not just descriptor presence.
9
9
  expect(() => toAgentConfig(agentDef)).not.toThrow();
10
10
  });
11
11
 
12
- test("exports an agent with all three pipeline providers", () => {
13
- // Presence of stt + llm + tts is what flips the runtime into
14
- // pipeline mode (see parseManifest). Smoke-test each is wired up.
12
+ test("declares only the LLM stage on the def", () => {
13
+ // The template's point: swap one stage, leave the rest unset. The
14
+ // unset stages are filled with the AssemblyAI defaults at parse time.
15
15
  expect(agentDef.name).toBe("pipeline-simple");
16
- expect(agentDef.stt).toBeDefined();
17
16
  expect(agentDef.llm).toBeDefined();
18
- expect(agentDef.tts).toBeDefined();
19
- });
20
-
21
- test("STT descriptor is AssemblyAI", () => {
22
- expect(agentDef.stt?.kind).toBe("assemblyai");
17
+ expect(agentDef.stt).toBeUndefined();
18
+ expect(agentDef.tts).toBeUndefined();
23
19
  });
24
20
 
25
21
  test("LLM descriptor is Anthropic", () => {
26
22
  expect(agentDef.llm?.kind).toBe("anthropic");
27
23
  });
28
24
 
29
- test("TTS descriptor is AssemblyAI", () => {
30
- expect(agentDef.tts?.kind).toBe("assemblyai");
25
+ test("unset stages fill to AssemblyAI in the deployable config", () => {
26
+ const config = toAgentConfig(agentDef);
27
+ expect(config.mode).toBe("pipeline");
28
+ expect(config.stt?.kind).toBe("assemblyai");
29
+ expect(config.tts?.kind).toBe("assemblyai");
30
+ expect(config.llm?.kind).toBe("anthropic");
31
31
  });
32
32
  });
@@ -1,13 +1,12 @@
1
- import { agent, assemblyAIPipeline } from "@alexkroman1/aai";
1
+ import { agent } from "@alexkroman1/aai";
2
2
  import { anthropic } from "@alexkroman1/aai/llm";
3
3
 
4
4
  export default agent({
5
5
  name: "pipeline-simple",
6
6
  greeting: "Hi! I'm running in pipeline mode. What can I help with?",
7
- // The preset fills all three stages with AssemblyAI, billed to the one key
8
- // a published agent is guaranteed to have. Spread it, then override the one
9
- // stage you want elsewhere here the LLM. Anything not overridden stays
10
- // AssemblyAI, so swapping a stage never means restating the other two.
11
- ...assemblyAIPipeline(),
7
+ // Declare only the stage you want elsewhere here the LLM. Every stage
8
+ // left unset (STT and TTS here) runs on the AssemblyAI default, billed to
9
+ // the one key a published agent is guaranteed to have, so swapping a stage
10
+ // never means restating the other two.
12
11
  llm: anthropic({ model: "claude-haiku-4-5" }),
13
12
  });
@@ -76,17 +76,19 @@ describe("pricing (shared.ts)", () => {
76
76
  expect(calculateTotal([a, b])).toBeCloseTo(8.99 * 4, 5);
77
77
  });
78
78
 
79
- test("add_pizza schema defaults quantity to 1 and rejects non-positive quantities", () => {
80
- const schema = getTool("add_pizza").parameters;
81
- if (!schema) throw new Error("add_pizza has no parameters schema");
82
- const parsed = schema.parse({ size: "small", crust: "thin", toppings: [] });
83
- expect(parsed.quantity).toBe(1);
84
- expect(() =>
85
- schema.parse({ size: "small", crust: "thin", toppings: [], quantity: 0 }),
86
- ).toThrow();
87
- expect(() =>
88
- schema.parse({ size: "small", crust: "thin", toppings: [], quantity: 1.5 }),
89
- ).toThrow();
79
+ test("add_pizza schema defaults quantity to 1 and rejects non-positive quantities", async () => {
80
+ const schema = getTool("add_pizza").inputSchema;
81
+ if (!schema) throw new Error("add_pizza has no input schema");
82
+ // Validate through the Standard Schema contract the vendor-neutral
83
+ // interface every inputSchema carries.
84
+ const validate = (value: unknown) => schema["~standard"].validate(value);
85
+ const ok = await validate({ size: "small", crust: "thin", toppings: [] });
86
+ if (ok.issues) throw new Error("expected valid input");
87
+ expect((ok.value as { quantity: number }).quantity).toBe(1);
88
+ const zero = await validate({ size: "small", crust: "thin", toppings: [], quantity: 0 });
89
+ expect(zero.issues).toBeDefined();
90
+ const frac = await validate({ size: "small", crust: "thin", toppings: [], quantity: 1.5 });
91
+ expect(frac.issues).toBeDefined();
90
92
  });
91
93
  });
92
94
 
@@ -1,4 +1,4 @@
1
- import { agent, assemblyAIPipeline, tool } from "@alexkroman1/aai";
1
+ import { agent, tool } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import {
4
4
  CRUSTS,
@@ -21,7 +21,6 @@ const crusts = z.enum(CRUSTS);
21
21
 
22
22
  export default agent({
23
23
  name: "Pizza Palace",
24
- ...assemblyAIPipeline(),
25
24
  // The cart, pushed to the client after every tool call. Replaces a
26
25
  // `ctx.send("order", ...)` in each of the five order tools, and the
27
26
  // event-diffing the client had to do to rebuild the cart from them.
@@ -35,7 +34,7 @@ export default agent({
35
34
  tools: {
36
35
  add_pizza: tool({
37
36
  description: "Add a pizza to the order. Use when the customer has decided on a pizza.",
38
- parameters: z.object({
37
+ inputSchema: z.object({
39
38
  size: sizes,
40
39
  crust: crusts,
41
40
  toppings: z
@@ -93,7 +92,7 @@ export default agent({
93
92
 
94
93
  remove_pizza: tool({
95
94
  description: "Remove a pizza from the order by its ID.",
96
- parameters: z.object({
95
+ inputSchema: z.object({
97
96
  pizza_id: z.number().describe("The pizza ID to remove"),
98
97
  }),
99
98
  async execute(args, ctx) {
@@ -113,7 +112,7 @@ export default agent({
113
112
 
114
113
  set_customer_name: tool({
115
114
  description: "Set the customer name for the order.",
116
- parameters: z.object({
115
+ inputSchema: z.object({
117
116
  name: z.string(),
118
117
  }),
119
118
  async execute(args, ctx) {
@@ -124,7 +123,7 @@ export default agent({
124
123
 
125
124
  update_pizza: tool({
126
125
  description: "Update an existing pizza in the order. Only provided fields are changed.",
127
- parameters: z.object({
126
+ inputSchema: z.object({
128
127
  pizza_id: z.number(),
129
128
  size: sizes.optional(),
130
129
  crust: crusts.optional(),
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import type { Address } from "./shared.ts";
3
+
4
+ /** Spread into both address tools' schemas. Two hand-copied zod shapes is how
5
+ * the order address and the profile address drift apart. */
6
+ export const AddressFields = {
7
+ address1: z.string().max(200).describe("First line, e.g. '123 Main St'"),
8
+ address2: z.string().max(200).describe("Second line, e.g. 'Apt 1' — pass '' if there is none"),
9
+ city: z.string().max(100).describe("City, e.g. 'San Francisco'"),
10
+ state: z.string().max(60).describe("State, e.g. 'CA'"),
11
+ country: z.string().max(60).describe("Country, e.g. 'USA'"),
12
+ zip: z.string().max(20).describe("Zip code, e.g. '12345'"),
13
+ };
14
+
15
+ export function toAddress(args: Address): Address {
16
+ return {
17
+ address1: args.address1,
18
+ address2: args.address2,
19
+ city: args.city,
20
+ state: args.state,
21
+ country: args.country,
22
+ zip: args.zip,
23
+ };
24
+ }
25
+
26
+ export function formatAddress(address: Address): string {
27
+ const lines = [address.address1, address.address2].filter(Boolean).join(", ");
28
+ return `${lines}, ${address.city} ${address.state} ${address.zip}, ${address.country}`;
29
+ }