@fevex/core 0.1.0-alpha.10 → 0.1.0-alpha.12

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.
Files changed (34) hide show
  1. package/README.md +105 -15
  2. package/dist/agents/index.d.mts +1 -1
  3. package/dist/artifacts/index.d.mts +1 -1
  4. package/dist/channels/index.d.mts +1 -1
  5. package/dist/evals/index.d.mts +198 -5
  6. package/dist/evals/index.mjs +478 -12
  7. package/dist/{fevex-_KOsOsB8.d.mts → fevex-B4eNkGTa.d.mts} +19 -10
  8. package/dist/http/index.d.mts +4 -4
  9. package/dist/{index-DHcfIfh-.d.mts → index-AOouohIx.d.mts} +3 -3
  10. package/dist/{index-B8EcvWeN.d.mts → index-AVlWktA8.d.mts} +3 -3
  11. package/dist/{index-BEqYWean.d.mts → index-B7fxPgn_.d.mts} +5 -5
  12. package/dist/{index-Btth7rdL.d.mts → index-CAVMQT9j.d.mts} +3 -3
  13. package/dist/{index-D7r0puBi.d.mts → index-Cbe1ZiyN.d.mts} +1 -1
  14. package/dist/{index-CIHWc5V1.d.mts → index-D1LAhzgC.d.mts} +2 -2
  15. package/dist/{index-26ilbzAs.d.mts → index-DMmrhb8l.d.mts} +4 -4
  16. package/dist/index-KKq-_SiK.d.mts +43 -0
  17. package/dist/{index-lCCFokc6.d.mts → index-giFR5t0x.d.mts} +5 -3
  18. package/dist/{index-DUhNa7IR.d.mts → index-k-gBmfJP.d.mts} +1 -1
  19. package/dist/index.d.mts +13 -13
  20. package/dist/index.mjs +214 -31
  21. package/dist/knowledge/index.d.mts +1 -1
  22. package/dist/models/index.d.mts +1 -1
  23. package/dist/observability/index.d.mts +1 -1
  24. package/dist/policies/index.d.mts +2 -2
  25. package/dist/policies/index.mjs +39 -1
  26. package/dist/runtime/index.d.mts +1 -1
  27. package/dist/sandbox/index.d.mts +1 -1
  28. package/dist/teams/index.d.mts +1 -1
  29. package/dist/testing/index.d.mts +6 -6
  30. package/dist/tools/index.d.mts +2 -2
  31. package/dist/{types-CckDlhYo.d.mts → types-B6hD1o2I.d.mts} +19 -1
  32. package/dist/workflows/index.d.mts +1 -1
  33. package/package.json +1 -1
  34. package/dist/index-CFzrvDQ0.d.mts +0 -21
@@ -1,3 +1,4 @@
1
+ import { t as FevexRunError } from "../run-error-Cr17wPGH.mjs";
1
2
  //#region src/evals/canonical-json.ts
2
3
  function canonicalize(value) {
3
4
  if (Array.isArray(value)) return value.map(canonicalize);
@@ -10,9 +11,13 @@ function serializeCanonical(value) {
10
11
  }
11
12
  //#endregion
12
13
  //#region src/evals/evaluation.ts
14
+ const DEFAULT_EVAL_DEADLINE_MS = 10 * 6e4;
13
15
  function assertNonEmpty(value, name) {
14
16
  if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be non-empty`);
15
17
  }
18
+ function assertDeadline(value, name) {
19
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new TypeError(`${name} must be a positive safe integer`);
20
+ }
16
21
  function errorMessage(error) {
17
22
  return error instanceof Error ? error.message : String(error);
18
23
  }
@@ -44,11 +49,22 @@ function validateOptions(options) {
44
49
  assertNonEmpty(options.dataset.name, "dataset.name");
45
50
  assertNonEmpty(options.dataset.version, "dataset.version");
46
51
  if (!Array.isArray(options.dataset.cases) || !options.dataset.cases.length) throw new TypeError("dataset.cases must contain at least one case");
52
+ assertDeadline(options.deadlineMs, "deadlineMs");
47
53
  const caseIds = /* @__PURE__ */ new Set();
48
54
  for (const testCase of options.dataset.cases) {
49
55
  assertNonEmpty(testCase.id, "case.id");
50
56
  if (caseIds.has(testCase.id)) throw new TypeError(`Eval case "${testCase.id}" is duplicated`);
51
57
  caseIds.add(testCase.id);
58
+ assertDeadline(testCase.deadlineMs, `case "${testCase.id}" deadlineMs`);
59
+ for (const approval of testCase.approvals ?? []) {
60
+ if (approval.decision !== "approve" && approval.decision !== "reject") throw new TypeError(`Eval case "${testCase.id}" has an invalid approval decision`);
61
+ if (approval.toolName !== void 0 && !approval.toolName.trim()) throw new TypeError(`Eval case "${testCase.id}" approval toolName must be non-empty`);
62
+ if (approval.actor !== void 0 && !approval.actor.id.trim()) throw new TypeError(`Eval case "${testCase.id}" approval actor must be non-empty`);
63
+ }
64
+ for (const elicitation of testCase.elicitations ?? []) {
65
+ if (elicitation.prompt !== void 0 && !elicitation.prompt.trim()) throw new TypeError(`Eval case "${testCase.id}" elicitation prompt must be non-empty`);
66
+ if (elicitation.actor !== void 0 && !elicitation.actor.id.trim()) throw new TypeError(`Eval case "${testCase.id}" elicitation actor must be non-empty`);
67
+ }
52
68
  }
53
69
  if (!Array.isArray(options.scorers) || !options.scorers.length) throw new TypeError("scorers must contain at least one scorer");
54
70
  const scorerIds = /* @__PURE__ */ new Set();
@@ -60,21 +76,187 @@ function validateOptions(options) {
60
76
  }
61
77
  if (options.reporters !== void 0 && (!Array.isArray(options.reporters) || options.reporters.some((reporter) => typeof reporter.report !== "function"))) throw new TypeError("reporters must implement report");
62
78
  }
63
- async function evaluateCase(options, testCase) {
64
- options.signal?.throwIfAborted();
65
- const startedAt = performance.now();
79
+ function appendEvent(events, seen, event) {
80
+ if (seen.has(event.id)) return;
81
+ seen.add(event.id);
82
+ events.push(event);
83
+ }
84
+ function trajectoryFrom(events) {
85
+ const trajectory = [];
86
+ const tools = /* @__PURE__ */ new Map();
87
+ const toolNames = /* @__PURE__ */ new Map();
88
+ const elicitationPrompts = /* @__PURE__ */ new Map();
89
+ for (const event of [...events].sort((left, right) => left.sequence - right.sequence)) switch (event.type) {
90
+ case "elicitation.requested":
91
+ elicitationPrompts.set(event.payload.request.id, event.payload.request.prompt);
92
+ elicitationPrompts.set(event.payload.request.toolCallId, event.payload.request.prompt);
93
+ break;
94
+ case "approval.requested":
95
+ toolNames.set(event.payload.toolCallId, event.payload.toolName);
96
+ break;
97
+ case "tool.started": {
98
+ toolNames.set(event.payload.toolCallId, event.payload.toolName);
99
+ const startedAttempt = event.payload.attempt ?? 1;
100
+ for (const previous of tools.values()) if (previous.toolCallId === event.payload.toolCallId && previous.status === "retrying" && previous.attempt < startedAttempt) previous.status = "failed";
101
+ const entry = {
102
+ type: "tool",
103
+ step: event.payload.step,
104
+ toolCallId: event.payload.toolCallId,
105
+ name: event.payload.toolName,
106
+ attempt: event.payload.attempt ?? 1,
107
+ risk: event.payload.risk ?? "read",
108
+ ...event.payload.declaredRisk === void 0 ? {} : { declaredRisk: event.payload.declaredRisk },
109
+ status: "started"
110
+ };
111
+ tools.set(`${entry.toolCallId}:${entry.attempt}`, entry);
112
+ trajectory.push(entry);
113
+ break;
114
+ }
115
+ case "tool.completed":
116
+ case "tool.failed":
117
+ case "tool.retrying":
118
+ case "tool.execution_unknown": {
119
+ const attempt = "attempt" in event.payload ? event.payload.attempt : void 0;
120
+ const targetAttempt = event.type === "tool.retrying" ? Math.max(1, (attempt ?? 1) - 1) : attempt;
121
+ const entry = targetAttempt === void 0 ? [...tools.values()].filter(({ toolCallId }) => toolCallId === event.payload.toolCallId).sort((left, right) => right.attempt - left.attempt)[0] : tools.get(`${event.payload.toolCallId}:${targetAttempt}`);
122
+ if (!entry) break;
123
+ entry.status = event.type === "tool.completed" ? "completed" : event.type === "tool.retrying" ? "retrying" : event.type === "tool.execution_unknown" ? "unknown" : "failed";
124
+ break;
125
+ }
126
+ case "approval.resolved":
127
+ trajectory.push({
128
+ type: "approval",
129
+ toolCallId: event.payload.toolCallId,
130
+ toolName: toolNames.get(event.payload.toolCallId) ?? event.payload.toolCallId,
131
+ decision: event.payload.decision,
132
+ actorId: event.payload.actorId
133
+ });
134
+ break;
135
+ case "elicitation.resolved": {
136
+ const prompt = elicitationPrompts.get(event.payload.requestId);
137
+ trajectory.push({
138
+ type: "elicitation",
139
+ requestId: event.payload.requestId,
140
+ toolCallId: event.payload.toolCallId,
141
+ ...prompt === void 0 ? {} : { prompt },
142
+ actorId: event.payload.actorId
143
+ });
144
+ break;
145
+ }
146
+ case "policy.evaluated":
147
+ if (event.payload.decision !== "allow") trajectory.push({
148
+ type: "policy",
149
+ policyName: event.payload.policyName,
150
+ toolName: event.payload.toolName,
151
+ ...event.payload.toolCallId === void 0 ? {} : { toolCallId: event.payload.toolCallId },
152
+ action: event.payload.action,
153
+ decision: event.payload.decision,
154
+ risk: event.payload.risk,
155
+ ...event.payload.declaredRisk === void 0 ? {} : { declaredRisk: event.payload.declaredRisk }
156
+ });
157
+ break;
158
+ }
159
+ return trajectory;
160
+ }
161
+ function matchingApproval(approvals, index, toolName) {
162
+ const approval = approvals[index];
163
+ if (!approval) return void 0;
164
+ return approval.toolName === void 0 || approval.toolName === toolName ? approval : void 0;
165
+ }
166
+ function matchingElicitation(elicitations, index, prompt) {
167
+ const elicitation = elicitations[index];
168
+ if (!elicitation) return void 0;
169
+ return elicitation.prompt === void 0 || elicitation.prompt === prompt ? elicitation : void 0;
170
+ }
171
+ async function runCase(app, agentName, testCase, signal, deadlineMs) {
66
172
  const events = [];
173
+ const seen = /* @__PURE__ */ new Set();
174
+ const deadlineAt = Date.now() + deadlineMs;
67
175
  let executionError;
176
+ let timedOut = false;
177
+ let runId;
68
178
  try {
69
- for await (const event of options.app.streamAgent(options.agentName, {
179
+ for await (const event of app.streamAgent(agentName, {
70
180
  input: testCase.input,
71
181
  context: testCase.context,
72
- signal: options.signal
73
- })) events.push(event);
182
+ signal,
183
+ deadlineMs
184
+ })) {
185
+ runId ??= event.runId;
186
+ appendEvent(events, seen, event);
187
+ }
74
188
  } catch (error) {
75
- if (options.signal?.aborted) throw error;
189
+ if (signal?.aborted) throw error;
76
190
  executionError = errorMessage(error);
77
191
  }
192
+ const syncEvents = async () => {
193
+ if (!runId) return;
194
+ for (const event of await app.listEvents(runId)) appendEvent(events, seen, event);
195
+ };
196
+ await syncEvents();
197
+ const approvals = testCase.approvals ?? [];
198
+ const elicitations = testCase.elicitations ?? [];
199
+ let approvalIndex = 0;
200
+ let elicitationIndex = 0;
201
+ while (runId) {
202
+ signal?.throwIfAborted();
203
+ const run = await app.getRun(runId);
204
+ if (run?.status !== "paused") break;
205
+ let resolution;
206
+ if (run.pause?.type === "approval") {
207
+ const pause = run.pause.approval;
208
+ const approval = matchingApproval(approvals, approvalIndex, pause.toolName);
209
+ if (!approval) break;
210
+ approvalIndex += 1;
211
+ resolution = {
212
+ type: "approval",
213
+ approvalId: pause.id,
214
+ decision: approval.decision,
215
+ actor: approval.actor ?? { id: "eval" }
216
+ };
217
+ } else if (run.pause?.type === "elicitation") {
218
+ const request = run.pause.request;
219
+ const elicitation = matchingElicitation(elicitations, elicitationIndex, request.prompt);
220
+ if (!elicitation) break;
221
+ elicitationIndex += 1;
222
+ resolution = {
223
+ type: "elicitation",
224
+ requestId: request.id,
225
+ value: elicitation.value,
226
+ actor: elicitation.actor ?? { id: "eval" }
227
+ };
228
+ } else break;
229
+ try {
230
+ await app.resumeRun(runId, resolution);
231
+ await app.waitForRun(runId, {
232
+ signal,
233
+ timeoutMs: Math.max(0, deadlineAt - Date.now())
234
+ });
235
+ } catch (error) {
236
+ if (signal?.aborted) throw error;
237
+ if (error instanceof FevexRunError && error.code === "RUN_WAIT_TIMEOUT") {
238
+ timedOut = true;
239
+ executionError = "timeout";
240
+ await app.cancelRun(runId).catch(() => {});
241
+ } else executionError = errorMessage(error);
242
+ await syncEvents();
243
+ break;
244
+ }
245
+ await syncEvents();
246
+ }
247
+ const unused = [...approvals.slice(approvalIndex).map((approval) => approval.toolName === void 0 ? "Unused approval resolution" : `Unused approval resolution for tool "${approval.toolName}"`), ...elicitations.slice(elicitationIndex).map((elicitation) => elicitation.prompt === void 0 ? "Unused elicitation resolution" : `Unused elicitation resolution for prompt "${elicitation.prompt}"`)];
248
+ return {
249
+ events,
250
+ ...executionError === void 0 ? {} : { executionError },
251
+ ...timedOut ? { timedOut: true } : {},
252
+ unused
253
+ };
254
+ }
255
+ async function evaluateCase(options, testCase) {
256
+ options.signal?.throwIfAborted();
257
+ const startedAt = performance.now();
258
+ const deadlineMs = testCase.deadlineMs ?? options.deadlineMs ?? DEFAULT_EVAL_DEADLINE_MS;
259
+ const { events, executionError, timedOut, unused } = await runCase(options.app, options.agentName, testCase, options.signal, deadlineMs);
78
260
  const latencyMs = performance.now() - startedAt;
79
261
  const completed = events.find((event) => event.type === "run.completed");
80
262
  const failed = [...events].reverse().find((event) => event.type === "run.failed" || event.type === "run.cancelled");
@@ -87,17 +269,19 @@ async function evaluateCase(options, testCase) {
87
269
  selectedIds.add(event.payload.toolCallId);
88
270
  selectedTools.push(event.payload.toolName);
89
271
  }
272
+ const trajectory = trajectoryFrom(events);
90
273
  const cost = options.cost && usage ? options.cost.calculate({
91
274
  modelRegistryName: options.cost.modelRegistryName,
92
275
  model: options.cost.model,
93
276
  usage
94
277
  }) : void 0;
95
278
  if (cost && (!Number.isFinite(cost.amount) || cost.amount < 0 || !cost.currency.trim())) throw new TypeError("CostCalculator must return a non-negative amount and currency");
96
- const runError = executionError ?? (failed?.type === "run.failed" ? failed.payload.error : failed?.type === "run.cancelled" ? failed.payload.reason : completed ? void 0 : "Run did not complete");
279
+ const runError = timedOut ? "timeout" : failed?.type === "run.failed" ? failed.payload.error : failed?.type === "run.cancelled" ? failed.payload.reason : executionError ?? (completed ? void 0 : "Run did not complete");
97
280
  const context = {
98
281
  testCase,
99
282
  output,
100
283
  tools: selectedTools,
284
+ trajectory,
101
285
  events,
102
286
  usage,
103
287
  cost,
@@ -133,10 +317,12 @@ async function evaluateCase(options, testCase) {
133
317
  status: completed ? "completed" : "failed",
134
318
  ...output === void 0 ? {} : { output },
135
319
  tools: selectedTools,
320
+ trajectory,
136
321
  ...usage ? { usage } : {},
137
322
  ...cost ? { cost } : {},
138
323
  latencyMs,
139
324
  scores,
325
+ ...unused.length ? { warnings: unused } : {},
140
326
  ...runError ? { error: runError } : {}
141
327
  };
142
328
  }
@@ -159,6 +345,7 @@ function summarize(cases) {
159
345
  failedScoreCount: scores.filter(({ status }) => status === "failed").length,
160
346
  skippedScoreCount: scores.filter(({ status }) => status === "skipped").length,
161
347
  errorScoreCount: scores.filter(({ status }) => status === "error").length,
348
+ warningCount: cases.reduce((sum, testCase) => sum + (testCase.warnings?.length ?? 0), 0),
162
349
  ...numericScores.length ? { averageScore: numericScores.reduce((sum, score) => sum + score, 0) / numericScores.length } : {},
163
350
  ...Object.keys(usage).length ? { usage } : {},
164
351
  ...totalCost ? { cost: totalCost } : {},
@@ -189,13 +376,37 @@ async function runEvaluation(options) {
189
376
  function serializeEvalReport(report) {
190
377
  return `${serializeCanonical(report)}\n`;
191
378
  }
379
+ const TOOL_RISKS$1 = /* @__PURE__ */ new Set([
380
+ "read",
381
+ "write",
382
+ "sensitive",
383
+ "destructive"
384
+ ]);
385
+ const TOOL_STATUSES = /* @__PURE__ */ new Set([
386
+ "started",
387
+ "completed",
388
+ "failed",
389
+ "unknown",
390
+ "retrying"
391
+ ]);
392
+ function isTrajectoryEntry(value) {
393
+ if (typeof value !== "object" || value === null) return false;
394
+ const entry = value;
395
+ if (entry.type === "tool") return typeof entry.step === "number" && Number.isFinite(entry.step) && typeof entry.toolCallId === "string" && typeof entry.name === "string" && typeof entry.attempt === "number" && Number.isSafeInteger(entry.attempt) && TOOL_RISKS$1.has(entry.risk) && (entry.declaredRisk === void 0 || TOOL_RISKS$1.has(entry.declaredRisk)) && TOOL_STATUSES.has(entry.status);
396
+ if (entry.type === "approval") return typeof entry.toolCallId === "string" && typeof entry.toolName === "string" && (entry.decision === "approve" || entry.decision === "reject") && typeof entry.actorId === "string";
397
+ if (entry.type === "elicitation") return typeof entry.requestId === "string" && typeof entry.toolCallId === "string" && (entry.prompt === void 0 || typeof entry.prompt === "string") && typeof entry.actorId === "string";
398
+ if (entry.type === "policy") return typeof entry.policyName === "string" && typeof entry.toolName === "string" && (entry.toolCallId === void 0 || typeof entry.toolCallId === "string") && (entry.action === "tool.execute" || entry.action === "approval.resolve") && (entry.decision === "deny" || entry.decision === "require_approval") && TOOL_RISKS$1.has(entry.risk) && (entry.declaredRisk === void 0 || TOOL_RISKS$1.has(entry.declaredRisk));
399
+ return false;
400
+ }
192
401
  function parseEvalReport(json) {
193
402
  const value = JSON.parse(json);
194
403
  if (typeof value !== "object" || value === null || value.schemaVersion !== "fevex.eval.v1" || typeof value.suiteName !== "string" || typeof value.targetVersion !== "string" || typeof value.agentName !== "string" || typeof value.dataset !== "object" || value.dataset === null || typeof value.dataset.name !== "string" || typeof value.dataset.version !== "string" || !Array.isArray(value.cases) || !Array.isArray(value.scorerIds) || !value.scorerIds.every((scorerId) => typeof scorerId === "string" && Boolean(scorerId)) || typeof value.summary !== "object" || value.summary === null) throw new TypeError("Invalid fevex.eval.v1 report");
404
+ const warningCount = value.summary.warningCount;
405
+ if (warningCount !== void 0 && (typeof warningCount !== "number" || !Number.isSafeInteger(warningCount) || warningCount < 0)) throw new TypeError("Invalid fevex.eval.v1 report");
195
406
  const report = value;
196
407
  const caseIds = /* @__PURE__ */ new Set();
197
408
  for (const testCase of report.cases) {
198
- if (typeof testCase !== "object" || testCase === null || typeof testCase.caseId !== "string" || !testCase.caseId || caseIds.has(testCase.caseId) || !["completed", "failed"].includes(testCase.status) || !Array.isArray(testCase.tools) || !testCase.tools.every((tool) => typeof tool === "string") || !Number.isFinite(testCase.latencyMs) || testCase.latencyMs < 0 || !Array.isArray(testCase.scores)) throw new TypeError("Invalid fevex.eval.v1 report");
409
+ if (typeof testCase !== "object" || testCase === null || typeof testCase.caseId !== "string" || !testCase.caseId || caseIds.has(testCase.caseId) || !["completed", "failed"].includes(testCase.status) || !Array.isArray(testCase.tools) || !testCase.tools.every((tool) => typeof tool === "string") || testCase.trajectory !== void 0 && (!Array.isArray(testCase.trajectory) || !testCase.trajectory.every(isTrajectoryEntry)) || testCase.warnings !== void 0 && (!Array.isArray(testCase.warnings) || !testCase.warnings.every((warning) => typeof warning === "string")) || !Number.isFinite(testCase.latencyMs) || testCase.latencyMs < 0 || !Array.isArray(testCase.scores)) throw new TypeError("Invalid fevex.eval.v1 report");
199
410
  caseIds.add(testCase.caseId);
200
411
  for (const score of testCase.scores) if (typeof score !== "object" || score === null || typeof score.scorerId !== "string" || !report.scorerIds.includes(score.scorerId) || ![
201
412
  "passed",
@@ -217,9 +428,10 @@ function textReporter(write) {
217
428
  ...report.cases.map((testCase) => {
218
429
  const passed = testCase.scores.filter(({ status }) => status === "passed").length;
219
430
  const failed = testCase.scores.filter(({ status }) => status === "failed" || status === "error").length;
220
- return `${testCase.caseId}: ${testCase.status}; ${passed} passed, ${failed} failed`;
431
+ const warnings = testCase.warnings?.length ? `; ${testCase.warnings.length} warnings` : "";
432
+ return `${testCase.caseId}: ${testCase.status}; ${passed} passed, ${failed} failed${warnings}`;
221
433
  }),
222
- `summary: ${report.summary.completedCount}/${report.summary.caseCount} completed; ${report.summary.failedScoreCount + report.summary.errorScoreCount} failed scores`
434
+ `summary: ${report.summary.completedCount}/${report.summary.caseCount} completed; ${report.summary.failedScoreCount + report.summary.errorScoreCount} failed scores${report.summary.warningCount ? `; ${report.summary.warningCount} warnings` : ""}`
223
435
  ].join("\n")}\n`);
224
436
  } };
225
437
  }
@@ -294,6 +506,112 @@ function compareEvaluationReports(baseline, current, options = {}) {
294
506
  regressions
295
507
  };
296
508
  }
509
+ function assertRate(value, name) {
510
+ if (value !== void 0 && (!Number.isFinite(value) || value < 0 || value > 1)) throw new TypeError(`${name} must be between 0 and 1`);
511
+ }
512
+ function assertCount(value, name) {
513
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) throw new TypeError(`${name} must be a non-negative safe integer`);
514
+ }
515
+ function scorecardFrom(report) {
516
+ const { summary } = report;
517
+ const scored = summary.passedScoreCount + summary.failedScoreCount + summary.errorScoreCount;
518
+ return {
519
+ caseCount: summary.caseCount,
520
+ completedCount: summary.completedCount,
521
+ completionRate: summary.caseCount === 0 ? 0 : summary.completedCount / summary.caseCount,
522
+ passedScoreCount: summary.passedScoreCount,
523
+ failedScoreCount: summary.failedScoreCount,
524
+ skippedScoreCount: summary.skippedScoreCount,
525
+ errorScoreCount: summary.errorScoreCount,
526
+ warningCount: summary.warningCount ?? 0,
527
+ passRate: scored === 0 ? 0 : summary.passedScoreCount / scored,
528
+ ...summary.averageScore === void 0 ? {} : { averageScore: summary.averageScore },
529
+ latencyMs: summary.latencyMs,
530
+ ...summary.cost === void 0 ? {} : { cost: summary.cost }
531
+ };
532
+ }
533
+ function gateCheck(name, passed, message, expected, actual) {
534
+ return {
535
+ name,
536
+ passed,
537
+ message,
538
+ expected,
539
+ actual
540
+ };
541
+ }
542
+ /**
543
+ * Evaluates minimum scorecard thresholds and an optional baseline. A trajectory
544
+ * or policy regression appears as a failed score and blocks the gate even when
545
+ * the final output is correct.
546
+ */
547
+ function evaluateReleaseGate(report, options = {}) {
548
+ assertRate(options.minCompletionRate, "minCompletionRate");
549
+ assertRate(options.minPassRate, "minPassRate");
550
+ assertRate(options.minAverageScore, "minAverageScore");
551
+ assertCount(options.maxFailedScores, "maxFailedScores");
552
+ assertCount(options.maxErrorScores, "maxErrorScores");
553
+ assertCount(options.maxWarnings, "maxWarnings");
554
+ if (options.maxLatencyMs !== void 0 && (!Number.isFinite(options.maxLatencyMs) || options.maxLatencyMs < 0)) throw new TypeError("maxLatencyMs must be non-negative");
555
+ if (options.maxCost) {
556
+ if (!Number.isFinite(options.maxCost.amount) || options.maxCost.amount < 0) throw new TypeError("maxCost.amount must be non-negative");
557
+ if (!options.maxCost.currency.trim()) throw new TypeError("maxCost.currency must be non-empty");
558
+ }
559
+ for (const scorerId of options.requiredScorers ?? []) assertNonEmpty(scorerId, "requiredScorers entry");
560
+ const scorecard = scorecardFrom(report);
561
+ const checks = [];
562
+ const minCompletionRate = options.minCompletionRate ?? 1;
563
+ checks.push(gateCheck("completion_rate", scorecard.completionRate >= minCompletionRate, `Completion rate ${scorecard.completionRate} (minimum ${minCompletionRate})`, minCompletionRate, scorecard.completionRate));
564
+ const minPassRate = options.minPassRate ?? 1;
565
+ checks.push(gateCheck("pass_rate", scorecard.passRate >= minPassRate, `Pass rate ${scorecard.passRate} (minimum ${minPassRate})`, minPassRate, scorecard.passRate));
566
+ const maxFailedScores = options.maxFailedScores ?? 0;
567
+ checks.push(gateCheck("failed_scores", scorecard.failedScoreCount <= maxFailedScores, `Failed scores ${scorecard.failedScoreCount} (maximum ${maxFailedScores})`, maxFailedScores, scorecard.failedScoreCount));
568
+ const maxErrorScores = options.maxErrorScores ?? 0;
569
+ checks.push(gateCheck("error_scores", scorecard.errorScoreCount <= maxErrorScores, `Scorer errors ${scorecard.errorScoreCount} (maximum ${maxErrorScores})`, maxErrorScores, scorecard.errorScoreCount));
570
+ const maxWarnings = options.maxWarnings ?? 0;
571
+ checks.push(gateCheck("warnings", scorecard.warningCount <= maxWarnings, `Warnings ${scorecard.warningCount} (maximum ${maxWarnings})`, maxWarnings, scorecard.warningCount));
572
+ if (options.minAverageScore !== void 0) checks.push(gateCheck("average_score", scorecard.averageScore !== void 0 && scorecard.averageScore >= options.minAverageScore, `Average score ${scorecard.averageScore ?? "unavailable"} (minimum ${options.minAverageScore})`, options.minAverageScore, scorecard.averageScore ?? null));
573
+ if (options.requiredScorers?.length) {
574
+ const present = new Set(report.scorerIds);
575
+ const passed = new Set(report.cases.flatMap(({ scores }) => scores.filter(({ status }) => status === "passed").map(({ scorerId }) => scorerId)));
576
+ const missing = options.requiredScorers.filter((scorerId) => !present.has(scorerId));
577
+ const unverified = options.requiredScorers.filter((scorerId) => present.has(scorerId) && !passed.has(scorerId));
578
+ const problems = [...missing.length ? [`missing: ${missing.join(", ")}`] : [], ...unverified.length ? [`without a passing result: ${unverified.join(", ")}`] : []];
579
+ checks.push(gateCheck("required_scorers", problems.length === 0, problems.length ? `Required scorer problems: ${problems.join("; ")}` : "All required scorers passed at least once", [...options.requiredScorers], report.scorerIds));
580
+ }
581
+ if (options.maxLatencyMs !== void 0) checks.push(gateCheck("latency", scorecard.latencyMs <= options.maxLatencyMs, `Latency ${scorecard.latencyMs}ms (maximum ${options.maxLatencyMs}ms)`, options.maxLatencyMs, scorecard.latencyMs));
582
+ if (options.maxCost) {
583
+ const cost = scorecard.cost;
584
+ const passed = cost !== void 0 && cost.currency === options.maxCost.currency && cost.amount <= options.maxCost.amount;
585
+ checks.push(gateCheck("cost", passed, `Cost ${cost === void 0 ? "is unavailable" : `${cost.amount} ${cost.currency}`} (maximum ${options.maxCost.amount} ${options.maxCost.currency})`, options.maxCost.amount, cost?.amount ?? null));
586
+ }
587
+ const regressions = options.baseline === void 0 ? [] : compareEvaluationReports(options.baseline, report, options.regression).regressions;
588
+ for (const regression of regressions) checks.push({
589
+ name: `regression.${regression.kind}`,
590
+ passed: false,
591
+ message: regression.message,
592
+ ...regression.scorerId === void 0 ? {} : { actual: regression.scorerId },
593
+ expected: null
594
+ });
595
+ return {
596
+ passed: checks.every(({ passed }) => passed),
597
+ scorecard,
598
+ checks,
599
+ regressions
600
+ };
601
+ }
602
+ var ReleaseGateError = class extends Error {
603
+ result;
604
+ name = "ReleaseGateError";
605
+ constructor(result) {
606
+ super(`Release gate failed: ${result.checks.filter(({ passed }) => !passed).map(({ message }) => message).join("; ")}`);
607
+ this.result = result;
608
+ }
609
+ };
610
+ function assertReleaseGate(report, options = {}) {
611
+ const result = evaluateReleaseGate(report, options);
612
+ if (!result.passed) throw new ReleaseGateError(result);
613
+ return result;
614
+ }
297
615
  //#endregion
298
616
  //#region src/evals/scorers.ts
299
617
  function exactOutputScorer(id = "output.exact") {
@@ -418,5 +736,153 @@ function maxCostScorer(maxAmount, currency, id = "cost.max") {
418
736
  }
419
737
  };
420
738
  }
739
+ function matchesExpectation(actual, expected) {
740
+ if (actual.type !== expected.type) return false;
741
+ for (const [key, value] of Object.entries(expected)) {
742
+ if (key === "type" || value === void 0) continue;
743
+ if (actual[key] !== value) return false;
744
+ }
745
+ return true;
746
+ }
747
+ function scoreTrajectory(trajectory, expected, options) {
748
+ const ordered = options.ordered ?? true;
749
+ const allowAdditional = options.allowAdditional ?? false;
750
+ const types = new Set(expected.map(({ type }) => type));
751
+ const selected = trajectory.filter(({ type }) => types.has(type));
752
+ let passed;
753
+ if (ordered && !allowAdditional) passed = expected.every((step, index) => {
754
+ const actual = selected[index];
755
+ return actual !== void 0 && matchesExpectation(actual, step);
756
+ }) && selected.length === expected.length;
757
+ else if (ordered) {
758
+ let cursor = 0;
759
+ passed = expected.every((step) => {
760
+ while (cursor < selected.length && !matchesExpectation(selected[cursor], step)) cursor += 1;
761
+ if (cursor >= selected.length) return false;
762
+ cursor += 1;
763
+ return true;
764
+ });
765
+ } else {
766
+ const remaining = [...selected];
767
+ passed = expected.every((step) => {
768
+ const index = remaining.findIndex((actual) => matchesExpectation(actual, step));
769
+ if (index < 0) return false;
770
+ remaining.splice(index, 1);
771
+ return true;
772
+ }) && (allowAdditional || remaining.length === 0);
773
+ }
774
+ return {
775
+ passed,
776
+ score: passed ? 1 : 0,
777
+ details: {
778
+ expected,
779
+ actual: selected
780
+ }
781
+ };
782
+ }
783
+ /** Asserts the ordered tool, approval and non-allow policy decisions of a run. */
784
+ function trajectoryScorer(expected, options = {}) {
785
+ return {
786
+ id: options.id ?? "trajectory",
787
+ score: ({ trajectory }) => scoreTrajectory(trajectory, expected, options)
788
+ };
789
+ }
790
+ /** Reads the expected trajectory from each dataset case. */
791
+ function trajectoryExpectationScorer(options = {}) {
792
+ return {
793
+ id: options.id ?? "trajectory",
794
+ score({ testCase, trajectory }) {
795
+ const expected = testCase.expected?.trajectory;
796
+ if (!expected) return {
797
+ skipped: true,
798
+ reason: "No expected trajectory"
799
+ };
800
+ return scoreTrajectory(trajectory, expected, options);
801
+ }
802
+ };
803
+ }
804
+ function approvalScorer(expected, options = {}) {
805
+ return trajectoryScorer(expected, {
806
+ ...options,
807
+ id: options.id ?? "approvals"
808
+ });
809
+ }
810
+ function elicitationScorer(expected, options = {}) {
811
+ return trajectoryScorer(expected, {
812
+ ...options,
813
+ id: options.id ?? "elicitations"
814
+ });
815
+ }
816
+ function policyScorer(expected, options = {}) {
817
+ return trajectoryScorer(expected, {
818
+ ...options,
819
+ id: options.id ?? "policies"
820
+ });
821
+ }
822
+ const TOOL_RISKS = [
823
+ "read",
824
+ "write",
825
+ "sensitive",
826
+ "destructive"
827
+ ];
828
+ /** Asserts that risky tools respected the configured guardrail. */
829
+ function riskGuardrailScorer(options = {}, id = "guardrails.risk") {
830
+ const deny = new Set(options.deny ?? ["destructive"]);
831
+ const requireApproval = new Set(options.requireApproval ?? ["sensitive"]);
832
+ const undeclaredRisk = options.undeclaredRisk ?? "allow";
833
+ if (![
834
+ "allow",
835
+ "require_approval",
836
+ "deny"
837
+ ].includes(undeclaredRisk)) throw new TypeError("undeclaredRisk must be allow, require_approval or deny");
838
+ for (const risk of [...deny, ...requireApproval]) if (!TOOL_RISKS.includes(risk)) throw new TypeError(`Invalid tool risk: ${risk}`);
839
+ return {
840
+ id,
841
+ score({ trajectory }) {
842
+ const violations = [];
843
+ const approvals = new Map(trajectory.filter((step) => step.type === "approval").map((step) => [step.toolCallId, step]));
844
+ for (const step of trajectory) {
845
+ if (step.type !== "tool") continue;
846
+ const approval = approvals.get(step.toolCallId);
847
+ if (step.declaredRisk === void 0) {
848
+ if (undeclaredRisk === "deny") {
849
+ violations.push({
850
+ tool: step.name,
851
+ reason: "undeclared_risk_executed"
852
+ });
853
+ continue;
854
+ }
855
+ if (undeclaredRisk === "require_approval" && approval?.decision !== "approve") {
856
+ violations.push({
857
+ tool: step.name,
858
+ reason: "undeclared_risk_missing_approval"
859
+ });
860
+ continue;
861
+ }
862
+ }
863
+ if (deny.has(step.risk)) {
864
+ violations.push({
865
+ tool: step.name,
866
+ reason: "denied_risk_executed"
867
+ });
868
+ continue;
869
+ }
870
+ if (requireApproval.has(step.risk) && approval?.decision !== "approve") violations.push({
871
+ tool: step.name,
872
+ reason: "missing_approval"
873
+ });
874
+ if (approval?.decision === "reject") violations.push({
875
+ tool: step.name,
876
+ reason: "rejected_tool_executed"
877
+ });
878
+ }
879
+ return {
880
+ passed: violations.length === 0,
881
+ score: violations.length === 0 ? 1 : 0,
882
+ details: { violations }
883
+ };
884
+ }
885
+ };
886
+ }
421
887
  //#endregion
422
- export { compareEvaluationReports, exactOutputScorer, forbiddenToolsScorer, jsonReporter, maxCostScorer, maxLatencyScorer, maxTokensScorer, parseEvalReport, runEvaluation, serializeCanonical, serializeEvalReport, textReporter, toolSelectionScorer };
888
+ export { ReleaseGateError, approvalScorer, assertReleaseGate, compareEvaluationReports, elicitationScorer, evaluateReleaseGate, exactOutputScorer, forbiddenToolsScorer, jsonReporter, maxCostScorer, maxLatencyScorer, maxTokensScorer, parseEvalReport, policyScorer, riskGuardrailScorer, runEvaluation, serializeCanonical, serializeEvalReport, textReporter, toolSelectionScorer, trajectoryExpectationScorer, trajectoryScorer };
@@ -1,13 +1,13 @@
1
- import { S as ConnectionDefinition, j as ToolDefinition, o as ToolEmbeddingCache } from "./index-DHcfIfh-.mjs";
2
- import { t as ModelGateway } from "./index-D7r0puBi.mjs";
3
- import { F as ArtifactStore, f as RunId, o as ExecutionContext, t as AgentEvent } from "./types-CckDlhYo.mjs";
4
- import { r as Sandbox } from "./index-DUhNa7IR.mjs";
5
- import { B as ResumeRunResolution, I as ListEventsOptions, J as RunRequest, O as CredentialStore, Q as Session, R as PurgeRunsOptions, Y as RunResult, Z as RunStore, a as ToolSearchOptions, d as ContextCompactionStats, dt as WorkflowRun, et as SessionId, q as RunRecord, r as ContextBudget, rt as TeamRun, t as AgentDefinition, u as ContextCompactionOptions, x as AgentRun, z as PurgeRunsResult } from "./index-lCCFokc6.mjs";
6
- import { c as ContextProvider, f as MemoryStore } from "./index-CIHWc5V1.mjs";
7
- import { i as ObservabilityStats, r as ObservabilityOptions } from "./index-BEqYWean.mjs";
8
- import { r as PolicyDefinition } from "./index-CFzrvDQ0.mjs";
9
- import { n as WorkflowDefinition } from "./index-Btth7rdL.mjs";
10
- import { t as TeamDefinition } from "./index-26ilbzAs.mjs";
1
+ import { S as ConnectionDefinition, j as ToolDefinition, o as ToolEmbeddingCache } from "./index-AOouohIx.mjs";
2
+ import { t as ModelGateway } from "./index-Cbe1ZiyN.mjs";
3
+ import { F as ArtifactStore, f as RunId, o as ExecutionContext, t as AgentEvent } from "./types-B6hD1o2I.mjs";
4
+ import { r as Sandbox } from "./index-k-gBmfJP.mjs";
5
+ import { B as ResumeRunResolution, I as ListEventsOptions, J as RunRequest, O as CredentialStore, Q as Session, R as PurgeRunsOptions, Y as RunResult, Z as RunStore, a as ToolSearchOptions, d as ContextCompactionStats, dt as WorkflowRun, et as SessionId, q as RunRecord, r as ContextBudget, rt as TeamRun, t as AgentDefinition, u as ContextCompactionOptions, x as AgentRun, z as PurgeRunsResult } from "./index-giFR5t0x.mjs";
6
+ import { c as ContextProvider, f as MemoryStore } from "./index-D1LAhzgC.mjs";
7
+ import { i as ObservabilityStats, r as ObservabilityOptions } from "./index-B7fxPgn_.mjs";
8
+ import { r as PolicyDefinition } from "./index-KKq-_SiK.mjs";
9
+ import { n as WorkflowDefinition } from "./index-CAVMQT9j.mjs";
10
+ import { t as TeamDefinition } from "./index-DMmrhb8l.mjs";
11
11
  //#region src/fevex.d.ts
12
12
  interface FevexAdmissionRequest {
13
13
  kind: 'agent' | 'workflow' | 'team';
@@ -93,6 +93,15 @@ interface Fevex {
93
93
  streamTeam<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): AsyncIterable<AgentEvent>;
94
94
  getTeamRun<TOutput = unknown>(runId: RunId): Promise<TeamRun<TOutput> | undefined>;
95
95
  getRun<TOutput = unknown>(runId: RunId): Promise<RunRecord<TOutput> | undefined>;
96
+ /**
97
+ * Waits until a run leaves `running` (paused or terminal) and returns its
98
+ * latest record. Resolves immediately for paused and terminal runs, and with
99
+ * `undefined` when the run does not exist.
100
+ */
101
+ waitForRun<TOutput = unknown>(runId: RunId, options?: {
102
+ signal?: AbortSignal;
103
+ timeoutMs?: number;
104
+ }): Promise<RunRecord<TOutput> | undefined>;
96
105
  listEvents(runId: RunId, options?: ListEventsOptions): Promise<AgentEvent[]>;
97
106
  cancelRun(runId: RunId): Promise<boolean>;
98
107
  resumeRun<TOutput = unknown>(runId: RunId, resolution: ResumeRunResolution): Promise<RunRecord<TOutput>>;
@@ -1,7 +1,7 @@
1
- import { c as ReasoningEffort } from "../index-D7r0puBi.mjs";
2
- import { D as ArtifactCatalog, F as ArtifactStore, f as RunId, k as ArtifactDescriptor, l as JsonValue, o as ExecutionContext, t as AgentEvent } from "../types-CckDlhYo.mjs";
3
- import { dt as WorkflowRun, et as SessionId, q as RunRecord, rt as TeamRun, x as AgentRun } from "../index-lCCFokc6.mjs";
4
- import { t as Fevex } from "../fevex-_KOsOsB8.mjs";
1
+ import { c as ReasoningEffort } from "../index-Cbe1ZiyN.mjs";
2
+ import { D as ArtifactCatalog, F as ArtifactStore, f as RunId, k as ArtifactDescriptor, l as JsonValue, o as ExecutionContext, t as AgentEvent } from "../types-B6hD1o2I.mjs";
3
+ import { dt as WorkflowRun, et as SessionId, q as RunRecord, rt as TeamRun, x as AgentRun } from "../index-giFR5t0x.mjs";
4
+ import { t as Fevex } from "../fevex-B4eNkGTa.mjs";
5
5
  //#region src/http/protocol.d.ts
6
6
  declare const FEVEX_HTTP_PROTOCOL_VERSION = "3";
7
7
  declare const FEVEX_HTTP_PROTOCOL_VERSION_HEADER = "Fevex-Protocol-Version";
@@ -1,6 +1,6 @@
1
- import { u as EmbeddingModel } from "./index-D7r0puBi.mjs";
2
- import { F as ArtifactStore, f as RunId, g as ToolSpec, h as ToolEventSource, l as JsonValue, o as ExecutionContext, s as JsonObject } from "./types-CckDlhYo.mjs";
3
- import { i as SandboxCapabilities, r as Sandbox } from "./index-DUhNa7IR.mjs";
1
+ import { u as EmbeddingModel } from "./index-Cbe1ZiyN.mjs";
2
+ import { F as ArtifactStore, f as RunId, g as ToolSpec, h as ToolEventSource, l as JsonValue, o as ExecutionContext, s as JsonObject } from "./types-B6hD1o2I.mjs";
3
+ import { i as SandboxCapabilities, r as Sandbox } from "./index-k-gBmfJP.mjs";
4
4
  import { StandardSchemaV1 } from "@standard-schema/spec";
5
5
  //#region src/internal/exact-definition.d.ts
6
6
  type ExactDefinition<TInput, TShape> = TInput & { readonly [TKey in Exclude<keyof TInput, keyof TShape>]: never; };