@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.
Files changed (236) hide show
  1. package/dist/{_agent-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
  2. package/dist/_agent.d.ts +18 -0
  3. package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
  4. package/dist/_api-client.d.ts +39 -0
  5. package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
  6. package/dist/_bundler.d.ts +43 -5
  7. package/dist/_cli-common.d.ts +67 -3
  8. package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
  9. package/dist/_deploy.d.ts +2 -5
  10. package/dist/_dev-restart.d.ts +6 -1
  11. package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
  12. package/dist/_dev-server.d.ts +30 -2
  13. package/dist/_e2e-test-utils.d.ts +80 -0
  14. package/dist/_fault-mode.d.ts +175 -0
  15. package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
  16. package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
  17. package/dist/_output.d.ts +9 -2
  18. package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
  19. package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
  20. package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
  21. package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
  22. package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
  23. package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
  24. package/dist/_utils.d.ts +18 -2
  25. package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
  26. package/dist/_vite-env.d.ts +30 -0
  27. package/dist/build-BO1Ni6oJ.mjs +62 -0
  28. package/dist/build.d.ts +19 -0
  29. package/dist/cli-workflow.d.ts +10 -0
  30. package/dist/cli.d.ts +1 -2
  31. package/dist/cli.mjs +406 -224
  32. package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
  33. package/dist/client-bundler.mjs +1 -1
  34. package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
  35. package/dist/delete.d.ts +2 -8
  36. package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
  37. package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
  38. package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
  39. package/dist/eject.d.ts +30 -3
  40. package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
  41. package/dist/init.d.ts +11 -0
  42. package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
  43. package/dist/project-config.mjs +1 -1
  44. package/dist/scaffold/CLAUDE.md +498 -91
  45. package/dist/scaffold/package.json +11 -8
  46. package/dist/scaffold/pnpm-workspace.yaml +32 -0
  47. package/dist/scaffold/server.mjs +53 -57
  48. package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
  49. package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
  50. package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
  51. package/dist/templates/code-interpreter/agent.ts +0 -2
  52. package/dist/templates/dispatch-center/agent.test.ts +21 -30
  53. package/dist/templates/dispatch-center/agent.ts +4 -31
  54. package/dist/templates/dispatch-center/client.tsx +41 -28
  55. package/dist/templates/dispatch-center/shared.ts +73 -66
  56. package/dist/templates/dispatch-center/tools/incident_add_note.ts +13 -15
  57. package/dist/templates/dispatch-center/tools/incident_create.ts +42 -45
  58. package/dist/templates/dispatch-center/tools/incident_escalate.ts +60 -62
  59. package/dist/templates/dispatch-center/tools/incident_get.ts +10 -6
  60. package/dist/templates/dispatch-center/tools/incident_triage.ts +44 -46
  61. package/dist/templates/dispatch-center/tools/incident_update_status.ts +40 -42
  62. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +3 -6
  63. package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
  64. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +21 -24
  65. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +53 -55
  66. package/dist/templates/dispatch-center/tools/resources_get_available.ts +3 -5
  67. package/dist/templates/dispatch-center/tools/resources_update_status.ts +40 -43
  68. package/dist/templates/embedded-assets/agent.test.ts +83 -0
  69. package/dist/templates/embedded-assets/agent.ts +1 -48
  70. package/dist/templates/embedded-assets/shared.ts +17 -0
  71. package/dist/templates/embedded-assets/system-prompt.md +13 -0
  72. package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
  73. package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
  74. package/dist/templates/health-assistant/agent.test.ts +169 -0
  75. package/dist/templates/health-assistant/agent.ts +1 -166
  76. package/dist/templates/health-assistant/fda.ts +100 -0
  77. package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
  78. package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
  79. package/dist/templates/infocom-adventure/agent.test.ts +205 -0
  80. package/dist/templates/infocom-adventure/agent.ts +3 -106
  81. package/dist/templates/infocom-adventure/client.tsx +32 -28
  82. package/dist/templates/infocom-adventure/shared.ts +36 -16
  83. package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
  84. package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
  85. package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
  86. package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
  87. package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
  88. package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
  89. package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
  90. package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
  91. package/dist/templates/link-digest/agent.test.ts +223 -0
  92. package/dist/templates/link-digest/agent.ts +79 -0
  93. package/dist/templates/link-digest/client.tsx +146 -0
  94. package/dist/templates/link-digest/workflows/digest.ts +248 -0
  95. package/dist/templates/math-buddy/agent.ts +0 -2
  96. package/dist/templates/night-owl/agent.test.ts +80 -0
  97. package/dist/templates/night-owl/agent.ts +1 -68
  98. package/dist/templates/night-owl/system-prompt.md +10 -0
  99. package/dist/templates/night-owl/tools/recommend.ts +64 -0
  100. package/dist/templates/personal-finance/agent.ts +0 -2
  101. package/dist/templates/pizza-ordering/agent.test.ts +59 -58
  102. package/dist/templates/pizza-ordering/agent.ts +10 -159
  103. package/dist/templates/pizza-ordering/client.tsx +4 -4
  104. package/dist/templates/pizza-ordering/shared.ts +44 -27
  105. package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
  106. package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
  107. package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
  108. package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
  109. package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
  110. package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
  111. package/dist/templates/plan-and-execute/agent.test.ts +411 -0
  112. package/dist/templates/plan-and-execute/agent.ts +23 -0
  113. package/dist/templates/plan-and-execute/client.tsx +115 -0
  114. package/dist/templates/plan-and-execute/graph.ts +178 -0
  115. package/dist/templates/plan-and-execute/prompts.ts +110 -0
  116. package/dist/templates/plan-and-execute/shared.ts +161 -0
  117. package/dist/templates/plan-and-execute/system-prompt.md +18 -0
  118. package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
  119. package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
  120. package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
  121. package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
  122. package/dist/templates/recap-workflow/agent.test.ts +762 -0
  123. package/dist/templates/recap-workflow/agent.ts +95 -0
  124. package/dist/templates/recap-workflow/shared.ts +43 -0
  125. package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
  126. package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
  127. package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
  128. package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
  129. package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
  130. package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
  131. package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
  132. package/dist/templates/redline/agent.test.ts +222 -0
  133. package/dist/templates/redline/agent.ts +89 -0
  134. package/dist/templates/redline/client.tsx +184 -0
  135. package/dist/templates/redline/workflows/prompts.ts +68 -0
  136. package/dist/templates/redline/workflows/redline.ts +258 -0
  137. package/dist/templates/research-workflow/agent.test.ts +525 -0
  138. package/dist/templates/research-workflow/agent.ts +94 -0
  139. package/dist/templates/research-workflow/shared.ts +35 -0
  140. package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
  141. package/dist/templates/research-workflow/tools/request_research.ts +33 -0
  142. package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
  143. package/dist/templates/research-workflow/tools/research_status.ts +40 -0
  144. package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
  145. package/dist/templates/research-workflow/workflows/research.ts +558 -0
  146. package/dist/templates/retail/agent.test.ts +319 -256
  147. package/dist/templates/retail/agent.ts +6 -46
  148. package/dist/templates/retail/authenticate.ts +2 -2
  149. package/dist/templates/retail/client.tsx +45 -24
  150. package/dist/templates/retail/registry.test.ts +40 -25
  151. package/dist/templates/retail/resolve.test.ts +17 -16
  152. package/dist/templates/retail/resolve.ts +32 -67
  153. package/dist/templates/retail/shared.test.ts +21 -19
  154. package/dist/templates/retail/shared.ts +61 -23
  155. package/dist/templates/retail/store.test.ts +47 -52
  156. package/dist/templates/retail/store.ts +92 -79
  157. package/dist/templates/retail/swap.test.ts +20 -17
  158. package/dist/templates/retail/swap.ts +7 -14
  159. package/dist/templates/retail/tools/cancel_pending_order.ts +7 -11
  160. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +26 -14
  161. package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
  162. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
  163. package/dist/templates/retail/tools/get_item_details.ts +7 -8
  164. package/dist/templates/retail/tools/get_order_details.ts +7 -8
  165. package/dist/templates/retail/tools/get_product_details.ts +6 -8
  166. package/dist/templates/retail/tools/get_user_details.ts +6 -8
  167. package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
  168. package/dist/templates/retail/tools/modify_pending_order_address.ts +6 -10
  169. package/dist/templates/retail/tools/modify_pending_order_items.ts +8 -20
  170. package/dist/templates/retail/tools/modify_pending_order_payment.ts +7 -13
  171. package/dist/templates/retail/tools/modify_user_address.ts +6 -10
  172. package/dist/templates/retail/tools/return_delivered_order_items.ts +7 -13
  173. package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
  174. package/dist/templates/solo-rpg/agent.test.ts +169 -54
  175. package/dist/templates/solo-rpg/agent.ts +4 -22
  176. package/dist/templates/solo-rpg/client.tsx +19 -6
  177. package/dist/templates/solo-rpg/shared.ts +20 -20
  178. package/dist/templates/solo-rpg/tools/action_roll.ts +3 -5
  179. package/dist/templates/solo-rpg/tools/burn_momentum.ts +3 -5
  180. package/dist/templates/solo-rpg/tools/check_state.ts +7 -7
  181. package/dist/templates/solo-rpg/tools/load_game.ts +3 -3
  182. package/dist/templates/solo-rpg/tools/oracle.ts +19 -12
  183. package/dist/templates/solo-rpg/tools/save_game.ts +11 -8
  184. package/dist/templates/solo-rpg/tools/setup_character.ts +3 -3
  185. package/dist/templates/solo-rpg/tools/update_state.ts +13 -13
  186. package/dist/templates/support-line/agent.test.ts +343 -0
  187. package/dist/templates/support-line/agent.ts +27 -0
  188. package/dist/templates/support-line/client.tsx +122 -0
  189. package/dist/templates/support-line/graph.ts +224 -0
  190. package/dist/templates/support-line/knowledge.json +65 -0
  191. package/dist/templates/support-line/prompts.ts +95 -0
  192. package/dist/templates/support-line/shared.ts +240 -0
  193. package/dist/templates/support-line/system-prompt.md +17 -0
  194. package/dist/templates/support-line/tools/answer_question.ts +72 -0
  195. package/dist/templates/support-line/tools/list_topics.ts +22 -0
  196. package/dist/templates/support-line/tools/log_ticket.ts +30 -0
  197. package/dist/templates/transcription-workflow/agent.test.ts +563 -0
  198. package/dist/templates/transcription-workflow/agent.ts +92 -0
  199. package/dist/templates/transcription-workflow/client.tsx +234 -0
  200. package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
  201. package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
  202. package/dist/templates/travel-concierge/agent.test.ts +338 -0
  203. package/dist/templates/travel-concierge/agent.ts +29 -0
  204. package/dist/templates/travel-concierge/client.tsx +161 -0
  205. package/dist/templates/travel-concierge/routing.ts +138 -0
  206. package/dist/templates/travel-concierge/shared.ts +547 -0
  207. package/dist/templates/travel-concierge/system-prompt.md +24 -0
  208. package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
  209. package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
  210. package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
  211. package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
  212. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
  213. package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
  214. package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
  215. package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
  216. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
  217. package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
  218. package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
  219. package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
  220. package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
  221. package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
  222. package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
  223. package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
  224. package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
  225. package/dist/templates/web-researcher/agent.ts +0 -2
  226. package/dist/templates/web-researcher/system-prompt.md +10 -0
  227. package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
  228. package/dist/typecheck.mjs +17 -15
  229. package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
  230. package/dist/worker-bundler.d.ts +14 -0
  231. package/dist/worker-bundler.mjs +1 -108
  232. package/dist/workflow-B-O97AqI.mjs +125 -0
  233. package/dist/workflow-bundler.d.ts +133 -0
  234. package/dist/workflow.d.ts +67 -0
  235. package/package.json +13 -9
  236. package/dist/build-DQJ1ZSm9.mjs +0 -39
@@ -1,5 +1,5 @@
1
- import type { ToolContext } from "@alexkroman1/aai";
2
- import { createKeyedLock, tool, withLock } from "@alexkroman1/aai";
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
- /** The session's live store. Mutations to the returned object stick — it is
58
- * the object held in `ctx.state`. */
59
- export function getState(ctx: ToolContext): RetailState {
60
- const slot = ctx.state as StateSlot;
61
- slot.retail ??= createDefaultState();
62
- return slot.retail;
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 | ErrorResult {
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 | ErrorResult {
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 | ErrorResult {
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 | ErrorResult {
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 } | ErrorResult {
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 | ErrorResult {
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 | ErrorResult {
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 | ErrorResult {
151
+ export function requireOwnOrder(state: RetailState, orderId: string): Order | ToolFailure {
143
152
  const user = authenticatedUser(state);
144
- if (isError(user)) return user;
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
- execute: (args: z.output<S>, ctx: ToolContext) => R | Promise<R>;
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
- state.activity.push({ seq: state.callSeq, tool: name, summary, at: Date.now() });
197
- if (state.activity.length > MAX_ACTIVITY) {
198
- state.activity = state.activity.slice(-MAX_ACTIVITY);
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
- return tool({
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
- updateState(ctx, async (state) => {
223
- const typedArgs = args as z.output<S>;
224
- if (requiresAuth && !state.authenticatedUserId) {
225
- record(state, spec.name, "blocked: not authenticated");
226
- return { error: NOT_AUTHENTICATED };
227
- }
228
- const result = await spec.execute(typedArgs, ctx);
229
- record(
230
- state,
231
- spec.name,
232
- isError(result) ? `error: ${result.error}` : spec.summary(typedArgs, result),
233
- );
234
- return result;
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, isError } from "./store.ts";
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 (isError(plan)) throw new Error(plan.error);
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(isError(plan) ? null : plan.diff).toBe(-40.86);
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 (isError(plan)) throw new Error(plan.error);
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 (isError(plan)) throw new Error(plan.error);
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(isError(plan) && plan.error).toContain("7292993796");
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(isError(plan) && plan.error.toLowerCase()).toContain("same number");
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(isError(planItemSwap(state, order, [], [], { requireDifferent: true }))).toBe(true);
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(isError(plan)).toBe(true);
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(isError(plan) && plan.error).toContain("Tea Kettle");
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(isError(plan) && plan.error.toLowerCase()).toContain("not available");
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(isError(strict)).toBe(true);
113
+ expect(isToolFailure(strict)).toBe(true);
111
114
  const loose = planItemSwap(state, order, ["7292993796"], ["7292993796"], {
112
115
  requireDifferent: false,
113
116
  });
114
- expect(isError(loose) ? null : loose.diff).toBe(0);
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 (isError(user)) throw new Error(user.error);
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 (isError(olivia)) throw new Error(olivia.error);
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 (isError(plan)) throw new Error(plan.error);
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 (isError(plan)) throw new Error(plan.error);
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 | ErrorResult {
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 (isError(product)) return product;
85
+ if (isToolFailure(product)) return product;
93
86
 
94
87
  const newVariant = findVariant(product, newItemId);
95
- if (isError(newVariant)) {
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): ErrorResult | null {
110
+ export function assertCanCoverDiff(user: User, methodId: string, diff: number): ToolFailure | null {
118
111
  const method = findPaymentMethod(user, methodId);
119
- if (isError(method)) return method;
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, getState, isError, retailTool, setFocus } from "../store.ts";
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 const cancelPendingOrder = retailTool({
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
- // `execute` before `summary`: TS infers the wrapper's generic `R` from
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 (isError(user)) return user;
28
+ if (isToolFailure(user)) return user;
33
29
 
34
30
  const order = resolveOrder(state, args.order_id);
35
- if (isError(order)) return order;
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
- "error" in result ? "cancel failed" : `cancelled ${result.order_id}`,
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, getState, isError, retailTool, setFocus } from "../store.ts";
4
+ import { authenticatedUser, money, retailTool, setFocus } from "../store.ts";
4
5
  import { assertCanCoverDiff, planItemSwap } from "../swap.ts";
5
6
 
6
- export const exchangeDeliveredOrderItems = retailTool({
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
- // `execute` before `summary`: TS infers the wrapper's generic `R` from
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 (isError(user)) return user;
35
+ if (isToolFailure(user)) return user;
40
36
 
41
37
  const order = resolveOrder(state, args.order_id);
42
- if (isError(order)) return order;
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 (isError(plan)) return plan;
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
- exchange_items: order.exchange_items,
74
- exchange_new_items: order.exchange_new_items,
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
- "error" in result ? "exchange failed" : `exchange requested on ${result.order_id}`,
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 { getState, retailTool } from "../store.ts";
4
+ import { retailTool } from "../store.ts";
4
5
 
5
- export const findUserIdByEmail = retailTool({
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
- // `execute` must come before `summary` in this object literal: TS infers the
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
- "error" in result ? "lookup failed" : `identified ${result.user_id}`,
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 { getState, retailTool } from "../store.ts";
4
+ import { retailTool } from "../store.ts";
4
5
 
5
- export const findUserIdByNameZip = retailTool({
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
- // `execute` before `summary`: see find_user_id_by_email.ts for why the order
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
- "error" in result ? "lookup failed" : `identified ${result.user_id}`,
35
+ isToolFailure(result) ? "lookup failed" : `identified ${result.user_id}`,
38
36
  });