anpord 0.1.3 → 0.1.5
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 +69 -4
- package/dist/cli.cjs +274 -19
- package/dist/cli.mjs +275 -20
- package/dist/client-BCZEcfVE.cjs +759 -0
- package/dist/client-BfhmGRQl.mjs +718 -0
- package/dist/compiler-CKiewwUc.mjs +188 -0
- package/dist/compiler-CZoX4jkx.cjs +198 -0
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +1 -1
- package/dist/{errors-C0E5A8fN.cjs → errors-Boum34f5.cjs} +30 -0
- package/dist/{errors-DEfS6oaQ.mjs → errors-C9bQUcA3.mjs} +25 -1
- package/dist/eval.cjs +4 -0
- package/dist/eval.d.cts +25 -0
- package/dist/eval.d.mts +25 -0
- package/dist/eval.mjs +2 -0
- package/dist/evals-api-DUNVUGUy.d.mts +93 -0
- package/dist/evals-api-sqVNeWet.d.cts +93 -0
- package/dist/index.cjs +428 -8
- package/dist/index.d.cts +2372 -195
- package/dist/index.d.mts +2372 -195
- package/dist/index.mjs +424 -10
- package/dist/source-DQ8HChlm.d.cts +478 -0
- package/dist/source-DQ8HChlm.d.mts +478 -0
- package/dist/source-DvlGawEw.cjs +76 -0
- package/dist/source-Fg1h2VNn.mjs +47 -0
- package/dist/source.cjs +5 -0
- package/dist/source.d.cts +2 -0
- package/dist/source.d.mts +2 -0
- package/dist/source.mjs +2 -0
- package/package.json +17 -2
- package/dist/client-Cno4LwRq.cjs +0 -277
- package/dist/client-bJBNwTKq.mjs +0 -236
package/README.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# anpord
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for [Anpord](https://anpord.com)
|
|
4
|
-
|
|
5
|
-
> **Placeholder release.** This version reserves the package name. There is no
|
|
6
|
-
> client yet — the API surface lands in a future release. Watch this space.
|
|
3
|
+
TypeScript SDK for [Anpord](https://anpord.com). Run coding agent evals across harnesses, models, and sandboxes, and manage versioned prompts from the same client.
|
|
7
4
|
|
|
8
5
|
## Install
|
|
9
6
|
|
|
@@ -11,6 +8,74 @@ TypeScript SDK for [Anpord](https://anpord.com) — customer configuration for A
|
|
|
11
8
|
npm install anpord
|
|
12
9
|
```
|
|
13
10
|
|
|
11
|
+
## Run an eval
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Anpord } from "anpord";
|
|
15
|
+
|
|
16
|
+
const anpord = new Anpord({ apiKey: process.env.ANPORD_API_KEY });
|
|
17
|
+
|
|
18
|
+
const run = await anpord.evals.startAndWait({
|
|
19
|
+
cases: [
|
|
20
|
+
{
|
|
21
|
+
name: "writes the requested file",
|
|
22
|
+
variables: { task: "Create hello.txt containing exactly hello" },
|
|
23
|
+
verify: "test \"$(cat hello.txt)\" = hello",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
prompt: "{{task}}",
|
|
27
|
+
tasks: [{ harness: "codex", model: "gpt-5.6-sol", provider: "daytona" }],
|
|
28
|
+
trials: 3,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
for (const cell of run.cells) {
|
|
32
|
+
console.log(cell.caseName, cell.distribution?.passRate);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For a TypeScript validator, export the function from its own file:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import type { Validator } from "anpord";
|
|
40
|
+
|
|
41
|
+
export const hasGreeting: Validator = async ({ readText }) =>
|
|
42
|
+
(await readText("hello.txt")) === "hello";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Reference it directly from `anpord.eval.ts`:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { defineEval } from "anpord";
|
|
49
|
+
import { hasGreeting } from "./validators/greeting";
|
|
50
|
+
|
|
51
|
+
export default defineEval({
|
|
52
|
+
name: "greeting",
|
|
53
|
+
cases: [
|
|
54
|
+
{
|
|
55
|
+
name: "greeting",
|
|
56
|
+
variables: { task: "Write hello.txt" },
|
|
57
|
+
validate: hasGreeting,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
prompt: "{{task}}",
|
|
61
|
+
tasks: [{ harness: "codex", model: "gpt-5.6-sol", provider: "daytona" }],
|
|
62
|
+
trials: 3,
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npx anpord eval
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Resolve a prompt
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const prompt = await anpord.prompts.get({ id: "support-reply" });
|
|
74
|
+
console.log(prompt.content, prompt.version);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
See the [documentation](https://docs.anpord.com) for eval concepts, prompt releases, SDK methods, and the API reference.
|
|
78
|
+
|
|
14
79
|
## License
|
|
15
80
|
|
|
16
81
|
MIT
|
package/dist/cli.cjs
CHANGED
|
@@ -1,20 +1,200 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_client = require("./client-
|
|
2
|
+
const require_client = require("./client-BCZEcfVE.cjs");
|
|
3
3
|
const require_config = require("./config.cjs");
|
|
4
|
-
const require_errors = require("./errors-
|
|
4
|
+
const require_errors = require("./errors-Boum34f5.cjs");
|
|
5
|
+
const require_compiler = require("./compiler-CZoX4jkx.cjs");
|
|
5
6
|
let _effect_platform = require("@effect/platform");
|
|
6
7
|
let effect = require("effect");
|
|
7
8
|
let _effect_cli = require("@effect/cli");
|
|
8
9
|
let _effect_platform_node = require("@effect/platform-node");
|
|
10
|
+
//#region package.json
|
|
11
|
+
var version$1 = "0.1.5";
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region ../template/src/extract.ts
|
|
14
|
+
/**
|
|
15
|
+
* Distinct variable names in source order, so callers can count or list them.
|
|
16
|
+
*
|
|
17
|
+
* Escaped braces are read here too, so a name the renderer will treat as
|
|
18
|
+
* literal text is never reported as a variable the editor should draw.
|
|
19
|
+
*/
|
|
20
|
+
function extractVariables(template) {
|
|
21
|
+
const names = [];
|
|
22
|
+
for (const [, open, close, name] of template.matchAll(require_errors.tokenMatcher())) if (open === void 0 && close === void 0 && name !== void 0) names.push(name);
|
|
23
|
+
return [...new Set(names)];
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/cli/declarations.ts
|
|
27
|
+
/**
|
|
28
|
+
* The generated file has to open by importing the module it augments.
|
|
29
|
+
*
|
|
30
|
+
* Without it `declare module` shadows the module rather than adding to it, and
|
|
31
|
+
* every import of the SDK stops resolving with an error naming the missing
|
|
32
|
+
* export rather than the file that hid it.
|
|
33
|
+
*/
|
|
34
|
+
const PREAMBLE = [
|
|
35
|
+
"// Generated by `anpord generate`. Do not edit.",
|
|
36
|
+
"// Run it again after changing which variables a prompt uses.",
|
|
37
|
+
"import \"anpord\";",
|
|
38
|
+
"",
|
|
39
|
+
"declare module \"anpord\" {",
|
|
40
|
+
" interface AnpordPromptVariables {"
|
|
41
|
+
].join("\n");
|
|
42
|
+
/** Quoted, because an id may carry characters an identifier cannot. */
|
|
43
|
+
const entry = (id, names) => {
|
|
44
|
+
return ` "${id}": ${names.length === 0 ? "Record<string, never>" : `{ ${names.map((name) => `"${name}": string`).join("; ")} }`};`;
|
|
45
|
+
};
|
|
46
|
+
const declarationFile = (prompts) => [
|
|
47
|
+
PREAMBLE,
|
|
48
|
+
...[...prompts].sort(([left], [right]) => left.localeCompare(right)).map(([id, names]) => entry(id, names)),
|
|
49
|
+
" }",
|
|
50
|
+
"}",
|
|
51
|
+
""
|
|
52
|
+
].join("\n");
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/cli/eval-files.ts
|
|
55
|
+
const SUFFIX = ".eval.ts";
|
|
56
|
+
const SKIPPED = /* @__PURE__ */ new Set([
|
|
57
|
+
"node_modules",
|
|
58
|
+
"dist",
|
|
59
|
+
"build",
|
|
60
|
+
".git"
|
|
61
|
+
]);
|
|
62
|
+
const hidden = (segment) => segment.startsWith(".");
|
|
63
|
+
const wanted = (path) => {
|
|
64
|
+
const segments = path.split("/");
|
|
65
|
+
return path.endsWith(SUFFIX) && segments.every((segment) => !(SKIPPED.has(segment) || hidden(segment)));
|
|
66
|
+
};
|
|
67
|
+
const evalFilesIn = (directory) => effect.Effect.gen(function* () {
|
|
68
|
+
return (yield* (yield* _effect_platform.FileSystem.FileSystem).readDirectory(directory, { recursive: true })).filter(wanted).sort();
|
|
69
|
+
}).pipe(effect.Effect.withSpan("Cli.evalFilesIn"));
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/cli/eval-gate.ts
|
|
72
|
+
const regressions = (run) => run.cells.filter((cell) => cell.comparison?.verdict === "regressed");
|
|
73
|
+
const unscored = (run) => run.cells.filter((cell) => (cell.distribution?.scored ?? 0) === 0);
|
|
74
|
+
const problemsWith = (run, failOn) => {
|
|
75
|
+
if (run.status === "failed") return [run.failure ?? "The run failed."];
|
|
76
|
+
if (failOn === "never") return [];
|
|
77
|
+
const found = regressions(run).map((cell) => `${cell.caseName} regressed against its baseline.`);
|
|
78
|
+
return failOn === "unscored" ? [...found, ...unscored(run).map((cell) => `${cell.caseName} produced no scored trials.`)] : found;
|
|
79
|
+
};
|
|
80
|
+
const failWhen = (problems) => problems.length === 0 ? effect.Effect.void : effect.Effect.fail(new EvalGateFailed({ problems }));
|
|
81
|
+
var EvalGateFailed = class extends effect.Data.TaggedError("EvalGateFailed") {
|
|
82
|
+
get message() {
|
|
83
|
+
return this.problems.join("\n");
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var NoEvalFiles = class extends effect.Data.TaggedError("NoEvalFiles") {
|
|
87
|
+
get message() {
|
|
88
|
+
return "No *.eval.ts file here. Name one, or pass a file to run.";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
9
92
|
//#region src/cli/render.ts
|
|
10
93
|
const json = (value) => effect.Console.log(JSON.stringify(value, null, 2));
|
|
11
94
|
const promptContent = (prompt) => effect.Effect.sync(() => {
|
|
12
95
|
process.stdout.write(prompt.content);
|
|
13
96
|
if (!prompt.content.endsWith("\n")) process.stdout.write("\n");
|
|
14
97
|
});
|
|
98
|
+
/** A result the caller may pipe into another tool, so it belongs on stdout
|
|
99
|
+
* alongside the prompt content rather than beside the status messages. */
|
|
100
|
+
const row = (line) => effect.Effect.sync(() => {
|
|
101
|
+
process.stdout.write(`${line}\n`);
|
|
102
|
+
});
|
|
15
103
|
const note = (message) => effect.Effect.sync(() => {
|
|
16
104
|
process.stderr.write(`${message}\n`);
|
|
17
105
|
});
|
|
106
|
+
const attended = effect.Effect.sync(() => globalThis.process?.stdout?.isTTY === true);
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/cli/eval-grid.ts
|
|
109
|
+
const DIM = "\x1B[2m";
|
|
110
|
+
const BOLD = "\x1B[1m";
|
|
111
|
+
const GREEN = "\x1B[32m";
|
|
112
|
+
const RED = "\x1B[31m";
|
|
113
|
+
const YELLOW = "\x1B[33m";
|
|
114
|
+
const RESET = "\x1B[0m";
|
|
115
|
+
const DONE = "●";
|
|
116
|
+
const RUNNING = "◐";
|
|
117
|
+
const FILLED = "▰";
|
|
118
|
+
const HOLLOW = "▱";
|
|
119
|
+
const PERCENT = 100;
|
|
120
|
+
const SECONDS = 1e3;
|
|
121
|
+
const MINUTE = 60;
|
|
122
|
+
const paint = (colour, text) => `${colour}${text}${RESET}`;
|
|
123
|
+
const elapsedOf = (ms) => {
|
|
124
|
+
const total = Math.floor(ms / SECONDS);
|
|
125
|
+
const minutes = Math.floor(total / MINUTE);
|
|
126
|
+
return minutes === 0 ? `${total}s` : `${minutes}m${String(total % MINUTE).padStart(2, "0")}s`;
|
|
127
|
+
};
|
|
128
|
+
const markOf = (cell) => {
|
|
129
|
+
if (cell.status === "finished") return paint(GREEN, DONE);
|
|
130
|
+
return cell.status === "failed" ? paint(RED, DONE) : paint(YELLOW, RUNNING);
|
|
131
|
+
};
|
|
132
|
+
const trialsOf = (cell, trials) => {
|
|
133
|
+
const settled = cell.trials.filter((trial) => trial.status !== "queued" && trial.status !== "running").length;
|
|
134
|
+
return `${FILLED.repeat(settled)}${paint(DIM, HOLLOW.repeat(Math.max(0, trials - settled)))}`;
|
|
135
|
+
};
|
|
136
|
+
const rateOf = (cell) => {
|
|
137
|
+
const rate = cell.distribution?.passRate;
|
|
138
|
+
if (rate === void 0 || cell.distribution?.scored === 0) return paint(DIM, "—");
|
|
139
|
+
const shown = `${Math.round(rate * PERCENT)}%`;
|
|
140
|
+
return paint(rate === 1 ? GREEN : RED, shown);
|
|
141
|
+
};
|
|
142
|
+
const variantOf = (run, cell) => {
|
|
143
|
+
const task = run.tasks[cell.taskIndex];
|
|
144
|
+
return task === void 0 ? "?" : `${task.harness}/${task.model}`;
|
|
145
|
+
};
|
|
146
|
+
const widest = (run) => run.cells.reduce((width, cell) => Math.max(width, variantOf(run, cell).length), 0);
|
|
147
|
+
const gridOf = (run, trials, elapsedMs) => {
|
|
148
|
+
const width = widest(run);
|
|
149
|
+
const lines = [];
|
|
150
|
+
for (const caseName of run.cases) {
|
|
151
|
+
lines.push(` ${BOLD}${caseName}${RESET}`);
|
|
152
|
+
for (const cell of run.cells.filter((one) => one.caseName === caseName)) lines.push(` ${markOf(cell)} ${variantOf(run, cell).padEnd(width)} ${trialsOf(cell, trials)} ${rateOf(cell)}`);
|
|
153
|
+
lines.push("");
|
|
154
|
+
}
|
|
155
|
+
lines.push(paint(DIM, ` ${elapsedOf(elapsedMs)} elapsed`));
|
|
156
|
+
return lines;
|
|
157
|
+
};
|
|
158
|
+
const up = (rows) => `[${rows}A[0J`;
|
|
159
|
+
const liveGrid = (trials, interactive) => effect.Effect.gen(function* () {
|
|
160
|
+
const drawn = yield* effect.Ref.make(0);
|
|
161
|
+
return (run, elapsedMs) => effect.Effect.gen(function* () {
|
|
162
|
+
if (!interactive) return;
|
|
163
|
+
const rows = yield* effect.Ref.getAndSet(drawn, 0);
|
|
164
|
+
const lines = gridOf(run, trials, elapsedMs);
|
|
165
|
+
yield* note(`${rows === 0 ? "" : up(rows)}${lines.join("\n")}`);
|
|
166
|
+
yield* effect.Ref.set(drawn, lines.length);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
const summaryOf = (run, trials, drawn) => drawn ? "" : gridOf(run, trials, 0).join("\n");
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/cli/eval-run.ts
|
|
172
|
+
const FIRST_POLL = 2e3;
|
|
173
|
+
const SLOWEST_POLL = 1e4;
|
|
174
|
+
const WIDENING = 1.5;
|
|
175
|
+
const running = ({ run }) => run === null || run.status === "running";
|
|
176
|
+
const widen = (gap) => Math.min(Math.round(gap * WIDENING), SLOWEST_POLL);
|
|
177
|
+
const waitForRun = (id, onProgress) => effect.Effect.gen(function* () {
|
|
178
|
+
const api = yield* require_client.AnpordApi;
|
|
179
|
+
const startedAt = yield* effect.Clock.currentTimeMillis;
|
|
180
|
+
const step = ({ gap, run }) => effect.Effect.gen(function* () {
|
|
181
|
+
if (run !== null) yield* effect.Effect.sleep(effect.Duration.millis(gap));
|
|
182
|
+
const next = yield* api.evals.get({ payload: { id } });
|
|
183
|
+
yield* onProgress(next, (yield* effect.Clock.currentTimeMillis) - startedAt);
|
|
184
|
+
return {
|
|
185
|
+
gap: widen(gap),
|
|
186
|
+
run: next
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
const { run } = yield* effect.Effect.iterate({
|
|
190
|
+
gap: FIRST_POLL,
|
|
191
|
+
run: null
|
|
192
|
+
}, {
|
|
193
|
+
body: step,
|
|
194
|
+
while: running
|
|
195
|
+
});
|
|
196
|
+
return run ?? (yield* api.evals.get({ payload: { id } }));
|
|
197
|
+
}).pipe(effect.Effect.withSpan("Cli.waitForRun", { attributes: { runId: id } }));
|
|
18
198
|
//#endregion
|
|
19
199
|
//#region src/cli/commands.ts
|
|
20
200
|
const promptId = _effect_cli.Args.text({ name: "id" }).pipe(_effect_cli.Args.withDescription("The prompt's id, such as support-reply"), _effect_cli.Args.withSchema(require_client.PromptId));
|
|
@@ -38,7 +218,7 @@ const get = _effect_cli.Command.make("get", {
|
|
|
38
218
|
const list = _effect_cli.Command.make("list", { asJson }, ({ asJson: wantsJson }) => effect.Effect.gen(function* () {
|
|
39
219
|
const { data } = yield* (yield* require_client.AnpordApi).prompts.list({ payload: {} });
|
|
40
220
|
if (wantsJson) return yield* json(data);
|
|
41
|
-
return yield* effect.Effect.forEach(data, (
|
|
221
|
+
return yield* effect.Effect.forEach(data, (summary) => row(`${summary.id}\tv${summary.latestVersion ?? "-"}\t${summary.name}`));
|
|
42
222
|
})).pipe(_effect_cli.Command.withDescription("List every prompt"));
|
|
43
223
|
const versions = _effect_cli.Command.make("versions", { promptId }, ({ promptId: id }) => effect.Effect.gen(function* () {
|
|
44
224
|
const prompt = yield* (yield* require_client.AnpordApi).prompts.get({ payload: {
|
|
@@ -59,25 +239,100 @@ const promote = _effect_cli.Command.make("promote", {
|
|
|
59
239
|
} });
|
|
60
240
|
return yield* note(`${id} v${pin} is now ${to}`);
|
|
61
241
|
})).pipe(_effect_cli.Command.withDescription("Point a channel at a version"));
|
|
62
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Read from the stream rather than by opening `/dev/stdin` as a file. The path
|
|
244
|
+
* only names the pipe, so reading it races whoever is writing: a body arriving
|
|
245
|
+
* in more than one chunk, which is what a pipe does under load, was read as
|
|
246
|
+
* whatever had landed by then.
|
|
247
|
+
*/
|
|
248
|
+
const readStdin = effect.Effect.async((resume) => {
|
|
249
|
+
let body = "";
|
|
250
|
+
process.stdin.setEncoding("utf8");
|
|
251
|
+
process.stdin.on("data", (chunk) => {
|
|
252
|
+
body += chunk;
|
|
253
|
+
});
|
|
254
|
+
process.stdin.on("end", () => resume(effect.Effect.succeed(body)));
|
|
255
|
+
process.stdin.on("error", (cause) => resume(effect.Effect.fail(new Error("Could not read the content from stdin", { cause }))));
|
|
256
|
+
});
|
|
63
257
|
const target = _effect_cli.Args.all([promptId, _effect_cli.Args.text({ name: "content" }).pipe(_effect_cli.Args.withDescription("The new content, or - to read stdin"))]);
|
|
258
|
+
const push = _effect_cli.Command.make("push", {
|
|
259
|
+
message,
|
|
260
|
+
target
|
|
261
|
+
}, ({ message: why, target: [id, content] }) => effect.Effect.gen(function* () {
|
|
262
|
+
const api = yield* require_client.AnpordApi;
|
|
263
|
+
const body = content === "-" ? yield* readStdin : content;
|
|
264
|
+
const prompt = yield* api.prompts.update({ payload: {
|
|
265
|
+
content: body,
|
|
266
|
+
id,
|
|
267
|
+
message: effect.Option.getOrUndefined(why)
|
|
268
|
+
} });
|
|
269
|
+
return yield* note(`${id} is now v${prompt.version}`);
|
|
270
|
+
})).pipe(_effect_cli.Command.withDescription("Add a version to a prompt"));
|
|
271
|
+
const out = _effect_cli.Options.file("out").pipe(_effect_cli.Options.withDescription("Where to write the declarations"), _effect_cli.Options.withDefault("anpord-env.d.ts"));
|
|
272
|
+
/** Reading one prompt at a time because the list carries no content, bounded
|
|
273
|
+
* so a large organisation does not open a connection per prompt. */
|
|
274
|
+
const READ_AT_ONCE = 8;
|
|
275
|
+
const writeDeclarations = ({ out: path }) => effect.Effect.gen(function* () {
|
|
276
|
+
const api = yield* require_client.AnpordApi;
|
|
277
|
+
const fs = yield* _effect_platform.FileSystem.FileSystem;
|
|
278
|
+
const { data } = yield* api.prompts.list({ payload: {} });
|
|
279
|
+
const prompts = yield* effect.Effect.forEach(data, (summary) => api.prompts.get({ payload: { id: summary.id } }).pipe(effect.Effect.map((prompt) => [prompt.id, extractVariables(prompt.content)])), { concurrency: READ_AT_ONCE });
|
|
280
|
+
yield* fs.writeFileString(path, declarationFile(prompts));
|
|
281
|
+
return yield* note(`Wrote ${prompts.length} ${prompts.length === 1 ? "prompt" : "prompts"} to ${path}`);
|
|
282
|
+
});
|
|
283
|
+
const DESCRIPTION = "Write TypeScript declarations for prompt variables";
|
|
284
|
+
const generate = _effect_cli.Command.make("generate", { out }, writeDeclarations).pipe(_effect_cli.Command.withDescription(DESCRIPTION));
|
|
285
|
+
/** A second command rather than an alias, because a command carries one name
|
|
286
|
+
* and the shorter one is what anybody types twice. */
|
|
287
|
+
const gen = _effect_cli.Command.make("gen", { out }, writeDeclarations).pipe(_effect_cli.Command.withDescription(DESCRIPTION));
|
|
288
|
+
const evalFile = _effect_cli.Args.text({ name: "file" }).pipe(_effect_cli.Args.withDescription("A TypeScript file that default exports defineEval(...); every *.eval.ts is run when omitted"), _effect_cli.Args.optional);
|
|
289
|
+
const noWait = _effect_cli.Options.boolean("no-wait").pipe(_effect_cli.Options.withDescription("Start the run and print its id, without waiting"));
|
|
290
|
+
const failOn = _effect_cli.Options.choice("fail-on", [
|
|
291
|
+
"never",
|
|
292
|
+
"regressed",
|
|
293
|
+
"unscored"
|
|
294
|
+
]).pipe(_effect_cli.Options.withDescription("What makes the command exit nonzero"), _effect_cli.Options.withDefault("regressed"));
|
|
295
|
+
const runOneEval = (file, options) => effect.Effect.gen(function* () {
|
|
296
|
+
const api = yield* require_client.AnpordApi;
|
|
297
|
+
const payload = yield* require_compiler.compileEvalEffect(file);
|
|
298
|
+
const started = yield* api.evals.start({ payload });
|
|
299
|
+
if (options.skipWait) {
|
|
300
|
+
yield* json(started);
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
const live = !options.wantsJson && (yield* attended);
|
|
304
|
+
if (live || options.label) yield* note(`${file} · run ${started.id}`);
|
|
305
|
+
const draw = yield* liveGrid(payload.trials, live);
|
|
306
|
+
const run = yield* waitForRun(started.id, draw);
|
|
307
|
+
yield* options.wantsJson ? json(run) : note(summaryOf(run, payload.trials, live));
|
|
308
|
+
return problemsWith(run, options.gate);
|
|
309
|
+
});
|
|
64
310
|
const commands = [
|
|
311
|
+
_effect_cli.Command.make("eval", {
|
|
312
|
+
asJson,
|
|
313
|
+
evalFile,
|
|
314
|
+
failOn,
|
|
315
|
+
noWait
|
|
316
|
+
}, ({ asJson: wantsJson, evalFile: file, failOn: gate, noWait: skipWait }) => effect.Effect.gen(function* () {
|
|
317
|
+
const files = yield* effect.Option.match(file, {
|
|
318
|
+
onNone: () => evalFilesIn("."),
|
|
319
|
+
onSome: (one) => effect.Effect.succeed([one])
|
|
320
|
+
});
|
|
321
|
+
if (files.length === 0) return yield* effect.Effect.fail(new NoEvalFiles());
|
|
322
|
+
const found = yield* effect.Effect.forEach(files, (one) => runOneEval(one, {
|
|
323
|
+
gate,
|
|
324
|
+
label: files.length > 1,
|
|
325
|
+
skipWait,
|
|
326
|
+
wantsJson
|
|
327
|
+
}));
|
|
328
|
+
return yield* failWhen(found.flat());
|
|
329
|
+
})).pipe(_effect_cli.Command.withDescription("Compile and run an eval from TypeScript")),
|
|
330
|
+
gen,
|
|
331
|
+
generate,
|
|
65
332
|
get,
|
|
66
333
|
list,
|
|
67
334
|
promote,
|
|
68
|
-
|
|
69
|
-
message,
|
|
70
|
-
target
|
|
71
|
-
}, ({ message: why, target: [id, content] }) => effect.Effect.gen(function* () {
|
|
72
|
-
const api = yield* require_client.AnpordApi;
|
|
73
|
-
const body = content === "-" ? yield* readStdin : content;
|
|
74
|
-
const prompt = yield* api.prompts.update({ payload: {
|
|
75
|
-
content: body,
|
|
76
|
-
id,
|
|
77
|
-
message: effect.Option.getOrUndefined(why)
|
|
78
|
-
} });
|
|
79
|
-
return yield* note(`${id} is now v${prompt.version}`);
|
|
80
|
-
})).pipe(_effect_cli.Command.withDescription("Add a version to a prompt")),
|
|
335
|
+
push,
|
|
81
336
|
versions
|
|
82
337
|
];
|
|
83
338
|
//#endregion
|
|
@@ -94,10 +349,10 @@ const reportFailure = (error) => effect.Effect.sync(() => {
|
|
|
94
349
|
});
|
|
95
350
|
//#endregion
|
|
96
351
|
//#region src/cli/main.ts
|
|
97
|
-
const anpord = _effect_cli.Command.make("anpord").pipe(_effect_cli.Command.withDescription("
|
|
352
|
+
const anpord = _effect_cli.Command.make("anpord").pipe(_effect_cli.Command.withDescription("Run evals and manage prompts from the terminal"), _effect_cli.Command.withSubcommands(commands));
|
|
98
353
|
const ClientLayer = effect.Layer.unwrapEffect(effect.Effect.map(require_config.clientOptionsConfig, require_client.layer));
|
|
99
354
|
_effect_cli.Command.run(anpord, {
|
|
100
355
|
name: "Anpord",
|
|
101
|
-
version:
|
|
356
|
+
version: version$1
|
|
102
357
|
})(process.argv).pipe(effect.Effect.provide(effect.Layer.mergeAll(ClientLayer, _effect_platform_node.NodeContext.layer)), effect.Effect.catchAllCause((cause) => effect.Cause.isInterruptedOnly(cause) ? effect.Effect.void : reportFailure(effect.Cause.failureOption(cause).pipe(effect.Option.getOrElse(() => effect.Cause.squash(cause))))), _effect_platform_node.NodeRuntime.runMain({ disableErrorReporting: true }));
|
|
103
358
|
//#endregion
|