@alexkroman1/aai-cli 6.5.0 → 6.5.1

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 (48) hide show
  1. package/dist/{_agent-vGW5SBnX.mjs → _agent-DpH2pBJd.mjs} +1 -5
  2. package/dist/{_bundler-BapCUUJN.mjs → _bundler-C2BDwU7r.mjs} +5 -7
  3. package/dist/{_dev-server-DQ1t9sbw.mjs → _dev-server-LLLWnFBs.mjs} +3 -3
  4. package/dist/_e2e-test-utils.d.ts +17 -17
  5. package/dist/{_init-CpISqoR8.mjs → _init-C57bWAQ8.mjs} +2 -2
  6. package/dist/{_slug-api-D2heILuC.mjs → _slug-api-BxrsJXVI.mjs} +3 -3
  7. package/dist/_slug-api.d.ts +1 -1
  8. package/dist/{_api-client-LgLksMhN.mjs → _studio-U3jhKlA3.mjs} +105 -2
  9. package/dist/_studio.d.ts +11 -0
  10. package/dist/{_templates-iDGjGiPf.mjs → _templates-Dxb_P2Wz.mjs} +1 -1
  11. package/dist/bin.d.mts +2 -0
  12. package/dist/{build-BO1Ni6oJ.mjs → build-Dfza2pRI.mjs} +2 -2
  13. package/dist/cli.mjs +43 -42
  14. package/dist/{client-bundler-B8Q9F8gJ.mjs → client-bundler-BEIqgOtd.mjs} +2 -2
  15. package/dist/client-bundler.mjs +1 -1
  16. package/dist/{delete-4I0uNR4a.mjs → delete-vbpjGzqs.mjs} +3 -3
  17. package/dist/{deploy-DJ3HRcia.mjs → deploy-B8lubiRT.mjs} +3 -4
  18. package/dist/{dev-C6SWaAZP.mjs → dev-Uq5ujP8a.mjs} +1 -1
  19. package/dist/{eject-Zrzk5KV3.mjs → eject-31gjtaHF.mjs} +2 -2
  20. package/dist/{init-BTRGiPQr.mjs → init-l6xfU4xX.mjs} +3 -3
  21. package/dist/{login-k0Z3VQu9.mjs → login-C71-qz8F.mjs} +1 -1
  22. package/dist/scaffold/package.json +3 -3
  23. package/dist/{secret-DzSQx1Ds.mjs → secret-BuMuFR4B.mjs} +2 -2
  24. package/dist/{storage-C-Do0mIA.mjs → storage-DzRZ-eCw.mjs} +2 -2
  25. package/dist/{studio-BGVmCA2G.mjs → studio-BCNUpDgP.mjs} +4 -5
  26. package/dist/templates/retail/resolve.ts +16 -0
  27. package/dist/templates/retail/store.ts +20 -0
  28. package/dist/templates/retail/tools/cancel_pending_order.ts +2 -5
  29. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -5
  30. package/dist/templates/retail/tools/get_order_details.ts +4 -6
  31. package/dist/templates/retail/tools/get_user_details.ts +2 -7
  32. package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -5
  33. package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -5
  34. package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -5
  35. package/dist/templates/retail/tools/modify_user_address.ts +2 -7
  36. package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -5
  37. package/dist/templates/transcription-workflow/agent.test.ts +2 -3
  38. package/dist/templates/transcription-workflow/workflows/batch.ts +2 -13
  39. package/dist/templates/transcription-workflow/workflows/stream.ts +1 -10
  40. package/dist/templates/transcription-workflow/workflows/sync-api.ts +8 -2
  41. package/dist/templates/transcription-workflow/workflows/transcribe.ts +8 -2
  42. package/dist/{test-B6ZBxSpk.mjs → test-9kPKJI-w.mjs} +1 -3
  43. package/dist/typecheck.mjs +4 -5
  44. package/dist/{worker-bundler-CIdDhZ0c.mjs → worker-bundler-CGD4r8Kc.mjs} +25 -11
  45. package/dist/worker-bundler.mjs +1 -1
  46. package/dist/{workflow-B-O97AqI.mjs → workflow-CFpxOFfQ.mjs} +1 -1
  47. package/package.json +4 -4
  48. package/dist/_studio-zAJycZ8j.mjs +0 -94
@@ -12,7 +12,7 @@ import { styleText } from "node:util";
12
12
  async function executeDev(opts) {
13
13
  const port = parsePort(opts.port);
14
14
  const agentName = path.basename(path.resolve(opts.cwd));
15
- const { startDevServer } = await import("./_dev-server-DQ1t9sbw.mjs");
15
+ const { startDevServer } = await import("./_dev-server-LLLWnFBs.mjs");
16
16
  let cleanup;
17
17
  let shuttingDown = false;
18
18
  const onSignal = () => {
@@ -2,7 +2,7 @@
2
2
  import { a as ok, t as CliError } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
4
  import { l as readJson, o as errorMessage, p as writeJson, s as fileExists } from "./_utils-B8QmtFhK.mjs";
5
- import { scaffoldDir } from "./_templates-iDGjGiPf.mjs";
5
+ import { scaffoldDir } from "./_templates-Dxb_P2Wz.mjs";
6
6
  import path from "node:path";
7
7
  import fs from "node:fs/promises";
8
8
  //#region eject.ts
@@ -69,7 +69,7 @@ async function ensureStartScript(cwd) {
69
69
  const existing = manifest.scripts?.start;
70
70
  if (existing === START_SCRIPT && manifest.scripts?.prestart === "aai build --skip-tests") return false;
71
71
  if (existing !== void 0 && existing !== START_SCRIPT) {
72
- log.warn(`Kept your existing "start" script (${existing}) — run \`${PRESTART_SCRIPT}\` then \`node ${SERVER_ENTRY}\`.`);
72
+ log.warn(`Kept your existing "start" script (${existing}) — run \`${PRESTART_SCRIPT}\` then \`${START_SCRIPT}\`.`);
73
73
  return false;
74
74
  }
75
75
  manifest.scripts = {
@@ -2,7 +2,7 @@
2
2
  import { a as ok } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log, s as unwrapCancel } from "./_ui-u7T4YooX.mjs";
4
4
  import { d as resolveCwd, l as readJson, o as errorMessage, s as fileExists, t as AGENT_ENTRY } from "./_utils-B8QmtFhK.mjs";
5
- import { r as isDevMode, t as getMonorepoRoot } from "./_agent-vGW5SBnX.mjs";
5
+ import { r as isDevMode, t as getMonorepoRoot } from "./_agent-DpH2pBJd.mjs";
6
6
  import path from "node:path";
7
7
  import { styleText } from "node:util";
8
8
  import * as p from "@clack/prompts";
@@ -105,7 +105,7 @@ function collectWarnings() {
105
105
  }
106
106
  /** Publish after init and return deploy metadata if successful. */
107
107
  async function tryPublish(cwd, server, warn) {
108
- const { executePublish } = await import("./studio-BGVmCA2G.mjs");
108
+ const { executePublish } = await import("./studio-BCNUpDgP.mjs");
109
109
  try {
110
110
  const result = await executePublish({
111
111
  cwd,
@@ -126,7 +126,7 @@ async function tryPublish(cwd, server, warn) {
126
126
  }
127
127
  /** Scaffold the project, optionally showing a spinner. */
128
128
  async function scaffoldProject(dir, cwd, template, silent) {
129
- const { runInit } = await import("./_init-CpISqoR8.mjs");
129
+ const { runInit } = await import("./_init-C57bWAQ8.mjs");
130
130
  await withSpinner(silent, {
131
131
  start: `Creating ${dir}`,
132
132
  done: "Project created",
@@ -2,7 +2,7 @@
2
2
  import { a as ok, t as CliError } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
4
  import { o as updateGlobalConfig, r as readGlobalConfig, t as approveServer } from "./_config-D_s09e7g.mjs";
5
- import { o as resolveServerUrl } from "./_agent-vGW5SBnX.mjs";
5
+ import { o as resolveServerUrl } from "./_agent-DpH2pBJd.mjs";
6
6
  import { linkConfirmationCode } from "@alexkroman1/aai/utils";
7
7
  import { spawn } from "node:child_process";
8
8
  import { sleep } from "@alexkroman1/aai/internal";
@@ -13,8 +13,8 @@
13
13
  "publish:agent": "aai publish"
14
14
  },
15
15
  "dependencies": {
16
- "@alexkroman1/aai": "^6.5.0",
17
- "@alexkroman1/aai-ui": "^6.5.0",
16
+ "@alexkroman1/aai": "^6.5.1",
17
+ "@alexkroman1/aai-ui": "^6.5.1",
18
18
  "@workflow/world-postgres": "4.3.3",
19
19
  "react": "^19.2.8",
20
20
  "react-dom": "^19.2.8",
@@ -23,7 +23,7 @@
23
23
  "zod": "^4.4.3"
24
24
  },
25
25
  "devDependencies": {
26
- "@alexkroman1/aai-cli": "^6.5.0",
26
+ "@alexkroman1/aai-cli": "^6.5.1",
27
27
  "@tailwindcss/vite": "^4.3.3",
28
28
  "@types/node": "^26.2.0",
29
29
  "@types/react": "^19.2.18",
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log, s as unwrapCancel } from "./_ui-u7T4YooX.mjs";
4
- import { a as isStringArray, i as checkedResponse } from "./_api-client-LgLksMhN.mjs";
5
- import { t as secretRequest } from "./_slug-api-D2heILuC.mjs";
4
+ import { f as checkedResponse, p as isStringArray } from "./_studio-U3jhKlA3.mjs";
5
+ import { t as secretRequest } from "./_slug-api-BxrsJXVI.mjs";
6
6
  import { isRecord } from "@alexkroman1/aai/utils";
7
7
  import * as p from "@clack/prompts";
8
8
  import { text } from "node:stream/consumers";
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
- import { i as checkedResponse } from "./_api-client-LgLksMhN.mjs";
5
- import { n as slugRequest } from "./_slug-api-D2heILuC.mjs";
4
+ import { f as checkedResponse } from "./_studio-U3jhKlA3.mjs";
5
+ import { n as slugRequest } from "./_slug-api-BxrsJXVI.mjs";
6
6
  import { isRecord } from "@alexkroman1/aai/utils";
7
7
  import * as p from "@clack/prompts";
8
8
  //#region storage.ts
@@ -3,10 +3,9 @@ import { a as ok, t as CliError } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log, t as fmtUrl } from "./_ui-u7T4YooX.mjs";
4
4
  import { s as updateProjectConfig } from "./_config-D_s09e7g.mjs";
5
5
  import { t as resolveServerEnv } from "./_server-common-6e0QI4mq.mjs";
6
- import { a as resolveDeployTarget } from "./_agent-vGW5SBnX.mjs";
7
- import { layerScaffold } from "./_templates-iDGjGiPf.mjs";
8
- import { i as checkedResponse, n as apiRequest } from "./_api-client-LgLksMhN.mjs";
9
- import { a as publishStudioProject, i as projectNameFromDir, n as fetchStudioProject, o as pushStudioSource, r as listStudioProjects, s as studioProjectUrl, t as collectSourceFiles } from "./_studio-zAJycZ8j.mjs";
6
+ import { a as resolveDeployTarget } from "./_agent-DpH2pBJd.mjs";
7
+ import { layerScaffold } from "./_templates-Dxb_P2Wz.mjs";
8
+ import { a as publishStudioProject, c as studioProjectUrl, f as checkedResponse, i as projectNameFromDir, n as fetchStudioProject, o as pushStudioSource, r as listStudioProjects, s as studioProjectApiUrl, t as collectSourceFiles, u as apiRequest } from "./_studio-U3jhKlA3.mjs";
10
9
  import path from "node:path";
11
10
  import { isRecord } from "@alexkroman1/aai/utils";
12
11
  import { mkdir, readdir, writeFile } from "node:fs/promises";
@@ -148,7 +147,7 @@ async function syncEnvSecrets(cwd, serverUrl, apiKey, project) {
148
147
  const env = await resolveServerEnv(cwd);
149
148
  const names = Object.keys(env);
150
149
  if (names.length === 0) return [];
151
- await apiRequest(`${serverUrl}/studio/projects/${encodeURIComponent(project)}/secret`, {
150
+ await apiRequest(`${studioProjectApiUrl(serverUrl, project)}/secret`, {
152
151
  apiKey,
153
152
  action: "secret",
154
153
  method: "PUT",
@@ -10,9 +10,25 @@
10
10
  */
11
11
 
12
12
  import { isToolFailure, resolveOne, spokenDigits, type ToolFailure } from "@alexkroman1/aai";
13
+ import { z } from "zod";
13
14
  import type { Order, OrderStatus, Product, RetailState } from "./shared.ts";
14
15
  import { authenticatedUser } from "./store.ts";
15
16
 
17
+ /**
18
+ * The `order_id` parameter, for the seven tools that take one.
19
+ *
20
+ * Same reasoning as `AddressFields` in `address.ts`, and here the stakes are the
21
+ * DESCRIPTION: it is the sentence that tells the model a spoken reference is
22
+ * acceptable, so a copy that lost the second half would quietly make one tool
23
+ * demand a literal id while its six siblings accept "the last one". It belongs in
24
+ * this module because "what an order id looks like when a caller reads it aloud"
25
+ * is exactly what {@link resolveOrder} implements.
26
+ */
27
+ export const OrderIdField = z
28
+ .string()
29
+ .max(120)
30
+ .describe("Order id such as '#W0000000', or a spoken reference to one of their orders");
31
+
16
32
  /** `#W5866402` from anything STT plausibly produces for it. */
17
33
  export function normalizeOrderId(spoken: string): string {
18
34
  const compact = spoken.toUpperCase().replace(/[^A-Z0-9]/g, "");
@@ -158,6 +158,26 @@ export function requireOwnOrder(state: RetailState, orderId: string): Order | To
158
158
  return order;
159
159
  }
160
160
 
161
+ /**
162
+ * The authenticated customer, when `userId` names them.
163
+ *
164
+ * The wrapper's auth gate says a customer is on the call; a tool whose schema
165
+ * takes a `user_id` also needs to know the caller named THAT customer, and only
166
+ * one message is right for the second. Two tools had the pair of guards written
167
+ * out, so the message was maintained in two places — the same reason
168
+ * {@link requireOwnOrder} exists one rule up.
169
+ */
170
+ export function requireOwnUser(state: RetailState, userId: string): User | ToolFailure {
171
+ const user = authenticatedUser(state);
172
+ if (isToolFailure(user)) return user;
173
+ if (user.user_id !== userId) {
174
+ return {
175
+ error: `${userId} is not the customer on this call. You can help only one customer per conversation.`,
176
+ };
177
+ }
178
+ return user;
179
+ }
180
+
161
181
  // ─── The tool wrapper ────────────────────────────────────────────────────────
162
182
 
163
183
  interface RetailToolSpec<S extends z.ZodType<Record<string, unknown>>, R> {
@@ -1,7 +1,7 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import { creditRefund, REFUND_DELAY_NOTE, REFUND_IMMEDIATE_NOTE } from "../refund.ts";
4
- import { resolveOrder } from "../resolve.ts";
4
+ import { OrderIdField, resolveOrder } from "../resolve.ts";
5
5
  import { authenticatedUser, retailTool, setFocus } from "../store.ts";
6
6
 
7
7
  /** tau2 accepts exactly these two. Anything else is refused. */
@@ -15,10 +15,7 @@ export default retailTool({
15
15
  "State the order, its total and the refund destination to the caller and get an explicit yes " +
16
16
  "before calling this.",
17
17
  inputSchema: z.object({
18
- order_id: z
19
- .string()
20
- .max(120)
21
- .describe("Order id such as '#W0000000', or a spoken reference to one of their orders"),
18
+ order_id: OrderIdField,
22
19
  reason: z
23
20
  .enum(CANCEL_REASONS)
24
21
  .describe("Either 'no longer needed' or 'ordered by mistake' — no other reason is accepted"),
@@ -1,6 +1,6 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { resolveOrder } from "../resolve.ts";
3
+ import { OrderIdField, resolveOrder } from "../resolve.ts";
4
4
  import { authenticatedUser, money, retailTool, setFocus } from "../store.ts";
5
5
  import { assertCanCoverDiff, planItemSwap } from "../swap.ts";
6
6
 
@@ -13,10 +13,7 @@ export default retailTool({
13
13
  "back, and get an explicit yes. No new order is needed — the customer gets an email explaining " +
14
14
  "how to send the originals back.",
15
15
  inputSchema: z.object({
16
- order_id: z
17
- .string()
18
- .max(120)
19
- .describe("Order id such as '#W0000000', or a spoken reference to one of their orders"),
16
+ order_id: OrderIdField,
20
17
  item_ids: z
21
18
  .array(z.string().max(60))
22
19
  .max(20)
@@ -1,6 +1,7 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
+ import { omitUndefined } from "@alexkroman1/aai/utils";
2
3
  import { z } from "zod";
3
- import { resolveOrder } from "../resolve.ts";
4
+ import { OrderIdField, resolveOrder } from "../resolve.ts";
4
5
  import { retailTool, setFocus } from "../store.ts";
5
6
 
6
7
  export default retailTool({
@@ -10,10 +11,7 @@ export default retailTool({
10
11
  "order id (e.g. '#W0000000' — note the leading '#'), or a spoken reference such as " +
11
12
  "'my pending order', 'the delivered one', or 'the second pending order'.",
12
13
  inputSchema: z.object({
13
- order_id: z
14
- .string()
15
- .max(120)
16
- .describe("Order id such as '#W0000000', or a spoken reference to one of their orders"),
14
+ order_id: OrderIdField,
17
15
  }),
18
16
  execute: (args, state) => {
19
17
  const order = resolveOrder(state, args.order_id);
@@ -31,7 +29,7 @@ export default retailTool({
31
29
  options: item.options,
32
30
  })),
33
31
  payment_history: order.payment_history,
34
- ...(order.fulfillments ? { fulfillments: order.fulfillments } : {}),
32
+ ...omitUndefined({ fulfillments: order.fulfillments }),
35
33
  ...(order.cancel_reason ? { cancel_reason: order.cancel_reason } : {}),
36
34
  };
37
35
  },
@@ -1,6 +1,6 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { authenticatedUser, retailTool } from "../store.ts";
3
+ import { requireOwnUser, retailTool } from "../store.ts";
4
4
 
5
5
  export default retailTool({
6
6
  name: "get_user_details",
@@ -11,13 +11,8 @@ export default retailTool({
11
11
  user_id: z.string().max(100).describe("The user id, e.g. 'sara_doe_496'"),
12
12
  }),
13
13
  execute: (args, state) => {
14
- const user = authenticatedUser(state);
14
+ const user = requireOwnUser(state, args.user_id);
15
15
  if (isToolFailure(user)) return user;
16
- if (user.user_id !== args.user_id) {
17
- return {
18
- error: `${args.user_id} is not the customer on this call. You can help only one customer per conversation.`,
19
- };
20
- }
21
16
  return {
22
17
  user_id: user.user_id,
23
18
  name: `${user.name.first_name} ${user.name.last_name}`,
@@ -1,7 +1,7 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import { AddressFields, formatAddress, toAddress } from "../address.ts";
4
- import { resolveOrder } from "../resolve.ts";
4
+ import { OrderIdField, resolveOrder } from "../resolve.ts";
5
5
  import { retailTool, setFocus } from "../store.ts";
6
6
 
7
7
  export default retailTool({
@@ -10,10 +10,7 @@ export default retailTool({
10
10
  "Change the shipping address of a pending order. Read the new address back to the caller and " +
11
11
  "get an explicit yes before calling this. This does not change the customer's default address.",
12
12
  inputSchema: z.object({
13
- order_id: z
14
- .string()
15
- .max(120)
16
- .describe("Order id such as '#W0000000', or a spoken reference to one of their orders"),
13
+ order_id: OrderIdField,
17
14
  ...AddressFields,
18
15
  }),
19
16
  execute: (args, state) => {
@@ -1,6 +1,6 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { resolveOrder } from "../resolve.ts";
3
+ import { OrderIdField, resolveOrder } from "../resolve.ts";
4
4
  import { authenticatedUser, isGiftCard, money, retailTool, setFocus } from "../store.ts";
5
5
  import { applySwap, assertCanCoverDiff, planItemSwap } from "../swap.ts";
6
6
 
@@ -13,10 +13,7 @@ export default retailTool({
13
13
  "list and the price difference back, and get an explicit yes first. The item and replacement " +
14
14
  "lists are positional and must be the same length.",
15
15
  inputSchema: z.object({
16
- order_id: z
17
- .string()
18
- .max(120)
19
- .describe("Order id such as '#W0000000', or a spoken reference to one of their orders"),
16
+ order_id: OrderIdField,
20
17
  item_ids: z
21
18
  .array(z.string().max(60))
22
19
  .max(20)
@@ -1,6 +1,6 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { resolveOrder } from "../resolve.ts";
3
+ import { OrderIdField, resolveOrder } from "../resolve.ts";
4
4
  import {
5
5
  authenticatedUser,
6
6
  findPaymentMethod,
@@ -17,10 +17,7 @@ export default retailTool({
17
17
  "from the current one, and a gift card must hold enough to cover the whole order. The original " +
18
18
  "method is refunded. Read the change back and get an explicit yes before calling this.",
19
19
  inputSchema: z.object({
20
- order_id: z
21
- .string()
22
- .max(120)
23
- .describe("Order id such as '#W0000000', or a spoken reference to one of their orders"),
20
+ order_id: OrderIdField,
24
21
  payment_method_id: z
25
22
  .string()
26
23
  .max(80)
@@ -1,7 +1,7 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
3
  import { AddressFields, formatAddress, toAddress } from "../address.ts";
4
- import { authenticatedUser, retailTool } from "../store.ts";
4
+ import { requireOwnUser, retailTool } from "../store.ts";
5
5
 
6
6
  export default retailTool({
7
7
  name: "modify_user_address",
@@ -14,13 +14,8 @@ export default retailTool({
14
14
  ...AddressFields,
15
15
  }),
16
16
  execute: (args, state) => {
17
- const user = authenticatedUser(state);
17
+ const user = requireOwnUser(state, args.user_id);
18
18
  if (isToolFailure(user)) return user;
19
- if (user.user_id !== args.user_id) {
20
- return {
21
- error: `${args.user_id} is not the customer on this call. You can help only one customer per conversation.`,
22
- };
23
- }
24
19
 
25
20
  user.address = toAddress(args);
26
21
  return {
@@ -1,6 +1,6 @@
1
1
  import { isToolFailure } from "@alexkroman1/aai";
2
2
  import { z } from "zod";
3
- import { resolveOrder } from "../resolve.ts";
3
+ import { OrderIdField, resolveOrder } from "../resolve.ts";
4
4
  import {
5
5
  authenticatedUser,
6
6
  findPaymentMethod,
@@ -18,10 +18,7 @@ export default retailTool({
18
18
  "explicit yes before calling this. The customer gets an email explaining how to send the " +
19
19
  "items back.",
20
20
  inputSchema: z.object({
21
- order_id: z
22
- .string()
23
- .max(120)
24
- .describe("Order id such as '#W0000000', or a spoken reference to one of their orders"),
21
+ order_id: OrderIdField,
25
22
  item_ids: z
26
23
  .array(z.string().max(60))
27
24
  .max(20)
@@ -18,14 +18,13 @@
18
18
  */
19
19
 
20
20
  import { stubReporter, stubStepFetch, stubUploads } from "@alexkroman1/aai/testing";
21
- import { readUpload } from "@alexkroman1/aai/utils";
21
+ import { omitUndefined, readUpload } from "@alexkroman1/aai/utils";
22
22
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
23
23
  import { FatalError, RetryableError } from "workflow";
24
24
  import { z } from "zod";
25
25
  import agentDef, { transcribe, transcribeBatch, transcribeStream } from "./agent.ts";
26
26
  import { createJob, pollTranscript, readTranscript, uploadToProvider } from "./workflows/batch.ts";
27
27
  import { planStreamed, probeUpload } from "./workflows/stream.ts";
28
-
29
28
  import {
30
29
  clock,
31
30
  mergeTranscript,
@@ -539,7 +538,7 @@ describe("transcribeSegment", () => {
539
538
  const stub = stubStepFetch(() => ({
540
539
  status: sync.status ?? 200,
541
540
  body: sync.body ?? { text: "hello there" },
542
- ...(sync.headers && { headers: sync.headers }),
541
+ ...omitUndefined({ headers: sync.headers }),
543
542
  }));
544
543
  stubs.push(stub.restore);
545
544
  return stub.calls;
@@ -45,8 +45,9 @@
45
45
  */
46
46
 
47
47
  import { throwFatalStepError, toStepError } from "@alexkroman1/aai/step-errors";
48
- import { readUpload, report, requireStepEnv, stepFetch, uploadInfo } from "@alexkroman1/aai/utils";
48
+ import { readUpload, report, stepFetch, uploadInfo } from "@alexkroman1/aai/utils";
49
49
  import { sleep } from "workflow";
50
+ import { apiKeyOrFatal } from "./sync-api.ts";
50
51
  import { countWords, startClock, type Transcript } from "./transcribe.ts";
51
52
 
52
53
  /** The async API's base. */
@@ -66,9 +67,6 @@ const API = "https://api.assemblyai.com";
66
67
  */
67
68
  const MODELS = ["universal-3-5-pro"];
68
69
 
69
- /** The key a step reads out of the agent env. Declared in `agent.ts`'s `requiredEnv`. */
70
- const API_KEY_ENV = "ASSEMBLYAI_API_KEY";
71
-
72
70
  /** How much of our stored upload one outbound window carries. */
73
71
  const UPLOAD_WINDOW_BYTES = 4 * 1024 * 1024;
74
72
 
@@ -277,15 +275,6 @@ async function* windows(uploadId: string, size: number): AsyncGenerator<Uint8Arr
277
275
  }
278
276
  }
279
277
 
280
- /** The API key, or a terminal failure — three more attempts find the same gap. */
281
- function apiKeyOrFatal(): string {
282
- try {
283
- return requireStepEnv(API_KEY_ENV);
284
- } catch (err: unknown) {
285
- return throwFatalStepError(err);
286
- }
287
- }
288
-
289
278
  /** A failed call, classified for the DevKit — see `sync-api.ts` for the three-way rule. */
290
279
  async function failure(res: Response, what: string): Promise<Error> {
291
280
  const body = (await res.json().catch(() => ({}))) as { error?: string };
@@ -123,6 +123,7 @@ import { mapConcurrent, readUpload, report, uploadInfo } from "@alexkroman1/aai/
123
123
  import { sleep } from "workflow";
124
124
  import {
125
125
  clock,
126
+ fatalOnUnsupported,
126
127
  mergeTranscript,
127
128
  type SegmentTranscript,
128
129
  segmentConcurrency,
@@ -323,16 +324,6 @@ function expectedSegments(plan: StreamPlan, size: number): number {
323
324
  return plan.segments.filter((segment) => segment.start < size).length;
324
325
  }
325
326
 
326
- /** Run a `wav.ts` helper, turning its "cannot cut this" into a terminal failure. */
327
- function fatalOnUnsupported<T>(read: () => T): T {
328
- try {
329
- return read();
330
- } catch (err: unknown) {
331
- if (err instanceof UnsupportedRecordingError) return throwFatalStepError(err);
332
- throw err;
333
- }
334
- }
335
-
336
327
  /**
337
328
  * Give up on an upload that stopped arriving.
338
329
  *
@@ -104,8 +104,14 @@ export async function transcribeWav(
104
104
  return (body.text ?? "").trim();
105
105
  }
106
106
 
107
- /** The API key, or a terminal failure — three more attempts find the same gap. */
108
- function apiKeyOrFatal(): string {
107
+ /**
108
+ * The API key, or a terminal failure — three more attempts find the same gap.
109
+ *
110
+ * Exported because `batch.ts` calls the same provider on the same key and had
111
+ * written this, and its own `API_KEY_ENV`, again. The key belongs to the
112
+ * PROVIDER; only the endpoint and its failure shapes belong to this module.
113
+ */
114
+ export function apiKeyOrFatal(): string {
109
115
  try {
110
116
  return requireStepEnv(API_KEY_ENV);
111
117
  } catch (err: unknown) {
@@ -422,8 +422,14 @@ export {
422
422
 
423
423
  // ---- I/O helpers ------------------------------------------------------------
424
424
 
425
- /** Run a `wav.ts` helper, turning its "cannot cut this" into a terminal failure. */
426
- function fatalOnUnsupported<T>(read: () => T): T {
425
+ /**
426
+ * Run a `wav.ts` helper, turning its "cannot cut this" into a terminal failure.
427
+ *
428
+ * Exported for the same reason `countWords` is: `stream.ts` plans with the same
429
+ * `wav.ts` helpers and owes the same classification, and it had this byte for
430
+ * byte.
431
+ */
432
+ export function fatalOnUnsupported<T>(read: () => T): T {
427
433
  try {
428
434
  return read();
429
435
  } catch (err: unknown) {
@@ -38,9 +38,7 @@ function resolveVitestCommand(cwd, resolve = createRequire(path.join(cwd, "packa
38
38
  * Throws on test failure.
39
39
  */
40
40
  function runVitest(cwd) {
41
- let testFile = null;
42
- if (existsSync(path.join(cwd, "agent.test.ts"))) testFile = "agent.test.ts";
43
- else if (existsSync(path.join(cwd, "agent.test.js"))) testFile = "agent.test.js";
41
+ const testFile = ["agent.test.ts", "agent.test.js"].find((name) => existsSync(path.join(cwd, name)));
44
42
  if (!testFile) return false;
45
43
  const { cmd, args } = resolveVitestCommand(cwd);
46
44
  execaSync(cmd, [
@@ -155,12 +155,11 @@ async function typecheckProject(cwd) {
155
155
  });
156
156
  let output = "";
157
157
  const keep = (s) => s.length > OUTPUT_CAP ? `…${s.slice(-16e3)}` : s;
158
- child.stdout.on("data", (chunk) => {
158
+ const append = (chunk) => {
159
159
  output = keep(output + chunk.toString());
160
- });
161
- child.stderr.on("data", (chunk) => {
162
- output = keep(output + chunk.toString());
163
- });
160
+ };
161
+ child.stdout.on("data", append);
162
+ child.stderr.on("data", append);
164
163
  child.on("error", reject);
165
164
  child.on("close", (code, signal) => {
166
165
  if (signal) {
@@ -145,8 +145,19 @@ var AaiWorkflowBuilder = class extends BaseBuilder {
145
145
  * repo's `erasableSyntaxOnly`, so the fields are declared the long way too.
146
146
  */
147
147
  output;
148
- outDir;
148
+ /**
149
+ * The two bundles `build()` reads back.
150
+ *
151
+ * Held as fields because `super()` is also told where to write them: derived
152
+ * a second time inside `build()`, the two spellings of each path were free to
153
+ * disagree, and a `build()` reading a file the builder never wrote is a
154
+ * "produced nothing" failure that names neither path.
155
+ */
156
+ flowFile;
157
+ stepFile;
149
158
  constructor(cwd, outDir) {
159
+ const flowFile = path.join(outDir, "flow.mjs");
160
+ const stepFile = path.join(outDir, "step.mjs");
150
161
  super({
151
162
  buildTarget: "standalone",
152
163
  dirs: [`./${WORKFLOWS_DIR}`],
@@ -154,31 +165,30 @@ var AaiWorkflowBuilder = class extends BaseBuilder {
154
165
  projectRoot: cwd,
155
166
  moduleSpecifierRoot: cwd,
156
167
  externalPackages: [...WDK_EXTERNAL],
157
- stepsBundlePath: path.join(outDir, "step.mjs"),
158
- workflowsBundlePath: path.join(outDir, "flow.mjs"),
168
+ stepsBundlePath: stepFile,
169
+ workflowsBundlePath: flowFile,
159
170
  webhookBundlePath: path.join(outDir, "webhook.mjs"),
160
171
  suppressCreateWorkflowsBundleLogs: true,
161
172
  suppressCreateWebhookBundleLogs: true,
162
173
  suppressCreateManifestLogs: true
163
174
  });
164
- this.outDir = outDir;
175
+ this.flowFile = flowFile;
176
+ this.stepFile = stepFile;
165
177
  }
166
178
  async build() {
167
179
  const inputFiles = await this.getInputFiles();
168
- const flowFile = path.join(this.outDir, "flow.mjs");
169
- const stepFile = path.join(this.outDir, "step.mjs");
170
180
  const { manifest } = await this.createWorkflowsBundle({
171
181
  inputFiles,
172
182
  format: "esm",
173
- outfile: flowFile,
183
+ outfile: this.flowFile,
174
184
  bundleFinalOutput: false
175
185
  });
176
186
  await this.createStepsBundle({
177
187
  inputFiles,
178
188
  format: "esm",
179
- outfile: stepFile
189
+ outfile: this.stepFile
180
190
  });
181
- const [workflowCode, stepCode] = await Promise.all([fs.readFile(flowFile, "utf-8"), fs.readFile(stepFile, "utf-8")]);
191
+ const [workflowCode, stepCode] = await Promise.all([fs.readFile(this.flowFile, "utf-8"), fs.readFile(this.stepFile, "utf-8")]);
182
192
  this.output = {
183
193
  workflowCode,
184
194
  stepCode: STEP_REQUIRE_SHIM + stepCode,
@@ -358,8 +368,12 @@ export const __aaiStepCode = ${JSON.stringify(workflows.stepCode)};
358
368
  */
359
369
  async function buildWorker(cwd, opts = {}) {
360
370
  const wrapperPath = path.join(cwd, WRAPPER_ENTRY_REL);
361
- await fs.mkdir(path.dirname(wrapperPath), { recursive: true });
362
- await fs.writeFile(wrapperPath, wrapperEntrySource(opts.runtime !== false, opts.workflows, await discoverToolFiles(cwd), await hasSystemPromptFile(cwd)), "utf-8");
371
+ const [, toolFiles, systemPromptFile] = await Promise.all([
372
+ fs.mkdir(path.dirname(wrapperPath), { recursive: true }),
373
+ discoverToolFiles(cwd),
374
+ hasSystemPromptFile(cwd)
375
+ ]);
376
+ await fs.writeFile(wrapperPath, wrapperEntrySource(opts.runtime !== false, opts.workflows, toolFiles, systemPromptFile), "utf-8");
363
377
  const plugins = [...opts.plugins ?? [], ...opts.workflows ? [workflowClientPlugin(cwd, opts.workflows.inputFiles)] : []];
364
378
  let result;
365
379
  try {
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { t as buildWorker } from "./worker-bundler-CIdDhZ0c.mjs";
2
+ import { t as buildWorker } from "./worker-bundler-CGD4r8Kc.mjs";
3
3
  export { buildWorker };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as ok, n as fail } from "./_output-CKkmWs7i.mjs";
3
3
  import { n as log } from "./_ui-u7T4YooX.mjs";
4
- import { n as getServerInfo } from "./_agent-vGW5SBnX.mjs";
4
+ import { n as getServerInfo } from "./_agent-DpH2pBJd.mjs";
5
5
  import { errorMessage, omitUndefined } from "@alexkroman1/aai/utils";
6
6
  import { createWorkflowApiClient } from "@alexkroman1/aai/workflow-api";
7
7
  //#region workflow.ts