@alexkroman1/aai-cli 5.14.0 → 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 +8 -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 +17 -19
- package/dist/templates/dispatch-center/agent.ts +2 -29
- package/dist/templates/dispatch-center/client.tsx +33 -16
- package/dist/templates/dispatch-center/shared.ts +53 -19
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +12 -14
- package/dist/templates/dispatch-center/tools/incident_create.ts +41 -44
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +59 -61
- package/dist/templates/dispatch-center/tools/incident_get.ts +3 -4
- package/dist/templates/dispatch-center/tools/incident_triage.ts +43 -45
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +39 -41
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +2 -5
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +20 -23
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +52 -54
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +2 -4
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +39 -42
- 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 +28 -12
- package/dist/templates/infocom-adventure/shared.ts +28 -11
- 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 +52 -47
- package/dist/templates/pizza-ordering/agent.ts +9 -158
- package/dist/templates/pizza-ordering/shared.ts +35 -12
- 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 +236 -172
- package/dist/templates/retail/agent.ts +5 -42
- package/dist/templates/retail/client.tsx +33 -11
- package/dist/templates/retail/registry.test.ts +31 -16
- package/dist/templates/retail/resolve.ts +28 -64
- package/dist/templates/retail/shared.test.ts +4 -3
- package/dist/templates/retail/shared.ts +24 -7
- package/dist/templates/retail/store.test.ts +12 -10
- package/dist/templates/retail/store.ts +69 -30
- package/dist/templates/retail/tools/cancel_pending_order.ts +4 -9
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -11
- 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 +5 -7
- package/dist/templates/retail/tools/get_order_details.ts +5 -7
- package/dist/templates/retail/tools/get_product_details.ts +4 -7
- package/dist/templates/retail/tools/get_user_details.ts +4 -7
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
- package/dist/templates/retail/tools/modify_pending_order_address.ts +4 -9
- package/dist/templates/retail/tools/modify_pending_order_items.ts +4 -16
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +3 -9
- package/dist/templates/retail/tools/modify_user_address.ts +4 -9
- package/dist/templates/retail/tools/return_delivered_order_items.ts +3 -9
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
- package/dist/templates/solo-rpg/agent.test.ts +152 -29
- package/dist/templates/solo-rpg/agent.ts +3 -21
- package/dist/templates/solo-rpg/client.tsx +19 -6
- package/dist/templates/solo-rpg/shared.ts +19 -11
- package/dist/templates/solo-rpg/tools/action_roll.ts +2 -4
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +2 -4
- package/dist/templates/solo-rpg/tools/check_state.ts +6 -6
- package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
- package/dist/templates/solo-rpg/tools/oracle.ts +18 -11
- package/dist/templates/solo-rpg/tools/save_game.ts +10 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
- package/dist/templates/solo-rpg/tools/update_state.ts +3 -4
- 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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
import type { ToolContext } from "@alexkroman1/aai";
|
|
4
|
+
import { createToolContext, runTool, withDiscoveredTools } from "@alexkroman1/aai/testing";
|
|
5
|
+
import { describe, expect, test } from "vitest";
|
|
6
|
+
import authoredAgent from "./agent.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The def a DEPLOYED agent runs: authored, plus what `tools/` declares.
|
|
10
|
+
*
|
|
11
|
+
* The glob is written HERE rather than reached for from a shared helper because
|
|
12
|
+
* this file SHIPS: it is what a scaffolded project runs, so it may not import
|
|
13
|
+
* anything outside its own template, and `import.meta.glob` is expanded against
|
|
14
|
+
* the file containing it either way. This is the pattern a user writes.
|
|
15
|
+
*/
|
|
16
|
+
const agentDef = withDiscoveredTools(
|
|
17
|
+
authoredAgent,
|
|
18
|
+
import.meta.glob("./tools/*.ts", { eager: true }),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
import { DEFAULT_GAME_STATE, gameSlot, MAX_HISTORY, REPORTED_HISTORY } from "./shared.ts";
|
|
22
|
+
|
|
23
|
+
/** A tool by the name the model calls it by, bound to this agent. */
|
|
24
|
+
const run = (name: string, args: Record<string, unknown>, ctx: ToolContext) =>
|
|
25
|
+
runTool(agentDef, name, args, ctx);
|
|
26
|
+
|
|
27
|
+
/** Each context owns its OWN slot store, which is what makes two playthroughs
|
|
28
|
+
* independent by construction. */
|
|
29
|
+
const makeCtx = () => createToolContext();
|
|
30
|
+
|
|
31
|
+
// ─── The frozen-vs-draft contract ────────────────────────────────────────────
|
|
32
|
+
//
|
|
33
|
+
// This whole block exists because the template shipped without it and two tools
|
|
34
|
+
// were wrong. `game_state_take` and `game_state_flag` were declared with
|
|
35
|
+
// `gameSlot.tool` — the READING half, whose value is deep-frozen — while
|
|
36
|
+
// pushing to `inventory` and writing into `flags`. Every call threw a
|
|
37
|
+
// `TypeError`, and nothing in the repo executed either body. The declaration is
|
|
38
|
+
// the fix; these are what keep it fixed.
|
|
39
|
+
|
|
40
|
+
describe("the mutating tools actually mutate", () => {
|
|
41
|
+
test("game_state_take adds to the inventory and does not double an item", async () => {
|
|
42
|
+
const ctx = makeCtx();
|
|
43
|
+
|
|
44
|
+
const first = (await run("game_state_take", { value: "lantern" }, ctx)) as {
|
|
45
|
+
inventory: string[];
|
|
46
|
+
};
|
|
47
|
+
expect(first.inventory).toEqual(["lantern"]);
|
|
48
|
+
// The stored value, not the one the body returned — a body handed a frozen
|
|
49
|
+
// value would have thrown, and a body handed a copy nothing stores would
|
|
50
|
+
// report success here and leave the slot empty.
|
|
51
|
+
expect(gameSlot.get(ctx).inventory).toEqual(["lantern"]);
|
|
52
|
+
|
|
53
|
+
const again = (await run("game_state_take", { value: "lantern" }, ctx)) as {
|
|
54
|
+
inventory: string[];
|
|
55
|
+
};
|
|
56
|
+
expect(again.inventory).toEqual(["lantern"]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("game_state_flag records a flag, and a second flag joins the first", async () => {
|
|
60
|
+
const ctx = makeCtx();
|
|
61
|
+
|
|
62
|
+
await run("game_state_flag", { value: "gate_opened" }, ctx);
|
|
63
|
+
const both = (await run("game_state_flag", { value: "rope_cut" }, ctx)) as {
|
|
64
|
+
flags: Record<string, boolean>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
expect(both.flags).toEqual({ gate_opened: true, rope_cut: true });
|
|
68
|
+
expect(gameSlot.get(ctx).flags).toEqual({ gate_opened: true, rope_cut: true });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("what a READ is handed is frozen, which is why the two above are updateTool", async () => {
|
|
72
|
+
const ctx = makeCtx();
|
|
73
|
+
await run("game_state_take", { value: "lantern" }, ctx);
|
|
74
|
+
|
|
75
|
+
const game = gameSlot.get(ctx);
|
|
76
|
+
// The type refuses this too (`readonly string[]` has no `push`), and the
|
|
77
|
+
// freeze is what makes the refusal true for a caller with no types.
|
|
78
|
+
expect(Object.isFrozen(game.inventory)).toBe(true);
|
|
79
|
+
expect(() => (game.inventory as string[]).push("sword")).toThrow(TypeError);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ─── The rest of the eight ───────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
describe("the adventure's tools", () => {
|
|
86
|
+
test("drop removes an item and is a no-op for one the player never took", async () => {
|
|
87
|
+
const ctx = makeCtx();
|
|
88
|
+
await run("game_state_take", { value: "lantern" }, ctx);
|
|
89
|
+
await run("game_state_take", { value: "rope" }, ctx);
|
|
90
|
+
|
|
91
|
+
const dropped = (await run("game_state_drop", { value: "lantern" }, ctx)) as {
|
|
92
|
+
inventory: string[];
|
|
93
|
+
};
|
|
94
|
+
expect(dropped.inventory).toEqual(["rope"]);
|
|
95
|
+
|
|
96
|
+
const nothing = (await run("game_state_drop", { value: "sword" }, ctx)) as {
|
|
97
|
+
inventory: string[];
|
|
98
|
+
};
|
|
99
|
+
expect(nothing.inventory).toEqual(["rope"]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("move sets the room and counts the move", async () => {
|
|
103
|
+
const ctx = makeCtx();
|
|
104
|
+
const moved = (await run("game_state_move", { value: "Echo Chamber" }, ctx)) as {
|
|
105
|
+
currentRoom: string;
|
|
106
|
+
moves: number;
|
|
107
|
+
};
|
|
108
|
+
expect(moved).toEqual({ currentRoom: "Echo Chamber", moves: 1 });
|
|
109
|
+
expect(gameSlot.get(ctx).currentRoom).toBe("Echo Chamber");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("score accumulates rather than replacing", async () => {
|
|
113
|
+
const ctx = makeCtx();
|
|
114
|
+
await run("game_state_score", { value: 10 }, ctx);
|
|
115
|
+
const total = (await run("game_state_score", { value: 5 }, ctx)) as { score: number };
|
|
116
|
+
expect(total.score).toBe(15);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("history logs the command, counts the move, and reports only the recent ones", async () => {
|
|
120
|
+
const ctx = makeCtx();
|
|
121
|
+
for (let i = 1; i <= REPORTED_HISTORY + 2; i++) {
|
|
122
|
+
await run("game_state_history", { value: `command ${i}` }, ctx);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const last = (await run("game_state_history", { value: "look" }, ctx)) as {
|
|
126
|
+
moves: number;
|
|
127
|
+
recentHistory: string[];
|
|
128
|
+
};
|
|
129
|
+
expect(last.moves).toBe(REPORTED_HISTORY + 3);
|
|
130
|
+
expect(last.recentHistory).toHaveLength(REPORTED_HISTORY);
|
|
131
|
+
expect(last.recentHistory.at(-1)).toBe("look");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("the history is capped, so a long playthrough does not grow without bound", async () => {
|
|
135
|
+
const ctx = makeCtx();
|
|
136
|
+
for (let i = 0; i < MAX_HISTORY + 10; i++) {
|
|
137
|
+
await run("game_state_history", { value: `command ${i}` }, ctx);
|
|
138
|
+
}
|
|
139
|
+
const game = gameSlot.get(ctx);
|
|
140
|
+
expect(game.history).toHaveLength(MAX_HISTORY);
|
|
141
|
+
// The cap drops the OLDEST — the newest command is the one a narrator needs.
|
|
142
|
+
expect(game.history.at(-1)).toBe(`command ${MAX_HISTORY + 9}`);
|
|
143
|
+
expect(game.moves).toBe(MAX_HISTORY + 10);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("get reports the whole board, with the history trimmed to what a narrator reads", async () => {
|
|
147
|
+
const ctx = makeCtx();
|
|
148
|
+
await run("game_state_take", { value: "lantern" }, ctx);
|
|
149
|
+
await run("game_state_flag", { value: "gate_opened" }, ctx);
|
|
150
|
+
await run("game_state_move", { value: "Echo Chamber" }, ctx);
|
|
151
|
+
await run("game_state_score", { value: 7 }, ctx);
|
|
152
|
+
for (let i = 0; i < REPORTED_HISTORY + 3; i++) {
|
|
153
|
+
await run("game_state_history", { value: `command ${i}` }, ctx);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
expect(await run("game_state_get", {}, ctx)).toEqual({
|
|
157
|
+
currentRoom: "Echo Chamber",
|
|
158
|
+
inventory: ["lantern"],
|
|
159
|
+
score: 7,
|
|
160
|
+
moves: REPORTED_HISTORY + 4,
|
|
161
|
+
flags: { gate_opened: true },
|
|
162
|
+
recentHistory: Array.from({ length: REPORTED_HISTORY }, (_, i) => `command ${i + 3}`),
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("restart replaces the whole game, and the fresh room is the one the greeting describes", async () => {
|
|
167
|
+
const ctx = makeCtx();
|
|
168
|
+
await run("game_state_take", { value: "lantern" }, ctx);
|
|
169
|
+
await run("game_state_score", { value: 30 }, ctx);
|
|
170
|
+
await run("game_state_move", { value: "Echo Chamber" }, ctx);
|
|
171
|
+
|
|
172
|
+
const restarted = (await run("game_state_restart", {}, ctx)) as {
|
|
173
|
+
restarted: boolean;
|
|
174
|
+
currentRoom: string;
|
|
175
|
+
};
|
|
176
|
+
expect(restarted).toEqual({ restarted: true, currentRoom: DEFAULT_GAME_STATE.currentRoom });
|
|
177
|
+
expect(gameSlot.get(ctx)).toEqual(DEFAULT_GAME_STATE);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("the game is per context", () => {
|
|
182
|
+
test("a second playthrough starts empty and cannot see the first", async () => {
|
|
183
|
+
// What this really checks: the state lives in the SLOT and not in a
|
|
184
|
+
// module-level variable. `createToolContext()` hands each call its own
|
|
185
|
+
// detached slot store, so the isolation is per CONTEXT — two distinct
|
|
186
|
+
// session ids would prove nothing extra, and `sessionSlot` could stop
|
|
187
|
+
// keying by session with this still passing.
|
|
188
|
+
const one = makeCtx();
|
|
189
|
+
const two = makeCtx();
|
|
190
|
+
|
|
191
|
+
await run("game_state_take", { value: "lantern" }, one);
|
|
192
|
+
await run("game_state_move", { value: "Echo Chamber" }, one);
|
|
193
|
+
|
|
194
|
+
expect(gameSlot.get(two)).toEqual(DEFAULT_GAME_STATE);
|
|
195
|
+
await run("game_state_take", { value: "rope" }, two);
|
|
196
|
+
expect(gameSlot.get(one).inventory).toEqual(["lantern"]);
|
|
197
|
+
expect(gameSlot.get(two).inventory).toEqual(["rope"]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("the module-level default is cloned, so no session can edit the next one's start", async () => {
|
|
201
|
+
const ctx = makeCtx();
|
|
202
|
+
await run("game_state_take", { value: "lantern" }, ctx);
|
|
203
|
+
expect(DEFAULT_GAME_STATE.inventory).toEqual([]);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -1,117 +1,14 @@
|
|
|
1
|
-
import { agent
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { gameSlot, recordCommand } from "./shared.ts";
|
|
4
|
-
import systemPrompt from "./system-prompt.md?raw";
|
|
1
|
+
import { agent } from "@alexkroman1/aai";
|
|
5
2
|
|
|
6
3
|
export default agent({
|
|
7
4
|
name: "Cavern Adventure",
|
|
5
|
+
// The world exists before the first command, so a resumed connection has
|
|
6
|
+
// something to project rather than an empty state object.
|
|
8
7
|
// A narrator wants a narrative voice; everything else stays on the
|
|
9
8
|
// default all-AssemblyAI pipeline.
|
|
10
9
|
voice: "paul",
|
|
11
|
-
systemPrompt,
|
|
12
10
|
// The opening scene here must agree with DEFAULT_GAME_STATE.currentRoom
|
|
13
11
|
// (shared.ts) and the world map in system-prompt.md.
|
|
14
12
|
greeting:
|
|
15
13
|
"Welcome, adventurer. You are standing at the mouth of a weathered cave at the edge of a pine forest. A cold wind carries the smell of damp stone up from the darkness below. A rusted lantern hangs from an iron hook beside the entrance. What would you like to do?",
|
|
16
|
-
|
|
17
|
-
tools: {
|
|
18
|
-
game_state_drop: tool({
|
|
19
|
-
description: "Remove an item from the player's inventory.",
|
|
20
|
-
inputSchema: z.object({
|
|
21
|
-
value: z.string().describe("Item name to drop"),
|
|
22
|
-
}),
|
|
23
|
-
async execute(args, ctx) {
|
|
24
|
-
const g = gameSlot.get(ctx);
|
|
25
|
-
g.inventory = g.inventory.filter((i) => i !== args.value);
|
|
26
|
-
return { inventory: g.inventory };
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
29
|
-
|
|
30
|
-
game_state_flag: tool({
|
|
31
|
-
description: "Set a game flag to true, used for tracking puzzle and event state.",
|
|
32
|
-
inputSchema: z.object({
|
|
33
|
-
value: z.string().describe("Flag name to set"),
|
|
34
|
-
}),
|
|
35
|
-
async execute(args, ctx) {
|
|
36
|
-
const g = gameSlot.get(ctx);
|
|
37
|
-
g.flags[args.value] = true;
|
|
38
|
-
return { flags: g.flags };
|
|
39
|
-
},
|
|
40
|
-
}),
|
|
41
|
-
|
|
42
|
-
game_state_get: tool({
|
|
43
|
-
description:
|
|
44
|
-
"Read the current game state including inventory, current room, score, moves, flags, and recent history.",
|
|
45
|
-
async execute(_args, ctx) {
|
|
46
|
-
const g = gameSlot.get(ctx);
|
|
47
|
-
return {
|
|
48
|
-
currentRoom: g.currentRoom,
|
|
49
|
-
inventory: g.inventory,
|
|
50
|
-
score: g.score,
|
|
51
|
-
moves: g.moves,
|
|
52
|
-
flags: g.flags,
|
|
53
|
-
recentHistory: g.history.slice(-5),
|
|
54
|
-
};
|
|
55
|
-
},
|
|
56
|
-
}),
|
|
57
|
-
|
|
58
|
-
game_state_history: tool({
|
|
59
|
-
description: "Log a player command to the history and increment the move counter.",
|
|
60
|
-
inputSchema: z.object({
|
|
61
|
-
value: z.string().describe("Command text to log"),
|
|
62
|
-
}),
|
|
63
|
-
async execute(args, ctx) {
|
|
64
|
-
const g = gameSlot.get(ctx);
|
|
65
|
-
recordCommand(g, args.value);
|
|
66
|
-
g.moves++;
|
|
67
|
-
return { moves: g.moves, recentHistory: g.history.slice(-5) };
|
|
68
|
-
},
|
|
69
|
-
}),
|
|
70
|
-
|
|
71
|
-
game_state_move: tool({
|
|
72
|
-
description: "Move the player to a new room and increment the move counter.",
|
|
73
|
-
inputSchema: z.object({
|
|
74
|
-
value: z.string().describe("Room name to move to"),
|
|
75
|
-
}),
|
|
76
|
-
async execute(args, ctx) {
|
|
77
|
-
const g = gameSlot.get(ctx);
|
|
78
|
-
g.currentRoom = args.value;
|
|
79
|
-
g.moves++;
|
|
80
|
-
return { currentRoom: g.currentRoom, moves: g.moves };
|
|
81
|
-
},
|
|
82
|
-
}),
|
|
83
|
-
|
|
84
|
-
game_state_restart: tool({
|
|
85
|
-
description:
|
|
86
|
-
"Reset the game to the beginning: empty inventory, zero score and moves, all flags cleared. Use when the player asks to restart, quit, or start a new game.",
|
|
87
|
-
async execute(_args, ctx) {
|
|
88
|
-
const g = gameSlot.reset(ctx);
|
|
89
|
-
return { restarted: true, currentRoom: g.currentRoom };
|
|
90
|
-
},
|
|
91
|
-
}),
|
|
92
|
-
|
|
93
|
-
game_state_score: tool({
|
|
94
|
-
description: "Add points to the player's score.",
|
|
95
|
-
inputSchema: z.object({
|
|
96
|
-
value: z.number().describe("Points to add"),
|
|
97
|
-
}),
|
|
98
|
-
async execute(args, ctx) {
|
|
99
|
-
const g = gameSlot.get(ctx);
|
|
100
|
-
g.score += args.value;
|
|
101
|
-
return { score: g.score };
|
|
102
|
-
},
|
|
103
|
-
}),
|
|
104
|
-
|
|
105
|
-
game_state_take: tool({
|
|
106
|
-
description: "Add an item to the player's inventory.",
|
|
107
|
-
inputSchema: z.object({
|
|
108
|
-
value: z.string().describe("Item name to take"),
|
|
109
|
-
}),
|
|
110
|
-
async execute(args, ctx) {
|
|
111
|
-
const g = gameSlot.get(ctx);
|
|
112
|
-
if (!g.inventory.includes(args.value)) g.inventory.push(args.value);
|
|
113
|
-
return { inventory: g.inventory };
|
|
114
|
-
},
|
|
115
|
-
}),
|
|
116
|
-
},
|
|
117
14
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "@alexkroman1/aai-ui/styles.css";
|
|
2
|
-
import type { ChatMessage } from "@alexkroman1/aai-ui";
|
|
3
|
-
import { AutoScroll, client, useSession } from "@alexkroman1/aai-ui";
|
|
2
|
+
import type { AgentState, ChatMessage } from "@alexkroman1/aai-ui";
|
|
3
|
+
import { AutoScroll, client, useSession, useUserTranscript } from "@alexkroman1/aai-ui";
|
|
4
4
|
|
|
5
5
|
const CSS = `
|
|
6
6
|
@keyframes ic-flicker {
|
|
@@ -52,8 +52,31 @@ const GREEN_DIM = "#00aa2a";
|
|
|
52
52
|
const GREEN_DARK = "#003300";
|
|
53
53
|
const CYAN = "#00ccff";
|
|
54
54
|
|
|
55
|
+
// The cursor dot's colour per session state, as an EXHAUSTIVE map rather than an
|
|
56
|
+
// if-chain with a grey default.
|
|
57
|
+
//
|
|
58
|
+
// The palette is this template's own — every client here paints the same six
|
|
59
|
+
// states in its own colours, so the SDK has nothing to share but the union. What
|
|
60
|
+
// the SDK does own is `AgentState`, and `satisfies Record<AgentState, string>`
|
|
61
|
+
// is what borrows it: a state added there stops compiling here, where the
|
|
62
|
+
// `state === "…"` chain this replaced answered a new state with a silent grey
|
|
63
|
+
// badge in three separate files and no way to notice.
|
|
64
|
+
const STATE_COLORS = {
|
|
65
|
+
disconnected: GREEN_DARK,
|
|
66
|
+
connecting: GREEN_DARK,
|
|
67
|
+
ready: GREEN_DARK,
|
|
68
|
+
listening: GREEN,
|
|
69
|
+
thinking: CYAN,
|
|
70
|
+
speaking: "#ffaa00",
|
|
71
|
+
error: GREEN_DARK,
|
|
72
|
+
} satisfies Record<AgentState, string>;
|
|
73
|
+
|
|
55
74
|
function InfocomAdventure() {
|
|
56
75
|
const session = useSession();
|
|
76
|
+
// `speaking` and `text` in place of a `userTranscript !== null` check: `null`
|
|
77
|
+
// is silence and `""` is speech detected with no words back yet, and reading
|
|
78
|
+
// them as one falsy value hides the indicator at the start of every turn.
|
|
79
|
+
const { speaking, text: userTranscript } = useUserTranscript();
|
|
57
80
|
|
|
58
81
|
const stateLabel =
|
|
59
82
|
session.state === "listening"
|
|
@@ -70,14 +93,7 @@ function InfocomAdventure() {
|
|
|
70
93
|
|
|
71
94
|
const msgCount = session.messages.filter((m: ChatMessage) => m.role === "user").length;
|
|
72
95
|
|
|
73
|
-
const dotColor =
|
|
74
|
-
session.state === "listening"
|
|
75
|
-
? GREEN
|
|
76
|
-
: session.state === "speaking"
|
|
77
|
-
? "#ffaa00"
|
|
78
|
-
: session.state === "thinking"
|
|
79
|
-
? CYAN
|
|
80
|
-
: GREEN_DARK;
|
|
96
|
+
const dotColor = STATE_COLORS[session.state];
|
|
81
97
|
|
|
82
98
|
if (!session.started) {
|
|
83
99
|
return (
|
|
@@ -192,12 +208,12 @@ function InfocomAdventure() {
|
|
|
192
208
|
{msg.content}
|
|
193
209
|
</div>
|
|
194
210
|
))}
|
|
195
|
-
{
|
|
211
|
+
{speaking && (
|
|
196
212
|
<div
|
|
197
213
|
className="ic-transcript italic"
|
|
198
214
|
style={{ color: "#007a1e", textShadow: "0 0 5px rgba(0,255,65,0.15)" }}
|
|
199
215
|
>
|
|
200
|
-
{
|
|
216
|
+
{userTranscript}
|
|
201
217
|
</div>
|
|
202
218
|
)}
|
|
203
219
|
</AutoScroll>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pushCapped,
|
|
1
|
+
import { pushCapped, sessionSlot } from "@alexkroman1/aai";
|
|
2
2
|
|
|
3
3
|
export type GameState = {
|
|
4
4
|
inventory: string[];
|
|
@@ -20,20 +20,37 @@ export const DEFAULT_GAME_STATE: GameState = {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* How many player commands the game remembers. The history is append-only and
|
|
23
|
-
*
|
|
24
|
-
* only the last few are ever read (`game_state_get` reports five), so
|
|
25
|
-
* older ones are cost without a reader.
|
|
23
|
+
* rides in the slot's stored value for the length of the call, so it needs a
|
|
24
|
+
* cap — and only the last few are ever read (`game_state_get` reports five), so
|
|
25
|
+
* the older ones are cost without a reader.
|
|
26
26
|
*/
|
|
27
27
|
export const MAX_HISTORY = 50;
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
/**
|
|
30
|
+
* How many of those commands a tool reports back to the model.
|
|
31
|
+
*
|
|
32
|
+
* Named because two tools answer with it (`game_state_get` and
|
|
33
|
+
* `game_state_history`), and they live in separate files now — an inline `-5`
|
|
34
|
+
* in each is a number that can disagree with itself.
|
|
35
|
+
*/
|
|
36
|
+
export const REPORTED_HISTORY = 5;
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
// The game lives in one `sessionSlot`, keyed per session — each session is its
|
|
39
|
+
// own playthrough, so concurrent players never see each other's game and a
|
|
40
|
+
// fresh session starts a fresh adventure. The clone is load-bearing:
|
|
41
|
+
// `DEFAULT_GAME_STATE` is one module-level object shared by every session in
|
|
42
|
+
// the process.
|
|
43
|
+
//
|
|
44
|
+
// Every tool here is declared through the slot, and WHICH HALF is the decision
|
|
45
|
+
// to get right: `gameSlot.tool` hands the body a deep-frozen value and
|
|
46
|
+
// `gameSlot.updateTool` hands it a draft that is stored when the body returns.
|
|
47
|
+
// `game_state_take` and `game_state_flag` were declared with the reading half
|
|
48
|
+
// while pushing to `inventory` and writing to `flags` — a `TypeError` on the
|
|
49
|
+
// first call and every call, once `freezeStorable` deep-froze what a read hands
|
|
50
|
+
// out. It is a compile error now, which is the reason to declare a tool through
|
|
51
|
+
// the slot at all rather than reaching for `gameSlot.get(ctx)` inside a
|
|
52
|
+
// `tool()`.
|
|
53
|
+
export const gameSlot = sessionSlot("game", () => structuredClone(DEFAULT_GAME_STATE));
|
|
37
54
|
|
|
38
55
|
/** Log a player command, holding {@link MAX_HISTORY}. */
|
|
39
56
|
export function recordCommand(game: GameState, command: string): void {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { gameSlot } from "../shared.ts";
|
|
3
|
+
|
|
4
|
+
export default gameSlot.updateTool({
|
|
5
|
+
description: "Remove an item from the player's inventory.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
value: z.string().describe("Item name to drop"),
|
|
8
|
+
}),
|
|
9
|
+
execute(args, game) {
|
|
10
|
+
game.inventory = game.inventory.filter((i) => i !== args.value);
|
|
11
|
+
return { inventory: game.inventory };
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { gameSlot } from "../shared.ts";
|
|
3
|
+
|
|
4
|
+
export default gameSlot.updateTool({
|
|
5
|
+
description: "Set a game flag to true, used for tracking puzzle and event state.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
value: z.string().describe("Flag name to set"),
|
|
8
|
+
}),
|
|
9
|
+
execute(args, game) {
|
|
10
|
+
game.flags[args.value] = true;
|
|
11
|
+
return { flags: game.flags };
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { gameSlot, REPORTED_HISTORY } from "../shared.ts";
|
|
2
|
+
|
|
3
|
+
export default gameSlot.tool({
|
|
4
|
+
description:
|
|
5
|
+
"Read the current game state including inventory, current room, score, moves, flags, and recent history.",
|
|
6
|
+
execute(_args, game) {
|
|
7
|
+
return {
|
|
8
|
+
currentRoom: game.currentRoom,
|
|
9
|
+
inventory: game.inventory,
|
|
10
|
+
score: game.score,
|
|
11
|
+
moves: game.moves,
|
|
12
|
+
flags: game.flags,
|
|
13
|
+
recentHistory: game.history.slice(-REPORTED_HISTORY),
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { gameSlot, REPORTED_HISTORY, recordCommand } from "../shared.ts";
|
|
3
|
+
|
|
4
|
+
export default gameSlot.updateTool({
|
|
5
|
+
description: "Log a player command to the history and increment the move counter.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
value: z.string().describe("Command text to log"),
|
|
8
|
+
}),
|
|
9
|
+
execute(args, game) {
|
|
10
|
+
recordCommand(game, args.value);
|
|
11
|
+
game.moves++;
|
|
12
|
+
return { moves: game.moves, recentHistory: game.history.slice(-REPORTED_HISTORY) };
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { gameSlot } from "../shared.ts";
|
|
3
|
+
|
|
4
|
+
export default gameSlot.updateTool({
|
|
5
|
+
description: "Move the player to a new room and increment the move counter.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
value: z.string().describe("Room name to move to"),
|
|
8
|
+
}),
|
|
9
|
+
execute(args, game) {
|
|
10
|
+
game.currentRoom = args.value;
|
|
11
|
+
game.moves++;
|
|
12
|
+
return { currentRoom: game.currentRoom, moves: game.moves };
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { gameSlot } from "../shared.ts";
|
|
2
|
+
|
|
3
|
+
export default gameSlot.tool({
|
|
4
|
+
description:
|
|
5
|
+
"Reset the game to the beginning: empty inventory, zero score and moves, all flags cleared. Use when the player asks to restart, quit, or start a new game.",
|
|
6
|
+
// The one tool here that does NOT want the live value: it replaces it. The
|
|
7
|
+
// slot is still what defines the tool, so the state type comes from the same
|
|
8
|
+
// place as every sibling's rather than from an annotation on `ctx`.
|
|
9
|
+
execute(_args, _game, ctx) {
|
|
10
|
+
const fresh = gameSlot.reset(ctx);
|
|
11
|
+
return { restarted: true, currentRoom: fresh.currentRoom };
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { gameSlot } from "../shared.ts";
|
|
3
|
+
|
|
4
|
+
export default gameSlot.updateTool({
|
|
5
|
+
description: "Add points to the player's score.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
value: z.number().describe("Points to add"),
|
|
8
|
+
}),
|
|
9
|
+
execute(args, game) {
|
|
10
|
+
game.score += args.value;
|
|
11
|
+
return { score: game.score };
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { gameSlot } from "../shared.ts";
|
|
3
|
+
|
|
4
|
+
export default gameSlot.updateTool({
|
|
5
|
+
description: "Add an item to the player's inventory.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
value: z.string().describe("Item name to take"),
|
|
8
|
+
}),
|
|
9
|
+
execute(args, game) {
|
|
10
|
+
if (!game.inventory.includes(args.value)) game.inventory.push(args.value);
|
|
11
|
+
return { inventory: game.inventory };
|
|
12
|
+
},
|
|
13
|
+
});
|