eve 0.14.0 → 0.15.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/CHANGELOG.md +14 -0
- package/dist/src/channel/compiled-channel.js +2 -1
- package/dist/src/cli/dev/tui/runner.js +1 -1
- package/dist/src/cli/dev/tui/setup-panel.d.ts +3 -4
- package/dist/src/cli/dev/tui/setup-panel.js +2 -2
- package/dist/src/cli/dev/tui/terminal-renderer.js +1 -1
- package/dist/src/evals/assertions/collector.d.ts +19 -6
- package/dist/src/evals/assertions/collector.js +1 -1
- package/dist/src/evals/assertions/run.d.ts +24 -17
- package/dist/src/evals/assertions/run.js +2 -2
- package/dist/src/evals/assertions/scoped.d.ts +17 -0
- package/dist/src/evals/assertions/scoped.js +1 -0
- package/dist/src/evals/context.d.ts +1 -0
- package/dist/src/evals/context.js +1 -1
- package/dist/src/evals/control-flow.d.ts +9 -0
- package/dist/src/evals/control-flow.js +1 -0
- package/dist/src/evals/define-eval.d.ts +1 -1
- package/dist/src/evals/define-eval.js +1 -1
- package/dist/src/evals/expect/index.d.ts +5 -3
- package/dist/src/evals/expect/index.js +1 -1
- package/dist/src/evals/index.d.ts +2 -2
- package/dist/src/evals/match.d.ts +50 -13
- package/dist/src/evals/match.js +1 -1
- package/dist/src/evals/runner/artifacts.js +1 -1
- package/dist/src/evals/runner/derive-run-facts.js +1 -1
- package/dist/src/evals/runner/execute-eval.js +1 -1
- package/dist/src/evals/runner/execute-task.d.ts +1 -0
- package/dist/src/evals/runner/execute-task.js +1 -1
- package/dist/src/evals/runner/reporters/braintrust.js +1 -1
- package/dist/src/evals/runner/reporters/console.js +1 -1
- package/dist/src/evals/runner/reporters/junit.js +3 -2
- package/dist/src/evals/runner/run-evals.js +1 -1
- package/dist/src/evals/runner/verdict.d.ts +1 -0
- package/dist/src/evals/runner/verdict.js +1 -1
- package/dist/src/evals/session.d.ts +9 -5
- package/dist/src/evals/session.js +1 -1
- package/dist/src/evals/types.d.ts +69 -47
- package/dist/src/harness/emission.d.ts +13 -27
- package/dist/src/harness/emission.js +1 -1
- package/dist/src/harness/step-hooks.d.ts +3 -3
- package/dist/src/harness/step-hooks.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/internal/application/compiled-artifacts.js +1 -1
- package/dist/src/internal/application/package.d.ts +12 -0
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/application/paths.d.ts +6 -0
- package/dist/src/internal/application/paths.js +1 -1
- package/dist/src/internal/nitro/host/configure-nitro-routes.js +2 -2
- package/dist/src/internal/workflow/configure-world.d.ts +6 -0
- package/dist/src/internal/workflow/configure-world.js +1 -1
- package/dist/src/internal/workflow/world-compatibility.d.ts +32 -0
- package/dist/src/internal/workflow/world-compatibility.js +1 -0
- package/dist/src/protocol/message.d.ts +7 -5
- package/dist/src/runtime/actions/types.d.ts +10 -11
- package/dist/src/setup/boxes/resolve-provisioning.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/docs/agent-config.md +13 -3
- package/docs/concepts/execution-model-and-durability.md +1 -1
- package/docs/concepts/sessions-runs-and-streaming.md +1 -1
- package/docs/connections/overview.mdx +40 -2
- package/docs/evals/assertions.mdx +97 -39
- package/docs/evals/cases.mdx +24 -15
- package/docs/evals/judge.mdx +2 -2
- package/docs/evals/overview.mdx +7 -5
- package/docs/evals/reporters.mdx +2 -2
- package/docs/evals/running.mdx +3 -1
- package/docs/evals/targets.mdx +2 -2
- package/docs/guides/auth-and-route-protection.md +48 -1
- package/docs/guides/client/streaming.mdx +1 -1
- package/docs/guides/deployment.md +3 -2
- package/docs/tutorial/connect-a-warehouse.mdx +2 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
function defineEval(e){return validateEvalInput(e),{...e,_tag:`EveEval`}}function validateEvalInput(e){if(`id`in e)throw Error("Eval must not specify `id`. Eval identity is derived from the file path under evals/.");if(`name`in e)throw Error("Eval must not specify `name`. Eval identity is derived from the file path under evals/.");if(rejectLegacyKey(e,`input`,"Send the prompt inside `test`: `async test(t) { await t.send(...) }`."),rejectLegacyKey(e,`run`,"Rename `run` to `test`; it receives the same context `t`."),rejectLegacyKey(e,`checks`,"Assert inline inside `test` (e.g. `t.
|
|
1
|
+
function defineEval(e){return validateEvalInput(e),{...e,_tag:`EveEval`}}function validateEvalInput(e){if(`id`in e)throw Error("Eval must not specify `id`. Eval identity is derived from the file path under evals/.");if(`name`in e)throw Error("Eval must not specify `name`. Eval identity is derived from the file path under evals/.");if(rejectLegacyKey(e,`input`,"Send the prompt inside `test`: `async test(t) { await t.send(...) }`."),rejectLegacyKey(e,`run`,"Rename `run` to `test`; it receives the same context `t`."),rejectLegacyKey(e,`checks`,"Assert inline inside `test` (e.g. `t.succeeded()`, `t.calledTool(...)`)."),rejectLegacyKey(e,`scores`,"Use soft assertions inside `test`: `t.check(...).atLeast(n)` or `t.judge.autoevals.*`."),rejectLegacyKey(e,`expected`,"Pass the reference value to the assertion (e.g. `t.check(t.reply, includes(value))`)."),rejectLegacyKey(e,`thresholds`,"Put the threshold on the assertion: `.atLeast(n)`."),rejectLegacyKey(e,`parseOutput`,"Read the value you want inside `test` and assert on it directly."),rejectLegacyKey(e,`model`,"Rename `model` to `judge: { model }`."),rejectLegacyKey(e,`modelOptions`,"Move it under `judge: { model, modelOptions }`."),rejectLegacyKey(e,`cases`,"Each eval file is one case; default-export an array of `defineEval(...)` for datasets."),rejectLegacyKey(e,`requires`,`Point real-model evals at credentialed targets directly; dev-only routes are enforced from the live target.`),typeof e.test!=`function`)throw Error("Eval requires a `test(t)` function.");if(e.timeoutMs!==void 0&&(e.timeoutMs<0||!Number.isFinite(e.timeoutMs)))throw Error("Eval `timeoutMs` must be a non-negative finite number.")}function rejectLegacyKey(e,t,n){if(t in e)throw Error(`Eval \`${t}\` is no longer supported. ${n}`)}export{defineEval};
|
|
@@ -2,10 +2,12 @@ import type { StandardSchemaV1 } from "#compiled/@standard-schema/spec/index.js"
|
|
|
2
2
|
import type { Assertion } from "#evals/types.js";
|
|
3
3
|
export type { Assertion, AssertionHandle, AssertionSeverity } from "#evals/types.js";
|
|
4
4
|
/**
|
|
5
|
-
* Passes when the value (coerced to a string) contains
|
|
6
|
-
* gate by default.
|
|
5
|
+
* Passes when the value (coerced to a string) contains a substring or matches
|
|
6
|
+
* a RegExp. A hard gate by default.
|
|
7
7
|
*/
|
|
8
|
-
export declare function includes(substring: string): Assertion;
|
|
8
|
+
export declare function includes(substring: string | RegExp): Assertion;
|
|
9
|
+
/** Passes when `predicate` returns true for the supplied value. */
|
|
10
|
+
export declare function satisfies<T = unknown>(predicate: (value: T) => boolean, label: string): Assertion;
|
|
9
11
|
/**
|
|
10
12
|
* Passes when the value deep-equals `expected` (exact structural equality).
|
|
11
13
|
* A hard gate by default.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Levenshtein}from"../../node_modules/.pnpm/autoevals@0.0.132_ws@8.21.0/node_modules/autoevals/jsdist/index.js";import{deepEquals}from"#evals/match.js";function makeAssertion(e){return{name:e.name,severity:e.severity,threshold:e.threshold,score:e.score,gate(t){return makeAssertion({...e,severity:`gate`,threshold:t})},soft(t){return makeAssertion({...e,severity:`soft`,threshold:t})},atLeast(t){return makeAssertion({...e,severity:`soft`,threshold:t})}}}function includes(e){return makeAssertion({name:`includes(${e})`,severity:`gate`,score:t
|
|
1
|
+
import{Levenshtein}from"../../node_modules/.pnpm/autoevals@0.0.132_ws@8.21.0/node_modules/autoevals/jsdist/index.js";import{deepEquals,testRegExp}from"#evals/match.js";function makeAssertion(e){return{name:e.name,severity:e.severity,threshold:e.threshold,score:e.score,gate(t){return makeAssertion({...e,severity:`gate`,threshold:t})},soft(t){return makeAssertion({...e,severity:`soft`,threshold:t})},atLeast(t){return makeAssertion({...e,severity:`soft`,threshold:t})}}}function includes(e){return makeAssertion({name:`includes(${e})`,severity:`gate`,score:t=>{let r=String(t??``);return(typeof e==`string`?r.includes(e):testRegExp(e,r))?1:0}})}function satisfies(e,t){if(t.trim().length===0)throw Error(`satisfies() requires a non-empty label.`);return makeAssertion({name:`satisfies(${t})`,severity:`gate`,score:t=>+!!e(t)})}function equals(e){return makeAssertion({name:`equals`,severity:`gate`,score:n=>+!!deepEquals(n,e)})}function matches(e){return makeAssertion({name:`matches`,severity:`gate`,score:async t=>{let n=await e[`~standard`].validate(t);return+(!(`issues`in n)||n.issues===void 0)}})}function similarity(t){return makeAssertion({name:`similarity`,severity:`soft`,score:async n=>(await Levenshtein({output:String(n??``),expected:t})).score??0})}export{equals,includes,matches,satisfies,similarity};
|
|
@@ -3,5 +3,5 @@ export { defineEvalConfig } from "#evals/define-eval-config.js";
|
|
|
3
3
|
export { EveEvalTurnFailedError } from "#evals/session.js";
|
|
4
4
|
export type { RuntimeIdentity } from "#protocol/message.js";
|
|
5
5
|
export type { InputRequest } from "#runtime/input/types.js";
|
|
6
|
-
export type { EveEvalValueMatcher, EveEvalToolCallMatchOptions, EveEvalSkillLoadMatchOptions, EveEvalSubagentCallMatchOptions, } from "#evals/match.js";
|
|
7
|
-
export type { Assertion, AssertionHandle, AssertionResult, AssertionSeverity, AutoevalsJudges, EveEvalContext, EveEvalDerivedFacts, EveEvalJudgeConfig, EveEvalRunSummary, EveEvalSession, EveEvalSessionResult, EveEvalScheduleDispatchResult, EveEvalSubagentCall, EveEval, EveEvalConfig, EveEvalConfigInput, EveEvalDefinition, EveEvalInput, EveEvalResult, EveEvalTarget, EveEvalTargetCapabilities, EveEvalTargetHandle, EveEvalTaskResult, EveEvalToolCall, EveEvalTurn, EveEvalVerdict, JudgeContext, JudgeOpts, } from "#evals/types.js";
|
|
6
|
+
export type { EveEvalEventMatch, EveEvalInputRequestMatchOptions, EveEvalValueMatcher, EveEvalToolCallMatchOptions, EveEvalSkillLoadMatchOptions, EveEvalSubagentCallMatchOptions, } from "#evals/match.js";
|
|
7
|
+
export type { Assertion, AssertionHandle, AssertionResult, AssertionSeverity, AutoevalsJudges, EveEvalActionStatus, EveEvalAssertions, EveEvalContext, EveEvalDerivedFacts, EveEvalJudgeConfig, EveEvalRunSummary, EveEvalSession, EveEvalSessionResult, EveEvalScheduleDispatchResult, EveEvalSubagentCall, EveEval, EveEvalConfig, EveEvalConfigInput, EveEvalDefinition, EveEvalInput, EveEvalResult, EveEvalTarget, EveEvalTargetCapabilities, EveEvalTargetHandle, EveEvalTaskResult, EveEvalToolCall, EveEvalTurn, EveEvalVerdict, JudgeContext, JudgeOpts, } from "#evals/types.js";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
2
|
+
import type { InputRequest } from "#runtime/input/types.js";
|
|
3
|
+
import type { JsonObject, JsonValue } from "#shared/json.js";
|
|
1
4
|
import type { EveEvalSubagentCall, EveEvalToolCall } from "#evals/types.js";
|
|
2
5
|
/**
|
|
3
6
|
* One matcher accepted by the assertion options (`t.calledTool`,
|
|
@@ -9,26 +12,25 @@ import type { EveEvalSubagentCall, EveEvalToolCall } from "#evals/types.js";
|
|
|
9
12
|
* compare with `Object.is`
|
|
10
13
|
* - a **RegExp** tests string values directly and the JSON serialization of
|
|
11
14
|
* anything else
|
|
12
|
-
* - a **function** receives the observed value and
|
|
13
|
-
* verdict, or returns a resolved expected value that is then compared like a
|
|
14
|
-
* literal — e.g. `(o) => o === process.env.EVE_WEATHER_AGENT_HOST`. To assert
|
|
15
|
-
* a literal boolean field, use the literal directly; boolean returns are
|
|
16
|
-
* always treated as verdicts.
|
|
15
|
+
* - a **function** receives the observed value and returns a boolean verdict
|
|
17
16
|
*/
|
|
18
|
-
export type EveEvalValueMatcher<T =
|
|
17
|
+
export type EveEvalValueMatcher<T = JsonValue | undefined> = EveEvalDeepMatcher<T>;
|
|
18
|
+
type EveEvalDeepMatcher<T> = RegExp | ((value: T) => boolean) | (T extends readonly (infer TEntry)[] ? readonly EveEvalDeepMatcher<TEntry>[] : T extends object ? {
|
|
19
|
+
readonly [K in keyof T]?: EveEvalDeepMatcher<T[K]>;
|
|
20
|
+
} : T);
|
|
19
21
|
/**
|
|
20
22
|
* Constraints applied to tool calls by `t.calledTool`. All provided
|
|
21
23
|
* constraints must hold for a call to match.
|
|
22
24
|
*/
|
|
23
25
|
export interface EveEvalToolCallMatchOptions {
|
|
24
26
|
/** Partial-deep matcher over the call input (see {@link EveEvalValueMatcher}). */
|
|
25
|
-
readonly input?: EveEvalValueMatcher
|
|
27
|
+
readonly input?: EveEvalValueMatcher<JsonObject>;
|
|
26
28
|
/** Matcher over the call output. */
|
|
27
29
|
readonly output?: EveEvalValueMatcher;
|
|
28
|
-
/** Required
|
|
29
|
-
readonly
|
|
30
|
+
/** Required lifecycle outcome. Defaults to `"completed"`. */
|
|
31
|
+
readonly status?: EveEvalToolCall["status"];
|
|
30
32
|
/** Exact number of matching calls required. Defaults to "at least one". */
|
|
31
|
-
readonly
|
|
33
|
+
readonly count?: number;
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Constraints applied to a `load_skill` call by `t.loadedSkill`. Identical to
|
|
@@ -41,10 +43,40 @@ export type EveEvalSkillLoadMatchOptions = Omit<EveEvalToolCallMatchOptions, "in
|
|
|
41
43
|
*/
|
|
42
44
|
export interface EveEvalSubagentCallMatchOptions {
|
|
43
45
|
/** Matcher over the `subagent.called` remote URL. */
|
|
44
|
-
readonly remoteUrl?: EveEvalValueMatcher
|
|
46
|
+
readonly remoteUrl?: EveEvalValueMatcher<string | undefined>;
|
|
45
47
|
/** Matcher over the `subagent.completed` output. */
|
|
46
48
|
readonly output?: EveEvalValueMatcher;
|
|
49
|
+
/** Required lifecycle outcome. Defaults to `"completed"`. */
|
|
50
|
+
readonly status?: EveEvalSubagentCall["status"];
|
|
51
|
+
/** Exact number of matching delegations required. Defaults to "at least one". */
|
|
52
|
+
readonly count?: number;
|
|
47
53
|
}
|
|
54
|
+
/** Constraints accepted by `requireInputRequest`. */
|
|
55
|
+
export interface EveEvalInputRequestMatchOptions {
|
|
56
|
+
/** Matcher over the request's display hint. */
|
|
57
|
+
readonly display?: EveEvalValueMatcher<InputRequest["display"]>;
|
|
58
|
+
/** Partial-deep matcher over a tool-call action's input. */
|
|
59
|
+
readonly input?: EveEvalValueMatcher<JsonObject>;
|
|
60
|
+
/** Matcher over the complete option-id list in request order. */
|
|
61
|
+
readonly optionIds?: EveEvalValueMatcher<readonly string[]>;
|
|
62
|
+
/** Matcher over the request prompt. */
|
|
63
|
+
readonly prompt?: EveEvalValueMatcher<string>;
|
|
64
|
+
/** Required tool name for a tool-call action. */
|
|
65
|
+
readonly toolName?: string;
|
|
66
|
+
}
|
|
67
|
+
/** One typed stream-event matcher used by scoped event assertions. */
|
|
68
|
+
export type EveEvalEventMatch<TType extends HandleMessageStreamEvent["type"] = HandleMessageStreamEvent["type"]> = TType extends HandleMessageStreamEvent["type"] ? {
|
|
69
|
+
/** Stream-event type to match. */
|
|
70
|
+
readonly type: TType;
|
|
71
|
+
/** Partial-deep matcher over the event data. */
|
|
72
|
+
readonly data?: EveEvalDeepMatcher<Extract<HandleMessageStreamEvent, {
|
|
73
|
+
type: TType;
|
|
74
|
+
}> extends {
|
|
75
|
+
data: infer TData;
|
|
76
|
+
} ? TData : never>;
|
|
77
|
+
/** Exact number of matching events required. Defaults to "at least one". */
|
|
78
|
+
readonly count?: number;
|
|
79
|
+
} : never;
|
|
48
80
|
/**
|
|
49
81
|
* Returns true when the observed value satisfies a matcher (literal, RegExp,
|
|
50
82
|
* or function — see {@link EveEvalValueMatcher}).
|
|
@@ -52,7 +84,7 @@ export interface EveEvalSubagentCallMatchOptions {
|
|
|
52
84
|
export declare function matchesValue(matcher: unknown, value: unknown): boolean;
|
|
53
85
|
/**
|
|
54
86
|
* Returns true when one derived tool call satisfies the `input`/`output`/
|
|
55
|
-
*
|
|
87
|
+
* lifecycle constraints (the `count` option is the caller's concern).
|
|
56
88
|
*/
|
|
57
89
|
export declare function toolCallMatches(call: EveEvalToolCall, options: EveEvalToolCallMatchOptions): boolean;
|
|
58
90
|
/**
|
|
@@ -60,8 +92,12 @@ export declare function toolCallMatches(call: EveEvalToolCall, options: EveEvalT
|
|
|
60
92
|
* `output` constraints.
|
|
61
93
|
*/
|
|
62
94
|
export declare function subagentCallMatches(call: EveEvalSubagentCall, options: EveEvalSubagentCallMatchOptions): boolean;
|
|
95
|
+
/** Returns true when one HITL request satisfies every supplied constraint. */
|
|
96
|
+
export declare function inputRequestMatches(request: InputRequest, options: EveEvalInputRequestMatchOptions): boolean;
|
|
97
|
+
/** Returns true when one stream event satisfies a typed event matcher. */
|
|
98
|
+
export declare function eventMatches(event: HandleMessageStreamEvent, matcher: EveEvalEventMatch): boolean;
|
|
63
99
|
/**
|
|
64
|
-
* Strict structural equality used by `
|
|
100
|
+
* Strict structural equality used by scoped `outputEquals`: unlike matcher
|
|
65
101
|
* comparison, objects must carry exactly the same keys on both sides.
|
|
66
102
|
*/
|
|
67
103
|
export declare function deepEquals(a: unknown, b: unknown): boolean;
|
|
@@ -71,3 +107,4 @@ export declare function deepEquals(a: unknown, b: unknown): boolean;
|
|
|
71
107
|
* a `g`/`y`-flagged pattern would otherwise return order-dependent results.
|
|
72
108
|
*/
|
|
73
109
|
export declare function testRegExp(pattern: RegExp, text: string): boolean;
|
|
110
|
+
export {};
|
package/dist/src/evals/match.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function matchesValue(e,t){
|
|
1
|
+
function matchesValue(e,t){return e instanceof RegExp?testRegExpAgainst(e,t):typeof e==`function`?e(t):Array.isArray(e)?!Array.isArray(t)||t.length!==e.length?!1:e.every((e,n)=>matchesValue(e,t[n])):isPlainObject(e)?isPlainObject(t)?Object.entries(e).every(([e,n])=>matchesValue(n,t[e])):!1:Object.is(e,t)}function toolCallMatches(e,t){return!(t.input!==void 0&&!matchesValue(t.input,e.input)||t.output!==void 0&&!matchesValue(t.output,e.output)||e.status!==(t.status??`completed`))}function subagentCallMatches(e,t){return!(t.remoteUrl!==void 0&&!matchesValue(t.remoteUrl,e.remoteUrl)||t.output!==void 0&&!matchesValue(t.output,e.output)||e.status!==(t.status??`completed`))}function inputRequestMatches(e,t){if(t.display!==void 0&&!matchesValue(t.display,e.display)||t.prompt!==void 0&&!matchesValue(t.prompt,e.prompt))return!1;if(t.optionIds!==void 0){let n=(e.options??[]).map(e=>e.id);if(!matchesValue(t.optionIds,n))return!1}return!(t.toolName!==void 0&&(e.action.kind!==`tool-call`||e.action.toolName!==t.toolName)||t.input!==void 0&&(e.action.kind!==`tool-call`||!matchesValue(t.input,e.action.input)))}function eventMatches(e,t){return e.type===t.type&&(t.data===void 0||matchesValue(t.data,`data`in e?e.data:void 0))}function deepEquals(e,t){if(Object.is(e,t))return!0;if(Array.isArray(e)||Array.isArray(t))return!Array.isArray(e)||!Array.isArray(t)||e.length!==t.length?!1:e.every((e,n)=>deepEquals(e,t[n]));if(isPlainObject(e)&&isPlainObject(t)){let n=Object.keys(e),r=Object.keys(t);return n.length===r.length?n.every(n=>deepEquals(e[n],t[n])):!1}return!1}function testRegExpAgainst(e,t){if(typeof t==`string`)return testRegExp(e,t);if(t===void 0)return!1;let n=JSON.stringify(t);return n!==void 0&&testRegExp(e,n)}function testRegExp(e,t){return(e.global||e.sticky)&&(e.lastIndex=0),e.test(t)}function isPlainObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}export{deepEquals,eventMatches,inputRequestMatches,matchesValue,subagentCallMatches,testRegExp,toolCallMatches};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{dirname,join}from"node:path";import{mkdir,writeFile}from"node:fs/promises";function resolveArtifactDirectory(e){return join(e,`.eve`,`evals`,new Date().toISOString().replace(/[:.]/g,`-`).slice(0,19))}async function writeArtifacts(t,n){let r=join(t,`evals`);await mkdir(r,{recursive:!0}),await writeFile(join(t,`summary.json`),JSON.stringify(buildSummaryArtifact(n),null,2));let i=n.results.map(e=>JSON.stringify(buildResultLine(e))).join(`
|
|
2
2
|
`);await writeFile(join(t,`results.jsonl`),`${i}\n`),await Promise.all(n.results.map(async t=>{let n=join(r,`${sanitizeArtifactPath(t.id)}.json`);await mkdir(dirname(n),{recursive:!0}),await writeFile(n,JSON.stringify(buildEvalArtifact(t),null,2));let i=t.result.events.map(e=>JSON.stringify(e)).join(`
|
|
3
|
-
`);await writeFile(join(r,`${sanitizeArtifactPath(t.id)}.events.ndjson`),`${i}\n`)}))}function buildSummaryArtifact(e){return{target:e.target,startedAt:e.startedAt,completedAt:e.completedAt,passed:e.passed,failed:e.failed,scored:e.scored,errored:e.errored,totalEvals:e.results.length,evals:e.results.map(e=>({id:e.id,verdict:e.verdict,status:e.result.status,assertions:e.assertions.map(e=>({name:e.name,score:e.score,severity:e.severity,passed:e.passed})),error:e.error}))}}function buildResultLine(e){return{id:e.id,verdict:e.verdict,status:e.result.status,output:e.result.output,assertions:e.assertions,error:e.error}}function buildEvalArtifact(e){return{id:e.id,result:{output:e.result.output,finalMessage:e.result.finalMessage,sessionId:e.result.sessionId,status:e.result.status,logs:e.result.logs,derived:e.result.derived,sessions:e.result.sessions},verdict:e.verdict,assertions:e.assertions,error:e.error}}function sanitizeArtifactPath(e){return e.split(`/`).map(e=>e.replace(/[^a-zA-Z0-9_-]/g,`_`)).join(`/`)}export{resolveArtifactDirectory,writeArtifacts};
|
|
3
|
+
`);await writeFile(join(r,`${sanitizeArtifactPath(t.id)}.events.ndjson`),`${i}\n`)}))}function buildSummaryArtifact(e){return{target:e.target,startedAt:e.startedAt,completedAt:e.completedAt,passed:e.passed,failed:e.failed,scored:e.scored,skipped:e.skipped,errored:e.errored,totalEvals:e.results.length,evals:e.results.map(e=>({id:e.id,verdict:e.verdict,status:e.result.status,assertions:e.assertions.map(e=>({name:e.name,score:e.score,severity:e.severity,passed:e.passed})),error:e.error,skipReason:e.skipReason}))}}function buildResultLine(e){return{id:e.id,verdict:e.verdict,status:e.result.status,output:e.result.output,assertions:e.assertions,error:e.error,skipReason:e.skipReason}}function buildEvalArtifact(e){return{id:e.id,result:{output:e.result.output,finalMessage:e.result.finalMessage,sessionId:e.result.sessionId,status:e.result.status,logs:e.result.logs,derived:e.result.derived,sessions:e.result.sessions},verdict:e.verdict,assertions:e.assertions,error:e.error,skipReason:e.skipReason}}function sanitizeArtifactPath(e){return e.split(`/`).map(e=>e.replace(/[^a-zA-Z0-9_-]/g,`_`)).join(`/`)}export{resolveArtifactDirectory,writeArtifacts};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const TURN_EPILOGUE_EVENT_TYPES=new Set([`turn.completed`,`session.waiting`,`session.completed`]);function deriveRunFacts(e,t){let n=t?.sessionId,r=[],i=new Map,a=[],o=new Map,s=[],c=-1,l=0,u=0,d,ensureSubagentCall=(e,t)=>{let r=o.get(e);if(r!==void 0)return r;let i={name:t,
|
|
1
|
+
const TURN_EPILOGUE_EVENT_TYPES=new Set([`turn.completed`,`session.waiting`,`session.completed`]);function deriveRunFacts(e,t){let n=t?.sessionId,r=[],i=new Map,a=[],o=new Map,s=[],c=-1,l=0,u=0,d,ensureToolCall=(e,t,a)=>{let o=i.get(e);if(o!==void 0)return o;let s={name:t,input:a,output:void 0,status:`pending`,turnIndex:Math.max(c,0),sessionId:n};return r.push(s),i.set(e,s),s},ensureSubagentCall=(e,t)=>{let r=o.get(e);if(r!==void 0)return r;let i={name:t,status:`pending`,turnIndex:Math.max(c,0),sessionId:n};return a.push(i),o.set(e,i),i};for(let t of e)switch(t.type){case`turn.started`:c+=1;break;case`actions.requested`:for(let e of t.data.actions)e.kind===`tool-call`&&ensureToolCall(e.callId,e.toolName,e.input);break;case`action.result`:{let{result:e,status:n}=t.data;if(e.kind===`tool-result`){let t=ensureToolCall(e.callId,e.toolName,{});t.output=e.output,t.status=n}else if(e.kind===`subagent-result`){let t=ensureSubagentCall(e.callId,e.subagentName);t.output=t.output??e.output,t.status=n}break}case`subagent.called`:{let e=ensureSubagentCall(t.data.callId,t.data.name);t.data.remote!==void 0&&(e.remoteUrl=t.data.remote.url);break}case`subagent.started`:ensureSubagentCall(t.data.callId,t.data.subagentName);break;case`subagent.completed`:{let e=ensureSubagentCall(t.data.callId,t.data.subagentName);e.output=t.data.output,e.status===`pending`&&(e.status=`completed`);break}case`input.requested`:s.push(...t.data.requests);for(let e of t.data.requests)ensureToolCall(e.action.callId,e.action.toolName,e.action.input);break;case`message.completed`:t.data.finishReason!==`tool-calls`&&(l+=1);break;case`reasoning.completed`:u+=1;break;case`session.failed`:d=t.data.code;break}return{toolCalls:r,toolCallCount:r.length,subagentCalls:a,subagentCallCount:a.length,inputRequests:s,parked:endedParkedOnInput(e),messageCount:l,reasoningBlockCount:u,failureCode:d}}function createEmptyDerivedFacts(){return{toolCalls:[],toolCallCount:0,subagentCalls:[],subagentCallCount:0,inputRequests:[],parked:!1,messageCount:0,reasoningBlockCount:0}}function endedParkedOnInput(t){for(let n=t.length-1;n>=0;n--){let r=t[n];if(!(r===void 0||TURN_EPILOGUE_EVENT_TYPES.has(r.type)))return r.type===`input.requested`}return!1}export{createEmptyDerivedFacts,deriveRunFacts};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toErrorMessage}from"#shared/errors.js";import{createEmptyDerivedFacts}from"#evals/runner/derive-run-facts.js";import{executeTask}from"#evals/runner/execute-task.js";import{computeEvalVerdict}from"#evals/runner/verdict.js";async function executeEval(n){let{evaluation:r,target:i,client:a}=n,o=new Date().toISOString(),s,c=[],l;try{let e=await executeTask({client:a,evaluation:r,onLog:n.onLog,target:i,timeoutMs:n.timeoutMs??r.timeoutMs});s=e.result,c=e.assertions,l=e.error}catch(t){l=toErrorMessage(t),s={output:null,finalMessage:null,status:`failed`,events:[],derived:createEmptyDerivedFacts()}}let
|
|
1
|
+
import{toErrorMessage}from"#shared/errors.js";import{createEmptyDerivedFacts}from"#evals/runner/derive-run-facts.js";import{executeTask}from"#evals/runner/execute-task.js";import{computeEvalVerdict}from"#evals/runner/verdict.js";async function executeEval(n){let{evaluation:r,target:i,client:a}=n,o=new Date().toISOString(),s,c=[],l,u;try{let e=await executeTask({client:a,evaluation:r,onLog:n.onLog,target:i,timeoutMs:n.timeoutMs??r.timeoutMs});s=e.result,c=e.assertions,l=e.error,u=e.skipReason}catch(t){l=toErrorMessage(t),s={output:null,finalMessage:null,status:`failed`,events:[],derived:createEmptyDerivedFacts()}}let d=computeEvalVerdict({error:l,assertions:c,skipReason:u});return{id:r.id,result:s,assertions:c,verdict:d,error:l,skipReason:u,startedAt:o,completedAt:new Date().toISOString()}}export{executeEval};
|
|
@@ -20,6 +20,7 @@ export interface ExecuteTaskResult {
|
|
|
20
20
|
readonly result: EveEvalTaskResult;
|
|
21
21
|
readonly assertions: readonly AssertionResult[];
|
|
22
22
|
readonly error?: string;
|
|
23
|
+
readonly skipReason?: string;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Executes one eval's `test(t)` against an eve agent target: drives the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toErrorMessage}from"#shared/errors.js";import{scopeEvalTargetHandle}from"#evals/target.js";import{EvalSessionManager}from"#evals/session.js";import{createEmptyDerivedFacts}from"#evals/runner/derive-run-facts.js";import{createEvalContext}from"#evals/context.js";async function executeTask(
|
|
1
|
+
import{toErrorMessage}from"#shared/errors.js";import{AssertionCollector}from"#evals/assertions/collector.js";import{scopeEvalTargetHandle}from"#evals/target.js";import{EvalSessionManager}from"#evals/session.js";import{EvalRequirementFailed,EvalSkipped}from"#evals/control-flow.js";import{createEmptyDerivedFacts}from"#evals/runner/derive-run-facts.js";import{createEvalContext}from"#evals/context.js";async function executeTask(a){let{client:o,evaluation:s,target:c,timeoutMs:l}=a,u=l===void 0?neverAbortSignal():AbortSignal.timeout(l),d=new AssertionCollector,f=new EvalSessionManager({client:o,collector:d,signal:u}),p=scopeEvalTargetHandle(c,{sessions:f}),m=[],{context:h}=createEvalContext({collector:d,manager:f,target:p,signal:u,judge:s.judge,log:e=>{m.push(e),a.onLog?.(e)}}),g,_;try{await s.test(h)}catch(t){t instanceof EvalSkipped?_=t.reason:t instanceof EvalRequirementFailed||(g=toErrorMessage(t))}let v=buildTaskResult({logs:m,sessions:f.snapshots(),turn:f.lastTurnSession()?.lastTurn});return{result:v,assertions:await d.finalize(v),error:g,skipReason:_}}function buildTaskResult(e){let t=e.sessions.flatMap(e=>e.events),n=e.turn?.message??null;return{output:e.turn?.data===void 0?n:e.turn.data,finalMessage:n,sessionId:selectPrimarySessionId(e.sessions),status:e.turn?.status??`completed`,events:t,logs:e.logs,derived:combineDerivedFacts(e.sessions),sessions:e.sessions,runtimeIdentity:extractRuntimeIdentity(t)}}function combineDerivedFacts(e){if(e.length===0)return createEmptyDerivedFacts();let t=e.flatMap(e=>e.derived.toolCalls),n=e.flatMap(e=>e.derived.subagentCalls),r=e.flatMap(e=>e.derived.inputRequests),i=e.find(e=>e.derived.failureCode!==void 0)?.derived.failureCode;return{toolCalls:t,toolCallCount:t.length,subagentCalls:n,subagentCallCount:n.length,inputRequests:r,parked:e.some(e=>e.derived.parked),messageCount:sum(e,e=>e.derived.messageCount),reasoningBlockCount:sum(e,e=>e.derived.reasoningBlockCount),failureCode:i}}function selectPrimarySessionId(e){return e.find(e=>e.primary)?.sessionId??e[0]?.sessionId}function extractRuntimeIdentity(e){for(let t of e)if(t.type===`session.started`&&t.data.runtime!==void 0)return t.data.runtime}function sum(e,t){return e.reduce((e,n)=>e+t(n),0)}function neverAbortSignal(){return new AbortController().signal}export{executeTask};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{resolveLocalGitMetadata}from"#evals/runner/resolve-git-metadata.js";function Braintrust(e={}){return new BraintrustReporter(e)}var BraintrustReporter=class{#e;#t;#n;#r=new Map;constructor(e){this.#e=e}async onRunStart(t,n){let r=await loadBraintrustSdk();this.#t=r;let i=resolveLocalGitMetadata(process.cwd());this.#r.clear();for(let e of t)this.#r.set(e.id,e);let a=resolveTags(t,n),o=resolveExperimentMetadata(t,n);this.#n=await r.init({project:this.#e.projectName??t[0]?.id??`eve evals`,projectId:this.#e.projectId,experiment:this.#e.experimentName,baseExperiment:this.#e.baseExperimentName,baseExperimentId:this.#e.baseExperimentId,update:this.#e.update,tags:a,metadata:o,noExitFlush:!0,setCurrent:!1,repoInfo:i.sha?{commit:i.sha,branch:i.branch}:null})}onEvalComplete(e){if(!this.#n)return;let t=this.#r.get(e.id),n={};for(let t of e.assertions){let e=t.severity===`gate`?`gate:${t.name}`:t.name;n[e]=t.score}let r=e.assertions.filter(e=>!e.passed).map(e=>({name:e.name,message:e.message})),i={...t?.metadata,eveSessionId:e.result.sessionId,eveStatus:e.result.status,eveVerdict:e.verdict,eveToolCalls:e.result.derived.toolCalls.map(e=>e.name),eveSubagentCalls:e.result.derived.subagentCalls.map(e=>e.name),eveParked:e.result.derived.parked};r.length>0&&(i.eveFailedAssertions=r),e.result.derived.failureCode&&(i.eveFailureCode=e.result.derived.failureCode);let a={toolCallCount:e.result.derived.toolCallCount,subagentCallCount:e.result.derived.subagentCallCount,messageCount:e.result.derived.messageCount,reasoningBlockCount:e.result.derived.reasoningBlockCount};this.#n.log({id:e.id,input:t?.description??``,output:e.result.output,error:e.error??void 0,scores:n,metadata:i,metrics:a,tags:t?.tags?[...t.tags]:void 0})}async onRunComplete(e){if(this.#n)try{this.#t&&await this.#t.flush();let e=await this.#n.summarize();e.experimentUrl&&console.log(`Braintrust experiment: ${e.experimentUrl}\n\n`)}finally{await this.#n.close(),this.#n=void 0,this.#t=void 0}}};async function loadBraintrustSdk(){try{return await import(`braintrust`)}catch{throw Error([`The 'braintrust' package is required for Braintrust reporting but was not found.`,``,`Install it with:`,` npm install braintrust`].join(`
|
|
1
|
+
import{resolveLocalGitMetadata}from"#evals/runner/resolve-git-metadata.js";function Braintrust(e={}){return new BraintrustReporter(e)}var BraintrustReporter=class{#e;#t;#n;#r=new Map;constructor(e){this.#e=e}async onRunStart(t,n){let r=await loadBraintrustSdk();this.#t=r;let i=resolveLocalGitMetadata(process.cwd());this.#r.clear();for(let e of t)this.#r.set(e.id,e);let a=resolveTags(t,n),o=resolveExperimentMetadata(t,n);this.#n=await r.init({project:this.#e.projectName??t[0]?.id??`eve evals`,projectId:this.#e.projectId,experiment:this.#e.experimentName,baseExperiment:this.#e.baseExperimentName,baseExperimentId:this.#e.baseExperimentId,update:this.#e.update,tags:a,metadata:o,noExitFlush:!0,setCurrent:!1,repoInfo:i.sha?{commit:i.sha,branch:i.branch}:null})}onEvalComplete(e){if(!this.#n)return;let t=this.#r.get(e.id),n={};for(let t of e.assertions){let e=t.severity===`gate`?`gate:${t.name}`:t.name;n[e]=t.score}let r=e.assertions.filter(e=>!e.passed).map(e=>({name:e.name,message:e.message})),i={...t?.metadata,eveSessionId:e.result.sessionId,eveStatus:e.result.status,eveVerdict:e.verdict,eveSkipReason:e.skipReason,eveToolCalls:e.result.derived.toolCalls.map(e=>e.name),eveSubagentCalls:e.result.derived.subagentCalls.map(e=>e.name),eveParked:e.result.derived.parked};r.length>0&&(i.eveFailedAssertions=r),e.result.derived.failureCode&&(i.eveFailureCode=e.result.derived.failureCode);let a={toolCallCount:e.result.derived.toolCallCount,subagentCallCount:e.result.derived.subagentCallCount,messageCount:e.result.derived.messageCount,reasoningBlockCount:e.result.derived.reasoningBlockCount};this.#n.log({id:e.id,input:t?.description??``,output:e.result.output,error:e.error??void 0,scores:n,metadata:i,metrics:a,tags:t?.tags?[...t.tags]:void 0})}async onRunComplete(e){if(this.#n)try{this.#t&&await this.#t.flush();let e=await this.#n.summarize();e.experimentUrl&&console.log(`Braintrust experiment: ${e.experimentUrl}\n\n`)}finally{await this.#n.close(),this.#n=void 0,this.#t=void 0}}};async function loadBraintrustSdk(){try{return await import(`braintrust`)}catch{throw Error([`The 'braintrust' package is required for Braintrust reporting but was not found.`,``,`Install it with:`,` npm install braintrust`].join(`
|
|
2
2
|
`))}}function resolveTags(e,t){let n=new Set([`eve`,`target:${t.kind}`]);for(let t of e){n.add(`eval:${t.id}`);for(let e of t.tags??[])n.add(e)}return[...n]}function resolveExperimentMetadata(e,t){return{eveEvalIds:e.map(e=>e.id),eveTargetKind:t.kind,eveTargetUrl:t.url,eveTimestamp:new Date().toISOString()}}export{Braintrust};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import picocolors from"#compiled/picocolors/index.js";var ConsoleReporter=class{#e;#t;constructor(t){this.#e=t?.log??console.log,this.#t=picocolors.createColors(t?.color??!!process.stdout.isTTY)}onRunStart(e,t){this.#e(``),this.#e(`${this.#t.bold(this.#t.cyan(`EVALS`))} ${this.#t.bold(String(e.length))}`),this.#e(`${this.#t.dim(`target`)} ${t.kind===`local`?this.#t.green(t.url):this.#t.blue(t.url)}`),this.#e(``)}onEvalComplete(e){let{assertions:t,verdict:n,error:r}=e,
|
|
1
|
+
import picocolors from"#compiled/picocolors/index.js";var ConsoleReporter=class{#e;#t;constructor(t){this.#e=t?.log??console.log,this.#t=picocolors.createColors(t?.color??!!process.stdout.isTTY)}onRunStart(e,t){this.#e(``),this.#e(`${this.#t.bold(this.#t.cyan(`EVALS`))} ${this.#t.bold(String(e.length))}`),this.#e(`${this.#t.dim(`target`)} ${t.kind===`local`?this.#t.green(t.url):this.#t.blue(t.url)}`),this.#e(``)}onEvalComplete(e){let{assertions:t,verdict:n,error:r,skipReason:i}=e,a=t.filter(e=>e.severity===`gate`),o=t.filter(e=>e.severity===`soft`),s=this.#n(n),c=a.length>0?this.#r(a.filter(e=>e.passed).length,a.length):``,l=o.map(e=>this.#i(e.name,e.score)).join(` `),u=[s,this.#t.dim(e.id),c,l].filter(Boolean).join(` `);this.#e(u);for(let e of t){if(e.passed)continue;let t=e.message===void 0?``:`: ${e.message}`;this.#e(` ${this.#t.red(`✗ ${e.name}${t}`)}`)}r&&this.#e(` ${this.#t.red(r)}`),i&&this.#e(` ${this.#t.dim(i)}`)}onRunComplete(e){this.#e(``);let{passed:t,failed:n,scored:r,skipped:i,results:a}=e,o=a.length,s=[];t>0&&s.push(this.#t.green(`${t} passed`)),n>0&&s.push(this.#t.red(`${n} failed`)),r>0&&s.push(this.#t.yellow(`${r} scored`)),i>0&&s.push(this.#t.dim(`${i} skipped`)),s.length===0&&s.push(this.#t.dim(`0 evals`)),this.#e(`${this.#t.bold(`Results:`)} ${s.join(`, `)} ${this.#t.dim(`(${o} total)`)}`);let c=this.#a(a);if(c.total>0){let e=this.#t.green(`${c.passed} passed`),t=c.failed>0?`, ${this.#t.red(`${c.failed} failed`)}`:``;this.#e(`${this.#t.bold(`Gates:`)} ${e}${t}`)}let l=this.#o(a);if(l.length>0){this.#e(``);for(let{name:e,avg:t,count:n}of l){let r=this.#i(e,t);this.#e(` ${r} ${this.#t.dim(`(${n} evals)`)}`)}}let u=computeDurationMs(e.startedAt,e.completedAt);this.#e(``),this.#e(this.#t.dim(`Completed in ${formatDuration(u)}`)),this.#e(``)}#n(e){switch(e){case`passed`:return this.#t.green(`✓`);case`failed`:return this.#t.red(`✗`);case`scored`:return this.#t.yellow(`○`);case`skipped`:return this.#t.dim(`–`)}}#r(e,t){let n=`gates ${e}/${t}`;return e===t?this.#t.green(n):this.#t.red(n)}#i(e,t){let n=`${e}: ${Math.round(t*100)}%`;return t===1?this.#t.green(n):t===0?this.#t.red(n):this.#t.yellow(n)}#a(e){let t=0,n=0;for(let r of e)for(let e of gatesOf(r))e.passed?t+=1:n+=1;return{passed:t,failed:n,total:t+n}}#o(e){let t=new Map;for(let n of e)for(let e of n.assertions){if(e.severity!==`soft`)continue;let n=t.get(e.name);n?(n.sum+=e.score,n.count+=1):t.set(e.name,{sum:e.score,count:1})}return[...t.entries()].map(([e,{sum:t,count:n}])=>({name:e,avg:t/n,count:n}))}};function gatesOf(e){return e.assertions.filter(e=>e.severity===`gate`)}function computeDurationMs(e,t){return new Date(t).getTime()-new Date(e).getTime()}function formatDuration(e){return e<1e3?`${e}ms`:e<6e4?`${(e/1e3).toFixed(1)}s`:`${Math.floor(e/6e4)}m ${(e%6e4/1e3).toFixed(0)}s`}export{ConsoleReporter};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import{dirname}from"node:path";import{mkdir,writeFile}from"node:fs/promises";function JUnit(e){return new JUnitReporter(e)}var JUnitReporter=class{#e;constructor(e){this.#e=e}onRunStart(){}onEvalComplete(){}async onRunComplete(r){let i=renderJUnit(r,{suiteName:this.#e.suiteName});await mkdir(dirname(this.#e.filePath),{recursive:!0}),await writeFile(this.#e.filePath,i)}};function renderJUnit(e,t){let n=e.failed+e.scored,r=e.results.map(renderTestCase);return[`<?xml version="1.0" encoding="UTF-8"?>`,`<testsuite name="${escapeXml(t.suiteName??`eve evals`)}" tests="${e.results.length}" failures="${n}" skipped="
|
|
2
|
-
`)}function renderTestCase(e){let t=`classname="eve.eval" name="${escapeXml(e.id)}" time="${formatSeconds(durationSeconds(e))}"`;if(e.verdict===`passed`)return` <testcase ${t}/>`;
|
|
1
|
+
import{dirname}from"node:path";import{mkdir,writeFile}from"node:fs/promises";function JUnit(e){return new JUnitReporter(e)}var JUnitReporter=class{#e;constructor(e){this.#e=e}onRunStart(){}onEvalComplete(){}async onRunComplete(r){let i=renderJUnit(r,{suiteName:this.#e.suiteName});await mkdir(dirname(this.#e.filePath),{recursive:!0}),await writeFile(this.#e.filePath,i)}};function renderJUnit(e,t){let n=e.failed+e.scored,r=e.results.map(renderTestCase);return[`<?xml version="1.0" encoding="UTF-8"?>`,`<testsuite name="${escapeXml(t.suiteName??`eve evals`)}" tests="${e.results.length}" failures="${n}" skipped="${e.skipped}" time="${formatSeconds(durationSeconds(e))}">`,...r,`</testsuite>`,``].join(`
|
|
2
|
+
`)}function renderTestCase(e){let t=`classname="eve.eval" name="${escapeXml(e.id)}" time="${formatSeconds(durationSeconds(e))}"`;if(e.verdict===`passed`)return` <testcase ${t}/>`;if(e.verdict===`skipped`)return[` <testcase ${t}>`,` <skipped message="${escapeXml(e.skipReason??`skipped`)}"/>`,` </testcase>`].join(`
|
|
3
|
+
`);let n=failureMessage(e);return[` <testcase ${t}>`,` <failure message="${escapeXml(n)}">${escapeXml(JSON.stringify(buildFailureDetail(e),null,2))}</failure>`,` </testcase>`].join(`
|
|
3
4
|
`)}function buildFailureDetail(e){return{verdict:e.verdict,error:e.error,assertions:e.assertions,logs:e.result.logs}}function failureMessage(e){if(e.error!==void 0)return e.error;let t=e.assertions.find(e=>!e.passed);return t===void 0?e.verdict===`scored`?`score below threshold`:e.verdict:t.message===void 0?t.name:`${t.name}: ${t.message}`}function durationSeconds(e){let t=new Date(e.completedAt).getTime()-new Date(e.startedAt).getTime();return Math.max(0,t/1e3)}function formatSeconds(e){return e.toFixed(3)}function escapeXml(e){return e.replace(/&/g,`&`).replace(/"/g,`"`).replace(/'/g,`'`).replace(/</g,`<`).replace(/>/g,`>`)}export{JUnit};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveArtifactDirectory,writeArtifacts}from"#evals/runner/artifacts.js";import{executeEval}from"#evals/runner/execute-eval.js";async function runEvals(r){let{config:i,target:a,client:o,appRoot:s}=r,c=r.maxConcurrency??i.maxConcurrency??8;if(!Number.isInteger(c)||c<1)throw Error(`Eval maxConcurrency must be a positive integer; got ${String(r.maxConcurrency??i.maxConcurrency)}.`);let l=r.evaluations.map(e=>applyConfigDefaults(e,i)),u=new Date().toISOString(),d=buildReporterBindings({...r,evaluations:l});for(let e of d)await e.reporter.onRunStart(l.filter(t=>e.evalIds.has(t.id)),a);let f=[],p=[...l],m=new Set,h=Promise.resolve();for(;p.length>0||m.size>0;){for(;p.length>0&&m.size<c;){let e=p.shift();if(e===void 0)break;let t=(async()=>{let t=await executeEval({client:o,evaluation:e,onLog:r.onEvalLog===void 0?void 0:t=>r.onEvalLog?.(e.id,t),target:a,timeoutMs:r.timeoutMs});f.push(t),h=h.then(async()=>{for(let e of d)e.evalIds.has(t.id)&&await e.reporter.onEvalComplete(t)})})().finally(()=>{m.delete(t)});m.add(t)}m.size>0&&await Promise.race(m)}await h;let g=new Map(l.map((e,t)=>[e.id,t]));f.sort((e,t)=>(g.get(e.id)??0)-(g.get(t.id)??0));let _=buildSummary(a,f,u);await writeArtifacts(resolveArtifactDirectory(s),_);for(let e of d)await e.reporter.onRunComplete(scopeSummary(_,e.evalIds));return _}function buildReporterBindings(e){let t=new Set(e.evaluations.map(e=>e.id)),n=new Set(e.reporters);if(e.includeEvalReporters!==!1)for(let t of e.config.reporters??[])n.add(t);let r=[...n].map(e=>({reporter:e,evalIds:t}));if(e.includeEvalReporters===!1)return r;let i=new Map;for(let t of e.evaluations)for(let e of t.reporters??[]){if(n.has(e))continue;let r=i.get(e)??new Set;r.add(t.id),i.set(e,r)}for(let[e,t]of i)r.push({reporter:e,evalIds:t});return r}function applyConfigDefaults(e,t){return e.judge!==void 0||t.judge===void 0?e:{...e,judge:t.judge}}function buildSummary(e,t,n){return{target:e,results:t,startedAt:n,completedAt:new Date().toISOString(),passed:countVerdicts(t,`passed`),failed:countVerdicts(t,`failed`),scored:countVerdicts(t,`scored`),errored:t.filter(e=>e.error!==void 0).length}}function scopeSummary(e,t){if(e.results.every(e=>t.has(e.id)))return e;let n=e.results.filter(e=>t.has(e.id));return{...e,results:n,passed:countVerdicts(n,`passed`),failed:countVerdicts(n,`failed`),scored:countVerdicts(n,`scored`),errored:n.filter(e=>e.error!==void 0).length}}function countVerdicts(e,t){return e.filter(e=>e.verdict===t).length}export{runEvals};
|
|
1
|
+
import{resolveArtifactDirectory,writeArtifacts}from"#evals/runner/artifacts.js";import{executeEval}from"#evals/runner/execute-eval.js";async function runEvals(r){let{config:i,target:a,client:o,appRoot:s}=r,c=r.maxConcurrency??i.maxConcurrency??8;if(!Number.isInteger(c)||c<1)throw Error(`Eval maxConcurrency must be a positive integer; got ${String(r.maxConcurrency??i.maxConcurrency)}.`);let l=r.evaluations.map(e=>applyConfigDefaults(e,i)),u=new Date().toISOString(),d=buildReporterBindings({...r,evaluations:l});for(let e of d)await e.reporter.onRunStart(l.filter(t=>e.evalIds.has(t.id)),a);let f=[],p=[...l],m=new Set,h=Promise.resolve();for(;p.length>0||m.size>0;){for(;p.length>0&&m.size<c;){let e=p.shift();if(e===void 0)break;let t=(async()=>{let t=await executeEval({client:o,evaluation:e,onLog:r.onEvalLog===void 0?void 0:t=>r.onEvalLog?.(e.id,t),target:a,timeoutMs:r.timeoutMs});f.push(t),h=h.then(async()=>{for(let e of d)e.evalIds.has(t.id)&&await e.reporter.onEvalComplete(t)})})().finally(()=>{m.delete(t)});m.add(t)}m.size>0&&await Promise.race(m)}await h;let g=new Map(l.map((e,t)=>[e.id,t]));f.sort((e,t)=>(g.get(e.id)??0)-(g.get(t.id)??0));let _=buildSummary(a,f,u);await writeArtifacts(resolveArtifactDirectory(s),_);for(let e of d)await e.reporter.onRunComplete(scopeSummary(_,e.evalIds));return _}function buildReporterBindings(e){let t=new Set(e.evaluations.map(e=>e.id)),n=new Set(e.reporters);if(e.includeEvalReporters!==!1)for(let t of e.config.reporters??[])n.add(t);let r=[...n].map(e=>({reporter:e,evalIds:t}));if(e.includeEvalReporters===!1)return r;let i=new Map;for(let t of e.evaluations)for(let e of t.reporters??[]){if(n.has(e))continue;let r=i.get(e)??new Set;r.add(t.id),i.set(e,r)}for(let[e,t]of i)r.push({reporter:e,evalIds:t});return r}function applyConfigDefaults(e,t){return e.judge!==void 0||t.judge===void 0?e:{...e,judge:t.judge}}function buildSummary(e,t,n){return{target:e,results:t,startedAt:n,completedAt:new Date().toISOString(),passed:countVerdicts(t,`passed`),failed:countVerdicts(t,`failed`),scored:countVerdicts(t,`scored`),skipped:countVerdicts(t,`skipped`),errored:t.filter(e=>e.error!==void 0).length}}function scopeSummary(e,t){if(e.results.every(e=>t.has(e.id)))return e;let n=e.results.filter(e=>t.has(e.id));return{...e,results:n,passed:countVerdicts(n,`passed`),failed:countVerdicts(n,`failed`),scored:countVerdicts(n,`scored`),skipped:countVerdicts(n,`skipped`),errored:n.filter(e=>e.error!==void 0).length}}function countVerdicts(e,t){return e.filter(e=>e.verdict===t).length}export{runEvals};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function computeEvalVerdict(e){if(e.error!==void 0)return`failed`;let t=!1;for(let n of e.assertions)if(!n.passed){if(n.severity===`gate`)return`failed`;t=!0}return t?`scored`:`passed`}export{computeEvalVerdict};
|
|
1
|
+
function computeEvalVerdict(e){if(e.error!==void 0)return`failed`;let t=!1;for(let n of e.assertions)if(!n.passed){if(n.severity===`gate`)return`failed`;t=!0}return e.skipReason===void 0?t?`scored`:`passed`:`skipped`}export{computeEvalVerdict};
|
|
@@ -3,7 +3,9 @@ import type { ClientSession } from "#client/session.js";
|
|
|
3
3
|
import type { SendTurnInput, SessionState } from "#client/types.js";
|
|
4
4
|
import type { HandleMessageStreamEvent, TurnFailureStreamEvent } from "#protocol/message.js";
|
|
5
5
|
import type { InputRequest, InputResponse } from "#runtime/input/types.js";
|
|
6
|
-
import
|
|
6
|
+
import { AssertionCollector } from "#evals/assertions/collector.js";
|
|
7
|
+
import type { EveEvalAssertions, EveEvalOutputAssertions, EveEvalSession, EveEvalSessionResult, EveEvalTurn } from "#evals/types.js";
|
|
8
|
+
import type { EveEvalInputRequestMatchOptions } from "#evals/match.js";
|
|
7
9
|
/**
|
|
8
10
|
* Error thrown by {@link EveEvalTurn.expectOk} when a turn failed.
|
|
9
11
|
*/
|
|
@@ -12,9 +14,12 @@ export declare class EveEvalTurnFailedError extends Error {
|
|
|
12
14
|
readonly turn: EveEvalTurn;
|
|
13
15
|
constructor(turn: EveEvalTurn);
|
|
14
16
|
}
|
|
17
|
+
export interface EvalSessionDriver extends EveEvalAssertions, EveEvalOutputAssertions {
|
|
18
|
+
}
|
|
15
19
|
export declare class EvalSessionDriver implements EveEvalSession {
|
|
16
20
|
#private;
|
|
17
21
|
constructor(input: {
|
|
22
|
+
readonly collector: AssertionCollector;
|
|
18
23
|
readonly session: ClientSession;
|
|
19
24
|
readonly signal?: AbortSignal;
|
|
20
25
|
});
|
|
@@ -23,10 +28,7 @@ export declare class EvalSessionDriver implements EveEvalSession {
|
|
|
23
28
|
get pendingInputRequests(): readonly InputRequest[];
|
|
24
29
|
get sessionId(): string | undefined;
|
|
25
30
|
get state(): SessionState;
|
|
26
|
-
|
|
27
|
-
readonly display?: InputRequest["display"];
|
|
28
|
-
readonly toolName?: string;
|
|
29
|
-
}): readonly InputRequest[];
|
|
31
|
+
requireInputRequest(filter?: EveEvalInputRequestMatchOptions): InputRequest;
|
|
30
32
|
respond(...responses: InputResponse[]): Promise<EveEvalTurn>;
|
|
31
33
|
respondAll(optionId: string): Promise<EveEvalTurn>;
|
|
32
34
|
send(input: SendTurnInput): Promise<EveEvalTurn>;
|
|
@@ -40,6 +42,7 @@ export declare class EvalSessionManager {
|
|
|
40
42
|
#private;
|
|
41
43
|
constructor(input: {
|
|
42
44
|
readonly client: Client;
|
|
45
|
+
readonly collector?: AssertionCollector;
|
|
43
46
|
readonly signal?: AbortSignal;
|
|
44
47
|
});
|
|
45
48
|
get primary(): EvalSessionDriver;
|
|
@@ -49,4 +52,5 @@ export declare class EvalSessionManager {
|
|
|
49
52
|
}): Promise<EvalSessionDriver>;
|
|
50
53
|
snapshots(): readonly EveEvalSessionResult[];
|
|
51
54
|
lastTurnSession(): EvalSessionDriver | undefined;
|
|
55
|
+
hasActivity(): boolean;
|
|
52
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{basename,extname}from"node:path";import{readFile}from"node:fs/promises";import{deriveResultStatus,extractCompletedMessage,extractInputRequests}from"#client/session-utils.js";import{createTextWithFileContent}from"#client/file-parts.js";import{isCurrentTurnBoundaryEvent,isTurnFailureEvent}from"#protocol/message.js";import{extractCompletedResult}from"#client/output-schema.js";import{deriveRunFacts}from"#evals/runner/derive-run-facts.js";var EveEvalTurnFailedError=class extends Error{event;turn;constructor(e){let t=e.events.find(isTurnFailureEvent),n=t===void 0?`turn ended with status "${e.status}"`:`${t.type}: ${t.data.code} ${t.data.message}`.trim();super(`Eval turn failed: ${n}`),this.name=`EveEvalTurnFailedError`,this.event=t,this.turn=e}},EvalSessionDriver=class{#e;#t;#n=[];#
|
|
1
|
+
import{basename,extname}from"node:path";import{readFile}from"node:fs/promises";import{deriveResultStatus,extractCompletedMessage,extractInputRequests}from"#client/session-utils.js";import{createTextWithFileContent}from"#client/file-parts.js";import{isCurrentTurnBoundaryEvent,isTurnFailureEvent}from"#protocol/message.js";import{extractCompletedResult}from"#client/output-schema.js";import{inputRequestMatches,toolCallMatches}from"#evals/match.js";import{AssertionCollector}from"#evals/assertions/collector.js";import{createOutputAssertions,createScopedAssertions}from"#evals/assertions/scoped.js";import{EvalRequirementFailed}from"#evals/control-flow.js";import{deriveRunFacts}from"#evals/runner/derive-run-facts.js";var EveEvalTurnFailedError=class extends Error{event;turn;constructor(e){let t=e.events.find(isTurnFailureEvent),n=t===void 0?`turn ended with status "${e.status}"`:`${t.type}: ${t.data.code} ${t.data.message}`.trim();super(`Eval turn failed: ${n}`),this.name=`EveEvalTurnFailedError`,this.event=t,this.turn=e}},EvalSessionDriver=class{#e;#t;#n;#r=[];#i;#a=[];constructor(e){this.#n=e.collector,this.#e=e.session,this.#t=e.signal,Object.assign(this,createScopedAssertions(this.#n,{timing:`snapshot`,select:()=>this.#s()}),createOutputAssertions(this.#n,{timing:`snapshot`,select:()=>this.#s()}))}get events(){return this.#r}get lastTurn(){return this.#i}get pendingInputRequests(){return this.#a}get sessionId(){return this.#e.state.sessionId??this.#i?.sessionId}get state(){return this.#e.state}requireInputRequest(e={}){this.#a.length===0&&this.#c(`requireInputRequest`,`expected one pending input request, but the last turn did not park`);let t=this.#a.filter(t=>inputRequestMatches(t,e));return(this.#a.length!==1||t.length!==1)&&this.#c(`requireInputRequest`,`expected exactly one pending input request matching ${formatInputRequestFilter(e)}, found ${t.length} match(es) across ${this.#a.length} pending request(s)`),this.#n.recordOutcome({name:`requireInputRequest`,outcome:{score:1}}),t[0]}async respond(...e){if(e.length===0)throw Error(`respond() requires at least one input response.`);return await this.send({inputResponses:e})}async respondAll(e){let t=this.#a;if(t.length===0)throw Error(`respondAll() requires at least one pending input request.`);for(let n of t)assertRequestHasOption(n,e);return await this.respond(...t.map(t=>({optionId:e,requestId:t.requestId})))}async send(e){let t=await(await this.#e.send(attachSignal(e,this.#t))).result();return this.#o({data:t.data,events:t.events,inputRequests:t.inputRequests,message:t.message,sessionId:t.sessionId,status:t.status})}async sendFile(t,r,i){let a=createTextWithFileContent({bytes:await readFile(r),filename:basename(r),mediaType:i??inferMediaType(r),text:t});return await this.send({message:a})}async readTurn(e){let t=this.sessionId,n=[],o=!1;for await(let t of this.#e.stream({signal:this.#t,startIndex:e?.startIndex}))if(n.push(t),isCurrentTurnBoundaryEvent(t)){o=!0;break}if(!o)throw Error(`Stream for session "${this.sessionId??`(unknown)`}" closed before a turn boundary.`);return this.#o({data:extractCompletedResult(n),events:n,inputRequests:extractInputRequests(n),message:extractCompletedMessage(n),sessionId:requireSessionId(t),status:deriveResultStatus(n)})}snapshot(e){let t=this.sessionId;return{derived:deriveRunFacts(this.#r,{sessionId:t}),events:[...this.#r],primary:e,sessionId:t,state:this.#e.state}}#o(e){this.#r.push(...e.events),this.#a=e.status===`waiting`?e.inputRequests:[];let t=deriveRunFacts(e.events,{sessionId:e.sessionId}),n=new EvalTurn({collector:this.#n,data:e.data,derived:t,events:e.events,inputRequests:e.inputRequests,message:e.message,sessionId:e.sessionId,status:e.status,toolCalls:t.toolCalls});return this.#i=n,n}#s(){let e=this.sessionId;return{derived:deriveRunFacts(this.#r,{sessionId:e}),events:[...this.#r],output:outputOf(this.#i),status:this.#i?.status??`completed`}}#c(e,t){throw this.#n.recordOutcome({name:e,outcome:{score:0,message:t}}),new EvalRequirementFailed}},EvalTurn=class{data;events;inputRequests;message;sessionId;status;toolCalls;#e;#t;constructor(e){this.data=e.data,this.events=e.events,this.inputRequests=e.inputRequests,this.message=e.message,this.sessionId=e.sessionId,this.status=e.status,this.toolCalls=e.toolCalls,this.#e=e.collector,this.#t=e.derived,Object.assign(this,createScopedAssertions(e.collector,{timing:`snapshot`,select:()=>this.#n()}),createOutputAssertions(e.collector,{timing:`snapshot`,select:()=>this.#n()}))}expectOk(){if(this.status!==`failed`)return this;throw new EveEvalTurnFailedError(this)}requireToolCall(e,t={}){let n=this.toolCalls.filter(n=>n.name===e&&toolCallMatches(n,t));return n.length!==1&&inputRequirementFailed(this.#e,`requireToolCall`,`expected exactly one matching "${e}" tool call in this turn, found ${n.length}; observed [${this.toolCalls.map(e=>e.name).join(`, `)}]`),this.#e.recordOutcome({name:`requireToolCall`,outcome:{score:1}}),n[0]}#n(){return{derived:this.#t,events:this.events,output:outputOf(this),status:this.status}}},EvalSessionManager=class{#e;#t;#n;#r=[];#i;constructor(e){this.#e=e.client,this.#n=e.collector??new AssertionCollector,this.#t=e.signal}get primary(){return this.#i??=this.#a(),this.#i}newSession(){return this.#a()}async attachSession(e,t){let n=new EvalSessionDriver({collector:this.#n,session:this.#e.session({sessionId:e,streamIndex:t?.startIndex??0}),signal:this.#t});return this.#r.push(n),await n.readTurn(t),n}snapshots(){return this.#r.map(e=>e.snapshot(e===this.#i))}lastTurnSession(){return this.#i?.lastTurn===void 0?this.#r.find(e=>e.lastTurn!==void 0):this.#i}hasActivity(){return this.#r.length>0}#a(){let e=new EvalSessionDriver({collector:this.#n,session:this.#e.session(),signal:this.#t});return this.#r.push(e),e}};function attachSignal(e,t){if(t===void 0)return e;if(typeof e==`string`)return{message:e,signal:t};let n=e;return n.signal===void 0?{...n,signal:t}:n}function formatInputRequestFilter(e){return JSON.stringify(e)}function inputRequirementFailed(e,t,n){throw e.recordOutcome({name:t,outcome:{score:0,message:n}}),new EvalRequirementFailed}function outputOf(e){return e===void 0?null:e.data===void 0?e.message??null:e.data}function requireSessionId(e){if(e===void 0)throw Error(`Eval session produced a turn without a session id.`);return e}function assertRequestHasOption(e,t){if(e.options===void 0||e.options.length===0)throw Error(`Input request "${e.requestId}" has no selectable options.`);if(!e.options.some(e=>e.id===t))throw Error(`Input request "${e.requestId}" does not offer option "${t}".`)}function inferMediaType(e){switch(extname(e).toLowerCase()){case`.gif`:return`image/gif`;case`.jpg`:case`.jpeg`:return`image/jpeg`;case`.png`:return`image/png`;case`.webp`:return`image/webp`;default:return`application/octet-stream`}}export{EvalSessionDriver,EvalSessionManager,EveEvalTurnFailedError};
|
|
@@ -3,10 +3,12 @@ import type { StandardSchemaV1 } from "#compiled/@standard-schema/spec/index.js"
|
|
|
3
3
|
import type { HandleMessageStreamEvent, RuntimeIdentity } from "#protocol/message.js";
|
|
4
4
|
import type { SendTurnInput, SessionState } from "#client/types.js";
|
|
5
5
|
import type { InputRequest, InputResponse } from "#runtime/input/types.js";
|
|
6
|
-
import type { JsonObject } from "#shared/json.js";
|
|
6
|
+
import type { JsonObject, JsonValue } from "#shared/json.js";
|
|
7
7
|
import type { AgentModelOptionsDefinition } from "#shared/agent-definition.js";
|
|
8
8
|
import type { EvalReporter } from "#evals/runner/reporters/types.js";
|
|
9
|
-
import type { EveEvalSkillLoadMatchOptions, EveEvalSubagentCallMatchOptions, EveEvalToolCallMatchOptions } from "#evals/match.js";
|
|
9
|
+
import type { EveEvalEventMatch, EveEvalInputRequestMatchOptions, EveEvalSkillLoadMatchOptions, EveEvalSubagentCallMatchOptions, EveEvalToolCallMatchOptions } from "#evals/match.js";
|
|
10
|
+
/** Lifecycle outcome of an eval-observed tool or subagent action. */
|
|
11
|
+
export type EveEvalActionStatus = "pending" | "completed" | "failed" | "rejected";
|
|
10
12
|
/**
|
|
11
13
|
* One tool call extracted from the captured stream, pairing the
|
|
12
14
|
* `actions.requested` request with its matching `action.result`.
|
|
@@ -17,9 +19,9 @@ export interface EveEvalToolCall {
|
|
|
17
19
|
/** Tool input as requested by the model. */
|
|
18
20
|
readonly input: JsonObject;
|
|
19
21
|
/** Tool output from the matching `action.result`; `undefined` when the call never resolved. */
|
|
20
|
-
readonly output:
|
|
21
|
-
/**
|
|
22
|
-
readonly
|
|
22
|
+
readonly output: JsonValue | undefined;
|
|
23
|
+
/** Whether the request is unresolved, completed, failed, or user-rejected. */
|
|
24
|
+
readonly status: EveEvalActionStatus;
|
|
23
25
|
/** Zero-based index of the turn the call happened in. */
|
|
24
26
|
readonly turnIndex: number;
|
|
25
27
|
/** Owning session id, when the runner knows it. */
|
|
@@ -35,9 +37,9 @@ export interface EveEvalSubagentCall {
|
|
|
35
37
|
/** Remote agent URL for remote delegations (`subagent.called` remote metadata). */
|
|
36
38
|
readonly remoteUrl?: string;
|
|
37
39
|
/** Output from the matching `subagent.completed` event; `undefined` when the call never completed. */
|
|
38
|
-
readonly output?:
|
|
39
|
-
/**
|
|
40
|
-
readonly
|
|
40
|
+
readonly output?: JsonValue;
|
|
41
|
+
/** Whether the delegation is unresolved, completed, failed, or rejected. */
|
|
42
|
+
readonly status: EveEvalActionStatus;
|
|
41
43
|
/** Zero-based index of the turn the delegation happened in. */
|
|
42
44
|
readonly turnIndex: number;
|
|
43
45
|
/** Owning session id, when the runner knows it. */
|
|
@@ -74,9 +76,8 @@ export interface EveEvalSessionResult {
|
|
|
74
76
|
*/
|
|
75
77
|
export interface EveEvalTaskResult {
|
|
76
78
|
/**
|
|
77
|
-
* The
|
|
78
|
-
* for reporters and artifacts that log
|
|
79
|
-
* because the runner assigns it after the run completes.
|
|
79
|
+
* The final turn's structured data when present, otherwise its last assistant
|
|
80
|
+
* message. Retained for reporters and artifacts that log one output value.
|
|
80
81
|
*/
|
|
81
82
|
output: unknown;
|
|
82
83
|
/** The agent's last assistant message, or null when none was produced. */
|
|
@@ -131,10 +132,10 @@ export interface Assertion {
|
|
|
131
132
|
/**
|
|
132
133
|
* Handle to a recorded assertion, returned by every `t` assertion method.
|
|
133
134
|
* Chain `gate`/`soft`/`atLeast` to override the recorded severity or
|
|
134
|
-
* threshold
|
|
135
|
-
*
|
|
135
|
+
* threshold. Recorded assertions are finalized by the runner; use `t.require`
|
|
136
|
+
* or a `require*` lookup when later control flow depends on a passing result.
|
|
136
137
|
*/
|
|
137
|
-
export interface AssertionHandle
|
|
138
|
+
export interface AssertionHandle {
|
|
138
139
|
gate(threshold?: number): this;
|
|
139
140
|
soft(threshold?: number): this;
|
|
140
141
|
atLeast(threshold: number): this;
|
|
@@ -153,10 +154,37 @@ export interface AssertionResult {
|
|
|
153
154
|
readonly message?: string;
|
|
154
155
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
155
156
|
}
|
|
156
|
-
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
/** Recorded assertions shared by aggregate, session, and turn scopes. */
|
|
158
|
+
export interface EveEvalAssertions {
|
|
159
|
+
succeeded(): AssertionHandle;
|
|
160
|
+
parked(): AssertionHandle;
|
|
161
|
+
messageIncludes(token: string | RegExp): AssertionHandle;
|
|
162
|
+
calledTool(name: string, options?: EveEvalToolCallMatchOptions): AssertionHandle;
|
|
163
|
+
/** Sugar for `calledTool("load_skill", { input: { skill }, ... })`. */
|
|
164
|
+
loadedSkill(skill: string, options?: EveEvalSkillLoadMatchOptions): AssertionHandle;
|
|
165
|
+
notCalledTool(name: string): AssertionHandle;
|
|
166
|
+
/** Asserts that tool requests appeared in order, allowing unrelated requests between them. */
|
|
167
|
+
toolOrder(names: readonly string[]): AssertionHandle;
|
|
168
|
+
usedNoTools(): AssertionHandle;
|
|
169
|
+
maxToolCalls(max: number): AssertionHandle;
|
|
170
|
+
calledSubagent(name: string, options?: EveEvalSubagentCallMatchOptions): AssertionHandle;
|
|
171
|
+
noFailedActions(): AssertionHandle;
|
|
172
|
+
event<TType extends HandleMessageStreamEvent["type"]>(type: TType, options?: Omit<Extract<EveEvalEventMatch, {
|
|
173
|
+
type: TType;
|
|
174
|
+
}>, "type">): AssertionHandle;
|
|
175
|
+
notEvent<TType extends HandleMessageStreamEvent["type"]>(type: TType, options?: Omit<Extract<EveEvalEventMatch, {
|
|
176
|
+
type: TType;
|
|
177
|
+
}>, "type" | "count">): AssertionHandle;
|
|
178
|
+
eventOrder(matchers: readonly EveEvalEventMatch[]): AssertionHandle;
|
|
179
|
+
eventsSatisfy(label: string, predicate: (events: readonly HandleMessageStreamEvent[]) => boolean): AssertionHandle;
|
|
180
|
+
}
|
|
181
|
+
/** Assertions over the final output captured by one session or immutable turn. */
|
|
182
|
+
export interface EveEvalOutputAssertions {
|
|
183
|
+
outputEquals(value: unknown): AssertionHandle;
|
|
184
|
+
outputMatches(schema: StandardSchemaV1): AssertionHandle;
|
|
185
|
+
}
|
|
186
|
+
/** Operations and state shared by the primary eval context and independent sessions. */
|
|
187
|
+
export interface EveEvalSessionDriver {
|
|
160
188
|
/** All events observed on this session so far. */
|
|
161
189
|
readonly events: readonly HandleMessageStreamEvent[];
|
|
162
190
|
/** Input requests left pending by the last parked turn. */
|
|
@@ -165,11 +193,8 @@ export interface EveEvalSession {
|
|
|
165
193
|
readonly state: SessionState;
|
|
166
194
|
/** eve session id after the first successful send. */
|
|
167
195
|
readonly sessionId: string | undefined;
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
readonly display?: InputRequest["display"];
|
|
171
|
-
readonly toolName?: string;
|
|
172
|
-
}): readonly InputRequest[];
|
|
196
|
+
/** Require exactly one pending input request matching `filter`, or abort dependent control flow. */
|
|
197
|
+
requireInputRequest(filter?: EveEvalInputRequestMatchOptions): InputRequest;
|
|
173
198
|
/** Resolve specific pending requests and run the resumed turn. */
|
|
174
199
|
respond(...responses: InputResponse[]): Promise<EveEvalTurn>;
|
|
175
200
|
/** Resolve every pending request with the same option id. */
|
|
@@ -179,16 +204,22 @@ export interface EveEvalSession {
|
|
|
179
204
|
/** Send one text turn with a local file attached as a data URL. */
|
|
180
205
|
sendFile(text: string, filePath: string, mediaType?: string): Promise<EveEvalTurn>;
|
|
181
206
|
}
|
|
207
|
+
/** Driver for one independent session, exposed by `t.newSession()` and target attachment helpers. */
|
|
208
|
+
export interface EveEvalSession extends EveEvalSessionDriver, EveEvalAssertions, EveEvalOutputAssertions {
|
|
209
|
+
}
|
|
182
210
|
/**
|
|
183
211
|
* One completed eval-driver turn.
|
|
184
212
|
*/
|
|
185
|
-
export interface EveEvalTurn {
|
|
213
|
+
export interface EveEvalTurn extends EveEvalAssertions, EveEvalOutputAssertions {
|
|
186
214
|
readonly data: unknown;
|
|
187
215
|
readonly events: readonly HandleMessageStreamEvent[];
|
|
188
216
|
readonly inputRequests: readonly InputRequest[];
|
|
189
217
|
readonly message: string | undefined;
|
|
218
|
+
readonly sessionId: string;
|
|
190
219
|
readonly status: "completed" | "failed" | "waiting";
|
|
191
220
|
readonly toolCalls: readonly EveEvalToolCall[];
|
|
221
|
+
/** Require exactly one matching tool call, record a gate, and return it for dependent checks. */
|
|
222
|
+
requireToolCall(name: string, options?: Omit<EveEvalToolCallMatchOptions, "count">): EveEvalToolCall;
|
|
192
223
|
expectOk(): this;
|
|
193
224
|
}
|
|
194
225
|
/**
|
|
@@ -232,15 +263,13 @@ export interface JudgeContext {
|
|
|
232
263
|
}
|
|
233
264
|
/**
|
|
234
265
|
* The single context passed to an eval's `test(t)` function. It drives the
|
|
235
|
-
* primary session
|
|
266
|
+
* primary session, carries the run-level
|
|
236
267
|
* and value-level assertion vocabulary, and exposes `judge` for LLM-as-judge.
|
|
237
268
|
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* evaluate the supplied value immediately. Use plain `throw` /
|
|
241
|
-
* `turn.expectOk()` for bespoke preconditions that should abort the run.
|
|
269
|
+
* Scoped assertions (`succeeded`, `calledTool`, …) record an entry evaluated
|
|
270
|
+
* after the test body; `check`, `require`, and `judge` evaluate explicit values.
|
|
242
271
|
*/
|
|
243
|
-
export interface EveEvalContext extends
|
|
272
|
+
export interface EveEvalContext extends EveEvalSessionDriver, EveEvalAssertions {
|
|
244
273
|
/** Eval timeout signal. */
|
|
245
274
|
readonly signal: AbortSignal;
|
|
246
275
|
/** Current target under test. */
|
|
@@ -253,24 +282,12 @@ export interface EveEvalContext extends EveEvalSession {
|
|
|
253
282
|
sleep(ms?: number): Promise<void>;
|
|
254
283
|
/** Create an additional independent session against the same target. */
|
|
255
284
|
newSession(): EveEvalSession;
|
|
256
|
-
completed(): AssertionHandle;
|
|
257
|
-
didNotFail(): AssertionHandle;
|
|
258
|
-
waiting(): AssertionHandle;
|
|
259
|
-
messageIncludes(token: string | RegExp): AssertionHandle;
|
|
260
|
-
calledTool(name: string, options?: EveEvalToolCallMatchOptions): AssertionHandle;
|
|
261
|
-
/** Sugar for `calledTool("load_skill", { input: { skill }, ... })`. */
|
|
262
|
-
loadedSkill(skill: string, options?: EveEvalSkillLoadMatchOptions): AssertionHandle;
|
|
263
|
-
notCalledTool(name: string): AssertionHandle;
|
|
264
|
-
toolOrder(names: readonly string[]): AssertionHandle;
|
|
265
|
-
usedNoTools(): AssertionHandle;
|
|
266
|
-
maxToolCalls(max: number): AssertionHandle;
|
|
267
|
-
calledSubagent(name: string, options?: EveEvalSubagentCallMatchOptions): AssertionHandle;
|
|
268
|
-
noFailedActions(): AssertionHandle;
|
|
269
|
-
event(predicate: (events: readonly HandleMessageStreamEvent[]) => boolean, label: string): AssertionHandle;
|
|
270
|
-
outputEquals(value: unknown): AssertionHandle;
|
|
271
|
-
outputMatches(schema: StandardSchemaV1): AssertionHandle;
|
|
272
285
|
/** Apply a value-level assertion (from `eve/evals/expect`) to a value. */
|
|
273
286
|
check(value: unknown, assertion: Assertion): AssertionHandle;
|
|
287
|
+
/** Record an immediate gate and abort dependent control flow when it fails. */
|
|
288
|
+
require<T>(value: T, assertion: Assertion): Promise<T>;
|
|
289
|
+
/** Mark this eval as intentionally skipped and stop executing its test body. */
|
|
290
|
+
skip(reason: string): never;
|
|
274
291
|
/** LLM-as-judge assertions, bound to the resolved judge model. */
|
|
275
292
|
readonly judge: JudgeContext;
|
|
276
293
|
}
|
|
@@ -372,8 +389,9 @@ export type EveEval = EveEvalDefinition & {
|
|
|
372
389
|
* - `"passed"` — no execution error, every gate held, every soft threshold met
|
|
373
390
|
* - `"failed"` — a gate assertion failed or execution errored (timeout, transport, thrown task)
|
|
374
391
|
* - `"scored"` — every gate held but a soft assertion fell below its threshold
|
|
392
|
+
* - `"skipped"` — the test body intentionally called `t.skip(reason)`
|
|
375
393
|
*/
|
|
376
|
-
export type EveEvalVerdict = "passed" | "failed" | "scored";
|
|
394
|
+
export type EveEvalVerdict = "passed" | "failed" | "scored" | "skipped";
|
|
377
395
|
/**
|
|
378
396
|
* Result of executing and asserting one eval.
|
|
379
397
|
*
|
|
@@ -388,6 +406,8 @@ export interface EveEvalResult {
|
|
|
388
406
|
/** Per-eval verdict; see {@link EveEvalVerdict}. */
|
|
389
407
|
readonly verdict: EveEvalVerdict;
|
|
390
408
|
readonly error?: string;
|
|
409
|
+
/** Why the eval intentionally skipped, present only for a `"skipped"` verdict. */
|
|
410
|
+
readonly skipReason?: string;
|
|
391
411
|
readonly startedAt: string;
|
|
392
412
|
readonly completedAt: string;
|
|
393
413
|
}
|
|
@@ -405,6 +425,8 @@ export interface EveEvalRunSummary {
|
|
|
405
425
|
readonly failed: number;
|
|
406
426
|
/** Evals with verdict `"scored"` (below-threshold soft assertions only). */
|
|
407
427
|
readonly scored: number;
|
|
428
|
+
/** Evals intentionally skipped by their test body. */
|
|
429
|
+
readonly skipped: number;
|
|
408
430
|
/** The execution-error subset of `failed` (timeouts, connection failures, exceptions). */
|
|
409
431
|
readonly errored: number;
|
|
410
432
|
}
|