@alexkroman1/aai-cli 6.4.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.
- 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/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 +75 -4
- package/dist/templates/transcription-workflow/api-help.tsx +8 -0
- package/dist/templates/transcription-workflow/client.tsx +149 -11
- package/dist/templates/transcription-workflow/workflows/batch.ts +32 -21
- package/dist/templates/transcription-workflow/workflows/stitch.ts +133 -0
- package/dist/templates/transcription-workflow/workflows/stream.ts +20 -20
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +8 -2
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +71 -86
- 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";
|
|
@@ -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.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.
|
|
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 {
|
|
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)
|
|
@@ -17,20 +17,21 @@
|
|
|
17
17
|
* the decoder happily transcribes into confident nonsense.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { stubStepFetch, stubUploads } from "@alexkroman1/aai/testing";
|
|
21
|
-
import { readUpload } from "@alexkroman1/aai/utils";
|
|
20
|
+
import { stubReporter, stubStepFetch, stubUploads } from "@alexkroman1/aai/testing";
|
|
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,
|
|
32
31
|
splitRecording,
|
|
32
|
+
stitchChunks,
|
|
33
33
|
stitchTranscript,
|
|
34
|
+
TRANSCRIPT_STREAM,
|
|
34
35
|
transcribeSegment,
|
|
35
36
|
} from "./workflows/transcribe.ts";
|
|
36
37
|
import {
|
|
@@ -421,6 +422,52 @@ describe("stitchTranscript", () => {
|
|
|
421
422
|
});
|
|
422
423
|
});
|
|
423
424
|
|
|
425
|
+
describe("stitchChunks — what the PAGE renders while a run is going", () => {
|
|
426
|
+
const chunk = (index: number, text: string) => ({
|
|
427
|
+
index,
|
|
428
|
+
startMs: index * 1000,
|
|
429
|
+
endMs: (index + 1) * 1000,
|
|
430
|
+
text,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("orders by index, because chunks arrive as the segments settle", () => {
|
|
434
|
+
// The one thing a live reader definitely does not have is arrival order: the
|
|
435
|
+
// segments are transcribed concurrently, so chunk 2 routinely precedes 1.
|
|
436
|
+
expect(stitchChunks([chunk(2, "gamma"), chunk(0, "alpha"), chunk(1, "beta")])).toBe(
|
|
437
|
+
"alpha beta gamma",
|
|
438
|
+
);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test("stitches the seams the same way the finished run does", () => {
|
|
442
|
+
// The whole reason the page imports the run's own function: a live
|
|
443
|
+
// transcript that de-duplicated differently would read as the model having
|
|
444
|
+
// changed its mind between the last poll and the result.
|
|
445
|
+
const parts = ["we should ship it on Friday", "ship it on Friday if the tests pass"];
|
|
446
|
+
expect(stitchChunks(parts.map((text, index) => chunk(index, text)))).toBe(
|
|
447
|
+
stitchTranscript(parts),
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test("keeps both pieces around a HOLE rather than seaming across it", () => {
|
|
452
|
+
// A partial list is the ordinary case here, and two pieces that were never
|
|
453
|
+
// adjacent share no overlap — so the live text reads with a jump in it until
|
|
454
|
+
// the missing segment lands, instead of quietly losing a sentence.
|
|
455
|
+
expect(stitchChunks([chunk(0, "alpha beta"), chunk(2, "epsilon zeta")])).toBe(
|
|
456
|
+
"alpha beta epsilon zeta",
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("does not mutate the caller's list, which is React state", () => {
|
|
461
|
+
const chunks = [chunk(1, "beta"), chunk(0, "alpha")];
|
|
462
|
+
stitchChunks(chunks);
|
|
463
|
+
expect(chunks.map((one) => one.index)).toEqual([1, 0]);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("renders nothing out of nothing", () => {
|
|
467
|
+
expect(stitchChunks([])).toBe("");
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
424
471
|
describe("clock", () => {
|
|
425
472
|
test("renders a position a reader can find in the recording", () => {
|
|
426
473
|
expect(clock(0)).toBe("0:00");
|
|
@@ -491,7 +538,7 @@ describe("transcribeSegment", () => {
|
|
|
491
538
|
const stub = stubStepFetch(() => ({
|
|
492
539
|
status: sync.status ?? 200,
|
|
493
540
|
body: sync.body ?? { text: "hello there" },
|
|
494
|
-
...(
|
|
541
|
+
...omitUndefined({ headers: sync.headers }),
|
|
495
542
|
}));
|
|
496
543
|
stubs.push(stub.restore);
|
|
497
544
|
return stub.calls;
|
|
@@ -525,6 +572,30 @@ describe("transcribeSegment", () => {
|
|
|
525
572
|
expect(decoded).toContain("RIFF");
|
|
526
573
|
});
|
|
527
574
|
|
|
575
|
+
test("EMITS the segment's words as it lands, into the transcript stream", async () => {
|
|
576
|
+
// What makes the run's answer streamable rather than only its narration: the
|
|
577
|
+
// page stitches whatever has arrived, so the transcript renders growing
|
|
578
|
+
// instead of appearing when the last segment does. The reporter is the SDK's
|
|
579
|
+
// published slot, which is the same seam `report()` goes through.
|
|
580
|
+
const reported = stubReporter();
|
|
581
|
+
stubs.push(reported.restore);
|
|
582
|
+
stubProvider();
|
|
583
|
+
|
|
584
|
+
await transcribeSegment(UPLOAD_ID, FORMAT, SEGMENT);
|
|
585
|
+
|
|
586
|
+
// The timestamps ride along for the READER — a partial transcript has holes
|
|
587
|
+
// in it, and "0:00–0:01" is what explains a jump.
|
|
588
|
+
expect(reported.emitted).toEqual([
|
|
589
|
+
{
|
|
590
|
+
namespace: TRANSCRIPT_STREAM,
|
|
591
|
+
chunk: { index: 0, startMs: 0, endMs: 1000, text: "hello there" },
|
|
592
|
+
},
|
|
593
|
+
]);
|
|
594
|
+
// And the narration is still its own stream: lines a page prints verbatim
|
|
595
|
+
// cannot share a channel with objects.
|
|
596
|
+
expect(reported.lines.some((line) => line.startsWith("Transcribing"))).toBe(true);
|
|
597
|
+
});
|
|
598
|
+
|
|
528
599
|
test("fails FATALLY with no API key rather than retrying five times", async () => {
|
|
529
600
|
vi.stubEnv("ASSEMBLYAI_API_KEY", "");
|
|
530
601
|
stubProvider();
|
|
@@ -136,6 +136,14 @@ const ROUTES: readonly { route: string; does: string }[] = [
|
|
|
136
136
|
route: "PUT /workflows/uploads/:id",
|
|
137
137
|
does: "store a file under YOUR id, readable as it arrives",
|
|
138
138
|
},
|
|
139
|
+
{
|
|
140
|
+
route: "POST /workflows/uploads/:id/parts",
|
|
141
|
+
does: "declare an upload its parts fill in · ?total=<bytes>",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
route: "PUT /workflows/uploads/:id/parts",
|
|
145
|
+
does: "one window of it · ?offset=<byte>, sent concurrently",
|
|
146
|
+
},
|
|
139
147
|
{ route: "GET /workflows/uploads/:id", does: "read the bytes back · Range honoured" },
|
|
140
148
|
{ route: "GET /workflows/uploads/:id/info", does: "name, bytes stored so far, and complete" },
|
|
141
149
|
];
|