@alexkroman1/aai-cli 8.2.1 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_dev-env.d.ts +15 -2
- package/dist/{_dev-server-Dt3TCjhj.mjs → _dev-server-BAR8Ya43.mjs} +182 -55
- package/dist/_dev-server.d.ts +5 -30
- package/dist/_dev-typecheck.d.ts +38 -0
- package/dist/_dev-vite-config.d.ts +74 -0
- package/dist/_e2e-test-utils.d.ts +33 -1
- package/dist/_init-BNotRgV1.mjs +163 -0
- package/dist/_resource-commands.d.ts +0 -1
- package/dist/{build-BBxf8vom.mjs → build-DvDdSwAS.mjs} +1 -1
- package/dist/cli.mjs +22 -93
- package/dist/{delete-CPf347lo.mjs → delete-8pjH8n8n.mjs} +2 -1
- package/dist/{deploy-BqV6ShIe.mjs → deploy-CjU3e-3P.mjs} +3 -3
- package/dist/{dev-QuuOOUtv.mjs → dev-dFzdvwa7.mjs} +3 -2
- package/dist/dev.d.ts +2 -0
- package/dist/{eval-DfQ8cm5p.mjs → eval-DwNwdqmS.mjs} +1 -1
- package/dist/{init-Cp5GZg0B.mjs → init-DK7GpIF9.mjs} +7 -6
- package/dist/scaffold/.env.example +7 -4
- package/dist/scaffold/CLAUDE.md +152 -97
- package/dist/scaffold/global.d.ts +12 -0
- package/dist/scaffold/package.json +4 -4
- package/dist/scaffold/server.mjs +49 -2
- package/dist/scaffold/tsconfig.json +1 -0
- package/dist/scaffold/vitest.config.ts +24 -0
- package/dist/{studio-DUfCH9I8.mjs → studio-D3-HWIfk.mjs} +3 -3
- package/dist/templates/briefing-desk/agent.test.ts +3 -15
- package/dist/templates/briefing-desk/shared.ts +3 -3
- package/dist/templates/call-audit/agent.eval.test.ts +8 -1
- package/dist/templates/call-audit/agent.test.ts +0 -1
- package/dist/templates/call-audit/agent.ts +4 -3
- package/dist/templates/call-audit/client.tsx +4 -7
- package/dist/templates/call-audit/workflows/audit.ts +10 -1
- package/dist/templates/code-interpreter/agent.eval.test.ts +14 -43
- package/dist/templates/dispatch-center/agent.eval.test.ts +16 -26
- package/dist/templates/embedded-assets/agent.eval.test.ts +6 -16
- package/dist/templates/embedded-assets/agent.test.ts +3 -16
- package/dist/templates/health-assistant/agent.eval.test.ts +5 -15
- package/dist/templates/health-assistant/agent.test.ts +3 -14
- package/dist/templates/infocom-adventure/agent.eval.test.ts +6 -16
- package/dist/templates/infocom-adventure/agent.test.ts +3 -17
- package/dist/templates/link-digest/agent.test.ts +0 -14
- package/dist/templates/link-digest/agent.ts +9 -5
- package/dist/templates/link-digest/client.tsx +8 -6
- package/dist/templates/math-buddy/agent.eval.test.ts +11 -40
- package/dist/templates/night-owl/agent.eval.test.ts +15 -44
- package/dist/templates/night-owl/agent.test.ts +2 -15
- package/dist/templates/personal-finance/agent.eval.test.ts +10 -39
- package/dist/templates/pipeline-simple/agent.test.ts +73 -0
- package/dist/templates/pizza-ordering/agent.eval.test.ts +2 -17
- package/dist/templates/pizza-ordering/agent.test.ts +2 -17
- package/dist/templates/plan-and-execute/agent.eval.test.ts +2 -12
- package/dist/templates/plan-and-execute/agent.test.ts +3 -23
- package/dist/templates/plan-and-execute/shared.ts +1 -1
- package/dist/templates/podcast-digest/agent.test.ts +0 -1
- package/dist/templates/podcast-digest/client.tsx +3 -6
- package/dist/templates/podcast-digest/workflows/digest.ts +16 -8
- package/dist/templates/recap-workflow/agent.eval.test.ts +7 -14
- package/dist/templates/recap-workflow/agent.test.ts +3 -18
- package/dist/templates/recap-workflow/agent.ts +7 -5
- package/dist/templates/redline/agent.test.ts +0 -13
- package/dist/templates/redline/agent.ts +3 -2
- package/dist/templates/redline/client.tsx +9 -4
- package/dist/templates/research-workflow/agent.eval.test.ts +7 -14
- package/dist/templates/research-workflow/agent.test.ts +3 -15
- package/dist/templates/research-workflow/agent.ts +7 -5
- package/dist/templates/research-workflow/workflows/research.ts +8 -2
- package/dist/templates/retail/agent.eval.test.ts +10 -25
- package/dist/templates/retail/registry.test.ts +16 -13
- package/dist/templates/solo-rpg/agent.eval.test.ts +5 -15
- package/dist/templates/solo-rpg/agent.test.ts +17 -124
- package/dist/templates/solo-rpg/shared.ts +13 -61
- package/dist/templates/spoken-summary/agent.test.ts +0 -1
- package/dist/templates/spoken-summary/agent.ts +5 -4
- package/dist/templates/spoken-summary/client.tsx +4 -7
- package/dist/templates/support-line/agent.eval.test.ts +2 -12
- package/dist/templates/support-line/agent.test.ts +3 -22
- package/dist/templates/transcription-workflow/agent.ts +4 -4
- package/dist/templates/transcription-workflow/client.tsx +5 -5
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +10 -1
- package/dist/templates/travel-concierge/agent.eval.test.ts +5 -15
- package/dist/templates/travel-concierge/agent.test.ts +3 -23
- package/dist/templates/web-researcher/agent.eval.test.ts +2 -5
- package/dist/test-CvwgeVSQ.mjs +181 -0
- package/dist/test.d.ts +33 -2
- package/package.json +4 -4
- package/dist/_init-BhEfQ6Yi.mjs +0 -93
- package/dist/storage-Bfs7QQWq.mjs +0 -66
- package/dist/storage.d.ts +0 -22
- package/dist/templates/solo-rpg/tools/load_game.ts +0 -37
- package/dist/templates/solo-rpg/tools/save_game.ts +0 -32
- package/dist/test-BvyT6sAc.mjs +0 -104
|
@@ -45,11 +45,11 @@ export const MAX_RESEARCH_STEPS = 6;
|
|
|
45
45
|
* "Finish with a summary" is not politeness. The parent gets the subagent's
|
|
46
46
|
* FINAL message, so a run that ends by saying "Done." has thrown away
|
|
47
47
|
* everything it read and no budget recovers it — this is the failure mode the
|
|
48
|
-
* `SubagentDef.
|
|
48
|
+
* `SubagentDef.systemPrompt` contract warns about, stated once, here.
|
|
49
49
|
*/
|
|
50
50
|
export const researcher = subagent({
|
|
51
51
|
name: "researcher",
|
|
52
|
-
|
|
52
|
+
systemPrompt: [
|
|
53
53
|
"You are a research agent working one angle of a briefing.",
|
|
54
54
|
"",
|
|
55
55
|
"Search, then open the two or three most promising pages and read them.",
|
|
@@ -77,7 +77,7 @@ export const researcher = subagent({
|
|
|
77
77
|
*/
|
|
78
78
|
export const factChecker = subagent({
|
|
79
79
|
name: "fact-checker",
|
|
80
|
-
|
|
80
|
+
systemPrompt: [
|
|
81
81
|
"You check ONE claim against what you can find on the web.",
|
|
82
82
|
"",
|
|
83
83
|
"Search for it. Answer in one sentence, starting with one of",
|
|
@@ -101,7 +101,14 @@ const describeWorkflowEvalWithFfmpeg: typeof describeWorkflowEval = (agent, defi
|
|
|
101
101
|
if ((process.env.AAI_REQUIRE_FFMPEG ?? "") !== "") {
|
|
102
102
|
throw new Error(`AAI_REQUIRE_FFMPEG is set but no ffmpeg was found.\n${HOW_TO}`);
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
// A direct stderr write, not `console.warn`: vitest intercepts `console`, and
|
|
105
|
+
// which reporter it hands the capture to is chosen for you — unset
|
|
106
|
+
// `reporters` resolves to vitest's AGENT reporter when it detects one, and
|
|
107
|
+
// that reporter prints a passing file's output nowhere. A skip passes. So the
|
|
108
|
+
// announcement that keeps this skip from being silent was dropped for exactly
|
|
109
|
+
// the reader most likely to miss it. `announceEvalMode` in the SDK carries the
|
|
110
|
+
// measurement.
|
|
111
|
+
process.stderr.write(`\n[skipped: no ffmpeg] ${agent.name} eval not run.\n${HOW_TO}\n`);
|
|
105
112
|
skipSuite(agent.name, () => {
|
|
106
113
|
// Named `test`, not aliased: Biome's `noMisplacedAssertion` matches the
|
|
107
114
|
// CALLEE IDENTIFIER, so an `expect` inside a `vitestTest(…)` is an error.
|
|
@@ -171,7 +171,6 @@ describe("the declaration", () => {
|
|
|
171
171
|
// The page calls `api.start("audit", …)`, so a rename here is a runtime 400
|
|
172
172
|
// rather than a compile error. This is what pins it.
|
|
173
173
|
expect(Object.keys(agentDef.workflows ?? {})).toEqual(["audit"]);
|
|
174
|
-
expect(agentDef.page).toBe("static");
|
|
175
174
|
});
|
|
176
175
|
|
|
177
176
|
test("declares no providers and exactly the one credential its steps read", () => {
|
|
@@ -57,9 +57,10 @@
|
|
|
57
57
|
* `aai secret put ASSEMBLYAI_API_KEY` once deployed. `requiredEnv` below is what
|
|
58
58
|
* makes a deploy check for it rather than letting the first run find out. One key
|
|
59
59
|
* covers transcription, the model and the voice alike.
|
|
60
|
-
* - **
|
|
61
|
-
*
|
|
62
|
-
* upload's record is a row, and this desk both reads an
|
|
60
|
+
* - **A `DATABASE_URL`** — a secret when deployed, `.env` under `aai dev`, and a
|
|
61
|
+
* Postgres you bring since the platform provisions none. REQUIRED, unlike most
|
|
62
|
+
* workflow apps: an upload's record is a row, and this desk both reads an
|
|
63
|
+
* upload and writes two.
|
|
63
64
|
* - **ffmpeg** — every deployed guest's image installs it (and `ffprobe` with it).
|
|
64
65
|
* Under `aai dev` it is whatever is on `PATH`, or what `AAI_FFMPEG_PATH` /
|
|
65
66
|
* `AAI_FFPROBE_PATH` name. That is the one place dev/prod parity is partial, so
|
|
@@ -29,7 +29,6 @@ import "@alexkroman1/aai-ui/styles.css";
|
|
|
29
29
|
// nothing — and it is what stops this file restating a shape `workflows/audit.ts`
|
|
30
30
|
// already declares.
|
|
31
31
|
import { formatBytes, formatDuration } from "@alexkroman1/aai/utils";
|
|
32
|
-
import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
|
|
33
32
|
import {
|
|
34
33
|
createWorkflowApi,
|
|
35
34
|
Form,
|
|
@@ -43,9 +42,6 @@ import {
|
|
|
43
42
|
} from "@alexkroman1/aai-ui";
|
|
44
43
|
import type { audit } from "./agent.ts";
|
|
45
44
|
|
|
46
|
-
/** What a completed run reports, derived from the workflow rather than restated. */
|
|
47
|
-
type Audit = WorkflowOutputOf<typeof audit>;
|
|
48
|
-
|
|
49
45
|
/**
|
|
50
46
|
* The workflow's name, as a page starts a run by one.
|
|
51
47
|
*
|
|
@@ -91,8 +87,9 @@ function Findings({ title, items }: { title: string; items: string[] }) {
|
|
|
91
87
|
export function App() {
|
|
92
88
|
// The generic is what makes `run.status === "completed"` narrow to a TYPED
|
|
93
89
|
// `run.output` instead of `unknown`.
|
|
94
|
-
const {
|
|
95
|
-
|
|
90
|
+
const { submitForm, run, pending, upload, pauseUpload, resumeUpload, error } = useWorkflowSubmit<
|
|
91
|
+
typeof audit
|
|
92
|
+
>(WORKFLOW, { api });
|
|
96
93
|
const output = run?.status === "completed" ? run.output : undefined;
|
|
97
94
|
// `useDownloadUrl` is the SDK's: the byte route takes the agent's bearer, so the
|
|
98
95
|
// bytes have to be FETCHED and handed to the element as an object URL — and the
|
|
@@ -109,7 +106,7 @@ export function App() {
|
|
|
109
106
|
</p>
|
|
110
107
|
</header>
|
|
111
108
|
|
|
112
|
-
<Form onSubmit={
|
|
109
|
+
<Form onSubmit={submitForm} error={error} className="flex flex-col gap-4">
|
|
113
110
|
{/* Every control, from the workflow's own input schema. See the module doc. */}
|
|
114
111
|
<WorkflowFields workflow={WORKFLOW} />
|
|
115
112
|
<SubmitButton pending={pending} pendingLabel="Auditing…">
|
|
@@ -78,7 +78,16 @@ import { transcribeSpan } from "./sync-api.ts";
|
|
|
78
78
|
* template measured as the point where the endpoint starts returning `503`s. So
|
|
79
79
|
* the byte bound never binds and what is left is the endpoint's own knee, which it
|
|
80
80
|
* measured at 32. Its `BYTES_IN_FLIGHT` and `MAX_SEGMENT_CONCURRENCY` docs carry
|
|
81
|
-
* both measurements; this is the one number that survives them.
|
|
81
|
+
* both measurements; this is the one number that survives them. *
|
|
82
|
+
* **What EXECUTES at this width is the world's call, not this number's.**
|
|
83
|
+
* `mapConcurrent` bounds how many step calls the body has in flight; how many
|
|
84
|
+
* run at once is the workflow world's worker concurrency, which on the
|
|
85
|
+
* `DATABASE_URL` path defaults to three — so on a default deployment a width
|
|
86
|
+
* above three is inert while still costing a queued job per item. That makes
|
|
87
|
+
* this the FAR SIDE's knee and the width to use once an operator has raised
|
|
88
|
+
* the ceiling, not a promise about a stock deployment. See "The WINDOW is not
|
|
89
|
+
* the concurrency" in `@alexkroman1/aai/step`'s `mapConcurrent`; the numbers
|
|
90
|
+
* above were measured against the endpoint and say nothing about that layer.
|
|
82
91
|
*/
|
|
83
92
|
export const SEGMENT_CONCURRENCY = 32;
|
|
84
93
|
|
|
@@ -19,19 +19,14 @@
|
|
|
19
19
|
// model-written JavaScript in the host process. That is right, and it left this
|
|
20
20
|
// template's whole subject assertable as a CALL and never as an answer: a
|
|
21
21
|
// `toBeDefined()` on the result is satisfied by the refusal itself. So the suite
|
|
22
|
-
// passes `
|
|
23
|
-
// not — and every case here asserts BOTH halves: that
|
|
24
|
-
// what the code came back with.
|
|
25
|
-
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
28
|
-
import type { RunCodeExecutor } from "@alexkroman1/aai-runtime/eval";
|
|
22
|
+
// passes `createVmRunCode()` — a developer's own machine may evaluate generated
|
|
23
|
+
// code, a deployment may not — and every case here asserts BOTH halves: that
|
|
24
|
+
// Coda reached for code, and what the code came back with.
|
|
25
|
+
|
|
26
|
+
import agentDef from "virtual:aai/agent";
|
|
27
|
+
import { createVmRunCode, toolResultsIn } from "@alexkroman1/aai-runtime/eval";
|
|
29
28
|
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
30
29
|
import { expect } from "vitest";
|
|
31
|
-
import authored from "./agent.ts";
|
|
32
|
-
import systemPrompt from "./system-prompt.md?raw";
|
|
33
|
-
|
|
34
|
-
const agentDef = withSystemPrompt(authored, systemPrompt);
|
|
35
30
|
|
|
36
31
|
/** The code every `run_code` call in this turn carried, joined. */
|
|
37
32
|
const codeIn = (turn: { toolCalls: readonly { name: string; args: Record<string, unknown> }[] }) =>
|
|
@@ -45,37 +40,13 @@ const codeIn = (turn: { toolCalls: readonly { name: string; args: Record<string,
|
|
|
45
40
|
*
|
|
46
41
|
* The builtin refuses without one — the Modal container is the security
|
|
47
42
|
* boundary, and off-platform there is none — so a case could assert the CALL and
|
|
48
|
-
* the code it carried, and never what the code came back with.
|
|
49
|
-
* context with a capturing `console.log
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* A template eval imports from `@alexkroman1/aai-runtime/eval` and
|
|
55
|
-
* `/eval/vitest` and nowhere else — the ROOT barrel drags the host runtime's
|
|
56
|
-
* node-reaching module graph into this project's TypeScript program, which is
|
|
57
|
-
* three errors in runtime files no eval ever calls. `RunCodeExecutor` is
|
|
58
|
-
* re-exported from `/eval` for exactly that reason.
|
|
43
|
+
* the code it carried, and never what the code came back with.
|
|
44
|
+
* `createVmRunCode()` is a `node:vm` context with a capturing `console.log`,
|
|
45
|
+
* which is enough here: what runs is arithmetic, not a program. It is NOT a
|
|
46
|
+
* sandbox and does not pretend to be one; a deployed agent still gets the
|
|
47
|
+
* refusal.
|
|
59
48
|
*/
|
|
60
|
-
const runCode
|
|
61
|
-
const lines: string[] = [];
|
|
62
|
-
const log = (...args: unknown[]): void => {
|
|
63
|
-
lines.push(args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" "));
|
|
64
|
-
};
|
|
65
|
-
try {
|
|
66
|
-
runInNewContext(code, { console: { log } }, { timeout: 1000 });
|
|
67
|
-
} catch (err) {
|
|
68
|
-
return { error: errorMessage(err) };
|
|
69
|
-
}
|
|
70
|
-
return lines.join("\n");
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/** What every `run_code` call in this turn PRINTED, joined. */
|
|
74
|
-
const outputIn = (turn: { toolCalls: readonly { name: string; result?: string }[] }) =>
|
|
75
|
-
turn.toolCalls
|
|
76
|
-
.filter((c) => c.name === "run_code")
|
|
77
|
-
.map((c) => c.result ?? "")
|
|
78
|
-
.join("\n");
|
|
49
|
+
const runCode = createVmRunCode();
|
|
79
50
|
|
|
80
51
|
describeEval(
|
|
81
52
|
agentDef,
|
|
@@ -97,7 +68,7 @@ describeEval(
|
|
|
97
68
|
// none, `run_code` answers "only available in the sandboxed runtime", so
|
|
98
69
|
// every claim above passes for an agent that then does the sum in its
|
|
99
70
|
// head — which is the exact regression the CRITICAL RULE exists to stop.
|
|
100
|
-
const output =
|
|
71
|
+
const output = toolResultsIn(turn.toolCalls, "run_code").join("\n");
|
|
101
72
|
expect(output, `run_code printed: ${output}`).toContain("107823");
|
|
102
73
|
|
|
103
74
|
// "Report RESULTS, never intentions": the call goes out before Coda says
|
|
@@ -129,7 +100,7 @@ describeEval(
|
|
|
129
100
|
// Greenwich (measured: this printed "Friday"). Asserting the weekday
|
|
130
101
|
// would measure the machine running the eval. The two arithmetic cases
|
|
131
102
|
// in this suite have no such dependency and do assert their answers.
|
|
132
|
-
const output =
|
|
103
|
+
const output = toolResultsIn(turn.toolCalls, "run_code").join("\n");
|
|
133
104
|
expect(output, `run_code printed: ${output}`).not.toMatch(
|
|
134
105
|
/only available in the sandboxed runtime/,
|
|
135
106
|
);
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The def a DEPLOYED agent runs: authored, plus what `tools/` declares, plus
|
|
3
|
+
* what `system-prompt.md` says.
|
|
4
|
+
*
|
|
5
|
+
* BOTH wrappers are load-bearing and neither is applied by `agent()` — they are
|
|
6
|
+
* applied by the BUILD (`aai build` enumerates `tools/` and resolves the prompt
|
|
7
|
+
* file), so an eval driving the raw default export would measure a twelve-tool
|
|
8
|
+
* desk with no tools and the FRAMEWORK DEFAULT prompt. For this template that
|
|
9
|
+
* is the whole subject: "location is always the first priority", the radio
|
|
10
|
+
* style and "never leave a critical incident without a resource" are all in
|
|
11
|
+
* that file.
|
|
12
|
+
*
|
|
13
|
+
* The glob is written here rather than reached for from a shared helper because
|
|
14
|
+
* this file SHIPS — it is what a scaffolded project runs.
|
|
15
|
+
*/
|
|
16
|
+
import dispatchAgent from "virtual:aai/agent";
|
|
4
17
|
import type { SessionEvent } from "@alexkroman1/aai/protocol";
|
|
5
|
-
import { withDiscoveredTools } from "@alexkroman1/aai/testing";
|
|
6
18
|
// An EVAL: does the desk actually behave? Run it with `aai eval`.
|
|
7
19
|
//
|
|
8
20
|
// `agent.test.ts` drives each tool directly. What it cannot ask is whether the
|
|
@@ -26,28 +38,6 @@ import { type EvalSession, type EvalTurn, lastStateIn } from "@alexkroman1/aai-r
|
|
|
26
38
|
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
27
39
|
import { expect } from "vitest";
|
|
28
40
|
import { z } from "zod";
|
|
29
|
-
import authoredAgent from "./agent.ts";
|
|
30
|
-
import systemPrompt from "./system-prompt.md?raw";
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The def a DEPLOYED agent runs: authored, plus what `tools/` declares, plus
|
|
34
|
-
* what `system-prompt.md` says.
|
|
35
|
-
*
|
|
36
|
-
* BOTH wrappers are load-bearing and neither is applied by `agent()` — they are
|
|
37
|
-
* applied by the BUILD (`aai build` enumerates `tools/` and resolves the prompt
|
|
38
|
-
* file), so an eval driving the raw default export would measure a twelve-tool
|
|
39
|
-
* desk with no tools and the FRAMEWORK DEFAULT prompt. For this template that
|
|
40
|
-
* is the whole subject: "location is always the first priority", the radio
|
|
41
|
-
* style and "never leave a critical incident without a resource" are all in
|
|
42
|
-
* that file.
|
|
43
|
-
*
|
|
44
|
-
* The glob is written here rather than reached for from a shared helper because
|
|
45
|
-
* this file SHIPS — it is what a scaffolded project runs.
|
|
46
|
-
*/
|
|
47
|
-
const dispatchAgent = withSystemPrompt(
|
|
48
|
-
withDiscoveredTools(authoredAgent, import.meta.glob("./tools/*.ts", { eager: true })),
|
|
49
|
-
systemPrompt,
|
|
50
|
-
);
|
|
51
41
|
|
|
52
42
|
/** The six tools gated on `working` — the ones that refuse until something has
|
|
53
43
|
* been logged. Listed here so ADDING an ungated mutating tool is a deliberate
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
|
2
|
-
|
|
3
1
|
// An EVAL: does the FAQ bot actually look things up?
|
|
4
2
|
//
|
|
5
3
|
// `agent.test.ts` scores the search index directly, which settles what
|
|
@@ -12,16 +10,6 @@
|
|
|
12
10
|
// SCRIPTED model (its `stubReply`) — the real session and the real tools, a
|
|
13
11
|
// fake reply — which proves the wiring and says nothing about the discipline.
|
|
14
12
|
|
|
15
|
-
import { withSystemPrompt } from "@alexkroman1/aai/manifest";
|
|
16
|
-
import { withDiscoveredTools } from "@alexkroman1/aai/testing";
|
|
17
|
-
import { toolResultIn } from "@alexkroman1/aai-runtime/eval";
|
|
18
|
-
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
19
|
-
import { expect } from "vitest";
|
|
20
|
-
import { z } from "zod";
|
|
21
|
-
import authoredAgent from "./agent.ts";
|
|
22
|
-
import { faqs } from "./shared.ts";
|
|
23
|
-
import systemPrompt from "./system-prompt.md?raw";
|
|
24
|
-
|
|
25
13
|
/**
|
|
26
14
|
* The def a DEPLOYED agent runs: authored, plus what `tools/` declares.
|
|
27
15
|
*
|
|
@@ -36,10 +24,12 @@ import systemPrompt from "./system-prompt.md?raw";
|
|
|
36
24
|
* drives it measures a different agent than the one that deploys, and every
|
|
37
25
|
* tool-choice claim below then passes or fails for the wrong reason.
|
|
38
26
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
import agentDef from "virtual:aai/agent";
|
|
28
|
+
import { toolResultIn } from "@alexkroman1/aai-runtime/eval";
|
|
29
|
+
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
30
|
+
import { expect } from "vitest";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { faqs } from "./shared.ts";
|
|
43
33
|
|
|
44
34
|
/**
|
|
45
35
|
* A knowledge-base HIT, as the model saw it — `tool.completed` carries it
|
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { toolRunner
|
|
1
|
+
/** The def a DEPLOYED agent runs: authored, plus what `tools/` declares. */
|
|
2
|
+
import agentDef from "virtual:aai/agent";
|
|
3
|
+
import { toolRunner } from "@alexkroman1/aai/testing";
|
|
4
4
|
import { describe, expect, test } from "vitest";
|
|
5
|
-
import authoredAgent from "./agent.ts";
|
|
6
5
|
import { faqs, searchable } from "./shared.ts";
|
|
7
6
|
|
|
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 — a scaffolded project has no `../../_discovery.ts` to import,
|
|
13
|
-
* and `import.meta.glob` is expanded against the file containing it either way.
|
|
14
|
-
*/
|
|
15
|
-
const agentDef = withDiscoveredTools(
|
|
16
|
-
authoredAgent,
|
|
17
|
-
import.meta.glob("./tools/*.ts", { eager: true }),
|
|
18
|
-
);
|
|
19
|
-
|
|
20
7
|
/**
|
|
21
8
|
* Both tools here are stateless, so no call passes a context: `runTool` builds
|
|
22
9
|
* a fresh one, which is a distinct session with empty slots — right for a tool
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
|
2
|
-
|
|
3
1
|
// An EVAL: does the desk read the label, or does it remember?
|
|
4
2
|
//
|
|
5
3
|
// `agent.test.ts` drives both tools against a faked openFDA, which settles the
|
|
@@ -21,15 +19,6 @@
|
|
|
21
19
|
// unreachable openFDA reads as a drug that could not be resolved, which this
|
|
22
20
|
// template already refuses on).
|
|
23
21
|
|
|
24
|
-
import { withSystemPrompt } from "@alexkroman1/aai/manifest";
|
|
25
|
-
import { withDiscoveredTools } from "@alexkroman1/aai/testing";
|
|
26
|
-
import { 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 systemPrompt from "./system-prompt.md?raw";
|
|
32
|
-
|
|
33
22
|
/**
|
|
34
23
|
* The def a DEPLOYED agent runs: authored, plus what `tools/` declares.
|
|
35
24
|
*
|
|
@@ -44,10 +33,11 @@ import systemPrompt from "./system-prompt.md?raw";
|
|
|
44
33
|
* drives it measures a different agent than the one that deploys, and every
|
|
45
34
|
* tool-choice claim below then passes or fails for the wrong reason.
|
|
46
35
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
import agentDef from "virtual:aai/agent";
|
|
37
|
+
import { toolResultIn } from "@alexkroman1/aai-runtime/eval";
|
|
38
|
+
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
39
|
+
import { expect } from "vitest";
|
|
40
|
+
import { z } from "zod";
|
|
51
41
|
|
|
52
42
|
/** The drugs an interaction check was actually asked about, lowercased. */
|
|
53
43
|
const drugsIn = (args: Record<string, unknown>): string[] =>
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
|
2
|
-
|
|
3
1
|
import { isToolFailure } from "@alexkroman1/aai";
|
|
4
|
-
import { toolInputIssues, toolRunner
|
|
2
|
+
import { toolInputIssues, toolRunner } from "@alexkroman1/aai/testing";
|
|
5
3
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
6
|
-
import authoredAgent from "./agent.ts";
|
|
7
4
|
import { excerptAround, type FdaLabel, toDrugInfo } from "./fda.ts";
|
|
8
5
|
|
|
9
6
|
/**
|
|
@@ -24,16 +21,8 @@ vi.mock("./fda.ts", async (importOriginal) => ({
|
|
|
24
21
|
const { fetchFdaLabel } = await import("./fda.ts");
|
|
25
22
|
const label = vi.mocked(fetchFdaLabel);
|
|
26
23
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
* The glob is written HERE rather than reached for from a shared helper because
|
|
31
|
-
* this file SHIPS — a scaffolded project has no repo helper to import.
|
|
32
|
-
*/
|
|
33
|
-
const agentDef = withDiscoveredTools(
|
|
34
|
-
authoredAgent,
|
|
35
|
-
import.meta.glob("./tools/*.ts", { eager: true }),
|
|
36
|
-
);
|
|
24
|
+
/** The def a DEPLOYED agent runs: authored, plus what `tools/` declares. */
|
|
25
|
+
import agentDef from "virtual:aai/agent";
|
|
37
26
|
|
|
38
27
|
/**
|
|
39
28
|
* Every tool here takes arguments and none of them touches session state, so no
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|
|
2
|
-
|
|
3
1
|
// An EVAL: does the game engine actually keep its world? Run it with `aai eval`.
|
|
4
2
|
//
|
|
5
3
|
// `agent.test.ts` drives the tools directly, one call at a time, against a
|
|
@@ -21,16 +19,6 @@
|
|
|
21
19
|
// What no eval here can see: anything below the audio boundary — endpointing,
|
|
22
20
|
// barge-in, two commands merging into one turn. Those need real paced audio.
|
|
23
21
|
|
|
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
22
|
/**
|
|
35
23
|
* The def a DEPLOYED agent runs, assembled the way the build assembles it: the
|
|
36
24
|
* authored export, plus what `tools/` declares, plus `system-prompt.md`.
|
|
@@ -41,10 +29,12 @@ import systemPrompt from "./system-prompt.md?raw";
|
|
|
41
29
|
* `game_state_take` — an eval run against the framework default prompt measures
|
|
42
30
|
* an agent nobody deployed.
|
|
43
31
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
import agentDef from "virtual:aai/agent";
|
|
33
|
+
import { type EvalTurn, toolResultIn } from "@alexkroman1/aai-runtime/eval";
|
|
34
|
+
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
35
|
+
import { expect } from "vitest";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
import { DEFAULT_GAME_STATE } from "./shared.ts";
|
|
48
38
|
|
|
49
39
|
/**
|
|
50
40
|
* What each of the three tools this file drives answers, off the wire.
|
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { createToolContext, toolRunner
|
|
1
|
+
/** The def a DEPLOYED agent runs: authored, plus what `tools/` declares. */
|
|
2
|
+
import agentDef from "virtual:aai/agent";
|
|
3
|
+
import { createToolContext, toolRunner } from "@alexkroman1/aai/testing";
|
|
4
4
|
import { describe, expect, test } from "vitest";
|
|
5
|
-
import authoredAgent from "./agent.ts";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The def a DEPLOYED agent runs: authored, plus what `tools/` declares.
|
|
9
|
-
*
|
|
10
|
-
* The glob is written HERE rather than reached for from a shared helper because
|
|
11
|
-
* this file SHIPS: it is what a scaffolded project runs, so it may not import
|
|
12
|
-
* anything outside its own template, and `import.meta.glob` is expanded against
|
|
13
|
-
* the file containing it either way. This is the pattern a user writes.
|
|
14
|
-
*/
|
|
15
|
-
const agentDef = withDiscoveredTools(
|
|
16
|
-
authoredAgent,
|
|
17
|
-
import.meta.glob("./tools/*.ts", { eager: true }),
|
|
18
|
-
);
|
|
19
5
|
|
|
20
6
|
import { DEFAULT_GAME_STATE, gameSlot, MAX_HISTORY, REPORTED_HISTORY } from "./shared.ts";
|
|
21
7
|
|
|
@@ -28,20 +28,6 @@ import agentDef, { digest } from "./agent.ts";
|
|
|
28
28
|
import { extractText, extractTitle, fetchArticle, summarize } from "./workflows/digest.ts";
|
|
29
29
|
|
|
30
30
|
describe("the agent declares itself a workflow app", () => {
|
|
31
|
-
test("its front door is a page, not a microphone", () => {
|
|
32
|
-
// Not decoration: `createServer` declines `/websocket` with a reason for a
|
|
33
|
-
// static agent, and telephony defaults off.
|
|
34
|
-
expect(agentDef.page).toBe("static");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("it declares no voice pipeline and no tools, because nothing talks", () => {
|
|
38
|
-
expect(agentDef.stt).toBeUndefined();
|
|
39
|
-
expect(agentDef.llm).toBeUndefined();
|
|
40
|
-
expect(agentDef.tts).toBeUndefined();
|
|
41
|
-
expect(agentDef.s2s).toBeUndefined();
|
|
42
|
-
expect(agentDef.tools).toEqual({});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
31
|
test("under the name the page starts a run by", () => {
|
|
46
32
|
// `api.start("digest", …)` in client.tsx names this key. Nothing else
|
|
47
33
|
// records it, so a rename here is a 400 there rather than a compile error.
|
|
@@ -44,11 +44,15 @@
|
|
|
44
44
|
* doc for the one thing that changes under `aai dev` (the key has to be in
|
|
45
45
|
* `.env`, not just your shell).
|
|
46
46
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
47
|
+
* Runs are DURABLE on the platform with nothing to configure — a deployed app's
|
|
48
|
+
* runs live on the platform's own database and survive a restart, a redeploy and
|
|
49
|
+
* an idle sandbox.
|
|
50
|
+
*
|
|
51
|
+
* A `DATABASE_URL` (a secret when deployed, `.env` under `aai dev`) still buys
|
|
52
|
+
* one thing here: the correlation-key index, which is what lets `find()` resolve
|
|
53
|
+
* a run by key. Without one that index is in memory, so it is forgotten on a
|
|
54
|
+
* restart even though the runs themselves are not. Under `aai dev` with no
|
|
55
|
+
* `DATABASE_URL` the runs go too — fine while you are building.
|
|
52
56
|
*/
|
|
53
57
|
|
|
54
58
|
import { workflow, workflowApp } from "@alexkroman1/aai";
|
|
@@ -42,13 +42,9 @@ import "@alexkroman1/aai-ui/styles.css";
|
|
|
42
42
|
// ERASED at build time, so naming the agent's own type costs the browser bundle
|
|
43
43
|
// nothing — and it is what stops this file restating a shape `workflows/
|
|
44
44
|
// digest.ts` already declares.
|
|
45
|
-
import type { WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
|
|
46
45
|
import { useState } from "react";
|
|
47
46
|
import type { digest } from "./agent.ts";
|
|
48
47
|
|
|
49
|
-
/** What a completed run reports, derived from the workflow rather than restated. */
|
|
50
|
-
type Digest = WorkflowOutputOf<typeof digest>;
|
|
51
|
-
|
|
52
48
|
export function App() {
|
|
53
49
|
const [url, setUrl] = useState("");
|
|
54
50
|
// The generic is what makes `run.status === "completed"` narrow to a TYPED
|
|
@@ -56,7 +52,7 @@ export function App() {
|
|
|
56
52
|
// rejected input, which is better copy than anything this page could write, and
|
|
57
53
|
// `wake` is bound to whatever run the hook is following — the whole reason this
|
|
58
54
|
// page no longer holds a `createWorkflowApi()` of its own.
|
|
59
|
-
const { submit, run, pending, error, wake } = useWorkflowSubmit<
|
|
55
|
+
const { submit, run, pending, error, wake } = useWorkflowSubmit<typeof digest>("digest");
|
|
60
56
|
|
|
61
57
|
// `submit()` resolves as soon as the run exists — deliberately not when it
|
|
62
58
|
// finishes. That is the whole mechanism: the digest sleeps for a while, and
|
|
@@ -76,7 +72,13 @@ export function App() {
|
|
|
76
72
|
required
|
|
77
73
|
value={url}
|
|
78
74
|
onChange={(e) => setUrl(e.target.value)}
|
|
79
|
-
placeholder
|
|
75
|
+
// A REAL article, because a placeholder is a suggestion and this one
|
|
76
|
+
// gets typed. `https://example.com/article` 404s, and the bare
|
|
77
|
+
// `example.com` a reader trims it to has no prose on it — so the first
|
|
78
|
+
// run of the template failed with the agent's own
|
|
79
|
+
// "returned no readable text — is the page rendered in JS?", which
|
|
80
|
+
// reads as the template being broken rather than the URL being empty.
|
|
81
|
+
placeholder="https://en.wikipedia.org/wiki/Speech_recognition"
|
|
80
82
|
className="flex-1 rounded-md border px-3 py-2"
|
|
81
83
|
/>
|
|
82
84
|
<button type="submit" disabled={pending} className="rounded-md border px-4 py-2">
|
|
@@ -12,16 +12,11 @@
|
|
|
12
12
|
// different agent; and `run_code` refuses unless the EVAL supplies an executor,
|
|
13
13
|
// which this suite does — so the cases below assert the answer the code came
|
|
14
14
|
// back with as well as the code the tutor wrote.
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import type { RunCodeExecutor } from "@alexkroman1/aai-runtime/eval";
|
|
15
|
+
|
|
16
|
+
import agentDef from "virtual:aai/agent";
|
|
17
|
+
import { createVmRunCode, toolResultsIn } from "@alexkroman1/aai-runtime/eval";
|
|
19
18
|
import { describeEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
20
19
|
import { expect } from "vitest";
|
|
21
|
-
import authored from "./agent.ts";
|
|
22
|
-
import systemPrompt from "./system-prompt.md?raw";
|
|
23
|
-
|
|
24
|
-
const agentDef = withSystemPrompt(authored, systemPrompt);
|
|
25
20
|
|
|
26
21
|
/** The code every `run_code` call in this turn carried, joined. */
|
|
27
22
|
const codeIn = (turn: { toolCalls: readonly { name: string; args: Record<string, unknown> }[] }) =>
|
|
@@ -35,37 +30,13 @@ const codeIn = (turn: { toolCalls: readonly { name: string; args: Record<string,
|
|
|
35
30
|
*
|
|
36
31
|
* The builtin refuses without one — the Modal container is the security
|
|
37
32
|
* boundary, and off-platform there is none — so a case could assert the CALL and
|
|
38
|
-
* the code it carried, and never what the code came back with.
|
|
39
|
-
* context with a capturing `console.log
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* A template eval imports from `@alexkroman1/aai-runtime/eval` and
|
|
45
|
-
* `/eval/vitest` and nowhere else — the ROOT barrel drags the host runtime's
|
|
46
|
-
* node-reaching module graph into this project's TypeScript program, which is
|
|
47
|
-
* three errors in runtime files no eval ever calls. `RunCodeExecutor` is
|
|
48
|
-
* re-exported from `/eval` for exactly that reason.
|
|
33
|
+
* the code it carried, and never what the code came back with.
|
|
34
|
+
* `createVmRunCode()` is a `node:vm` context with a capturing `console.log`,
|
|
35
|
+
* which is enough here: what runs is arithmetic, not a program. It is NOT a
|
|
36
|
+
* sandbox and does not pretend to be one; a deployed agent still gets the
|
|
37
|
+
* refusal.
|
|
49
38
|
*/
|
|
50
|
-
const runCode
|
|
51
|
-
const lines: string[] = [];
|
|
52
|
-
const log = (...args: unknown[]): void => {
|
|
53
|
-
lines.push(args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" "));
|
|
54
|
-
};
|
|
55
|
-
try {
|
|
56
|
-
runInNewContext(code, { console: { log } }, { timeout: 1000 });
|
|
57
|
-
} catch (err) {
|
|
58
|
-
return { error: errorMessage(err) };
|
|
59
|
-
}
|
|
60
|
-
return lines.join("\n");
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
/** What every `run_code` call in this turn PRINTED, joined. */
|
|
64
|
-
const outputIn = (turn: { toolCalls: readonly { name: string; result?: string }[] }) =>
|
|
65
|
-
turn.toolCalls
|
|
66
|
-
.filter((c) => c.name === "run_code")
|
|
67
|
-
.map((c) => c.result ?? "")
|
|
68
|
-
.join("\n");
|
|
39
|
+
const runCode = createVmRunCode();
|
|
69
40
|
|
|
70
41
|
describeEval(
|
|
71
42
|
agentDef,
|
|
@@ -84,7 +55,7 @@ describeEval(
|
|
|
84
55
|
expect(code).toMatch(/1\.60|1\.61|0\.621|8\.04/);
|
|
85
56
|
// And the factor was applied rather than merely mentioned: five miles is
|
|
86
57
|
// 8.0467 km, so whatever rounding the tutor chose the answer starts 8.0.
|
|
87
|
-
const output =
|
|
58
|
+
const output = toolResultsIn(turn.toolCalls, "run_code").join("\n");
|
|
88
59
|
expect(output, `run_code printed: ${output}`).toMatch(/8\.0/);
|
|
89
60
|
},
|
|
90
61
|
{ live: true },
|
|
@@ -108,7 +79,7 @@ describeEval(
|
|
|
108
79
|
// face of a twenty-sided die. `Math.random` in the code says the tutor
|
|
109
80
|
// asked for a roll; this says it GOT one — a `run_code` that refused
|
|
110
81
|
// prints a sentence with no dice in it at all.
|
|
111
|
-
const output =
|
|
82
|
+
const output = toolResultsIn(turn.toolCalls, "run_code").join("\n");
|
|
112
83
|
const rolled = [...output.matchAll(/\d+/g)].map((m) => Number(m[0]));
|
|
113
84
|
expect(rolled.length, `run_code printed: ${output}`).toBeGreaterThanOrEqual(3);
|
|
114
85
|
for (const face of rolled) {
|