@foldrun/core 2.10.1 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/src/catalog.d.ts +3 -0
- package/dist/src/catalog.js +4 -1
- package/dist/src/deploy.js +16 -0
- package/dist/src/eval-compare.d.ts +74 -0
- package/dist/src/eval-compare.js +187 -0
- package/dist/src/evals.d.ts +62 -3
- package/dist/src/evals.js +161 -36
- package/dist/src/flow-lint.js +4 -0
- package/dist/src/frontmatter-fields.js +4 -1
- package/dist/src/git-hook.d.ts +1 -0
- package/dist/src/git-hook.js +70 -0
- package/dist/src/git.d.ts +37 -0
- package/dist/src/git.js +101 -0
- package/dist/src/gitrepo.d.ts +113 -0
- package/dist/src/gitrepo.js +404 -0
- package/dist/src/grades.d.ts +118 -0
- package/dist/src/grades.js +481 -0
- package/dist/src/judge-verdict.d.ts +1 -0
- package/dist/src/judge-verdict.js +3 -1
- package/dist/src/online-checks.d.ts +4 -0
- package/dist/src/online-checks.js +5 -1
- package/dist/src/openapi.d.ts +4 -0
- package/dist/src/openapi.js +5 -1
- package/dist/src/platform.d.ts +12 -3
- package/dist/src/platform.js +9 -1
- package/dist/src/starter.js +5 -0
- package/dist/src/store.d.ts +8 -0
- package/dist/src/store.js +5 -1
- package/dist/src/tar.d.ts +30 -0
- package/dist/src/tar.js +112 -0
- package/dist/src/tool-test.js +19 -7
- package/dist/src/translator.d.ts +4 -0
- package/dist/src/translator.js +22 -3
- package/dist/src/trigger-filter.d.ts +28 -0
- package/dist/src/trigger-filter.js +272 -0
- package/dist/src/trigger-log.d.ts +1 -1
- package/dist/src/triggers.d.ts +2 -0
- package/dist/src/triggers.js +13 -2
- package/dist/src/untrusted-fetch.d.ts +16 -0
- package/dist/src/untrusted-fetch.js +60 -2
- package/index.ts +3 -0
- package/package.json +2 -2
- package/src/catalog.ts +5 -1
- package/src/deploy.ts +10 -0
- package/src/eval-compare.ts +243 -0
- package/src/evals.ts +195 -38
- package/src/flow-lint.ts +4 -0
- package/src/frontmatter-fields.ts +4 -1
- package/src/grades.ts +532 -0
- package/src/judge-verdict.ts +4 -2
- package/src/online-checks.ts +5 -1
- package/src/openapi.ts +6 -1
- package/src/platform.ts +16 -4
- package/src/starter.ts +5 -0
- package/src/store.ts +11 -1
- package/src/tool-test.ts +21 -7
- package/src/translator.ts +24 -3
- package/src/trigger-filter.ts +264 -0
- package/src/trigger-log.ts +1 -0
- package/src/triggers.ts +14 -2
- package/src/untrusted-fetch.ts +65 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ export * from "./src/starter.ts";
|
|
|
6
6
|
export * from "./src/runner.ts";
|
|
7
7
|
export * from "./src/okf.ts";
|
|
8
8
|
export * from "./src/evals.ts";
|
|
9
|
+
export * from "./src/grades.ts";
|
|
10
|
+
export * from "./src/eval-compare.ts";
|
|
9
11
|
export * from "./src/flow-lint.ts";
|
|
12
|
+
export * from "./src/trigger-filter.ts";
|
|
10
13
|
export * from "./src/catalog.ts";
|
|
11
14
|
export * from "./src/model-probe.ts";
|
|
12
15
|
export * from "./src/providers.ts";
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,10 @@ export * from "./src/starter.js";
|
|
|
17
17
|
export * from "./src/runner.js";
|
|
18
18
|
export * from "./src/okf.js";
|
|
19
19
|
export * from "./src/evals.js";
|
|
20
|
+
export * from "./src/grades.js";
|
|
21
|
+
export * from "./src/eval-compare.js";
|
|
20
22
|
export * from "./src/flow-lint.js";
|
|
23
|
+
export * from "./src/trigger-filter.js";
|
|
21
24
|
export * from "./src/catalog.js";
|
|
22
25
|
export * from "./src/model-probe.js";
|
|
23
26
|
export * from "./src/providers.js";
|
package/dist/src/catalog.d.ts
CHANGED
|
@@ -35,6 +35,9 @@ export declare function parseModels(payload: unknown): CatalogModel[];
|
|
|
35
35
|
/** The cache as it stands, however old — null only when nothing was ever
|
|
36
36
|
* fetched. Reading never touches the network. */
|
|
37
37
|
export declare function readCatalog(baseUrl: string): Catalog | null;
|
|
38
|
+
/** The most a gateway's model list may be, read: OpenRouter's is about
|
|
39
|
+
* 800 KB and grows, near fetchUntrusted's 1 MiB default. */
|
|
40
|
+
export declare const CATALOG_MAX_BYTES: number;
|
|
38
41
|
/**
|
|
39
42
|
* The cache, refreshed over the network when it is older than the TTL.
|
|
40
43
|
* A failed fetch keeps the stale copy — offline is a reason to be out of
|
package/dist/src/catalog.js
CHANGED
|
@@ -83,6 +83,9 @@ export function readCatalog(baseUrl) {
|
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
/** The most a gateway's model list may be, read: OpenRouter's is about
|
|
87
|
+
* 800 KB and grows, near fetchUntrusted's 1 MiB default. */
|
|
88
|
+
export const CATALOG_MAX_BYTES = 8 * 1024 * 1024;
|
|
86
89
|
/**
|
|
87
90
|
* The cache, refreshed over the network when it is older than the TTL.
|
|
88
91
|
* A failed fetch keeps the stale copy — offline is a reason to be out of
|
|
@@ -96,7 +99,7 @@ export async function loadCatalog(baseUrl) {
|
|
|
96
99
|
try {
|
|
97
100
|
const res = await fetchUntrusted(`${trimSlashes(baseUrl)}/v1/models`, {
|
|
98
101
|
signal: AbortSignal.timeout(10_000),
|
|
99
|
-
});
|
|
102
|
+
}, { maxBytes: CATALOG_MAX_BYTES });
|
|
100
103
|
if (!res.ok)
|
|
101
104
|
throw new Error(`${res.status}`);
|
|
102
105
|
const models = parseModels(await res.json());
|
package/dist/src/deploy.js
CHANGED
|
@@ -611,6 +611,22 @@ export function deployIssues(files) {
|
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
|
+
// An eval whose settings cannot be read: a `sample:` past 100, a grader
|
|
615
|
+
// (`trigger: runs`) with a task nothing receives or a file:/run: check
|
|
616
|
+
// that would act on the platform's host. Refused where they are written.
|
|
617
|
+
for (const f of files) {
|
|
618
|
+
if (!/^evals\/[^/]+\.md$/.test(f.path))
|
|
619
|
+
continue;
|
|
620
|
+
let info;
|
|
621
|
+
try {
|
|
622
|
+
info = parseEval(f.path, f.content);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
for (const p of info.problems)
|
|
628
|
+
at(f.path, p);
|
|
629
|
+
}
|
|
614
630
|
// OKF conformance, which needs the files as a tree. Staged in a temp
|
|
615
631
|
// directory and thrown away — nothing touches the live workspace until every
|
|
616
632
|
// check above has passed.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { runEval, type EvalInfo } from "./evals.ts";
|
|
2
|
+
import { type Effort } from "./store.ts";
|
|
3
|
+
export declare const MAX_CONFIGS = 4;
|
|
4
|
+
export declare const MAX_REPEAT = 5;
|
|
5
|
+
export declare const MAX_TRIALS = 60;
|
|
6
|
+
export declare const DEFAULT_MAX_USD = 5;
|
|
7
|
+
export declare const MAX_MAX_USD = 100;
|
|
8
|
+
export interface CompareConfig {
|
|
9
|
+
/** A tier (fast, default, max) or a model id. */
|
|
10
|
+
model: string;
|
|
11
|
+
effort?: Effort | null;
|
|
12
|
+
/** Flow evals: only this agent's steps take the model. */
|
|
13
|
+
agent?: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface ComparePlan {
|
|
16
|
+
configs: CompareConfig[];
|
|
17
|
+
repeat: number;
|
|
18
|
+
maxUsd: number;
|
|
19
|
+
/** cases × configs × repeat. */
|
|
20
|
+
trials: number;
|
|
21
|
+
}
|
|
22
|
+
export interface CompareCaseRow {
|
|
23
|
+
case: string;
|
|
24
|
+
/** Per configuration, in order: passed of trials. */
|
|
25
|
+
passed: number[];
|
|
26
|
+
trials: number[];
|
|
27
|
+
}
|
|
28
|
+
export interface CompareConfigResult extends CompareConfig {
|
|
29
|
+
label: string;
|
|
30
|
+
trials: number;
|
|
31
|
+
passed: number;
|
|
32
|
+
/** Trials that were not run (the cap) or could not run (an error before any check). */
|
|
33
|
+
notRun: number;
|
|
34
|
+
/** passed / decided trials; null when none ran. */
|
|
35
|
+
rate: number | null;
|
|
36
|
+
/** The runs and their judges, USD. */
|
|
37
|
+
costUsd: number;
|
|
38
|
+
judgeCostUsd: number;
|
|
39
|
+
/** Per case run that finished, the mean, USD and seconds. */
|
|
40
|
+
avgCostUsd: number | null;
|
|
41
|
+
avgSecs: number | null;
|
|
42
|
+
/** Every run this configuration started, for opening one. */
|
|
43
|
+
runIds: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface EvalComparison {
|
|
46
|
+
eval: string;
|
|
47
|
+
target: {
|
|
48
|
+
flow: string | null;
|
|
49
|
+
agent: string | null;
|
|
50
|
+
};
|
|
51
|
+
judge: {
|
|
52
|
+
model: string;
|
|
53
|
+
effort: Effort | null;
|
|
54
|
+
};
|
|
55
|
+
startedAt: string;
|
|
56
|
+
finishedAt: string;
|
|
57
|
+
repeat: number;
|
|
58
|
+
maxUsd: number;
|
|
59
|
+
spentUsd: number;
|
|
60
|
+
/** Why it stopped early, when it did. */
|
|
61
|
+
stopped: string | null;
|
|
62
|
+
configs: CompareConfigResult[];
|
|
63
|
+
cases: CompareCaseRow[];
|
|
64
|
+
}
|
|
65
|
+
export declare const configLabel: (c: CompareConfig) => string;
|
|
66
|
+
/**
|
|
67
|
+
* Read and bound a request. Throws with a status: 400 for a shape, 413 when
|
|
68
|
+
* it is too big to run. The eval must have cases and not be a grader.
|
|
69
|
+
*/
|
|
70
|
+
export declare function planComparison(info: EvalInfo, body: unknown): ComparePlan;
|
|
71
|
+
export declare function compareEval(tenant: string, workspace: string, info: EvalInfo, plan: ComparePlan, opts?: {
|
|
72
|
+
onProgress?: (done: number, total: number) => void | Promise<void>;
|
|
73
|
+
run?: typeof runEval;
|
|
74
|
+
}): Promise<EvalComparison>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Model comparisons: one eval's cases, run under two to four model settings,
|
|
2
|
+
// side by side (feature 2, 24 Oct 2026).
|
|
3
|
+
//
|
|
4
|
+
// "Could this step run on the fast model?" was a guess: switch it, watch a
|
|
5
|
+
// week of desks, switch it back. A comparison answers it from the cases you
|
|
6
|
+
// already have — each configuration runs every case, `repeat` times, and the
|
|
7
|
+
// result says per configuration how many passed, what it cost (the runs and
|
|
8
|
+
// their judges) and how long a run took, and per case where they differ.
|
|
9
|
+
//
|
|
10
|
+
// The rules, settled in a Codex debate (24 Oct 2026):
|
|
11
|
+
// · The model is forced on the steps (every step of a flow eval, or only
|
|
12
|
+
// `agent`'s), so it wins over the agent's own `model:` and the flow's.
|
|
13
|
+
// · Every run is a test run, whatever the eval or the flow says about
|
|
14
|
+
// `live:` — a comparison is never the reason something was sent.
|
|
15
|
+
// · The judge is the eval's own, the same for every configuration: what
|
|
16
|
+
// changes is the subject, never the grader.
|
|
17
|
+
// · Bounded twice: at most MAX_TRIALS case runs, and `maxUsd` — once the
|
|
18
|
+
// runs and judges have spent it, the rest are recorded as not run.
|
|
19
|
+
// · A run's time is from a worker picking it up to its end; queueing is
|
|
20
|
+
// left out, so a busy account does not read as a slow model.
|
|
21
|
+
// · The repeats interleave (each configuration once, then again), so a
|
|
22
|
+
// provider that slows down mid-way slows every configuration alike.
|
|
23
|
+
// · Graders and saved inputs are not comparable: they have no cases to run.
|
|
24
|
+
// · Nothing here writes the eval's latest result or its history.
|
|
25
|
+
import { runEval } from "./evals.js";
|
|
26
|
+
import { resolveEffort } from "./store.js";
|
|
27
|
+
import { loadFlow } from "./runner.js";
|
|
28
|
+
export const MAX_CONFIGS = 4;
|
|
29
|
+
export const MAX_REPEAT = 5;
|
|
30
|
+
export const MAX_TRIALS = 60;
|
|
31
|
+
export const DEFAULT_MAX_USD = 5;
|
|
32
|
+
export const MAX_MAX_USD = 100;
|
|
33
|
+
function refuse(message, status) {
|
|
34
|
+
return Object.assign(new Error(message), { status });
|
|
35
|
+
}
|
|
36
|
+
const MODEL = /^[A-Za-z0-9][\w.:/-]{0,99}$/;
|
|
37
|
+
export const configLabel = (c) => `${c.model}${c.effort ? ` · ${c.effort}` : ""}${c.agent ? ` · ${c.agent} only` : ""}`;
|
|
38
|
+
/**
|
|
39
|
+
* Read and bound a request. Throws with a status: 400 for a shape, 413 when
|
|
40
|
+
* it is too big to run. The eval must have cases and not be a grader.
|
|
41
|
+
*/
|
|
42
|
+
export function planComparison(info, body) {
|
|
43
|
+
if (info.trigger === "runs")
|
|
44
|
+
throw refuse(`${info.name} grades real runs — it has no cases to compare models on`, 400);
|
|
45
|
+
if (info.inputs)
|
|
46
|
+
throw refuse(`${info.name} is a file of saved inputs, not an eval`, 400);
|
|
47
|
+
if (info.cases.length === 0)
|
|
48
|
+
throw refuse(`${info.name} has no cases`, 400);
|
|
49
|
+
// A comparison promises test runs only; a run: check is a command on the
|
|
50
|
+
// platform's host, run again for every case of every setting and repeat.
|
|
51
|
+
const runs = info.cases.find((c) => c.expect.some((a) => a.type === "run"));
|
|
52
|
+
if (runs)
|
|
53
|
+
throw refuse(`${info.name} has a run: check (case "${runs.name}") — a comparison repeats every check many times and run: executes a command, so it cannot be compared; use contains, matches or judge`, 400);
|
|
54
|
+
const b = (body && typeof body === "object" ? body : {});
|
|
55
|
+
if (!Array.isArray(b.configs) || b.configs.length < 2)
|
|
56
|
+
throw refuse("expected { configs: [{ model, effort?, agent? }, …] } — two to four settings to compare", 400);
|
|
57
|
+
if (b.configs.length > MAX_CONFIGS)
|
|
58
|
+
throw refuse(`at most ${MAX_CONFIGS} settings in one comparison`, 400);
|
|
59
|
+
const configs = b.configs.map((raw, i) => {
|
|
60
|
+
const r = (raw && typeof raw === "object" ? raw : {});
|
|
61
|
+
if (typeof r.model !== "string" || !MODEL.test(r.model.trim()))
|
|
62
|
+
throw refuse(`configs[${i}].model is a tier (fast, default, max) or a model id`, 400);
|
|
63
|
+
let effort = null;
|
|
64
|
+
if (r.effort !== undefined && r.effort !== null && r.effort !== "") {
|
|
65
|
+
effort = resolveEffort(r.effort);
|
|
66
|
+
if (!effort)
|
|
67
|
+
throw refuse(`configs[${i}].effort is low, medium, high, xhigh or max`, 400);
|
|
68
|
+
}
|
|
69
|
+
let agent = null;
|
|
70
|
+
if (r.agent !== undefined && r.agent !== null && r.agent !== "") {
|
|
71
|
+
if (!info.flow)
|
|
72
|
+
throw refuse(`configs[${i}].agent picks one agent's steps of a flow eval — ${info.name} tests agent ${info.agent} already`, 400);
|
|
73
|
+
if (typeof r.agent !== "string" || !/^[a-z0-9][a-z0-9-]{0,60}$/.test(r.agent))
|
|
74
|
+
throw refuse(`configs[${i}].agent is an agent's name`, 400);
|
|
75
|
+
agent = r.agent;
|
|
76
|
+
}
|
|
77
|
+
return { model: r.model.trim(), effort, agent };
|
|
78
|
+
});
|
|
79
|
+
const keys = configs.map(configLabel);
|
|
80
|
+
if (new Set(keys).size !== keys.length)
|
|
81
|
+
throw refuse("two settings are the same — each one should differ", 400);
|
|
82
|
+
const repeat = b.repeat === undefined ? 1 : Number(b.repeat);
|
|
83
|
+
if (!Number.isInteger(repeat) || repeat < 1 || repeat > MAX_REPEAT)
|
|
84
|
+
throw refuse(`repeat is a whole number from 1 to ${MAX_REPEAT}`, 400);
|
|
85
|
+
const maxUsd = b.maxUsd === undefined ? DEFAULT_MAX_USD : Number(b.maxUsd);
|
|
86
|
+
if (!Number.isFinite(maxUsd) || maxUsd <= 0 || maxUsd > MAX_MAX_USD)
|
|
87
|
+
throw refuse(`maxUsd is a spend limit in USD, above 0 and at most ${MAX_MAX_USD}`, 400);
|
|
88
|
+
const trials = info.cases.length * configs.length * repeat;
|
|
89
|
+
if (trials > MAX_TRIALS) {
|
|
90
|
+
throw refuse(`${info.cases.length} cases × ${configs.length} settings × ${repeat} = ${trials} runs — at most ${MAX_TRIALS}; fewer settings or repeats`, 413);
|
|
91
|
+
}
|
|
92
|
+
return { configs, repeat, maxUsd, trials };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Run the comparison. `onProgress` hears each finished pass of one
|
|
96
|
+
* configuration (done of total case runs). `run` is the eval runner —
|
|
97
|
+
* injectable, so a test needs no model.
|
|
98
|
+
*/
|
|
99
|
+
/** A flow eval's `agent` must have steps in the flow, or the comparison
|
|
100
|
+
* would run the original models under the requested labels. */
|
|
101
|
+
function assertAgentsInFlow(tenant, workspace, info, plan) {
|
|
102
|
+
const named = plan.configs.map((c) => c.agent).filter((a) => !!a);
|
|
103
|
+
if (!info.flow || !named.length)
|
|
104
|
+
return;
|
|
105
|
+
const flow = loadFlow(tenant, workspace, info.flow);
|
|
106
|
+
const agents = new Set((flow?.steps ?? []).map((s) => s.agent));
|
|
107
|
+
const missing = named.find((a) => !agents.has(a));
|
|
108
|
+
if (missing)
|
|
109
|
+
throw refuse(`flow ${info.flow} has no step by ${missing} — pick an agent it runs`, 400);
|
|
110
|
+
}
|
|
111
|
+
export async function compareEval(tenant, workspace, info, plan, opts = {}) {
|
|
112
|
+
if (!opts.run)
|
|
113
|
+
assertAgentsInFlow(tenant, workspace, info, plan);
|
|
114
|
+
const run = opts.run ?? runEval;
|
|
115
|
+
const startedAt = new Date().toISOString();
|
|
116
|
+
const results = plan.configs.map(() => []);
|
|
117
|
+
let spent = 0;
|
|
118
|
+
let stopped = null;
|
|
119
|
+
let done = 0;
|
|
120
|
+
for (let r = 0; r < plan.repeat; r++) {
|
|
121
|
+
for (let i = 0; i < plan.configs.length; i++) {
|
|
122
|
+
const cfg = plan.configs[i];
|
|
123
|
+
const result = await run(tenant, workspace, info, {
|
|
124
|
+
model: cfg.model,
|
|
125
|
+
effort: cfg.effort ?? null,
|
|
126
|
+
agent: cfg.agent ?? null,
|
|
127
|
+
forceTest: true,
|
|
128
|
+
beforeCase: () => {
|
|
129
|
+
if (spent >= plan.maxUsd) {
|
|
130
|
+
stopped ??= `the spend limit of $${plan.maxUsd.toFixed(2)} was reached`;
|
|
131
|
+
return stopped;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
},
|
|
135
|
+
// Counted as each case ends, so the limit holds inside a setting too.
|
|
136
|
+
afterCase: (c) => {
|
|
137
|
+
spent += c.costUsd + (c.judgeCostUsd ?? 0);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
results[i].push(result);
|
|
141
|
+
done += info.cases.length;
|
|
142
|
+
await opts.onProgress?.(done, plan.trials);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const round = (n, d = 4) => Math.round(n * 10 ** d) / 10 ** d;
|
|
146
|
+
const configs = plan.configs.map((cfg, i) => {
|
|
147
|
+
const cases = results[i].flatMap((r) => r.cases);
|
|
148
|
+
const ran = cases.filter((c) => c.runId);
|
|
149
|
+
const notRun = cases.filter((c) => !c.passed && c.error && !c.assertions.length).length;
|
|
150
|
+
const passed = cases.filter((c) => c.passed).length;
|
|
151
|
+
const decided = cases.length - notRun;
|
|
152
|
+
const cost = cases.reduce((n, c) => n + c.costUsd, 0);
|
|
153
|
+
const judge = cases.reduce((n, c) => n + (c.judgeCostUsd ?? 0), 0);
|
|
154
|
+
const timed = ran.filter((c) => typeof c.secs === "number");
|
|
155
|
+
return {
|
|
156
|
+
...cfg,
|
|
157
|
+
label: configLabel(cfg),
|
|
158
|
+
trials: cases.length,
|
|
159
|
+
passed,
|
|
160
|
+
notRun,
|
|
161
|
+
rate: decided ? round(passed / decided, 3) : null,
|
|
162
|
+
costUsd: round(cost + judge),
|
|
163
|
+
judgeCostUsd: round(judge),
|
|
164
|
+
avgCostUsd: ran.length ? round((cost + judge) / ran.length) : null,
|
|
165
|
+
avgSecs: timed.length ? Math.round((timed.reduce((n, c) => n + (c.secs ?? 0), 0) / timed.length) * 10) / 10 : null,
|
|
166
|
+
runIds: ran.map((c) => c.runId),
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
const cases = info.cases.map((c) => ({
|
|
170
|
+
case: c.name,
|
|
171
|
+
passed: results.map((rs) => rs.flatMap((r) => r.cases).filter((x) => x.name === c.name && x.passed).length),
|
|
172
|
+
trials: results.map((rs) => rs.flatMap((r) => r.cases).filter((x) => x.name === c.name).length),
|
|
173
|
+
}));
|
|
174
|
+
return {
|
|
175
|
+
eval: info.name,
|
|
176
|
+
target: { flow: info.flow, agent: info.agent },
|
|
177
|
+
judge: { model: info.model, effort: info.effort },
|
|
178
|
+
startedAt,
|
|
179
|
+
finishedAt: new Date().toISOString(),
|
|
180
|
+
repeat: plan.repeat,
|
|
181
|
+
maxUsd: plan.maxUsd,
|
|
182
|
+
spentUsd: round(spent),
|
|
183
|
+
stopped,
|
|
184
|
+
configs,
|
|
185
|
+
cases,
|
|
186
|
+
};
|
|
187
|
+
}
|
package/dist/src/evals.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TranslatorSpec } from "./translator.ts";
|
|
1
2
|
import { type Effort } from "./store.ts";
|
|
2
3
|
export type Assertion = {
|
|
3
4
|
type: "contains";
|
|
@@ -36,8 +37,20 @@ export interface EvalInfo {
|
|
|
36
37
|
effort: Effort | null;
|
|
37
38
|
/** When it runs. `deploy` (default): after every push, with the rest.
|
|
38
39
|
* `manual`: only when a person presses Run or the CLI asks — for a flow
|
|
39
|
-
* eval that costs a whole run and touches real systems every time.
|
|
40
|
-
|
|
40
|
+
* eval that costs a whole run and touches real systems every time.
|
|
41
|
+
* `runs`: never run as cases — it grades real finished runs of its agent
|
|
42
|
+
* or flow (grades.ts), a `sample` of them, as they finish. */
|
|
43
|
+
trigger: "deploy" | "manual" | "runs";
|
|
44
|
+
/** trigger: runs — the percent of finished runs graded, 0–100. */
|
|
45
|
+
sample: number;
|
|
46
|
+
/** trigger: runs — propose a fix (improve.ts) once this many graded runs
|
|
47
|
+
* failed and no person has looked yet. Null: never on its own. */
|
|
48
|
+
improve: number | null;
|
|
49
|
+
/** What in the file cannot be read as written, one sentence each — the
|
|
50
|
+
* deploy's checks and `foldrun check` report them. */
|
|
51
|
+
problems: string[];
|
|
52
|
+
/** A hash of the file as it was read (listEvals), when it was. */
|
|
53
|
+
revision?: string;
|
|
41
54
|
/** An eval's runs are test runs — nothing outward, state untouched —
|
|
42
55
|
* unless the file says `live: true`. A flow eval that must really send
|
|
43
56
|
* to prove itself opts in; nothing opts in by accident. */
|
|
@@ -55,6 +68,8 @@ export declare function parseEval(file: string, raw: string, defaults?: {
|
|
|
55
68
|
model?: string;
|
|
56
69
|
effort?: string;
|
|
57
70
|
}): EvalInfo;
|
|
71
|
+
/** Judges one graded run may cost — the grade pass counts runs, not calls. */
|
|
72
|
+
export declare const MAX_GRADER_JUDGES = 5;
|
|
58
73
|
export declare function listEvals(tenant: string, workspace: string): EvalInfo[];
|
|
59
74
|
/** `judge:` from AGENTS.md, field by field: the workspace's model (or
|
|
60
75
|
* effort), else the account's — so a workspace that sets only an effort
|
|
@@ -92,6 +107,8 @@ export interface AssertionResult {
|
|
|
92
107
|
assertion: Assertion;
|
|
93
108
|
passed: boolean;
|
|
94
109
|
detail: string;
|
|
110
|
+
/** What a judge cost, USD; absent for the free checks. */
|
|
111
|
+
costUsd?: number;
|
|
95
112
|
}
|
|
96
113
|
export interface CaseResult {
|
|
97
114
|
name: string;
|
|
@@ -101,7 +118,34 @@ export interface CaseResult {
|
|
|
101
118
|
/** Set when the agent itself failed, before any assertion could run. */
|
|
102
119
|
error: string | null;
|
|
103
120
|
assertions: AssertionResult[];
|
|
121
|
+
/** What the case's run cost — the subject, not the judge. */
|
|
104
122
|
costUsd: number;
|
|
123
|
+
/** What its judges cost, USD. Absent on results from before 24 Oct 2026. */
|
|
124
|
+
judgeCostUsd?: number;
|
|
125
|
+
/** How long the run took from a worker picking it up to its end — its
|
|
126
|
+
* time in the queue left out, so a busy account does not read as a slow
|
|
127
|
+
* model. Null when it never ran. */
|
|
128
|
+
secs?: number | null;
|
|
129
|
+
}
|
|
130
|
+
/** How a model comparison (eval-compare.ts) runs one eval: every step it
|
|
131
|
+
* forces onto a model, always as a test run, and whether to go on. */
|
|
132
|
+
export interface EvalRunOptions {
|
|
133
|
+
/** Forced onto the subject's steps — every step of a flow eval, or only
|
|
134
|
+
* `agent`'s when it is set — as a step-level setting, so it wins over
|
|
135
|
+
* the agent's own `model:` and the flow's defaults. `effort` likewise
|
|
136
|
+
* when given; none keeps each step's own effort, so a model-only
|
|
137
|
+
* comparison changes the model and nothing else. */
|
|
138
|
+
model?: string;
|
|
139
|
+
effort?: Effort | null;
|
|
140
|
+
agent?: string | null;
|
|
141
|
+
/** A test run whatever the eval or flow says about `live:`. */
|
|
142
|
+
forceTest?: boolean;
|
|
143
|
+
/** Asked before each case; a reason stops the eval there, the rest of its
|
|
144
|
+
* cases recorded as not run with it. */
|
|
145
|
+
beforeCase?: () => string | null;
|
|
146
|
+
/** Told each case as it ends — what a comparison counts its spend by, so
|
|
147
|
+
* its limit holds between the cases of one setting, not only after. */
|
|
148
|
+
afterCase?: (c: CaseResult) => void;
|
|
105
149
|
}
|
|
106
150
|
export interface EvalResult {
|
|
107
151
|
eval: string;
|
|
@@ -117,6 +161,17 @@ export interface EvalResult {
|
|
|
117
161
|
* answers. */
|
|
118
162
|
error?: string;
|
|
119
163
|
}
|
|
164
|
+
/** How long one judge may take before it is cut off: a verdict on one
|
|
165
|
+
* sentence is seconds; a stalled model must not hold a grade pass. */
|
|
166
|
+
export declare const JUDGE_TIMEOUT_MS = 120000;
|
|
167
|
+
/** One judge call: its words and what it cost (the SDK's result line). */
|
|
168
|
+
export declare function judgeCall(rubric: string, output: string, model: string, effort: Effort | null, env: Record<string, string | undefined>,
|
|
169
|
+
/** What the output was an answer to — a live run's task, so the judge
|
|
170
|
+
* grades the reply against what was asked rather than in a vacuum. */
|
|
171
|
+
context?: string): Promise<{
|
|
172
|
+
text: string;
|
|
173
|
+
costUsd: number;
|
|
174
|
+
}>;
|
|
120
175
|
/**
|
|
121
176
|
* The judge's environment: this process's, with the platform's model
|
|
122
177
|
* credential read live. The process's own CLAUDE_CODE_OAUTH_TOKEN is the copy
|
|
@@ -126,7 +181,11 @@ export interface EvalResult {
|
|
|
126
181
|
* agent steps (which read the live file) kept working (2026-10-21).
|
|
127
182
|
*/
|
|
128
183
|
export declare function judgeProcessEnv(): Record<string, string | undefined>;
|
|
129
|
-
export declare function
|
|
184
|
+
export declare function checkAssertion(assertion: Assertion, output: string, agentDir: string, model: string, effort: Effort | null, judgeEnv?: () => {
|
|
185
|
+
env: Record<string, string | undefined>;
|
|
186
|
+
translator?: TranslatorSpec | null;
|
|
187
|
+
}): Promise<AssertionResult>;
|
|
188
|
+
export declare function runEval(tenant: string, workspace: string, info: EvalInfo, opts?: EvalRunOptions): Promise<EvalResult>;
|
|
130
189
|
/**
|
|
131
190
|
* The latest result, and one line of history.
|
|
132
191
|
*
|