@alexkroman1/aai-cli 5.13.2 → 6.1.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-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
- package/dist/_agent.d.ts +18 -0
- package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
- package/dist/_api-client.d.ts +39 -0
- package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
- package/dist/_bundler.d.ts +43 -5
- package/dist/_cli-common.d.ts +67 -3
- package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
- package/dist/_deploy.d.ts +2 -5
- package/dist/_dev-restart.d.ts +6 -1
- package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
- package/dist/_dev-server.d.ts +30 -2
- package/dist/_e2e-test-utils.d.ts +80 -0
- package/dist/_fault-mode.d.ts +175 -0
- package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
- package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
- package/dist/_output.d.ts +9 -2
- package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
- package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
- package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
- package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
- package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
- package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
- package/dist/_utils.d.ts +18 -2
- package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
- package/dist/_vite-env.d.ts +30 -0
- package/dist/build-BO1Ni6oJ.mjs +62 -0
- package/dist/build.d.ts +19 -0
- package/dist/cli-workflow.d.ts +10 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +406 -224
- package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
- package/dist/delete.d.ts +2 -8
- package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
- package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
- package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
- package/dist/eject.d.ts +30 -3
- package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
- package/dist/init.d.ts +11 -0
- package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +498 -91
- package/dist/scaffold/package.json +11 -8
- package/dist/scaffold/pnpm-workspace.yaml +32 -0
- package/dist/scaffold/server.mjs +53 -57
- package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
- package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
- package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
- package/dist/templates/code-interpreter/agent.ts +0 -2
- package/dist/templates/dispatch-center/agent.test.ts +21 -30
- package/dist/templates/dispatch-center/agent.ts +4 -31
- package/dist/templates/dispatch-center/client.tsx +41 -28
- package/dist/templates/dispatch-center/shared.ts +73 -66
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +13 -15
- package/dist/templates/dispatch-center/tools/incident_create.ts +42 -45
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +60 -62
- package/dist/templates/dispatch-center/tools/incident_get.ts +10 -6
- package/dist/templates/dispatch-center/tools/incident_triage.ts +44 -46
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +40 -42
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +3 -6
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +21 -24
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +53 -55
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +3 -5
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +40 -43
- package/dist/templates/embedded-assets/agent.test.ts +83 -0
- package/dist/templates/embedded-assets/agent.ts +1 -48
- package/dist/templates/embedded-assets/shared.ts +17 -0
- package/dist/templates/embedded-assets/system-prompt.md +13 -0
- package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
- package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
- package/dist/templates/health-assistant/agent.test.ts +169 -0
- package/dist/templates/health-assistant/agent.ts +1 -166
- package/dist/templates/health-assistant/fda.ts +100 -0
- package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
- package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
- package/dist/templates/infocom-adventure/agent.test.ts +205 -0
- package/dist/templates/infocom-adventure/agent.ts +3 -106
- package/dist/templates/infocom-adventure/client.tsx +32 -28
- package/dist/templates/infocom-adventure/shared.ts +36 -16
- package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
- package/dist/templates/link-digest/agent.test.ts +223 -0
- package/dist/templates/link-digest/agent.ts +79 -0
- package/dist/templates/link-digest/client.tsx +146 -0
- package/dist/templates/link-digest/workflows/digest.ts +248 -0
- package/dist/templates/math-buddy/agent.ts +0 -2
- package/dist/templates/night-owl/agent.test.ts +80 -0
- package/dist/templates/night-owl/agent.ts +1 -68
- package/dist/templates/night-owl/system-prompt.md +10 -0
- package/dist/templates/night-owl/tools/recommend.ts +64 -0
- package/dist/templates/personal-finance/agent.ts +0 -2
- package/dist/templates/pizza-ordering/agent.test.ts +59 -58
- package/dist/templates/pizza-ordering/agent.ts +10 -159
- package/dist/templates/pizza-ordering/client.tsx +4 -4
- package/dist/templates/pizza-ordering/shared.ts +44 -27
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
- package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
- package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
- package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
- package/dist/templates/plan-and-execute/agent.test.ts +411 -0
- package/dist/templates/plan-and-execute/agent.ts +23 -0
- package/dist/templates/plan-and-execute/client.tsx +115 -0
- package/dist/templates/plan-and-execute/graph.ts +178 -0
- package/dist/templates/plan-and-execute/prompts.ts +110 -0
- package/dist/templates/plan-and-execute/shared.ts +161 -0
- package/dist/templates/plan-and-execute/system-prompt.md +18 -0
- package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
- package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
- package/dist/templates/recap-workflow/agent.test.ts +762 -0
- package/dist/templates/recap-workflow/agent.ts +95 -0
- package/dist/templates/recap-workflow/shared.ts +43 -0
- package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
- package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
- package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
- package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
- package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
- package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
- package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
- package/dist/templates/redline/agent.test.ts +222 -0
- package/dist/templates/redline/agent.ts +89 -0
- package/dist/templates/redline/client.tsx +184 -0
- package/dist/templates/redline/workflows/prompts.ts +68 -0
- package/dist/templates/redline/workflows/redline.ts +258 -0
- package/dist/templates/research-workflow/agent.test.ts +525 -0
- package/dist/templates/research-workflow/agent.ts +94 -0
- package/dist/templates/research-workflow/shared.ts +35 -0
- package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
- package/dist/templates/research-workflow/tools/request_research.ts +33 -0
- package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
- package/dist/templates/research-workflow/tools/research_status.ts +40 -0
- package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
- package/dist/templates/research-workflow/workflows/research.ts +558 -0
- package/dist/templates/retail/agent.test.ts +319 -256
- package/dist/templates/retail/agent.ts +6 -46
- package/dist/templates/retail/authenticate.ts +2 -2
- package/dist/templates/retail/client.tsx +45 -24
- package/dist/templates/retail/registry.test.ts +40 -25
- package/dist/templates/retail/resolve.test.ts +17 -16
- package/dist/templates/retail/resolve.ts +32 -67
- package/dist/templates/retail/shared.test.ts +21 -19
- package/dist/templates/retail/shared.ts +61 -23
- package/dist/templates/retail/store.test.ts +47 -52
- package/dist/templates/retail/store.ts +92 -79
- package/dist/templates/retail/swap.test.ts +20 -17
- package/dist/templates/retail/swap.ts +7 -14
- package/dist/templates/retail/tools/cancel_pending_order.ts +7 -11
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +26 -14
- package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
- package/dist/templates/retail/tools/get_item_details.ts +7 -8
- package/dist/templates/retail/tools/get_order_details.ts +7 -8
- package/dist/templates/retail/tools/get_product_details.ts +6 -8
- package/dist/templates/retail/tools/get_user_details.ts +6 -8
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
- package/dist/templates/retail/tools/modify_pending_order_address.ts +6 -10
- package/dist/templates/retail/tools/modify_pending_order_items.ts +8 -20
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +7 -13
- package/dist/templates/retail/tools/modify_user_address.ts +6 -10
- package/dist/templates/retail/tools/return_delivered_order_items.ts +7 -13
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
- package/dist/templates/solo-rpg/agent.test.ts +169 -54
- package/dist/templates/solo-rpg/agent.ts +4 -22
- package/dist/templates/solo-rpg/client.tsx +19 -6
- package/dist/templates/solo-rpg/shared.ts +20 -20
- package/dist/templates/solo-rpg/tools/action_roll.ts +3 -5
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +3 -5
- package/dist/templates/solo-rpg/tools/check_state.ts +7 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +3 -3
- package/dist/templates/solo-rpg/tools/oracle.ts +19 -12
- package/dist/templates/solo-rpg/tools/save_game.ts +11 -8
- package/dist/templates/solo-rpg/tools/setup_character.ts +3 -3
- package/dist/templates/solo-rpg/tools/update_state.ts +13 -13
- package/dist/templates/support-line/agent.test.ts +343 -0
- package/dist/templates/support-line/agent.ts +27 -0
- package/dist/templates/support-line/client.tsx +122 -0
- package/dist/templates/support-line/graph.ts +224 -0
- package/dist/templates/support-line/knowledge.json +65 -0
- package/dist/templates/support-line/prompts.ts +95 -0
- package/dist/templates/support-line/shared.ts +240 -0
- package/dist/templates/support-line/system-prompt.md +17 -0
- package/dist/templates/support-line/tools/answer_question.ts +72 -0
- package/dist/templates/support-line/tools/list_topics.ts +22 -0
- package/dist/templates/support-line/tools/log_ticket.ts +30 -0
- package/dist/templates/transcription-workflow/agent.test.ts +563 -0
- package/dist/templates/transcription-workflow/agent.ts +92 -0
- package/dist/templates/transcription-workflow/client.tsx +234 -0
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
- package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
- package/dist/templates/travel-concierge/agent.test.ts +338 -0
- package/dist/templates/travel-concierge/agent.ts +29 -0
- package/dist/templates/travel-concierge/client.tsx +161 -0
- package/dist/templates/travel-concierge/routing.ts +138 -0
- package/dist/templates/travel-concierge/shared.ts +547 -0
- package/dist/templates/travel-concierge/system-prompt.md +24 -0
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
- package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
- package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
- package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
- package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
- package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
- package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
- package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
- package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
- package/dist/templates/web-researcher/agent.ts +0 -2
- package/dist/templates/web-researcher/system-prompt.md +10 -0
- package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
- package/dist/typecheck.mjs +17 -15
- package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
- package/dist/worker-bundler.d.ts +14 -0
- package/dist/worker-bundler.mjs +1 -108
- package/dist/workflow-B-O97AqI.mjs +125 -0
- package/dist/workflow-bundler.d.ts +133 -0
- package/dist/workflow.d.ts +67 -0
- package/package.json +13 -9
- package/dist/build-DQJ1ZSm9.mjs +0 -39
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ToolContext } from "@alexkroman1/aai";
|
|
2
|
-
import {
|
|
1
|
+
import type { ToolContext, ToolFailure } from "@alexkroman1/aai";
|
|
2
|
+
import { isToolFailure, pushCapped, sessionSlot, toolFailure } from "@alexkroman1/aai";
|
|
3
3
|
import type { z } from "zod";
|
|
4
4
|
import seedJson from "./seed.json";
|
|
5
5
|
import type {
|
|
@@ -8,12 +8,11 @@ import type {
|
|
|
8
8
|
PaymentMethod,
|
|
9
9
|
Product,
|
|
10
10
|
RetailState,
|
|
11
|
-
StateSlot,
|
|
12
11
|
Store,
|
|
13
12
|
User,
|
|
14
13
|
Variant,
|
|
15
14
|
} from "./shared.ts";
|
|
16
|
-
import { MAX_ACTIVITY } from "./shared.ts";
|
|
15
|
+
import { emptyRetailState, MAX_ACTIVITY } from "./shared.ts";
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* The JSON import's inferred type has `status: string` where `Order` wants a
|
|
@@ -23,12 +22,6 @@ import { MAX_ACTIVITY } from "./shared.ts";
|
|
|
23
22
|
*/
|
|
24
23
|
const SEED = seedJson as unknown as Store;
|
|
25
24
|
|
|
26
|
-
export type ErrorResult = { error: string };
|
|
27
|
-
|
|
28
|
-
export function isError(value: unknown): value is ErrorResult {
|
|
29
|
-
return typeof value === "object" && value !== null && "error" in value;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
25
|
/** Round to cents. Gift-card balances and price differences are compared for
|
|
33
26
|
* equality, and raw float arithmetic makes that a coin toss. */
|
|
34
27
|
export function money(n: number): number {
|
|
@@ -45,23 +38,39 @@ export function isGiftCard(method: PaymentMethod): method is GiftCard {
|
|
|
45
38
|
* module-level object shared by every session in the process, so a mutation
|
|
46
39
|
* without it would let one caller's cancellation show up in another's. */
|
|
47
40
|
export function createDefaultState(): RetailState {
|
|
48
|
-
return {
|
|
49
|
-
store: structuredClone(SEED),
|
|
50
|
-
authenticatedUserId: null,
|
|
51
|
-
callSeq: 0,
|
|
52
|
-
activity: [],
|
|
53
|
-
focus: {},
|
|
54
|
-
};
|
|
41
|
+
return { ...emptyRetailState(), store: seedStore() };
|
|
55
42
|
}
|
|
56
43
|
|
|
57
|
-
/**
|
|
58
|
-
*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
/**
|
|
45
|
+
* A pristine deep copy of the seed store.
|
|
46
|
+
*
|
|
47
|
+
* Exported so a caller needs no cast of its own. `shared.test.ts` had written
|
|
48
|
+
* `structuredClone(seedJson) as unknown as Store` twice — a second and third
|
|
49
|
+
* copy of the narrowing this module already owns, each of which would keep
|
|
50
|
+
* compiling if `Store` grew a field the JSON does not carry. The one cast is
|
|
51
|
+
* `SEED`'s above, and `seed.test.ts` is what makes it honest.
|
|
52
|
+
*/
|
|
53
|
+
export function seedStore(): Store {
|
|
54
|
+
return structuredClone(SEED);
|
|
63
55
|
}
|
|
64
56
|
|
|
57
|
+
/**
|
|
58
|
+
* The session's store, as one typed slot.
|
|
59
|
+
*
|
|
60
|
+
* Every tool goes through `retailSlot.updateTool` (via `retailTool`), so every
|
|
61
|
+
* tool body is handed a mutable draft of the whole store and mutates it in
|
|
62
|
+
* place: the ~106 KB of catalogue here is exactly the state a functional replace
|
|
63
|
+
* would make unwritable without hand-built spread chains three levels deep.
|
|
64
|
+
* What makes that safe is that the window is SYNCHRONOUS — the LLM loop runs a
|
|
65
|
+
* step's tool calls concurrently, and a draft that cannot span an await cannot
|
|
66
|
+
* interleave with another one.
|
|
67
|
+
*
|
|
68
|
+
* No `after` hook — unlike dispatch-center, this store has no derived field to
|
|
69
|
+
* recalculate, and its one growth cap (`activity`) is held on append by
|
|
70
|
+
* `record` below.
|
|
71
|
+
*/
|
|
72
|
+
export const retailSlot = sessionSlot("retail", createDefaultState);
|
|
73
|
+
|
|
65
74
|
export function setFocus(
|
|
66
75
|
state: RetailState,
|
|
67
76
|
focus: { orderId?: string; productId?: string },
|
|
@@ -71,15 +80,15 @@ export function setFocus(
|
|
|
71
80
|
|
|
72
81
|
// ─── Lookups ─────────────────────────────────────────────────────────────────
|
|
73
82
|
|
|
74
|
-
export function findUser(state: RetailState, userId: string): User |
|
|
83
|
+
export function findUser(state: RetailState, userId: string): User | ToolFailure {
|
|
75
84
|
return state.store.users[userId] ?? { error: `User ${userId} not found.` };
|
|
76
85
|
}
|
|
77
86
|
|
|
78
|
-
export function findOrder(state: RetailState, orderId: string): Order |
|
|
87
|
+
export function findOrder(state: RetailState, orderId: string): Order | ToolFailure {
|
|
79
88
|
return state.store.orders[orderId] ?? { error: `Order ${orderId} not found.` };
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
export function findProduct(state: RetailState, productId: string): Product |
|
|
91
|
+
export function findProduct(state: RetailState, productId: string): Product | ToolFailure {
|
|
83
92
|
return (
|
|
84
93
|
state.store.products[productId] ?? {
|
|
85
94
|
error: `Product ${productId} not found. Note a product id is not an item id.`,
|
|
@@ -87,7 +96,7 @@ export function findProduct(state: RetailState, productId: string): Product | Er
|
|
|
87
96
|
);
|
|
88
97
|
}
|
|
89
98
|
|
|
90
|
-
export function findVariant(product: Product, itemId: string): Variant |
|
|
99
|
+
export function findVariant(product: Product, itemId: string): Variant | ToolFailure {
|
|
91
100
|
return (
|
|
92
101
|
product.variants[itemId] ?? {
|
|
93
102
|
error: `Item ${itemId} is not a variant of ${product.name} (${product.product_id}).`,
|
|
@@ -99,7 +108,7 @@ export function findVariant(product: Product, itemId: string): Variant | ErrorRe
|
|
|
99
108
|
export function findItem(
|
|
100
109
|
state: RetailState,
|
|
101
110
|
itemId: string,
|
|
102
|
-
): { product: Product; variant: Variant } |
|
|
111
|
+
): { product: Product; variant: Variant } | ToolFailure {
|
|
103
112
|
for (const product of Object.values(state.store.products)) {
|
|
104
113
|
const variant = product.variants[itemId];
|
|
105
114
|
if (variant) return { product, variant };
|
|
@@ -107,7 +116,7 @@ export function findItem(
|
|
|
107
116
|
return { error: `Item ${itemId} not found. Note an item id is not a product id.` };
|
|
108
117
|
}
|
|
109
118
|
|
|
110
|
-
export function findPaymentMethod(user: User, methodId: string): PaymentMethod |
|
|
119
|
+
export function findPaymentMethod(user: User, methodId: string): PaymentMethod | ToolFailure {
|
|
111
120
|
return (
|
|
112
121
|
user.payment_methods[methodId] ?? {
|
|
113
122
|
error: `Payment method ${methodId} is not on this customer's profile. Available: ${Object.keys(
|
|
@@ -123,7 +132,7 @@ const NOT_AUTHENTICATED =
|
|
|
123
132
|
"Not authenticated. Identify the customer first with find_user_id_by_email, " +
|
|
124
133
|
"or find_user_id_by_name_zip if they cannot remember their email.";
|
|
125
134
|
|
|
126
|
-
export function authenticatedUser(state: RetailState): User |
|
|
135
|
+
export function authenticatedUser(state: RetailState): User | ToolFailure {
|
|
127
136
|
if (!state.authenticatedUserId) return { error: NOT_AUTHENTICATED };
|
|
128
137
|
return findUser(state, state.authenticatedUserId);
|
|
129
138
|
}
|
|
@@ -139,9 +148,9 @@ export function authenticatedUser(state: RetailState): User | ErrorResult {
|
|
|
139
148
|
* matches every sibling lookup (`findUser`, `findOrder`, `findProduct`, …),
|
|
140
149
|
* which all echo the id precisely so an LLM can target a repair retry.
|
|
141
150
|
*/
|
|
142
|
-
export function requireOwnOrder(state: RetailState, orderId: string): Order |
|
|
151
|
+
export function requireOwnOrder(state: RetailState, orderId: string): Order | ToolFailure {
|
|
143
152
|
const user = authenticatedUser(state);
|
|
144
|
-
if (
|
|
153
|
+
if (isToolFailure(user)) return user;
|
|
145
154
|
const order = state.store.orders[orderId];
|
|
146
155
|
if (!order || order.user_id !== user.user_id) {
|
|
147
156
|
return { error: `Order ${orderId} was not found on this customer's account.` };
|
|
@@ -149,32 +158,6 @@ export function requireOwnOrder(state: RetailState, orderId: string): Order | Er
|
|
|
149
158
|
return order;
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
// ─── Serialized state updates ────────────────────────────────────────────────
|
|
153
|
-
|
|
154
|
-
const sessionLock = createKeyedLock();
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Serialized update of the session's store.
|
|
158
|
-
*
|
|
159
|
-
* The LLM loop can execute parallel tool calls, and a mutator may be async —
|
|
160
|
-
* two interleaving async mutators can each observe the other's half-applied
|
|
161
|
-
* changes. Holding the session's key runs each update against the previous
|
|
162
|
-
* one's finished result. `createKeyedLock` is the SDK's primitive for exactly
|
|
163
|
-
* this; the hand-rolled promise chain it replaced also had to remember to drop
|
|
164
|
-
* each key's entry by ownership once idle.
|
|
165
|
-
*
|
|
166
|
-
* NOT exported, unlike dispatch-center's equivalent. `retailTool` is the only
|
|
167
|
-
* caller, so a tool body cannot re-enter it — which would wait on a key only
|
|
168
|
-
* the outer call can release, i.e. deadlock. A hazard you can't reach beats a
|
|
169
|
-
* hazard you documented.
|
|
170
|
-
*/
|
|
171
|
-
function updateState<R>(
|
|
172
|
-
ctx: ToolContext,
|
|
173
|
-
mutator: (state: RetailState) => R | Promise<R>,
|
|
174
|
-
): Promise<R> {
|
|
175
|
-
return withLock(sessionLock, ctx.sessionId, async () => mutator(getState(ctx)));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
161
|
// ─── The tool wrapper ────────────────────────────────────────────────────────
|
|
179
162
|
|
|
180
163
|
interface RetailToolSpec<S extends z.ZodType<Record<string, unknown>>, R> {
|
|
@@ -188,15 +171,43 @@ interface RetailToolSpec<S extends z.ZodType<Record<string, unknown>>, R> {
|
|
|
188
171
|
* out; everything else touches customer data. */
|
|
189
172
|
requiresAuth?: boolean;
|
|
190
173
|
summary: (args: z.output<S>, result: R) => string;
|
|
191
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Handed the store as its second argument, and SYNCHRONOUS.
|
|
176
|
+
*
|
|
177
|
+
* **Declare it BEFORE `summary` in the object literal.** TS infers this
|
|
178
|
+
* wrapper's generic `R` from `execute`'s return type and processes an object
|
|
179
|
+
* literal's properties in SOURCE ORDER, so with `summary` written first its
|
|
180
|
+
* `result` parameter has nothing to infer from and silently falls back to
|
|
181
|
+
* `unknown` — every `isToolFailure(result) ? … : result.order_id` in the
|
|
182
|
+
* fifteen tool files then stops compiling, or worse, stops meaning anything.
|
|
183
|
+
* It lives here rather than in each tool file because it is a property of
|
|
184
|
+
* this type: the same four lines were pasted into eight of the fifteen and
|
|
185
|
+
* pointed at from five more, which is a rule maintained in fourteen places.
|
|
186
|
+
*
|
|
187
|
+
* ---
|
|
188
|
+
*
|
|
189
|
+
* **The draft is passed in rather than re-read**, which is the one change the
|
|
190
|
+
* durable store forced on this template. The body used to open with
|
|
191
|
+
* `retailSlot.get(ctx)`; under the draft write model that returns the value as
|
|
192
|
+
* it was BEFORE this call, frozen, so a body that mutated it would be writing
|
|
193
|
+
* to something nothing was going to store. Passing it removes the possibility
|
|
194
|
+
* rather than documenting it.
|
|
195
|
+
*
|
|
196
|
+
* Synchronous for the same reason: the window cannot span an await — see
|
|
197
|
+
* {@link SessionSlot.update}. Every tool here is a pure function of the store,
|
|
198
|
+
* so none of them wants to; a tool that DID would await outside the wrapper and
|
|
199
|
+
* call `retailSlot.update` itself, the way `plan-and-execute`'s do.
|
|
200
|
+
*/
|
|
201
|
+
execute: (args: z.output<S>, state: RetailState, ctx: ToolContext) => R;
|
|
192
202
|
}
|
|
193
203
|
|
|
194
204
|
function record(state: RetailState, name: string, summary: string): void {
|
|
195
205
|
state.callSeq += 1;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
state.
|
|
199
|
-
|
|
206
|
+
pushCapped(
|
|
207
|
+
state.activity,
|
|
208
|
+
{ seq: state.callSeq, tool: name, summary, at: Date.now() },
|
|
209
|
+
MAX_ACTIVITY,
|
|
210
|
+
);
|
|
200
211
|
}
|
|
201
212
|
|
|
202
213
|
/**
|
|
@@ -215,23 +226,25 @@ export function retailTool<S extends z.ZodType<Record<string, unknown>>, R>(
|
|
|
215
226
|
spec: RetailToolSpec<S, R>,
|
|
216
227
|
) {
|
|
217
228
|
const requiresAuth = spec.requiresAuth ?? true;
|
|
218
|
-
|
|
229
|
+
// `updateTool` rather than `tool` + a hand-written `retailSlot.update`: it runs
|
|
230
|
+
// the body inside the slot's mutation window and hands it the draft, so this
|
|
231
|
+
// wrapper is left with only what is specific to THIS agent.
|
|
232
|
+
return retailSlot.updateTool({
|
|
219
233
|
description: spec.description,
|
|
220
234
|
inputSchema: spec.inputSchema,
|
|
221
|
-
execute: (args, ctx) =>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}),
|
|
235
|
+
execute: (args, state, ctx) => {
|
|
236
|
+
const typedArgs = args as z.output<S>;
|
|
237
|
+
if (requiresAuth && !state.authenticatedUserId) {
|
|
238
|
+
record(state, spec.name, "blocked: not authenticated");
|
|
239
|
+
return toolFailure(NOT_AUTHENTICATED);
|
|
240
|
+
}
|
|
241
|
+
const result = spec.execute(typedArgs, state, ctx);
|
|
242
|
+
record(
|
|
243
|
+
state,
|
|
244
|
+
spec.name,
|
|
245
|
+
isToolFailure(result) ? `error: ${result.error}` : spec.summary(typedArgs, result),
|
|
246
|
+
);
|
|
247
|
+
return result;
|
|
248
|
+
},
|
|
236
249
|
});
|
|
237
250
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
1
2
|
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { createDefaultState, findUser
|
|
3
|
+
import { createDefaultState, findUser } from "./store.ts";
|
|
3
4
|
import { applySwap, assertCanCoverDiff, planItemSwap } from "./swap.ts";
|
|
4
5
|
|
|
5
6
|
function fixture(orderId: string) {
|
|
@@ -15,7 +16,7 @@ describe("planItemSwap", () => {
|
|
|
15
16
|
const plan = planItemSwap(state, order, ["1304426904"], ["4725166838"], {
|
|
16
17
|
requireDifferent: true,
|
|
17
18
|
});
|
|
18
|
-
if (
|
|
19
|
+
if (isToolFailure(plan)) throw new Error(plan.error);
|
|
19
20
|
expect(plan.diff).toBe(36.32);
|
|
20
21
|
expect(plan.pairs).toHaveLength(1);
|
|
21
22
|
expect(plan.pairs[0]?.newVariant.item_id).toBe("4725166838");
|
|
@@ -26,7 +27,7 @@ describe("planItemSwap", () => {
|
|
|
26
27
|
const plan = planItemSwap(state, order, ["6242772310"], ["6200867091"], {
|
|
27
28
|
requireDifferent: true,
|
|
28
29
|
});
|
|
29
|
-
expect(
|
|
30
|
+
expect(isToolFailure(plan) ? null : plan.diff).toBe(-40.86);
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
test("handles a duplicate item id swapped twice", () => {
|
|
@@ -38,7 +39,7 @@ describe("planItemSwap", () => {
|
|
|
38
39
|
["3909406921", "3909406921"],
|
|
39
40
|
{ requireDifferent: true },
|
|
40
41
|
);
|
|
41
|
-
if (
|
|
42
|
+
if (isToolFailure(plan)) throw new Error(plan.error);
|
|
42
43
|
expect(plan.diff).toBe(6.9);
|
|
43
44
|
expect(plan.pairs.map((p) => p.index)).toEqual([0, 1]);
|
|
44
45
|
});
|
|
@@ -48,7 +49,7 @@ describe("planItemSwap", () => {
|
|
|
48
49
|
const plan = planItemSwap(state, order, ["7292993796"], ["3909406921"], {
|
|
49
50
|
requireDifferent: true,
|
|
50
51
|
});
|
|
51
|
-
if (
|
|
52
|
+
if (isToolFailure(plan)) throw new Error(plan.error);
|
|
52
53
|
expect(plan.pairs).toHaveLength(1);
|
|
53
54
|
expect(plan.diff).toBe(3.45);
|
|
54
55
|
});
|
|
@@ -62,7 +63,7 @@ describe("planItemSwap", () => {
|
|
|
62
63
|
["3909406921", "3909406921", "3909406921"],
|
|
63
64
|
{ requireDifferent: true },
|
|
64
65
|
);
|
|
65
|
-
expect(
|
|
66
|
+
expect(isToolFailure(plan) && plan.error).toContain("7292993796");
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
test("refuses mismatched list lengths", () => {
|
|
@@ -70,12 +71,14 @@ describe("planItemSwap", () => {
|
|
|
70
71
|
const plan = planItemSwap(state, order, ["7292993796"], ["3909406921", "3738831434"], {
|
|
71
72
|
requireDifferent: true,
|
|
72
73
|
});
|
|
73
|
-
expect(
|
|
74
|
+
expect(isToolFailure(plan) && plan.error.toLowerCase()).toContain("same number");
|
|
74
75
|
});
|
|
75
76
|
|
|
76
77
|
test("refuses an empty swap", () => {
|
|
77
78
|
const { state, order } = fixture("#W4316152");
|
|
78
|
-
expect(
|
|
79
|
+
expect(isToolFailure(planItemSwap(state, order, [], [], { requireDifferent: true }))).toBe(
|
|
80
|
+
true,
|
|
81
|
+
);
|
|
79
82
|
});
|
|
80
83
|
|
|
81
84
|
test("refuses an item the order does not contain", () => {
|
|
@@ -83,7 +86,7 @@ describe("planItemSwap", () => {
|
|
|
83
86
|
const plan = planItemSwap(state, order, ["1304426904"], ["4725166838"], {
|
|
84
87
|
requireDifferent: true,
|
|
85
88
|
});
|
|
86
|
-
expect(
|
|
89
|
+
expect(isToolFailure(plan)).toBe(true);
|
|
87
90
|
});
|
|
88
91
|
|
|
89
92
|
test("refuses a target of a different product", () => {
|
|
@@ -91,7 +94,7 @@ describe("planItemSwap", () => {
|
|
|
91
94
|
const plan = planItemSwap(state, order, ["7292993796"], ["4725166838"], {
|
|
92
95
|
requireDifferent: true,
|
|
93
96
|
});
|
|
94
|
-
expect(
|
|
97
|
+
expect(isToolFailure(plan) && plan.error).toContain("Tea Kettle");
|
|
95
98
|
});
|
|
96
99
|
|
|
97
100
|
test("refuses an unavailable target", () => {
|
|
@@ -99,7 +102,7 @@ describe("planItemSwap", () => {
|
|
|
99
102
|
const plan = planItemSwap(state, order, ["7292993796"], ["6454334990"], {
|
|
100
103
|
requireDifferent: true,
|
|
101
104
|
});
|
|
102
|
-
expect(
|
|
105
|
+
expect(isToolFailure(plan) && plan.error.toLowerCase()).toContain("not available");
|
|
103
106
|
});
|
|
104
107
|
|
|
105
108
|
test("requireDifferent refuses a no-op swap; exchange allows it", () => {
|
|
@@ -107,11 +110,11 @@ describe("planItemSwap", () => {
|
|
|
107
110
|
const strict = planItemSwap(state, order, ["7292993796"], ["7292993796"], {
|
|
108
111
|
requireDifferent: true,
|
|
109
112
|
});
|
|
110
|
-
expect(
|
|
113
|
+
expect(isToolFailure(strict)).toBe(true);
|
|
111
114
|
const loose = planItemSwap(state, order, ["7292993796"], ["7292993796"], {
|
|
112
115
|
requireDifferent: false,
|
|
113
116
|
});
|
|
114
|
-
expect(
|
|
117
|
+
expect(isToolFailure(loose) ? null : loose.diff).toBe(0);
|
|
115
118
|
});
|
|
116
119
|
});
|
|
117
120
|
|
|
@@ -119,7 +122,7 @@ describe("assertCanCoverDiff", () => {
|
|
|
119
122
|
function aarav() {
|
|
120
123
|
const state = createDefaultState();
|
|
121
124
|
const user = findUser(state, "aarav_anderson_8794");
|
|
122
|
-
if (
|
|
125
|
+
if (isToolFailure(user)) throw new Error(user.error);
|
|
123
126
|
return user;
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -140,7 +143,7 @@ describe("assertCanCoverDiff", () => {
|
|
|
140
143
|
test("does not gate a non-gift-card method on any balance", () => {
|
|
141
144
|
const state = createDefaultState();
|
|
142
145
|
const olivia = findUser(state, "olivia_ito_3591");
|
|
143
|
-
if (
|
|
146
|
+
if (isToolFailure(olivia)) throw new Error(olivia.error);
|
|
144
147
|
expect(assertCanCoverDiff(olivia, "credit_card_9753331", 100_000)).toBeNull();
|
|
145
148
|
});
|
|
146
149
|
|
|
@@ -161,7 +164,7 @@ describe("applySwap", () => {
|
|
|
161
164
|
["4725166838", "3909406921"],
|
|
162
165
|
{ requireDifferent: true },
|
|
163
166
|
);
|
|
164
|
-
if (
|
|
167
|
+
if (isToolFailure(plan)) throw new Error(plan.error);
|
|
165
168
|
applySwap(order, plan);
|
|
166
169
|
|
|
167
170
|
const vacuum = order.items.find((i) => i.item_id === "4725166838");
|
|
@@ -179,7 +182,7 @@ describe("applySwap", () => {
|
|
|
179
182
|
const plan = planItemSwap(state, order, ["7292993796"], ["3909406921"], {
|
|
180
183
|
requireDifferent: true,
|
|
181
184
|
});
|
|
182
|
-
if (
|
|
185
|
+
if (isToolFailure(plan)) throw new Error(plan.error);
|
|
183
186
|
applySwap(order, plan);
|
|
184
187
|
expect(order.items.map((i) => i.item_id)).toEqual(["3909406921", "7292993796"]);
|
|
185
188
|
});
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
+
import { isToolFailure, type ToolFailure } from "@alexkroman1/aai";
|
|
1
2
|
import type { Order, OrderItem, RetailState, User, Variant } from "./shared.ts";
|
|
2
|
-
import {
|
|
3
|
-
type ErrorResult,
|
|
4
|
-
findPaymentMethod,
|
|
5
|
-
findProduct,
|
|
6
|
-
findVariant,
|
|
7
|
-
isError,
|
|
8
|
-
isGiftCard,
|
|
9
|
-
money,
|
|
10
|
-
} from "./store.ts";
|
|
3
|
+
import { findPaymentMethod, findProduct, findVariant, isGiftCard, money } from "./store.ts";
|
|
11
4
|
|
|
12
5
|
export interface SwapPair {
|
|
13
6
|
/** Index into `order.items`. Matching by index rather than by "first item
|
|
@@ -37,7 +30,7 @@ export function planItemSwap(
|
|
|
37
30
|
itemIds: string[],
|
|
38
31
|
newItemIds: string[],
|
|
39
32
|
opts: { requireDifferent: boolean },
|
|
40
|
-
): SwapPlan |
|
|
33
|
+
): SwapPlan | ToolFailure {
|
|
41
34
|
if (itemIds.length === 0) {
|
|
42
35
|
return { error: "No items were listed to change." };
|
|
43
36
|
}
|
|
@@ -89,10 +82,10 @@ export function planItemSwap(
|
|
|
89
82
|
consumed.add(index);
|
|
90
83
|
|
|
91
84
|
const product = findProduct(state, item.product_id);
|
|
92
|
-
if (
|
|
85
|
+
if (isToolFailure(product)) return product;
|
|
93
86
|
|
|
94
87
|
const newVariant = findVariant(product, newItemId);
|
|
95
|
-
if (
|
|
88
|
+
if (isToolFailure(newVariant)) {
|
|
96
89
|
return {
|
|
97
90
|
error: `${newItemId} is not an option of ${product.name} (${product.product_id}). An item can only be changed to a different option of the same product — you cannot change a ${product.name} into something else.`,
|
|
98
91
|
};
|
|
@@ -114,9 +107,9 @@ export function planItemSwap(
|
|
|
114
107
|
* Gate the price difference on the chosen payment method. Only a gift card has
|
|
115
108
|
* a balance to run out of; a negative difference is a refund and never gated.
|
|
116
109
|
*/
|
|
117
|
-
export function assertCanCoverDiff(user: User, methodId: string, diff: number):
|
|
110
|
+
export function assertCanCoverDiff(user: User, methodId: string, diff: number): ToolFailure | null {
|
|
118
111
|
const method = findPaymentMethod(user, methodId);
|
|
119
|
-
if (
|
|
112
|
+
if (isToolFailure(method)) return method;
|
|
120
113
|
if (isGiftCard(method) && method.balance < diff) {
|
|
121
114
|
return {
|
|
122
115
|
error: `Gift card ${methodId}'s balance ($${method.balance.toFixed(2)}) does not cover the $${diff.toFixed(2)} difference. Ask for another payment method.`,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { creditRefund, REFUND_DELAY_NOTE, REFUND_IMMEDIATE_NOTE } from "../refund.ts";
|
|
3
4
|
import { resolveOrder } from "../resolve.ts";
|
|
4
|
-
import { authenticatedUser,
|
|
5
|
+
import { authenticatedUser, retailTool, setFocus } from "../store.ts";
|
|
5
6
|
|
|
6
7
|
/** tau2 accepts exactly these two. Anything else is refused. */
|
|
7
8
|
const CANCEL_REASONS = ["no longer needed", "ordered by mistake"] as const;
|
|
8
9
|
|
|
9
|
-
export
|
|
10
|
+
export default retailTool({
|
|
10
11
|
name: "cancel_pending_order",
|
|
11
12
|
description:
|
|
12
13
|
"Cancel a pending order. Only an order whose status is exactly 'pending' can be cancelled — " +
|
|
@@ -22,17 +23,12 @@ export const cancelPendingOrder = retailTool({
|
|
|
22
23
|
.enum(CANCEL_REASONS)
|
|
23
24
|
.describe("Either 'no longer needed' or 'ordered by mistake' — no other reason is accepted"),
|
|
24
25
|
}),
|
|
25
|
-
|
|
26
|
-
// `execute`'s return type, and processes object literal properties in
|
|
27
|
-
// source order — with `summary` first, `result` in its signature can't be
|
|
28
|
-
// inferred and silently falls back to `unknown`.
|
|
29
|
-
execute: (args, ctx) => {
|
|
30
|
-
const state = getState(ctx);
|
|
26
|
+
execute: (args, state) => {
|
|
31
27
|
const user = authenticatedUser(state);
|
|
32
|
-
if (
|
|
28
|
+
if (isToolFailure(user)) return user;
|
|
33
29
|
|
|
34
30
|
const order = resolveOrder(state, args.order_id);
|
|
35
|
-
if (
|
|
31
|
+
if (isToolFailure(order)) return order;
|
|
36
32
|
setFocus(state, { orderId: order.order_id });
|
|
37
33
|
|
|
38
34
|
if (order.status !== "pending") {
|
|
@@ -78,5 +74,5 @@ export const cancelPendingOrder = retailTool({
|
|
|
78
74
|
};
|
|
79
75
|
},
|
|
80
76
|
summary: (_args, result) =>
|
|
81
|
-
|
|
77
|
+
isToolFailure(result) ? "cancel failed" : `cancelled ${result.order_id}`,
|
|
82
78
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { resolveOrder } from "../resolve.ts";
|
|
3
|
-
import { authenticatedUser,
|
|
4
|
+
import { authenticatedUser, money, retailTool, setFocus } from "../store.ts";
|
|
4
5
|
import { assertCanCoverDiff, planItemSwap } from "../swap.ts";
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
+
export default retailTool({
|
|
7
8
|
name: "exchange_delivered_order_items",
|
|
8
9
|
description:
|
|
9
10
|
"Request an exchange of items in a delivered order for different options of the SAME products. " +
|
|
@@ -29,17 +30,12 @@ export const exchangeDeliveredOrderItems = retailTool({
|
|
|
29
30
|
.max(80)
|
|
30
31
|
.describe("Method to charge or refund the price difference"),
|
|
31
32
|
}),
|
|
32
|
-
|
|
33
|
-
// `execute`'s return type, and processes object literal properties in
|
|
34
|
-
// source order — with `summary` first, `result` in its signature can't be
|
|
35
|
-
// inferred and silently falls back to `unknown`.
|
|
36
|
-
execute: (args, ctx) => {
|
|
37
|
-
const state = getState(ctx);
|
|
33
|
+
execute: (args, state) => {
|
|
38
34
|
const user = authenticatedUser(state);
|
|
39
|
-
if (
|
|
35
|
+
if (isToolFailure(user)) return user;
|
|
40
36
|
|
|
41
37
|
const order = resolveOrder(state, args.order_id);
|
|
42
|
-
if (
|
|
38
|
+
if (isToolFailure(order)) return order;
|
|
43
39
|
setFocus(state, { orderId: order.order_id });
|
|
44
40
|
|
|
45
41
|
if (order.status !== "delivered") {
|
|
@@ -53,7 +49,7 @@ export const exchangeDeliveredOrderItems = retailTool({
|
|
|
53
49
|
const plan = planItemSwap(state, order, args.item_ids, args.new_item_ids, {
|
|
54
50
|
requireDifferent: false,
|
|
55
51
|
});
|
|
56
|
-
if (
|
|
52
|
+
if (isToolFailure(plan)) return plan;
|
|
57
53
|
|
|
58
54
|
const blocked = assertCanCoverDiff(user, args.payment_method_id, plan.diff);
|
|
59
55
|
if (blocked) return blocked;
|
|
@@ -61,6 +57,18 @@ export const exchangeDeliveredOrderItems = retailTool({
|
|
|
61
57
|
// No balance moves and no items change: this records a REQUEST. The money
|
|
62
58
|
// and the item swap settle when the originals come back.
|
|
63
59
|
order.status = "exchange requested";
|
|
60
|
+
// SORTED INDEPENDENTLY, verbatim from tau2 (`order.exchange_items =
|
|
61
|
+
// sorted(item_ids)` / `sorted(new_item_ids)`, the same shape
|
|
62
|
+
// `return_delivered_order_items` uses one list of) — kept because
|
|
63
|
+
// `seed.json` is tau2's data and these fields are what a tau2 task's
|
|
64
|
+
// expected end state is compared against. They are therefore two SETS of
|
|
65
|
+
// ids, not a pairing: sorting each list separately permutes the second
|
|
66
|
+
// against the first whenever the caller named more than one item.
|
|
67
|
+
//
|
|
68
|
+
// Which is why the ANSWER carries `exchanges` instead. `planItemSwap`
|
|
69
|
+
// priced this positionally, so returning the two sorted lists as if they
|
|
70
|
+
// lined up told the caller a pairing the quote was not for — the one thing
|
|
71
|
+
// this result must not do, since the model reads it back down a phone.
|
|
64
72
|
order.exchange_items = [...args.item_ids].sort();
|
|
65
73
|
order.exchange_new_items = [...args.new_item_ids].sort();
|
|
66
74
|
order.exchange_payment_method_id = args.payment_method_id;
|
|
@@ -70,8 +78,12 @@ export const exchangeDeliveredOrderItems = retailTool({
|
|
|
70
78
|
order_id: order.order_id,
|
|
71
79
|
status: order.status,
|
|
72
80
|
price_difference: plan.diff,
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
// The pairing AS PRICED, per line, in the order the caller gave it.
|
|
82
|
+
exchanges: plan.pairs.map((pair) => ({
|
|
83
|
+
item_id: pair.item.item_id,
|
|
84
|
+
new_item_id: pair.newVariant.item_id,
|
|
85
|
+
price_difference: money(pair.newVariant.price - pair.item.price),
|
|
86
|
+
})),
|
|
75
87
|
message:
|
|
76
88
|
plan.diff > 0
|
|
77
89
|
? `Exchange requested on ${order.order_id}. $${plan.diff.toFixed(2)} will be charged to ${args.payment_method_id}. An email with return instructions is on its way.`
|
|
@@ -79,5 +91,5 @@ export const exchangeDeliveredOrderItems = retailTool({
|
|
|
79
91
|
};
|
|
80
92
|
},
|
|
81
93
|
summary: (_args, result) =>
|
|
82
|
-
|
|
94
|
+
isToolFailure(result) ? "exchange failed" : `exchange requested on ${result.order_id}`,
|
|
83
95
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { authenticateAs } from "../authenticate.ts";
|
|
3
|
-
import {
|
|
4
|
+
import { retailTool } from "../store.ts";
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
+
export default retailTool({
|
|
6
7
|
name: "find_user_id_by_email",
|
|
7
8
|
description:
|
|
8
9
|
"Find a customer's user id by their email address. This is how you authenticate the caller — " +
|
|
@@ -12,12 +13,7 @@ export const findUserIdByEmail = retailTool({
|
|
|
12
13
|
email: z.string().max(200).describe("The customer's email, e.g. 'something@example.com'"),
|
|
13
14
|
}),
|
|
14
15
|
requiresAuth: false,
|
|
15
|
-
|
|
16
|
-
// wrapper's generic `R` from `execute`'s return type, and processes object
|
|
17
|
-
// literal properties in source order — with `summary` first, `result` in its
|
|
18
|
-
// signature can't be inferred and silently falls back to `unknown`.
|
|
19
|
-
execute: (args, ctx) => {
|
|
20
|
-
const state = getState(ctx);
|
|
16
|
+
execute: (args, state) => {
|
|
21
17
|
const target = args.email.trim().toLowerCase();
|
|
22
18
|
const match = Object.values(state.store.users).find(
|
|
23
19
|
(user) => user.email.toLowerCase() === target,
|
|
@@ -28,5 +24,5 @@ export const findUserIdByEmail = retailTool({
|
|
|
28
24
|
return authenticateAs(state, match);
|
|
29
25
|
},
|
|
30
26
|
summary: (_args, result) =>
|
|
31
|
-
|
|
27
|
+
isToolFailure(result) ? "lookup failed" : `identified ${result.user_id}`,
|
|
32
28
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { authenticateAs } from "../authenticate.ts";
|
|
3
|
-
import {
|
|
4
|
+
import { retailTool } from "../store.ts";
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
+
export default retailTool({
|
|
6
7
|
name: "find_user_id_by_name_zip",
|
|
7
8
|
description:
|
|
8
9
|
"Find a customer's user id by first name, last name and zip code. Use this only when the " +
|
|
@@ -13,10 +14,7 @@ export const findUserIdByNameZip = retailTool({
|
|
|
13
14
|
zip: z.string().max(20).describe("Zip code, e.g. '12345'"),
|
|
14
15
|
}),
|
|
15
16
|
requiresAuth: false,
|
|
16
|
-
|
|
17
|
-
// is load-bearing for the generic `result` type in `summary`.
|
|
18
|
-
execute: (args, ctx) => {
|
|
19
|
-
const state = getState(ctx);
|
|
17
|
+
execute: (args, state) => {
|
|
20
18
|
const first = args.first_name.trim().toLowerCase();
|
|
21
19
|
const last = args.last_name.trim().toLowerCase();
|
|
22
20
|
const zip = args.zip.replace(/\D/g, "");
|
|
@@ -34,5 +32,5 @@ export const findUserIdByNameZip = retailTool({
|
|
|
34
32
|
return authenticateAs(state, match);
|
|
35
33
|
},
|
|
36
34
|
summary: (_args, result) =>
|
|
37
|
-
|
|
35
|
+
isToolFailure(result) ? "lookup failed" : `identified ${result.user_id}`,
|
|
38
36
|
});
|