@alexkroman1/aai-cli 6.11.0 → 8.0.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/README.md +1 -0
- package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-CDuYl5Gb.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-Me06wgeF.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-BhEfQ6Yi.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/_resource-commands.d.ts +23 -0
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-z-USxPtL.mjs} +16 -12
- package/dist/_slug-api.d.ts +5 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-4WcKOjS5.mjs} +5 -6
- package/dist/_templates.d.ts +3 -4
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-BBxf8vom.mjs} +6 -3
- package/dist/cli.mjs +346 -349
- package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
- package/dist/{deploy-B8lubiRT.mjs → deploy-DRC8jWr8.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-BvcctCn2.mjs} +1 -1
- package/dist/eval-DfQ8cm5p.mjs +56 -0
- package/dist/eval.d.ts +43 -0
- package/dist/{init-l6xfU4xX.mjs → init-CUo87gcI.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-CQ2T46dr.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +442 -80
- package/dist/scaffold/package.json +6 -3
- package/dist/scaffold/server.mjs +11 -4
- package/dist/scaffold/tsconfig.json +0 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-Ct34kdgU.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-i_79ZwAR.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio-CzTzPqe1.mjs} +4 -4
- package/dist/templates/briefing-desk/agent.test.ts +350 -0
- package/dist/templates/briefing-desk/agent.ts +30 -0
- package/dist/templates/briefing-desk/shared.ts +200 -0
- package/dist/templates/briefing-desk/system-prompt.md +39 -0
- package/dist/templates/briefing-desk/tools/briefing_so_far.ts +35 -0
- package/dist/templates/briefing-desk/tools/research_topic.ts +88 -0
- package/dist/templates/briefing-desk/tools/verify_claim.ts +77 -0
- package/dist/templates/call-audit/agent.eval.test.ts +474 -0
- package/dist/templates/call-audit/agent.test.ts +57 -204
- package/dist/templates/call-audit/agent.ts +32 -19
- package/dist/templates/call-audit/client.tsx +17 -54
- package/dist/templates/call-audit/workflows/audit.ts +11 -18
- package/dist/templates/call-audit/workflows/ingest.ts +106 -114
- package/dist/templates/call-audit/workflows/media.ts +2 -12
- package/dist/templates/call-audit/workflows/summarize.ts +47 -52
- package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
- package/dist/templates/code-interpreter/agent.eval.test.ts +165 -0
- package/dist/templates/dispatch-center/agent.eval.test.ts +349 -0
- package/dist/templates/dispatch-center/agent.test.ts +149 -25
- package/dist/templates/dispatch-center/client.tsx +239 -129
- package/dist/templates/dispatch-center/shared.ts +99 -1
- package/dist/templates/dispatch-center/system-prompt.md +3 -1
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
- package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
- package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
- package/dist/templates/embedded-assets/agent.eval.test.ts +138 -0
- package/dist/templates/embedded-assets/agent.test.ts +17 -14
- package/dist/templates/health-assistant/agent.eval.test.ts +167 -0
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.eval.test.ts +158 -0
- package/dist/templates/infocom-adventure/agent.test.ts +53 -24
- package/dist/templates/infocom-adventure/agent.ts +21 -0
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- package/dist/templates/infocom-adventure/shared.ts +20 -9
- package/dist/templates/infocom-adventure/system-prompt.md +1 -1
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +3 -2
- package/dist/templates/link-digest/agent.eval.test.ts +195 -0
- package/dist/templates/link-digest/agent.test.ts +24 -19
- package/dist/templates/link-digest/client.tsx +47 -61
- package/dist/templates/link-digest/workflows/digest.ts +19 -31
- package/dist/templates/math-buddy/agent.eval.test.ts +146 -0
- package/dist/templates/night-owl/agent.eval.test.ts +272 -0
- package/dist/templates/night-owl/agent.test.ts +70 -19
- package/dist/templates/night-owl/agent.ts +5 -0
- package/dist/templates/night-owl/client.tsx +56 -56
- package/dist/templates/night-owl/shared.ts +24 -0
- package/dist/templates/night-owl/tools/recommend.ts +18 -6
- package/dist/templates/personal-finance/agent.eval.test.ts +168 -0
- package/dist/templates/personal-finance/system-prompt.md +4 -0
- package/dist/templates/pipeline-simple/agent.eval.test.ts +78 -0
- package/dist/templates/pipeline-simple/agent.ts +2 -2
- package/dist/templates/pizza-ordering/agent.eval.test.ts +171 -0
- package/dist/templates/pizza-ordering/agent.test.ts +42 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- package/dist/templates/pizza-ordering/shared.ts +20 -1
- package/dist/templates/pizza-ordering/system-prompt.md +6 -0
- package/dist/templates/plan-and-execute/agent.eval.test.ts +209 -0
- package/dist/templates/plan-and-execute/agent.test.ts +99 -58
- package/dist/templates/plan-and-execute/agent.ts +1 -1
- package/dist/templates/plan-and-execute/client.tsx +12 -15
- package/dist/templates/plan-and-execute/shared.ts +71 -2
- package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
- package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
- package/dist/templates/podcast-digest/agent.eval.test.ts +411 -0
- package/dist/templates/podcast-digest/agent.test.ts +717 -0
- package/dist/templates/podcast-digest/agent.ts +139 -0
- package/dist/templates/podcast-digest/client.tsx +154 -0
- package/dist/templates/podcast-digest/workflows/digest.ts +444 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +88 -0
- package/dist/templates/recap-workflow/agent.eval.test.ts +366 -0
- package/dist/templates/recap-workflow/agent.test.ts +97 -94
- package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
- package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
- package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
- package/dist/templates/recap-workflow/workflows/recap.ts +36 -40
- package/dist/templates/redline/agent.eval.test.ts +209 -0
- package/dist/templates/redline/agent.test.ts +17 -15
- package/dist/templates/redline/client.tsx +12 -12
- package/dist/templates/redline/workflows/redline.ts +19 -31
- package/dist/templates/research-workflow/agent.eval.test.ts +329 -0
- package/dist/templates/research-workflow/agent.test.ts +60 -59
- package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
- package/dist/templates/research-workflow/tools/research_status.ts +9 -11
- package/dist/templates/research-workflow/workflows/research.ts +44 -61
- package/dist/templates/retail/address.ts +87 -1
- package/dist/templates/retail/agent.eval.test.ts +314 -0
- package/dist/templates/retail/agent.test.ts +195 -112
- package/dist/templates/retail/agent.ts +35 -1
- package/dist/templates/retail/cancel.ts +116 -0
- package/dist/templates/retail/client.tsx +265 -117
- package/dist/templates/retail/payment.ts +107 -0
- package/dist/templates/retail/pending.test.ts +196 -0
- package/dist/templates/retail/pending.ts +137 -0
- package/dist/templates/retail/registry.test.ts +150 -7
- package/dist/templates/retail/returns.ts +102 -0
- package/dist/templates/retail/shared.test.ts +42 -0
- package/dist/templates/retail/shared.ts +35 -0
- package/dist/templates/retail/store.test.ts +84 -15
- package/dist/templates/retail/store.ts +231 -48
- package/dist/templates/retail/swap.test.ts +4 -4
- package/dist/templates/retail/swap.ts +248 -15
- package/dist/templates/retail/system-prompt.md +69 -28
- package/dist/templates/retail/tools/cancel_change.ts +33 -0
- package/dist/templates/retail/tools/cancel_pending_order.ts +16 -60
- package/dist/templates/retail/tools/confirm_change.ts +42 -0
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -67
- package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
- package/dist/templates/retail/tools/get_item_details.ts +3 -4
- package/dist/templates/retail/tools/get_order_details.ts +3 -4
- package/dist/templates/retail/tools/get_product_details.ts +3 -4
- package/dist/templates/retail/tools/get_user_details.ts +2 -2
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
- package/dist/templates/retail/tools/modify_pending_order_address.ts +13 -25
- package/dist/templates/retail/tools/modify_pending_order_items.ts +23 -62
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +16 -78
- package/dist/templates/retail/tools/modify_user_address.ts +13 -16
- package/dist/templates/retail/tools/return_delivered_order_items.ts +17 -72
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +12 -6
- package/dist/templates/simple/agent.eval.test.ts +54 -0
- package/dist/templates/solo-rpg/agent.eval.test.ts +320 -0
- package/dist/templates/solo-rpg/agent.test.ts +227 -48
- package/dist/templates/solo-rpg/client.tsx +38 -37
- package/dist/templates/solo-rpg/shared.ts +145 -19
- package/dist/templates/solo-rpg/system-prompt.md +3 -2
- package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
- package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
- package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
- package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
- package/dist/templates/spoken-summary/agent.eval.test.ts +342 -0
- package/dist/templates/spoken-summary/agent.test.ts +68 -97
- package/dist/templates/spoken-summary/agent.ts +30 -17
- package/dist/templates/spoken-summary/client.tsx +10 -50
- package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
- package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
- package/dist/templates/support-line/agent.eval.test.ts +273 -0
- package/dist/templates/support-line/agent.test.ts +11 -16
- package/dist/templates/support-line/agent.ts +1 -1
- package/dist/templates/support-line/client.tsx +9 -9
- package/dist/templates/support-line/nodes.ts +100 -0
- package/dist/templates/support-line/procedure.ts +407 -0
- package/dist/templates/support-line/system-prompt.md +2 -1
- package/dist/templates/support-line/tools/answer_question.ts +66 -4
- package/dist/templates/transcription-workflow/agent.eval.test.ts +234 -0
- package/dist/templates/transcription-workflow/agent.test.ts +94 -194
- package/dist/templates/transcription-workflow/agent.ts +1 -1
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
- package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
- package/dist/templates/transcription-workflow/workflows/wav.ts +0 -31
- package/dist/templates/travel-concierge/agent.eval.test.ts +323 -0
- package/dist/templates/travel-concierge/agent.test.ts +174 -43
- package/dist/templates/travel-concierge/agent.ts +18 -9
- package/dist/templates/travel-concierge/client.tsx +11 -23
- package/dist/templates/travel-concierge/routing.ts +34 -15
- package/dist/templates/travel-concierge/shared.ts +180 -12
- package/dist/templates/travel-concierge/system-prompt.md +7 -1
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +8 -5
- package/dist/templates/travel-concierge/tools/book_excursion.ts +8 -5
- package/dist/templates/travel-concierge/tools/book_hotel.ts +8 -5
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +8 -5
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +9 -5
- package/dist/templates/travel-concierge/tools/search_excursions.ts +9 -5
- package/dist/templates/travel-concierge/tools/search_flights.ts +9 -6
- package/dist/templates/travel-concierge/tools/search_hotels.ts +9 -5
- package/dist/templates/travel-concierge/tools/update_ticket.ts +8 -5
- package/dist/templates/web-researcher/agent.eval.test.ts +121 -0
- package/dist/templates/web-researcher/system-prompt.md +15 -4
- package/dist/{test-9kPKJI-w.mjs → test-BvyT6sAc.mjs} +24 -10
- package/dist/test.d.ts +29 -5
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-CCVEDjm1.mjs} +241 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-BflATMmU.mjs} +47 -12
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- package/dist/eject-31gjtaHF.mjs +0 -116
- package/dist/eject.d.ts +0 -60
- package/dist/templates/call-audit/workflows/temp-media.ts +0 -138
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +0 -14
- package/dist/templates/support-line/graph.ts +0 -224
- package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
- /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
2
|
|
|
3
|
-
import { isToolFailure
|
|
4
|
-
import {
|
|
3
|
+
import { isToolFailure } from "@alexkroman1/aai";
|
|
4
|
+
import { toolInputIssues, toolRunner, withDiscoveredTools } from "@alexkroman1/aai/testing";
|
|
5
5
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
6
6
|
import authoredAgent from "./agent.ts";
|
|
7
7
|
import { excerptAround, type FdaLabel, toDrugInfo } from "./fda.ts";
|
|
@@ -35,8 +35,13 @@ const agentDef = withDiscoveredTools(
|
|
|
35
35
|
import.meta.glob("./tools/*.ts", { eager: true }),
|
|
36
36
|
);
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Every tool here takes arguments and none of them touches session state, so no
|
|
40
|
+
* call passes a context: `runTool` builds a fresh one per call, which is a
|
|
41
|
+
* distinct session — right for a stateless tool, and never what two calls
|
|
42
|
+
* sharing state want.
|
|
43
|
+
*/
|
|
44
|
+
const run = toolRunner(agentDef);
|
|
40
45
|
|
|
41
46
|
const IBUPROFEN: FdaLabel = {
|
|
42
47
|
openfda: { generic_name: ["IBUPROFEN"], brand_name: ["Advil"], manufacturer_name: ["Acme"] },
|
|
@@ -150,6 +155,19 @@ describe("check_drug_interaction", () => {
|
|
|
150
155
|
expect(isToolFailure(result) && result.error).toContain("at least two");
|
|
151
156
|
expect(label).not.toHaveBeenCalled();
|
|
152
157
|
});
|
|
158
|
+
|
|
159
|
+
test("the schema itself accepts those names, which is why the body re-checks", async () => {
|
|
160
|
+
// The other half of the claim above, asked of the schema directly rather
|
|
161
|
+
// than through `~standard`: `min(2)` counts ENTRIES and `min(1)` counts
|
|
162
|
+
// CHARACTERS, so `" "` is a valid entry and the refusal is the body's.
|
|
163
|
+
expect(
|
|
164
|
+
await toolInputIssues(agentDef, "check_drug_interaction", { drugs: ["ibuprofen", " "] }),
|
|
165
|
+
).toBeUndefined();
|
|
166
|
+
// And the schema is still doing its own half — one drug is not a check.
|
|
167
|
+
expect(
|
|
168
|
+
await toolInputIssues(agentDef, "check_drug_interaction", { drugs: ["ibuprofen"] }),
|
|
169
|
+
).toBeDefined();
|
|
170
|
+
});
|
|
153
171
|
});
|
|
154
172
|
|
|
155
173
|
describe("fda.ts helpers", () => {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
// An EVAL: does the game engine actually keep its world? Run it with `aai eval`.
|
|
4
|
+
//
|
|
5
|
+
// `agent.test.ts` drives the tools directly, one call at a time, against a
|
|
6
|
+
// context it made itself. This drives the AGENT: a real session, the real tool
|
|
7
|
+
// executor, the real event stream, and — the part no unit test can reach — MORE
|
|
8
|
+
// THAN ONE TURN through the same session slot. Everything worth asserting here
|
|
9
|
+
// is a claim about state that has to survive a turn boundary, or about the world
|
|
10
|
+
// really being replaced when the player asks to start over.
|
|
11
|
+
//
|
|
12
|
+
// Two modes, and `describeEval` announces which it picked:
|
|
13
|
+
//
|
|
14
|
+
// * with ASSEMBLYAI_API_KEY — a LIVE model. It really has to map "I pick up
|
|
15
|
+
// the rusted lantern" onto `game_state_take`, which is the behaviour the
|
|
16
|
+
// system prompt spends a whole section on.
|
|
17
|
+
// * without one — a SCRIPTED model, whose tool calls REALLY EXECUTE. The
|
|
18
|
+
// state changes for real, so a scripted take followed by a scripted read is
|
|
19
|
+
// a genuine two-turn state test with no model involved.
|
|
20
|
+
//
|
|
21
|
+
// What no eval here can see: anything below the audio boundary — endpointing,
|
|
22
|
+
// barge-in, two commands merging into one turn. Those need real paced audio.
|
|
23
|
+
|
|
24
|
+
import { withSystemPrompt } from "@alexkroman1/aai/manifest";
|
|
25
|
+
import { withDiscoveredTools } from "@alexkroman1/aai/testing";
|
|
26
|
+
import { type EvalTurn, toolResultIn } from "@alexkroman1/aai-runtime/eval";
|
|
27
|
+
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
28
|
+
import { expect } from "vitest";
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
import authoredAgent from "./agent.ts";
|
|
31
|
+
import { DEFAULT_GAME_STATE } from "./shared.ts";
|
|
32
|
+
import systemPrompt from "./system-prompt.md?raw";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The def a DEPLOYED agent runs, assembled the way the build assembles it: the
|
|
36
|
+
* authored export, plus what `tools/` declares, plus `system-prompt.md`.
|
|
37
|
+
*
|
|
38
|
+
* Both wrappers matter here in a way they do not in `agent.test.ts`. A tool
|
|
39
|
+
* missing from the registry is a tool the model cannot call, and the prompt is
|
|
40
|
+
* the only thing that tells a live model that "grab the rope" means
|
|
41
|
+
* `game_state_take` — an eval run against the framework default prompt measures
|
|
42
|
+
* an agent nobody deployed.
|
|
43
|
+
*/
|
|
44
|
+
const agentDef = withSystemPrompt(
|
|
45
|
+
withDiscoveredTools(authoredAgent, import.meta.glob("./tools/*.ts", { eager: true })),
|
|
46
|
+
systemPrompt,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* What each of the three tools this file drives answers, off the wire.
|
|
51
|
+
*
|
|
52
|
+
* `tool.completed` carries a tool result as a JSON STRING, so a case either
|
|
53
|
+
* casts it or validates it — and a cast is silent exactly when a tool's result
|
|
54
|
+
* changed shape underneath the eval, which is the regression an eval exists to
|
|
55
|
+
* catch. `toolResultIn` takes a schema for that reason, and these name only the
|
|
56
|
+
* fields asserted below.
|
|
57
|
+
*/
|
|
58
|
+
const Carried = z.object({ inventory: z.array(z.string()) });
|
|
59
|
+
const Restarted = z.object({ restarted: z.boolean() });
|
|
60
|
+
const Status = z.object({
|
|
61
|
+
inventory: z.array(z.string()),
|
|
62
|
+
score: z.number(),
|
|
63
|
+
moves: z.number(),
|
|
64
|
+
currentRoom: z.string(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* What the ONE call to `name` answered on this turn.
|
|
69
|
+
*
|
|
70
|
+
* `toolResultIn` is the SDK's reader: it throws rather than returning
|
|
71
|
+
* undefined, and names what the agent called instead — "it called a different
|
|
72
|
+
* tool" is the finding, and a case that read `undefined` off a missing call
|
|
73
|
+
* would assert against nothing. A turn that called it TWICE is refused too,
|
|
74
|
+
* rather than silently answered with the first.
|
|
75
|
+
*/
|
|
76
|
+
const answerOf = <T>(turn: EvalTurn, name: string, schema: z.ZodType<T>): T =>
|
|
77
|
+
toolResultIn(turn.toolCalls, name, schema);
|
|
78
|
+
|
|
79
|
+
describeEval(agentDef, (test) => {
|
|
80
|
+
test(
|
|
81
|
+
"an item taken on one turn is still carried on the next",
|
|
82
|
+
async ({ session }) => {
|
|
83
|
+
const taken = await session.say("I pick up the rusted lantern.");
|
|
84
|
+
// The write really wrote. `game_state_take` is a `gameSlot.updateTool`,
|
|
85
|
+
// and it shipped once as the READING half — pushing to a deep-frozen
|
|
86
|
+
// array, which throws on every call. A tool that threw answers with an
|
|
87
|
+
// error here, not an inventory.
|
|
88
|
+
expect(answerOf(taken, "game_state_take", Carried).inventory).toContain("rusted lantern");
|
|
89
|
+
|
|
90
|
+
// A SECOND turn, which is the whole point: the slot is keyed per session,
|
|
91
|
+
// so this is the only place the take can be shown to have outlived the
|
|
92
|
+
// turn that made it.
|
|
93
|
+
const status = await session.say("Check the game state. What am I carrying?");
|
|
94
|
+
expect(answerOf(status, "game_state_get", Status).inventory).toContain("rusted lantern");
|
|
95
|
+
expect(status.completed).toBe(true);
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
stubReply: [
|
|
99
|
+
{ tool: "game_state_take", args: { value: "rusted lantern" } },
|
|
100
|
+
"You lift the rusted lantern from its iron hook. It is heavier than it looks.",
|
|
101
|
+
{ tool: "game_state_get" },
|
|
102
|
+
"You are carrying the rusted lantern, and nothing else.",
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
test(
|
|
108
|
+
"starting over really empties the world",
|
|
109
|
+
async ({ session }) => {
|
|
110
|
+
const taken = await session.say("I pick up the rusted lantern.");
|
|
111
|
+
expect(answerOf(taken, "game_state_take", Carried).inventory).toContain("rusted lantern");
|
|
112
|
+
|
|
113
|
+
const again = await session.say("Forget all that. Start a new game from the beginning.");
|
|
114
|
+
expect(answerOf(again, "game_state_restart", Restarted).restarted).toBe(true);
|
|
115
|
+
|
|
116
|
+
// `game_state_restart` is the one tool that REPLACES the slot's value
|
|
117
|
+
// (`gameSlot.reset`), so what has to be checked is the state a LATER turn
|
|
118
|
+
// reads — a reset that only rebuilt the value it returned would satisfy
|
|
119
|
+
// the assertion above and leave the lantern in the player's hands.
|
|
120
|
+
const status = await session.say("Check the game state. What am I carrying, and where am I?");
|
|
121
|
+
const fresh = answerOf(status, "game_state_get", Status);
|
|
122
|
+
expect(fresh.inventory).toEqual([]);
|
|
123
|
+
expect(fresh.score).toBe(0);
|
|
124
|
+
expect(fresh.currentRoom).toBe(DEFAULT_GAME_STATE.currentRoom);
|
|
125
|
+
// ONE, not zero — and this is the assertion that proves the turn counter
|
|
126
|
+
// is the framework's. The reset emptied it, then the player said the line
|
|
127
|
+
// above, and the `user-transcript.committed` hook counted it before the
|
|
128
|
+
// narrator took its turn. No tool call is involved anywhere in that.
|
|
129
|
+
expect(fresh.moves).toBe(1);
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
stubReply: [
|
|
133
|
+
{ tool: "game_state_take", args: { value: "rusted lantern" } },
|
|
134
|
+
"You lift the rusted lantern from its iron hook.",
|
|
135
|
+
{ tool: "game_state_restart" },
|
|
136
|
+
"Very well. We begin again at the mouth of the cave.",
|
|
137
|
+
{ tool: "game_state_get" },
|
|
138
|
+
"You carry nothing. Your score is zero, and you stand at the cave mouth.",
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
test(
|
|
144
|
+
"a restart is narrated, not merely recorded",
|
|
145
|
+
async ({ session }) => {
|
|
146
|
+
// LIVE only: the claim is about what the narrator SAYS after the reset,
|
|
147
|
+
// and a scripted reply is a line this file wrote. The prompt asks for the
|
|
148
|
+
// opening scene again — a restart that answers "done" leaves a voice
|
|
149
|
+
// player with no idea where they are.
|
|
150
|
+
const again = await session.say("Start over. New game, please.");
|
|
151
|
+
|
|
152
|
+
expect(answerOf(again, "game_state_restart", Restarted).restarted).toBe(true);
|
|
153
|
+
expect(again.text).toMatch(/cave|cavern|forest|entrance|lantern/i);
|
|
154
|
+
expect(again.completed).toBe(true);
|
|
155
|
+
},
|
|
156
|
+
{ live: true },
|
|
157
|
+
);
|
|
158
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { createToolContext, runTool, withDiscoveredTools } from "@alexkroman1/aai/testing";
|
|
3
|
+
import { createToolContext, toolRunner, withDiscoveredTools } from "@alexkroman1/aai/testing";
|
|
5
4
|
import { describe, expect, test } from "vitest";
|
|
6
5
|
import authoredAgent from "./agent.ts";
|
|
7
6
|
|
|
@@ -21,8 +20,22 @@ const agentDef = withDiscoveredTools(
|
|
|
21
20
|
import { DEFAULT_GAME_STATE, gameSlot, MAX_HISTORY, REPORTED_HISTORY } from "./shared.ts";
|
|
22
21
|
|
|
23
22
|
/** A tool by the name the model calls it by, bound to this agent. */
|
|
24
|
-
const run = (
|
|
25
|
-
|
|
23
|
+
const run = toolRunner(agentDef);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* What the player said, delivered the way the RUNTIME delivers it.
|
|
27
|
+
*
|
|
28
|
+
* A session event handler is a plain function on the def, so a template can
|
|
29
|
+
* drive one with no harness — which is the point of asserting on it here rather
|
|
30
|
+
* than trusting the wiring: `moves` and `history` are now maintained by
|
|
31
|
+
* something the model never calls, so nothing else in this file would notice if
|
|
32
|
+
* the hook stopped running.
|
|
33
|
+
*/
|
|
34
|
+
const say = (text: string, ctx: ReturnType<typeof makeCtx>) =>
|
|
35
|
+
agentDef.events?.["user-transcript.committed"]?.(
|
|
36
|
+
{ type: "user-transcript.committed", text, meta: { id: "evt_1", at: 0 } },
|
|
37
|
+
ctx,
|
|
38
|
+
);
|
|
26
39
|
|
|
27
40
|
/** Each context owns its OWN slot store, which is what makes two playthroughs
|
|
28
41
|
* independent by construction. */
|
|
@@ -99,13 +112,15 @@ describe("the adventure's tools", () => {
|
|
|
99
112
|
expect(nothing.inventory).toEqual(["rope"]);
|
|
100
113
|
});
|
|
101
114
|
|
|
102
|
-
test("move sets the room and
|
|
115
|
+
test("move sets the room and reports the turn count without touching it", async () => {
|
|
103
116
|
const ctx = makeCtx();
|
|
104
117
|
const moved = (await run("game_state_move", { value: "Echo Chamber" }, ctx)) as {
|
|
105
118
|
currentRoom: string;
|
|
106
119
|
moves: number;
|
|
107
120
|
};
|
|
108
|
-
|
|
121
|
+
// `moves` is 0 because nobody has SAID anything — see `recordTurn`. It is
|
|
122
|
+
// still reported, because it is what the narrator wants back.
|
|
123
|
+
expect(moved).toEqual({ currentRoom: "Echo Chamber", moves: 0 });
|
|
109
124
|
expect(gameSlot.get(ctx).currentRoom).toBe("Echo Chamber");
|
|
110
125
|
});
|
|
111
126
|
|
|
@@ -116,26 +131,42 @@ describe("the adventure's tools", () => {
|
|
|
116
131
|
expect(total.score).toBe(15);
|
|
117
132
|
});
|
|
118
133
|
|
|
119
|
-
test("
|
|
134
|
+
test("what the player SAYS logs the command and counts the turn", async () => {
|
|
120
135
|
const ctx = makeCtx();
|
|
121
|
-
for (let i = 1; i <= REPORTED_HISTORY + 2; i++) {
|
|
122
|
-
|
|
123
|
-
}
|
|
136
|
+
for (let i = 1; i <= REPORTED_HISTORY + 2; i++) say(`command ${i}`, ctx);
|
|
137
|
+
say("look", ctx);
|
|
124
138
|
|
|
125
|
-
const
|
|
139
|
+
const game = gameSlot.get(ctx);
|
|
140
|
+
expect(game.moves).toBe(REPORTED_HISTORY + 3);
|
|
141
|
+
expect(game.history.at(-1)).toBe("look");
|
|
142
|
+
|
|
143
|
+
// And the narrator reads it back through the ordinary state tool — the hook
|
|
144
|
+
// writes, the model reads, and the two never have to agree about who counts.
|
|
145
|
+
const read = (await run("game_state_get", {}, ctx)) as {
|
|
126
146
|
moves: number;
|
|
127
147
|
recentHistory: string[];
|
|
128
148
|
};
|
|
129
|
-
expect(
|
|
130
|
-
expect(
|
|
131
|
-
expect(last.recentHistory.at(-1)).toBe("look");
|
|
149
|
+
expect(read.moves).toBe(REPORTED_HISTORY + 3);
|
|
150
|
+
expect(read.recentHistory).toHaveLength(REPORTED_HISTORY);
|
|
132
151
|
});
|
|
133
152
|
|
|
134
|
-
test("
|
|
153
|
+
test("a turn is counted once, even when the narrator also moves the player", async () => {
|
|
135
154
|
const ctx = makeCtx();
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
say("go north", ctx);
|
|
156
|
+
await run("game_state_move", { value: "Echo Chamber" }, ctx);
|
|
157
|
+
|
|
158
|
+
// Both used to bump `moves`, so this turn scored 2 — and a turn where the
|
|
159
|
+
// narrator called neither scored 0. A move is a room change; a turn is the
|
|
160
|
+
// player saying something.
|
|
161
|
+
const game = gameSlot.get(ctx);
|
|
162
|
+
expect(game.moves).toBe(1);
|
|
163
|
+
expect(game.currentRoom).toBe("Echo Chamber");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("the history is capped, so a long playthrough does not grow without bound", () => {
|
|
167
|
+
const ctx = makeCtx();
|
|
168
|
+
for (let i = 0; i < MAX_HISTORY + 10; i++) say(`command ${i}`, ctx);
|
|
169
|
+
|
|
139
170
|
const game = gameSlot.get(ctx);
|
|
140
171
|
expect(game.history).toHaveLength(MAX_HISTORY);
|
|
141
172
|
// The cap drops the OLDEST — the newest command is the one a narrator needs.
|
|
@@ -149,15 +180,13 @@ describe("the adventure's tools", () => {
|
|
|
149
180
|
await run("game_state_flag", { value: "gate_opened" }, ctx);
|
|
150
181
|
await run("game_state_move", { value: "Echo Chamber" }, ctx);
|
|
151
182
|
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
|
-
}
|
|
183
|
+
for (let i = 0; i < REPORTED_HISTORY + 3; i++) say(`command ${i}`, ctx);
|
|
155
184
|
|
|
156
|
-
expect(await run("game_state_get",
|
|
185
|
+
expect(await run("game_state_get", ctx)).toEqual({
|
|
157
186
|
currentRoom: "Echo Chamber",
|
|
158
187
|
inventory: ["lantern"],
|
|
159
188
|
score: 7,
|
|
160
|
-
moves: REPORTED_HISTORY +
|
|
189
|
+
moves: REPORTED_HISTORY + 3,
|
|
161
190
|
flags: { gate_opened: true },
|
|
162
191
|
recentHistory: Array.from({ length: REPORTED_HISTORY }, (_, i) => `command ${i + 3}`),
|
|
163
192
|
});
|
|
@@ -169,7 +198,7 @@ describe("the adventure's tools", () => {
|
|
|
169
198
|
await run("game_state_score", { value: 30 }, ctx);
|
|
170
199
|
await run("game_state_move", { value: "Echo Chamber" }, ctx);
|
|
171
200
|
|
|
172
|
-
const restarted = (await run("game_state_restart",
|
|
201
|
+
const restarted = (await run("game_state_restart", ctx)) as {
|
|
173
202
|
restarted: boolean;
|
|
174
203
|
currentRoom: string;
|
|
175
204
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { agent } from "@alexkroman1/aai";
|
|
2
|
+
import { gameSlot, recordTurn } from "./shared.ts";
|
|
2
3
|
|
|
3
4
|
export default agent({
|
|
4
5
|
name: "Cavern Adventure",
|
|
@@ -11,4 +12,24 @@ export default agent({
|
|
|
11
12
|
// (shared.ts) and the world map in system-prompt.md.
|
|
12
13
|
greeting:
|
|
13
14
|
"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?",
|
|
15
|
+
/**
|
|
16
|
+
* The turn counter and the command log are the FRAMEWORK's, not the model's.
|
|
17
|
+
*
|
|
18
|
+
* Both used to be a `game_state_history` tool the system prompt told the
|
|
19
|
+
* narrator to call on every turn, handing back the player's own words — which
|
|
20
|
+
* the runtime already had. A hook is strictly better on all three counts a
|
|
21
|
+
* template is meant to teach: it costs no model call, it cannot be forgotten,
|
|
22
|
+
* and it needs no prose in the prompt to enforce it.
|
|
23
|
+
*
|
|
24
|
+
* `.committed` rather than `.updated`: partials arrive several times per
|
|
25
|
+
* utterance and would count one sentence as a dozen turns.
|
|
26
|
+
*
|
|
27
|
+
* It writes and does not speak, which is the whole line a session event hook
|
|
28
|
+
* draws — nothing here can decide what the narrator says next. The narrator
|
|
29
|
+
* reads the result on its next `game_state_get`.
|
|
30
|
+
*/
|
|
31
|
+
events: {
|
|
32
|
+
"user-transcript.committed": (event, ctx) =>
|
|
33
|
+
gameSlot.update(ctx, (game) => recordTurn(game, event.text)),
|
|
34
|
+
},
|
|
14
35
|
});
|