@alexkroman1/aai-cli 6.5.0 → 6.6.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-vGW5SBnX.mjs → _agent-DpH2pBJd.mjs} +1 -5
- package/dist/{_bundler-BapCUUJN.mjs → _bundler-C2BDwU7r.mjs} +5 -7
- package/dist/{_dev-server-DQ1t9sbw.mjs → _dev-server-LLLWnFBs.mjs} +3 -3
- package/dist/_e2e-test-utils.d.ts +17 -17
- package/dist/{_init-CpISqoR8.mjs → _init-C57bWAQ8.mjs} +2 -2
- package/dist/{_slug-api-D2heILuC.mjs → _slug-api-BxrsJXVI.mjs} +3 -3
- package/dist/_slug-api.d.ts +1 -1
- package/dist/{_api-client-LgLksMhN.mjs → _studio-U3jhKlA3.mjs} +105 -2
- package/dist/_studio.d.ts +11 -0
- package/dist/{_templates-iDGjGiPf.mjs → _templates-Dxb_P2Wz.mjs} +1 -1
- package/dist/bin.d.mts +2 -0
- package/dist/{build-BO1Ni6oJ.mjs → build-Dfza2pRI.mjs} +2 -2
- package/dist/cli.mjs +43 -42
- package/dist/{client-bundler-B8Q9F8gJ.mjs → client-bundler-BEIqgOtd.mjs} +2 -2
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-4I0uNR4a.mjs → delete-vbpjGzqs.mjs} +3 -3
- package/dist/{deploy-DJ3HRcia.mjs → deploy-B8lubiRT.mjs} +3 -4
- package/dist/{dev-C6SWaAZP.mjs → dev-Uq5ujP8a.mjs} +1 -1
- package/dist/{eject-Zrzk5KV3.mjs → eject-31gjtaHF.mjs} +2 -2
- package/dist/{init-BTRGiPQr.mjs → init-l6xfU4xX.mjs} +3 -3
- package/dist/{login-k0Z3VQu9.mjs → login-C71-qz8F.mjs} +1 -1
- package/dist/scaffold/.env.example +21 -0
- package/dist/scaffold/CLAUDE.md +23 -0
- package/dist/scaffold/package.json +3 -3
- package/dist/{secret-DzSQx1Ds.mjs → secret-BuMuFR4B.mjs} +2 -2
- package/dist/{storage-C-Do0mIA.mjs → storage-DzRZ-eCw.mjs} +2 -2
- package/dist/{studio-BGVmCA2G.mjs → studio-BCNUpDgP.mjs} +4 -5
- package/dist/templates/retail/resolve.ts +16 -0
- package/dist/templates/retail/store.ts +20 -0
- package/dist/templates/retail/tools/cancel_pending_order.ts +2 -5
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -5
- package/dist/templates/retail/tools/get_order_details.ts +4 -6
- package/dist/templates/retail/tools/get_user_details.ts +2 -7
- package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -5
- package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -5
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -5
- package/dist/templates/retail/tools/modify_user_address.ts +2 -7
- package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -5
- package/dist/templates/transcription-workflow/agent.test.ts +2 -3
- package/dist/templates/transcription-workflow/client.tsx +9 -6
- package/dist/templates/transcription-workflow/workflows/batch.ts +2 -13
- package/dist/templates/transcription-workflow/workflows/stream.ts +1 -10
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +8 -2
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +8 -2
- package/dist/{test-B6ZBxSpk.mjs → test-9kPKJI-w.mjs} +1 -3
- package/dist/typecheck.mjs +4 -5
- package/dist/{worker-bundler-CIdDhZ0c.mjs → worker-bundler-CGD4r8Kc.mjs} +25 -11
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-B-O97AqI.mjs → workflow-CFpxOFfQ.mjs} +1 -1
- package/package.json +4 -4
- 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-
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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";
|
|
@@ -9,3 +9,24 @@
|
|
|
9
9
|
# Required only if your agent enables the `web_search` builtin
|
|
10
10
|
# (free key: https://brave.com/search/api/):
|
|
11
11
|
# BRAVE_API_KEY=
|
|
12
|
+
|
|
13
|
+
# --- Durable workflows and uploads -----------------------------------------
|
|
14
|
+
#
|
|
15
|
+
# A durable run needs a database; `api.upload(file)` needs a database AND
|
|
16
|
+
# somewhere to put the bytes. A DEPLOYED agent gets both from the platform
|
|
17
|
+
# (`aai storage enable`), so these four are the LOCAL equivalents and none of
|
|
18
|
+
# them is needed by an agent that has no workflow.
|
|
19
|
+
#
|
|
20
|
+
# All three storage keys or none: two of three resolves to nothing and refuses
|
|
21
|
+
# by name rather than half-working. Nothing creates the bucket for you — a
|
|
22
|
+
# bucket is dashboard state, not a migration.
|
|
23
|
+
#
|
|
24
|
+
# From the Supabase CLI: `supabase start`, then `supabase status -o env` for
|
|
25
|
+
# API_URL and SERVICE_ROLE_KEY. The local stack declares one bucket, `blobs`,
|
|
26
|
+
# and uploads land under an `uploads/` prefix inside it — the same layout
|
|
27
|
+
# production uses beside its `blobs/<sha256>` deploy artifacts.
|
|
28
|
+
#
|
|
29
|
+
# DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
|
|
30
|
+
# AAI_UPLOAD_STORAGE_URL=http://127.0.0.1:54321
|
|
31
|
+
# AAI_UPLOAD_STORAGE_KEY=<SERVICE_ROLE_KEY>
|
|
32
|
+
# AAI_UPLOAD_STORAGE_BUCKET=blobs
|
package/dist/scaffold/CLAUDE.md
CHANGED
|
@@ -1155,6 +1155,29 @@ A query returning more than 1000 rows throws — always bound reads with
|
|
|
1155
1155
|
preview and published agents, each with its own schema)
|
|
1156
1156
|
- `aai dev`: set `DATABASE_URL` in the project `.env`
|
|
1157
1157
|
|
|
1158
|
+
**A workflow UPLOAD needs one more thing locally: somewhere to put the bytes.**
|
|
1159
|
+
A deployed agent gets it from the platform; under `aai dev` the bytes go to a
|
|
1160
|
+
bucket you point it at, and the Supabase CLI prints the two values
|
|
1161
|
+
(`supabase start`, then `supabase status -o env`):
|
|
1162
|
+
|
|
1163
|
+
```sh
|
|
1164
|
+
DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
|
|
1165
|
+
AAI_UPLOAD_STORAGE_URL=http://127.0.0.1:54321
|
|
1166
|
+
AAI_UPLOAD_STORAGE_KEY=<SERVICE_ROLE_KEY>
|
|
1167
|
+
AAI_UPLOAD_STORAGE_BUCKET=blobs
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
`blobs`, not `uploads`: that is the one bucket the local stack declares
|
|
1171
|
+
(`supabase/config.toml`, applied by `supabase start`), and uploads land under an
|
|
1172
|
+
`uploads/` PREFIX inside it — the same layout production uses beside its
|
|
1173
|
+
`blobs/<sha256>` deploy artifacts. Nothing creates a bucket for you.
|
|
1174
|
+
|
|
1175
|
+
`.env.example` in a scaffolded project carries this block commented out.
|
|
1176
|
+
|
|
1177
|
+
Without both halves `api.upload(file)` fails naming the one that is missing —
|
|
1178
|
+
never quietly into a directory, which is what it used to do and then lose by the
|
|
1179
|
+
time a resumed run read it.
|
|
1180
|
+
|
|
1158
1181
|
Create tables lazily from tool code and upsert with `on conflict`:
|
|
1159
1182
|
|
|
1160
1183
|
```ts no-check
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"publish:agent": "aai publish"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@alexkroman1/aai": "^6.
|
|
17
|
-
"@alexkroman1/aai-ui": "^6.
|
|
16
|
+
"@alexkroman1/aai": "^6.6.0",
|
|
17
|
+
"@alexkroman1/aai-ui": "^6.6.0",
|
|
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.
|
|
26
|
+
"@alexkroman1/aai-cli": "^6.6.0",
|
|
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 {
|
|
5
|
-
import { t as secretRequest } from "./_slug-api-
|
|
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 {
|
|
5
|
-
import { n as slugRequest } from "./_slug-api-
|
|
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-
|
|
7
|
-
import { layerScaffold } from "./_templates-
|
|
8
|
-
import {
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
...(
|
|
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 {
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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 {
|
|
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 =
|
|
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:
|
|
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
|
-
...(
|
|
541
|
+
...omitUndefined({ headers: sync.headers }),
|
|
543
542
|
}));
|
|
544
543
|
stubs.push(stub.restore);
|
|
545
544
|
return stub.calls;
|
|
@@ -61,11 +61,13 @@
|
|
|
61
61
|
* CONTIGUOUS prefix, which is honest whether one connection or four are filling
|
|
62
62
|
* it).
|
|
63
63
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* is
|
|
64
|
+
* The SDK does this by DEFAULT, so the checkbox is an opt-OUT rather than an
|
|
65
|
+
* opt-in — and it is here for the reason the mode radios are: this is the template
|
|
66
|
+
* where a reader runs both over the same recording and sees what each costs. It
|
|
67
|
+
* also degrades on its own — a small file, or an agent deployed before the
|
|
68
|
+
* `/parts` routes existed, sends the single request instead — so leaving it on is
|
|
69
|
+
* safe, and turning it off costs retries as well as speed (a single request is the
|
|
70
|
+
* one upload path that cannot be re-sent).
|
|
69
71
|
*
|
|
70
72
|
* ## The transcript ARRIVES, rather than appearing at the end
|
|
71
73
|
*
|
|
@@ -342,7 +344,8 @@ function UploadPicker({
|
|
|
342
344
|
<span>Split the file across connections</span>
|
|
343
345
|
<span className="text-xs opacity-70">
|
|
344
346
|
Sends the recording as several parts at once instead of in one request, which is most of
|
|
345
|
-
the wait on a long file
|
|
347
|
+
the wait on a long file — and is the only upload a dropped connection can resume. Falls
|
|
348
|
+
back to the single request on a small one.
|
|
346
349
|
</span>
|
|
347
350
|
</span>
|
|
348
351
|
</label>
|
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
import { throwFatalStepError, toStepError } from "@alexkroman1/aai/step-errors";
|
|
48
|
-
import { readUpload, report,
|
|
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
|
-
/**
|
|
108
|
-
|
|
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
|
-
/**
|
|
426
|
-
|
|
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
|
-
|
|
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, [
|
package/dist/typecheck.mjs
CHANGED
|
@@ -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
|
-
|
|
158
|
+
const append = (chunk) => {
|
|
159
159
|
output = keep(output + chunk.toString());
|
|
160
|
-
}
|
|
161
|
-
child.
|
|
162
|
-
|
|
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) {
|