@assemble-dev/evals 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1203 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ EvalCasePerformanceSchema: () => EvalCasePerformanceSchema,
24
+ EvalCaseResultSchema: () => EvalCaseResultSchema,
25
+ EvalMetricBreakdownSchema: () => EvalMetricBreakdownSchema,
26
+ EvalMetricResultSchema: () => EvalMetricResultSchema,
27
+ EvalRunFileSchema: () => EvalRunFileSchema,
28
+ EvalSuitePerformanceSummarySchema: () => EvalSuitePerformanceSummarySchema,
29
+ EvalSuiteSummarySchema: () => EvalSuiteSummarySchema,
30
+ EvalWebhookCaseSchema: () => EvalWebhookCaseSchema,
31
+ EvalWebhookPayloadSchema: () => EvalWebhookPayloadSchema,
32
+ ExpectedApprovalDecisionSchema: () => ExpectedApprovalDecisionSchema,
33
+ ExpectedToolCallSchema: () => ExpectedToolCallSchema,
34
+ JudgeVerdictSchema: () => JudgeVerdictSchema,
35
+ SerializedEvalCaseExpectationSchema: () => SerializedEvalCaseExpectationSchema,
36
+ SerializedEvalCaseSchema: () => SerializedEvalCaseSchema,
37
+ aggregateSuiteMetrics: () => aggregateSuiteMetrics,
38
+ approvalCorrectnessMetricName: () => approvalCorrectnessMetricName,
39
+ areJsonValuesDeeplyEqual: () => areJsonValuesDeeplyEqual,
40
+ buildEvalWebhookPayload: () => buildEvalWebhookPayload,
41
+ collectRunPerformance: () => collectRunPerformance,
42
+ createFailedMetricResult: () => createFailedMetricResult,
43
+ createPassedMetricResult: () => createPassedMetricResult,
44
+ createScoredMetricResult: () => createScoredMetricResult,
45
+ createSkippedMetricResult: () => createSkippedMetricResult,
46
+ defaultEvalResultsDirectory: () => defaultEvalResultsDirectory,
47
+ defaultJudgeMetricName: () => defaultJudgeMetricName,
48
+ defaultJudgePassThreshold: () => defaultJudgePassThreshold,
49
+ defineJudgeMetric: () => defineJudgeMetric,
50
+ determineEvalSuiteStatus: () => determineEvalSuiteStatus,
51
+ evalSuite: () => evalSuite,
52
+ evaluateApprovalCorrectnessMetric: () => evaluateApprovalCorrectnessMetric,
53
+ evaluateCaseMetrics: () => evaluateCaseMetrics,
54
+ evaluateJudgeMetric: () => evaluateJudgeMetric,
55
+ evaluateSchemaValidityMetric: () => evaluateSchemaValidityMetric,
56
+ evaluateTaskSuccessMetric: () => evaluateTaskSuccessMetric,
57
+ evaluateToolAccuracyMetric: () => evaluateToolAccuracyMetric,
58
+ isJsonObjectValue: () => isJsonObjectValue,
59
+ loadEvalCases: () => loadEvalCases,
60
+ loadEvalCasesFromJsonlFile: () => loadEvalCasesFromJsonlFile,
61
+ loadEvalRunFile: () => loadEvalRunFile,
62
+ postEvalResultsToWebhook: () => postEvalResultsToWebhook,
63
+ resolveEvalRunFilePath: () => resolveEvalRunFilePath,
64
+ resolveEvalRunsUploadUrl: () => resolveEvalRunsUploadUrl,
65
+ runEvalSuite: () => runEvalSuite,
66
+ runStatusMetricName: () => runStatusMetricName,
67
+ saveEvalRunFile: () => saveEvalRunFile,
68
+ schemaValidityMetricName: () => schemaValidityMetricName,
69
+ taskSuccessMetricName: () => taskSuccessMetricName,
70
+ toolAccuracyMetricName: () => toolAccuracyMetricName
71
+ });
72
+ module.exports = __toCommonJS(index_exports);
73
+
74
+ // src/eval-case.ts
75
+ var import_zod = require("zod");
76
+ var ExpectedToolCallSchema = import_zod.z.object({
77
+ toolName: import_zod.z.string().min(1),
78
+ count: import_zod.z.number().int().min(1).optional()
79
+ });
80
+ var ExpectedApprovalDecisionSchema = import_zod.z.object({
81
+ toolName: import_zod.z.string().min(1).optional(),
82
+ status: import_zod.z.enum(["approved", "rejected"])
83
+ });
84
+
85
+ // src/eval-suite.ts
86
+ var import_errors = require("@assemble-dev/shared-types/errors");
87
+ var import_errors2 = require("@assemble-dev/shared-utils/errors");
88
+
89
+ // src/schema-validation-formatting.ts
90
+ function formatSchemaValidationError(error) {
91
+ if (error.issues === void 0 || error.issues.length === 0) {
92
+ return error.message;
93
+ }
94
+ return error.issues.map(formatSchemaValidationIssue).join("; ");
95
+ }
96
+ function formatSchemaValidationIssue(issue) {
97
+ if (issue.path === void 0 || issue.path.length === 0) {
98
+ return issue.message;
99
+ }
100
+ return `${issue.path.map(String).join(".")}: ${issue.message}`;
101
+ }
102
+
103
+ // src/eval-suite.ts
104
+ function evalSuite(config) {
105
+ ensureSuiteNameIsPresent(config.name);
106
+ const rawCases = config.cases;
107
+ ensureSuiteHasCases(config.name, rawCases);
108
+ ensureCaseNamesAreUnique(config.name, rawCases);
109
+ const validatedCases = rawCases.map(
110
+ (evalCase) => buildValidatedEvalCase(config.workflow, evalCase)
111
+ );
112
+ return Object.freeze({
113
+ name: config.name,
114
+ workflow: config.workflow,
115
+ cases: Object.freeze(validatedCases)
116
+ });
117
+ }
118
+ function ensureSuiteNameIsPresent(suiteName) {
119
+ if (suiteName.trim() !== "") {
120
+ return;
121
+ }
122
+ throw new import_errors2.AppError({
123
+ code: import_errors.AppErrorCode.ValidationFailed,
124
+ message: "eval suite name must not be empty",
125
+ statusCode: 422
126
+ });
127
+ }
128
+ function ensureSuiteHasCases(suiteName, cases) {
129
+ if (cases.length > 0) {
130
+ return;
131
+ }
132
+ throw new import_errors2.AppError({
133
+ code: import_errors.AppErrorCode.ValidationFailed,
134
+ message: `eval suite "${suiteName}" must define at least one case`,
135
+ statusCode: 422
136
+ });
137
+ }
138
+ function ensureCaseNamesAreUnique(suiteName, cases) {
139
+ const seenCaseNames = /* @__PURE__ */ new Set();
140
+ for (const evalCase of cases) {
141
+ if (evalCase.name.trim() === "") {
142
+ throw new import_errors2.AppError({
143
+ code: import_errors.AppErrorCode.ValidationFailed,
144
+ message: `eval suite "${suiteName}" contains a case with an empty name`,
145
+ statusCode: 422
146
+ });
147
+ }
148
+ if (seenCaseNames.has(evalCase.name)) {
149
+ throw new import_errors2.AppError({
150
+ code: import_errors.AppErrorCode.ValidationFailed,
151
+ message: `eval suite "${suiteName}" contains duplicate case name "${evalCase.name}"`,
152
+ statusCode: 422
153
+ });
154
+ }
155
+ seenCaseNames.add(evalCase.name);
156
+ }
157
+ }
158
+ function buildValidatedEvalCase(workflowDefinition, evalCase) {
159
+ const validatedInput = parseEvalCaseInput(workflowDefinition, evalCase);
160
+ const validatedExpectation = buildValidatedExpectation(
161
+ workflowDefinition,
162
+ evalCase
163
+ );
164
+ if (validatedExpectation === void 0) {
165
+ return Object.freeze({ name: evalCase.name, input: validatedInput });
166
+ }
167
+ return Object.freeze({
168
+ name: evalCase.name,
169
+ input: validatedInput,
170
+ expected: validatedExpectation
171
+ });
172
+ }
173
+ function parseEvalCaseInput(workflowDefinition, evalCase) {
174
+ const parsedInput = workflowDefinition.inputSchema.safeParse(evalCase.input);
175
+ if (!parsedInput.success) {
176
+ throw new import_errors2.AppError({
177
+ code: import_errors.AppErrorCode.ValidationFailed,
178
+ message: `eval case "${evalCase.name}" input failed workflow "${workflowDefinition.name}" input schema validation: ${formatSchemaValidationError(parsedInput.error)}`,
179
+ statusCode: 422
180
+ });
181
+ }
182
+ return parsedInput.data;
183
+ }
184
+ function buildValidatedExpectation(workflowDefinition, evalCase) {
185
+ const expectation = evalCase.expected;
186
+ if (expectation === void 0) {
187
+ return void 0;
188
+ }
189
+ const validatedExpectation = {};
190
+ if (expectation.output !== void 0) {
191
+ validatedExpectation.output = parseExpectedOutput(
192
+ workflowDefinition,
193
+ evalCase.name,
194
+ expectation.output
195
+ );
196
+ }
197
+ if (expectation.outputFields !== void 0) {
198
+ validatedExpectation.outputFields = { ...expectation.outputFields };
199
+ }
200
+ if (expectation.outputPredicate !== void 0) {
201
+ validatedExpectation.outputPredicate = expectation.outputPredicate;
202
+ }
203
+ if (expectation.toolCalls !== void 0) {
204
+ validatedExpectation.toolCalls = expectation.toolCalls.map((toolCall) => ({
205
+ ...toolCall
206
+ }));
207
+ }
208
+ if (expectation.approvalDecisions !== void 0) {
209
+ validatedExpectation.approvalDecisions = expectation.approvalDecisions.map(
210
+ (approvalDecision) => ({ ...approvalDecision })
211
+ );
212
+ }
213
+ return Object.freeze(validatedExpectation);
214
+ }
215
+ function parseExpectedOutput(workflowDefinition, caseName, expectedOutput) {
216
+ if (workflowDefinition.outputSchema === null) {
217
+ return expectedOutput;
218
+ }
219
+ const parsedOutput = workflowDefinition.outputSchema.safeParse(expectedOutput);
220
+ if (!parsedOutput.success) {
221
+ throw new import_errors2.AppError({
222
+ code: import_errors.AppErrorCode.ValidationFailed,
223
+ message: `eval case "${caseName}" expected output failed workflow "${workflowDefinition.name}" output schema validation: ${formatSchemaValidationError(parsedOutput.error)}`,
224
+ statusCode: 422
225
+ });
226
+ }
227
+ return parsedOutput.data;
228
+ }
229
+
230
+ // src/dataset-loading.ts
231
+ var import_node_fs = require("fs");
232
+ var import_errors3 = require("@assemble-dev/shared-types/errors");
233
+ var import_json = require("@assemble-dev/shared-types/json");
234
+ var import_errors4 = require("@assemble-dev/shared-utils/errors");
235
+ var import_zod2 = require("zod");
236
+ var SerializedEvalCaseExpectationSchema = import_zod2.z.strictObject({
237
+ output: import_json.JsonValueSchema.optional(),
238
+ outputFields: import_zod2.z.record(import_zod2.z.string(), import_json.JsonValueSchema).optional(),
239
+ toolCalls: import_zod2.z.array(ExpectedToolCallSchema).optional(),
240
+ approvalDecisions: import_zod2.z.array(ExpectedApprovalDecisionSchema).optional()
241
+ });
242
+ var SerializedEvalCaseSchema = import_zod2.z.strictObject({
243
+ name: import_zod2.z.string().min(1),
244
+ input: import_json.JsonValueSchema,
245
+ expected: SerializedEvalCaseExpectationSchema.optional()
246
+ });
247
+ function loadEvalCasesFromJsonlFile(input) {
248
+ const fileContents = readEvalDatasetFile(input.filePath);
249
+ const lines = fileContents.split(/\r?\n/);
250
+ const cases = [];
251
+ for (const [lineIndex, line] of lines.entries()) {
252
+ if (line.trim() === "") {
253
+ continue;
254
+ }
255
+ const lineNumber = lineIndex + 1;
256
+ const lineValue = parseJsonlLine(line, lineNumber, input.filePath);
257
+ cases.push(parseSerializedEvalCase(lineValue, lineNumber, input.filePath));
258
+ }
259
+ return cases;
260
+ }
261
+ function loadEvalCases(source) {
262
+ if ("jsonlFilePath" in source) {
263
+ return loadEvalCasesFromJsonlFile({ filePath: source.jsonlFilePath });
264
+ }
265
+ return source;
266
+ }
267
+ function readEvalDatasetFile(filePath) {
268
+ if (!(0, import_node_fs.existsSync)(filePath)) {
269
+ throw new import_errors4.AppError({
270
+ code: import_errors3.AppErrorCode.NotFound,
271
+ message: `eval dataset file not found: ${filePath}`,
272
+ statusCode: 404
273
+ });
274
+ }
275
+ return (0, import_node_fs.readFileSync)(filePath, "utf8");
276
+ }
277
+ function parseJsonlLine(line, lineNumber, filePath) {
278
+ try {
279
+ return import_json.JsonValueSchema.parse(JSON.parse(line));
280
+ } catch (error) {
281
+ throw new import_errors4.AppError({
282
+ code: import_errors3.AppErrorCode.ValidationFailed,
283
+ message: `invalid JSON on line ${lineNumber} of ${filePath}`,
284
+ statusCode: 422,
285
+ cause: error instanceof Error ? error : void 0
286
+ });
287
+ }
288
+ }
289
+ function parseSerializedEvalCase(lineValue, lineNumber, filePath) {
290
+ const parsedCase = SerializedEvalCaseSchema.safeParse(lineValue);
291
+ if (!parsedCase.success) {
292
+ throw new import_errors4.AppError({
293
+ code: import_errors3.AppErrorCode.ValidationFailed,
294
+ message: `invalid eval case on line ${lineNumber} of ${filePath}: ${formatSchemaValidationError(parsedCase.error)}`,
295
+ statusCode: 422
296
+ });
297
+ }
298
+ return parsedCase.data;
299
+ }
300
+
301
+ // src/judge/judge-metric.ts
302
+ var import_zod3 = require("zod");
303
+
304
+ // src/metrics/metric-result.ts
305
+ function createPassedMetricResult(name, reason) {
306
+ return { name, passed: true, score: 1, reason, skipped: false };
307
+ }
308
+ function createFailedMetricResult(name, score, reason) {
309
+ return { name, passed: false, score, reason, skipped: false };
310
+ }
311
+ function createSkippedMetricResult(name, reason) {
312
+ return { name, passed: false, score: 0, reason, skipped: true };
313
+ }
314
+ function createScoredMetricResult(name, outcomes) {
315
+ const satisfiedCount = outcomes.filter(
316
+ (outcome) => outcome.satisfied
317
+ ).length;
318
+ const reason = outcomes.map((outcome) => outcome.reason).join("; ");
319
+ if (satisfiedCount === outcomes.length) {
320
+ return createPassedMetricResult(name, reason);
321
+ }
322
+ return createFailedMetricResult(
323
+ name,
324
+ satisfiedCount / outcomes.length,
325
+ reason
326
+ );
327
+ }
328
+
329
+ // src/judge/judge-metric.ts
330
+ var defaultJudgeMetricName = "llm-judge";
331
+ var defaultJudgePassThreshold = 0.7;
332
+ var JudgeVerdictSchema = import_zod3.z.object({
333
+ score: import_zod3.z.number().min(0).max(1),
334
+ reasoning: import_zod3.z.string().min(1)
335
+ });
336
+ function defineJudgeMetric(config) {
337
+ return {
338
+ name: config.name ?? defaultJudgeMetricName,
339
+ model: config.model,
340
+ rubric: config.rubric,
341
+ passThreshold: config.passThreshold ?? defaultJudgePassThreshold
342
+ };
343
+ }
344
+ async function evaluateJudgeMetric(input) {
345
+ if (input.runState.status !== "completed") {
346
+ return createSkippedMetricResult(
347
+ input.definition.name,
348
+ `judge skipped because run finished with status "${input.runState.status}" instead of "completed"`
349
+ );
350
+ }
351
+ try {
352
+ const structuredOutput = await input.definition.model.generateStructuredOutput({
353
+ messages: buildJudgeChatMessages(input),
354
+ schema: JudgeVerdictSchema
355
+ });
356
+ return mapJudgeVerdictToMetricResult(
357
+ input.definition,
358
+ structuredOutput.value
359
+ );
360
+ } catch (caughtError) {
361
+ const failureReason = caughtError instanceof Error ? caughtError.message : String(caughtError);
362
+ return createFailedMetricResult(
363
+ input.definition.name,
364
+ 0,
365
+ `judge model call failed: ${failureReason}`
366
+ );
367
+ }
368
+ }
369
+ function mapJudgeVerdictToMetricResult(definition, verdict) {
370
+ return {
371
+ name: definition.name,
372
+ passed: verdict.score >= definition.passThreshold,
373
+ score: verdict.score,
374
+ reason: verdict.reasoning,
375
+ skipped: false
376
+ };
377
+ }
378
+ function buildJudgeChatMessages(input) {
379
+ return [
380
+ { role: "system", content: buildJudgeSystemPrompt(input.definition.rubric) },
381
+ {
382
+ role: "user",
383
+ content: JSON.stringify(buildJudgeCaseSummary(input), null, 2)
384
+ }
385
+ ];
386
+ }
387
+ function buildJudgeSystemPrompt(rubric) {
388
+ return [
389
+ "You are an impartial judge that evaluates one eval case from an AI workflow run.",
390
+ "Score how well the final output satisfies the rubric, from 0 (fails completely) to 1 (fully satisfies).",
391
+ "Rubric:",
392
+ rubric,
393
+ 'Respond as pure JSON matching {"score": number between 0 and 1, "reasoning": string} with no surrounding text.'
394
+ ].join("\n");
395
+ }
396
+ function buildJudgeCaseSummary(input) {
397
+ return {
398
+ caseName: input.evalCase.name,
399
+ input: input.evalCase.input,
400
+ expectedSummary: buildExpectedSummary(input.evalCase.expected),
401
+ finalOutput: input.runState.finalOutput
402
+ };
403
+ }
404
+ function buildExpectedSummary(expectation) {
405
+ if (expectation === void 0) {
406
+ return null;
407
+ }
408
+ const expectedSummary = {};
409
+ if (expectation.output !== void 0) {
410
+ expectedSummary.output = expectation.output;
411
+ }
412
+ if (expectation.outputFields !== void 0) {
413
+ expectedSummary.outputFields = expectation.outputFields;
414
+ }
415
+ if (expectation.outputPredicate !== void 0) {
416
+ expectedSummary.outputPredicateDefined = true;
417
+ }
418
+ if (expectation.toolCalls !== void 0) {
419
+ expectedSummary.toolCalls = expectation.toolCalls.map((expectedToolCall) => ({
420
+ toolName: expectedToolCall.toolName,
421
+ count: expectedToolCall.count ?? null
422
+ }));
423
+ }
424
+ if (expectation.approvalDecisions !== void 0) {
425
+ expectedSummary.approvalDecisions = expectation.approvalDecisions.map(
426
+ (expectedApprovalDecision) => ({
427
+ toolName: expectedApprovalDecision.toolName ?? null,
428
+ status: expectedApprovalDecision.status
429
+ })
430
+ );
431
+ }
432
+ return expectedSummary;
433
+ }
434
+
435
+ // src/metrics/approval-correctness-metric.ts
436
+ var approvalCorrectnessMetricName = "approval-correctness";
437
+ function evaluateApprovalCorrectnessMetric(input) {
438
+ const expectedApprovalDecisions = input.expectedApprovalDecisions;
439
+ if (expectedApprovalDecisions === void 0 || expectedApprovalDecisions.length === 0) {
440
+ return createSkippedMetricResult(
441
+ approvalCorrectnessMetricName,
442
+ "no approval decision expectations defined for this case"
443
+ );
444
+ }
445
+ const outcomes = expectedApprovalDecisions.map(
446
+ (expectedApprovalDecision) => evaluateExpectedApprovalDecision(expectedApprovalDecision, input.approvals)
447
+ );
448
+ return createScoredMetricResult(approvalCorrectnessMetricName, outcomes);
449
+ }
450
+ function evaluateExpectedApprovalDecision(expectedApprovalDecision, approvals) {
451
+ const candidateApprovals = filterApprovalsByToolName(
452
+ approvals,
453
+ expectedApprovalDecision.toolName
454
+ );
455
+ const approvalScope = describeApprovalScope(
456
+ expectedApprovalDecision.toolName
457
+ );
458
+ if (candidateApprovals.length === 0) {
459
+ return {
460
+ satisfied: false,
461
+ reason: `no approval found ${approvalScope}`
462
+ };
463
+ }
464
+ const hasMatchingStatus = candidateApprovals.some(
465
+ (approval) => approval.status === expectedApprovalDecision.status
466
+ );
467
+ if (hasMatchingStatus) {
468
+ return {
469
+ satisfied: true,
470
+ reason: `approval ${approvalScope} has status "${expectedApprovalDecision.status}"`
471
+ };
472
+ }
473
+ const observedStatuses = candidateApprovals.map((approval) => `"${approval.status}"`).join(", ");
474
+ return {
475
+ satisfied: false,
476
+ reason: `expected approval ${approvalScope} with status "${expectedApprovalDecision.status}" but found ${observedStatuses}`
477
+ };
478
+ }
479
+ function filterApprovalsByToolName(approvals, toolName) {
480
+ if (toolName === void 0) {
481
+ return approvals;
482
+ }
483
+ return approvals.filter((approval) => approval.toolName === toolName);
484
+ }
485
+ function describeApprovalScope(toolName) {
486
+ if (toolName === void 0) {
487
+ return "for the run";
488
+ }
489
+ return `for tool "${toolName}"`;
490
+ }
491
+
492
+ // src/metrics/schema-validity-metric.ts
493
+ var schemaValidityMetricName = "schema-validity";
494
+ function evaluateSchemaValidityMetric(input) {
495
+ if (input.outputSchema === null) {
496
+ return createSkippedMetricResult(
497
+ schemaValidityMetricName,
498
+ "workflow does not define an output schema"
499
+ );
500
+ }
501
+ const parsedOutput = input.outputSchema.safeParse(input.finalOutput);
502
+ if (parsedOutput.success) {
503
+ return createPassedMetricResult(
504
+ schemaValidityMetricName,
505
+ "final output matches the workflow output schema"
506
+ );
507
+ }
508
+ return createFailedMetricResult(
509
+ schemaValidityMetricName,
510
+ 0,
511
+ `final output failed workflow output schema validation: ${formatSchemaValidationError(parsedOutput.error)}`
512
+ );
513
+ }
514
+
515
+ // src/metrics/json-value-equality.ts
516
+ function isJsonObjectValue(value) {
517
+ return typeof value === "object" && value !== null && !Array.isArray(value);
518
+ }
519
+ function areJsonValuesDeeplyEqual(left, right) {
520
+ if (left === right) {
521
+ return true;
522
+ }
523
+ if (Array.isArray(left) && Array.isArray(right)) {
524
+ return areJsonArraysDeeplyEqual(left, right);
525
+ }
526
+ if (isJsonObjectValue(left) && isJsonObjectValue(right)) {
527
+ return areJsonObjectsDeeplyEqual(left, right);
528
+ }
529
+ return false;
530
+ }
531
+ function areJsonArraysDeeplyEqual(left, right) {
532
+ if (left.length !== right.length) {
533
+ return false;
534
+ }
535
+ return left.every(
536
+ (item, index) => areJsonValuesDeeplyEqual(item, right[index] ?? null)
537
+ );
538
+ }
539
+ function areJsonObjectsDeeplyEqual(left, right) {
540
+ const leftKeys = Object.keys(left);
541
+ const rightKeys = Object.keys(right);
542
+ if (leftKeys.length !== rightKeys.length) {
543
+ return false;
544
+ }
545
+ return leftKeys.every(
546
+ (key) => key in right && areJsonValuesDeeplyEqual(left[key] ?? null, right[key] ?? null)
547
+ );
548
+ }
549
+
550
+ // src/metrics/task-success-metric.ts
551
+ var taskSuccessMetricName = "task-success";
552
+ function evaluateTaskSuccessMetric(input) {
553
+ const expectation = input.expectation;
554
+ if (expectation === void 0 || hasNoOutputExpectations(expectation)) {
555
+ return createSkippedMetricResult(
556
+ taskSuccessMetricName,
557
+ "no output expectations defined for this case"
558
+ );
559
+ }
560
+ const failureReasons = [
561
+ ...collectExactOutputFailureReasons(expectation, input.finalOutput),
562
+ ...collectOutputFieldFailureReasons(expectation, input.finalOutput),
563
+ ...collectOutputPredicateFailureReasons(expectation, input.finalOutput)
564
+ ];
565
+ if (failureReasons.length > 0) {
566
+ return createFailedMetricResult(
567
+ taskSuccessMetricName,
568
+ 0,
569
+ failureReasons.join("; ")
570
+ );
571
+ }
572
+ return createPassedMetricResult(
573
+ taskSuccessMetricName,
574
+ "all output expectations matched the final output"
575
+ );
576
+ }
577
+ function hasNoOutputExpectations(expectation) {
578
+ return expectation.output === void 0 && expectation.outputFields === void 0 && expectation.outputPredicate === void 0;
579
+ }
580
+ function collectExactOutputFailureReasons(expectation, finalOutput) {
581
+ if (expectation.output === void 0) {
582
+ return [];
583
+ }
584
+ if (areJsonValuesDeeplyEqual(expectation.output, finalOutput)) {
585
+ return [];
586
+ }
587
+ return [
588
+ `final output ${JSON.stringify(finalOutput)} does not deeply equal expected output ${JSON.stringify(expectation.output)}`
589
+ ];
590
+ }
591
+ function collectOutputFieldFailureReasons(expectation, finalOutput) {
592
+ const outputFields = expectation.outputFields;
593
+ if (outputFields === void 0) {
594
+ return [];
595
+ }
596
+ if (!isJsonObjectValue(finalOutput)) {
597
+ return [
598
+ "final output is not an object, so expected output fields cannot match"
599
+ ];
600
+ }
601
+ const failureReasons = [];
602
+ for (const [fieldName, expectedValue] of Object.entries(outputFields)) {
603
+ if (!(fieldName in finalOutput)) {
604
+ failureReasons.push(
605
+ `expected output field "${fieldName}" is missing from the final output`
606
+ );
607
+ continue;
608
+ }
609
+ if (!areJsonValuesDeeplyEqual(finalOutput[fieldName] ?? null, expectedValue)) {
610
+ failureReasons.push(
611
+ `output field "${fieldName}" is ${JSON.stringify(finalOutput[fieldName])} but expected ${JSON.stringify(expectedValue)}`
612
+ );
613
+ }
614
+ }
615
+ return failureReasons;
616
+ }
617
+ function collectOutputPredicateFailureReasons(expectation, finalOutput) {
618
+ const outputPredicate = expectation.outputPredicate;
619
+ if (outputPredicate === void 0) {
620
+ return [];
621
+ }
622
+ try {
623
+ const predicateResult = outputPredicate(finalOutput);
624
+ if (predicateResult === true) {
625
+ return [];
626
+ }
627
+ if (predicateResult === false) {
628
+ return ["output predicate returned false"];
629
+ }
630
+ return [`output predicate failed: ${predicateResult}`];
631
+ } catch (caughtError) {
632
+ const errorMessage = caughtError instanceof Error ? caughtError.message : String(caughtError);
633
+ return [`output predicate threw an error: ${errorMessage}`];
634
+ }
635
+ }
636
+
637
+ // src/metrics/tool-accuracy-metric.ts
638
+ var toolAccuracyMetricName = "tool-accuracy";
639
+ function evaluateToolAccuracyMetric(input) {
640
+ const expectedToolCalls = input.expectedToolCalls;
641
+ if (expectedToolCalls === void 0 || expectedToolCalls.length === 0) {
642
+ return createSkippedMetricResult(
643
+ toolAccuracyMetricName,
644
+ "no tool call expectations defined for this case"
645
+ );
646
+ }
647
+ const completedCallCountsByToolName = countCompletedToolCallsByName(
648
+ input.toolCalls
649
+ );
650
+ const outcomes = expectedToolCalls.map(
651
+ (expectedToolCall) => evaluateExpectedToolCall(expectedToolCall, completedCallCountsByToolName)
652
+ );
653
+ return createScoredMetricResult(toolAccuracyMetricName, outcomes);
654
+ }
655
+ function countCompletedToolCallsByName(toolCalls) {
656
+ const completedCallCounts = /* @__PURE__ */ new Map();
657
+ for (const toolCall of toolCalls) {
658
+ if (toolCall.status !== "completed") {
659
+ continue;
660
+ }
661
+ const currentCount = completedCallCounts.get(toolCall.toolName) ?? 0;
662
+ completedCallCounts.set(toolCall.toolName, currentCount + 1);
663
+ }
664
+ return completedCallCounts;
665
+ }
666
+ function evaluateExpectedToolCall(expectedToolCall, completedCallCountsByToolName) {
667
+ const completedCount = completedCallCountsByToolName.get(expectedToolCall.toolName) ?? 0;
668
+ if (expectedToolCall.count !== void 0) {
669
+ return {
670
+ satisfied: completedCount === expectedToolCall.count,
671
+ reason: `tool "${expectedToolCall.toolName}" completed ${completedCount} time(s), expected exactly ${expectedToolCall.count}`
672
+ };
673
+ }
674
+ return {
675
+ satisfied: completedCount >= 1,
676
+ reason: `tool "${expectedToolCall.toolName}" completed ${completedCount} time(s), expected at least 1`
677
+ };
678
+ }
679
+
680
+ // src/metrics/case-metrics.ts
681
+ var runStatusMetricName = "run-status";
682
+ function collectRunPerformance(runState) {
683
+ const tokenUsage = runState.metrics.tokenUsage;
684
+ return {
685
+ totalLatencyMs: runState.metrics.totalLatencyMs,
686
+ tokenUsage: tokenUsage === null ? null : { ...tokenUsage }
687
+ };
688
+ }
689
+ function evaluateCaseMetrics(input) {
690
+ const performance = collectRunPerformance(input.runState);
691
+ if (input.runState.status !== "completed") {
692
+ return {
693
+ caseName: input.evalCase.name,
694
+ metrics: [buildRunStatusFailureMetric(input.runState)],
695
+ passed: false,
696
+ performance
697
+ };
698
+ }
699
+ const metrics = evaluateCompletedRunMetrics(input);
700
+ return {
701
+ caseName: input.evalCase.name,
702
+ metrics,
703
+ passed: metrics.every((metric) => metric.skipped || metric.passed),
704
+ performance
705
+ };
706
+ }
707
+ function buildRunStatusFailureMetric(runState) {
708
+ return createFailedMetricResult(
709
+ runStatusMetricName,
710
+ 0,
711
+ `run finished with status "${runState.status}" instead of "completed"`
712
+ );
713
+ }
714
+ function evaluateCompletedRunMetrics(input) {
715
+ return [
716
+ evaluateTaskSuccessMetric({
717
+ expectation: input.evalCase.expected,
718
+ finalOutput: input.runState.finalOutput
719
+ }),
720
+ evaluateSchemaValidityMetric({
721
+ outputSchema: input.workflow.outputSchema,
722
+ finalOutput: input.runState.finalOutput
723
+ }),
724
+ evaluateToolAccuracyMetric({
725
+ expectedToolCalls: input.evalCase.expected?.toolCalls,
726
+ toolCalls: input.runState.toolCalls
727
+ }),
728
+ evaluateApprovalCorrectnessMetric({
729
+ expectedApprovalDecisions: input.evalCase.expected?.approvalDecisions,
730
+ approvals: input.runState.approvals
731
+ })
732
+ ];
733
+ }
734
+
735
+ // src/metrics/suite-metrics.ts
736
+ function determineEvalSuiteStatus(summary) {
737
+ return summary.failedCases > 0 ? "failed" : "passed";
738
+ }
739
+ function aggregateSuiteMetrics(caseResults) {
740
+ const totalCases = caseResults.length;
741
+ const passedCases = caseResults.filter(
742
+ (caseResult) => caseResult.passed
743
+ ).length;
744
+ return {
745
+ totalCases,
746
+ passedCases,
747
+ failedCases: totalCases - passedCases,
748
+ passRate: totalCases === 0 ? 0 : passedCases / totalCases,
749
+ metricBreakdown: buildMetricBreakdown(caseResults),
750
+ performance: summarizeSuitePerformance(caseResults)
751
+ };
752
+ }
753
+ function buildMetricBreakdown(caseResults) {
754
+ const metricBreakdown = {};
755
+ for (const caseResult of caseResults) {
756
+ for (const metric of caseResult.metrics) {
757
+ const entry = metricBreakdown[metric.name] ?? {
758
+ evaluated: 0,
759
+ passed: 0,
760
+ failed: 0,
761
+ skipped: 0
762
+ };
763
+ if (metric.skipped) {
764
+ entry.skipped += 1;
765
+ } else {
766
+ entry.evaluated += 1;
767
+ if (metric.passed) {
768
+ entry.passed += 1;
769
+ } else {
770
+ entry.failed += 1;
771
+ }
772
+ }
773
+ metricBreakdown[metric.name] = entry;
774
+ }
775
+ }
776
+ return metricBreakdown;
777
+ }
778
+ function summarizeSuitePerformance(caseResults) {
779
+ const latencies = caseResults.map((caseResult) => caseResult.performance.totalLatencyMs).filter((latencyMs) => latencyMs !== null);
780
+ const tokenUsages = caseResults.map((caseResult) => caseResult.performance.tokenUsage).filter((tokenUsage) => tokenUsage !== null);
781
+ const totalLatencyMs = latencies.length === 0 ? null : latencies.reduce((sum, latencyMs) => sum + latencyMs, 0);
782
+ return {
783
+ totalLatencyMs,
784
+ averageLatencyMs: totalLatencyMs === null ? null : totalLatencyMs / latencies.length,
785
+ tokenUsage: tokenUsages.length === 0 ? null : sumTokenUsages(tokenUsages)
786
+ };
787
+ }
788
+ function sumTokenUsages(tokenUsages) {
789
+ return tokenUsages.reduce(
790
+ (summedUsage, tokenUsage) => ({
791
+ inputTokens: summedUsage.inputTokens + tokenUsage.inputTokens,
792
+ outputTokens: summedUsage.outputTokens + tokenUsage.outputTokens,
793
+ totalTokens: summedUsage.totalTokens + tokenUsage.totalTokens
794
+ }),
795
+ { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
796
+ );
797
+ }
798
+
799
+ // src/eval-webhook.ts
800
+ var import_zod5 = require("zod");
801
+
802
+ // src/runner/eval-run-file.ts
803
+ var import_node_fs2 = require("fs");
804
+ var import_node_path = require("path");
805
+ var import_runs = require("@assemble-dev/shared-types/runs");
806
+ var import_trace_events = require("@assemble-dev/shared-types/trace-events");
807
+ var import_zod4 = require("zod");
808
+ var defaultEvalResultsDirectory = (0, import_node_path.join)(".assemble", "evals");
809
+ var EvalMetricResultSchema = import_zod4.z.object({
810
+ name: import_zod4.z.string().min(1),
811
+ passed: import_zod4.z.boolean(),
812
+ score: import_zod4.z.number(),
813
+ reason: import_zod4.z.string(),
814
+ skipped: import_zod4.z.boolean()
815
+ });
816
+ var EvalCasePerformanceSchema = import_zod4.z.object({
817
+ totalLatencyMs: import_zod4.z.number().nonnegative().nullable(),
818
+ tokenUsage: import_runs.TokenUsageSchema.nullable()
819
+ });
820
+ var EvalCaseResultSchema = import_zod4.z.object({
821
+ caseName: import_zod4.z.string().min(1),
822
+ runId: import_zod4.z.string().min(1),
823
+ passed: import_zod4.z.boolean(),
824
+ metrics: import_zod4.z.array(EvalMetricResultSchema),
825
+ performance: EvalCasePerformanceSchema
826
+ });
827
+ var EvalMetricBreakdownSchema = import_zod4.z.object({
828
+ evaluated: import_zod4.z.number().int().nonnegative(),
829
+ passed: import_zod4.z.number().int().nonnegative(),
830
+ failed: import_zod4.z.number().int().nonnegative(),
831
+ skipped: import_zod4.z.number().int().nonnegative()
832
+ });
833
+ var EvalSuitePerformanceSummarySchema = import_zod4.z.object({
834
+ totalLatencyMs: import_zod4.z.number().nonnegative().nullable(),
835
+ averageLatencyMs: import_zod4.z.number().nonnegative().nullable(),
836
+ tokenUsage: import_runs.TokenUsageSchema.nullable()
837
+ });
838
+ var EvalSuiteSummarySchema = import_zod4.z.object({
839
+ totalCases: import_zod4.z.number().int().nonnegative(),
840
+ passedCases: import_zod4.z.number().int().nonnegative(),
841
+ failedCases: import_zod4.z.number().int().nonnegative(),
842
+ passRate: import_zod4.z.number().min(0).max(1),
843
+ metricBreakdown: import_zod4.z.record(import_zod4.z.string(), EvalMetricBreakdownSchema),
844
+ performance: EvalSuitePerformanceSummarySchema
845
+ });
846
+ var EvalRunFileSchema = import_zod4.z.object({
847
+ evalRunId: import_zod4.z.string().min(1),
848
+ suiteName: import_zod4.z.string().min(1),
849
+ workflowName: import_zod4.z.string().min(1),
850
+ startedAt: import_zod4.z.iso.datetime(),
851
+ completedAt: import_zod4.z.iso.datetime(),
852
+ summary: EvalSuiteSummarySchema,
853
+ caseResults: import_zod4.z.array(EvalCaseResultSchema),
854
+ events: import_zod4.z.array(import_trace_events.TraceEventSchema)
855
+ });
856
+ function resolveEvalRunFilePath(evalRunId, directory = defaultEvalResultsDirectory) {
857
+ return (0, import_node_path.join)(directory, `${evalRunId}.json`);
858
+ }
859
+ function saveEvalRunFile(input) {
860
+ const directory = input.directory ?? defaultEvalResultsDirectory;
861
+ const filePath = resolveEvalRunFilePath(
862
+ input.evalRunFile.evalRunId,
863
+ directory
864
+ );
865
+ const validatedEvalRunFile = EvalRunFileSchema.parse(input.evalRunFile);
866
+ (0, import_node_fs2.mkdirSync)(directory, { recursive: true });
867
+ (0, import_node_fs2.writeFileSync)(
868
+ filePath,
869
+ JSON.stringify(validatedEvalRunFile, null, 2),
870
+ "utf8"
871
+ );
872
+ return filePath;
873
+ }
874
+ function loadEvalRunFile(input) {
875
+ const filePath = resolveEvalRunFilePath(input.evalRunId, input.directory);
876
+ const rawContents = (0, import_node_fs2.readFileSync)(filePath, "utf8");
877
+ return EvalRunFileSchema.parse(JSON.parse(rawContents));
878
+ }
879
+
880
+ // src/eval-webhook.ts
881
+ var EvalWebhookCaseSchema = import_zod5.z.object({
882
+ caseName: import_zod5.z.string().min(1),
883
+ passed: import_zod5.z.boolean(),
884
+ metrics: import_zod5.z.array(EvalMetricResultSchema)
885
+ });
886
+ var EvalWebhookPayloadSchema = import_zod5.z.object({
887
+ suiteName: import_zod5.z.string().min(1),
888
+ status: import_zod5.z.enum(["passed", "failed"]),
889
+ totalCases: import_zod5.z.number().int().nonnegative(),
890
+ passedCases: import_zod5.z.number().int().nonnegative(),
891
+ failedCases: import_zod5.z.number().int().nonnegative(),
892
+ cases: import_zod5.z.array(EvalWebhookCaseSchema)
893
+ });
894
+ function buildEvalWebhookPayload(runResult) {
895
+ return {
896
+ suiteName: runResult.suiteName,
897
+ status: determineEvalSuiteStatus(runResult.summary),
898
+ totalCases: runResult.summary.totalCases,
899
+ passedCases: runResult.summary.passedCases,
900
+ failedCases: runResult.summary.failedCases,
901
+ cases: runResult.caseResults.map((caseResult) => ({
902
+ caseName: caseResult.caseName,
903
+ passed: caseResult.passed,
904
+ metrics: caseResult.metrics
905
+ }))
906
+ };
907
+ }
908
+ async function postEvalResultsToWebhook(input) {
909
+ const validatedPayload = EvalWebhookPayloadSchema.parse(input.payload);
910
+ const webhookFetch = input.fetchImplementation ?? fetch;
911
+ try {
912
+ const response = await webhookFetch(input.webhookUrl, {
913
+ method: "POST",
914
+ headers: { "content-type": "application/json" },
915
+ body: JSON.stringify(validatedPayload)
916
+ });
917
+ if (response.ok) {
918
+ return { outcome: "delivered" };
919
+ }
920
+ return {
921
+ outcome: "failed",
922
+ message: `Webhook responded with status ${response.status}`
923
+ };
924
+ } catch (caughtError) {
925
+ const reason = caughtError instanceof Error ? caughtError.message : String(caughtError);
926
+ return {
927
+ outcome: "failed",
928
+ message: `Webhook request failed: ${reason}`
929
+ };
930
+ }
931
+ }
932
+
933
+ // src/runner/run-eval-suite.ts
934
+ var import_sdk = require("@assemble-dev/sdk");
935
+ var import_errors5 = require("@assemble-dev/shared-types/errors");
936
+ var import_errors6 = require("@assemble-dev/shared-utils/errors");
937
+ var import_ids = require("@assemble-dev/shared-utils/ids");
938
+ async function runEvalSuite(input) {
939
+ const context = buildEvalRunContext(input.suite, input.options);
940
+ emitEvalStartedEvent(context);
941
+ const caseResults = await executeEvalSuiteCases(context);
942
+ const summary = aggregateSuiteMetrics(caseResults);
943
+ emitEvalCompletedEvent(context, summary);
944
+ await context.evalTraceBus.flushSinks();
945
+ const evalRunFile = buildEvalRunFile(context, summary, caseResults);
946
+ const resultsFilePath = saveEvalRunFile({
947
+ evalRunFile,
948
+ directory: context.resultsDirectory
949
+ });
950
+ const uploadStatus = await uploadEvalRunFile(context, evalRunFile);
951
+ return {
952
+ evalRunId: context.evalRunId,
953
+ suiteName: context.suite.name,
954
+ summary,
955
+ caseResults,
956
+ resultsFilePath,
957
+ uploadStatus
958
+ };
959
+ }
960
+ function buildEvalRunContext(suite, options) {
961
+ const resolvedOptions = options ?? {};
962
+ const now = resolvedOptions.now ?? (() => /* @__PURE__ */ new Date());
963
+ const startedAtDate = now();
964
+ const caseRunIds = suite.cases.map(() => (0, import_ids.generateRunId)());
965
+ const evalEventsRunId = caseRunIds[0] ?? (0, import_ids.generateRunId)();
966
+ return {
967
+ suite,
968
+ effectiveWorkflow: buildWorkflowWithMockedTools(
969
+ suite.workflow,
970
+ resolvedOptions.toolMocks
971
+ ),
972
+ options: resolvedOptions,
973
+ evalRunId: resolvedOptions.evalRunId ?? (0, import_ids.generateEvalRunId)(),
974
+ caseRunIds,
975
+ evalEventsRunId,
976
+ now,
977
+ startedAt: startedAtDate.toISOString(),
978
+ startedAtMs: startedAtDate.getTime(),
979
+ evalTraceBus: (0, import_sdk.createTraceBus)({
980
+ runId: evalEventsRunId,
981
+ workflowName: suite.workflow.name,
982
+ input: null,
983
+ sinks: resolvedOptions.sinks ?? [],
984
+ now
985
+ }),
986
+ resultsDirectory: resolvedOptions.resultsDirectory ?? defaultEvalResultsDirectory
987
+ };
988
+ }
989
+ function buildWorkflowWithMockedTools(workflowDefinition, toolMocks) {
990
+ if (toolMocks === void 0 || Object.keys(toolMocks).length === 0) {
991
+ return workflowDefinition;
992
+ }
993
+ ensureToolMocksMatchRegisteredTools(workflowDefinition, toolMocks);
994
+ const toolsWithMocks = workflowDefinition.tools.map((workflowTool) => {
995
+ const toolMock = toolMocks[workflowTool.name];
996
+ if (toolMock === void 0) {
997
+ return workflowTool;
998
+ }
999
+ return {
1000
+ ...workflowTool,
1001
+ execute: async (toolInput) => await Promise.resolve(toolMock(toolInput))
1002
+ };
1003
+ });
1004
+ return { ...workflowDefinition, tools: toolsWithMocks };
1005
+ }
1006
+ function ensureToolMocksMatchRegisteredTools(workflowDefinition, toolMocks) {
1007
+ const registeredToolNames = new Set(
1008
+ workflowDefinition.tools.map((workflowTool) => workflowTool.name)
1009
+ );
1010
+ const unknownToolNames = Object.keys(toolMocks).filter(
1011
+ (mockedToolName) => !registeredToolNames.has(mockedToolName)
1012
+ );
1013
+ if (unknownToolNames.length === 0) {
1014
+ return;
1015
+ }
1016
+ throw new import_errors6.AppError({
1017
+ code: import_errors5.AppErrorCode.ValidationFailed,
1018
+ message: `toolMocks reference tools not registered on workflow "${workflowDefinition.name}": ${unknownToolNames.join(", ")}`,
1019
+ statusCode: 422
1020
+ });
1021
+ }
1022
+ async function executeEvalSuiteCases(context) {
1023
+ const caseResults = [];
1024
+ for (const [caseIndex, evalCase] of context.suite.cases.entries()) {
1025
+ const runId = context.caseRunIds[caseIndex] ?? (0, import_ids.generateRunId)();
1026
+ const runResult = await (0, import_sdk.runWorkflow)({
1027
+ definition: context.effectiveWorkflow,
1028
+ input: evalCase.input,
1029
+ options: {
1030
+ runId,
1031
+ now: context.options.now,
1032
+ sinks: context.options.sinks,
1033
+ persistRunState: false
1034
+ }
1035
+ });
1036
+ const caseMetrics = evaluateCaseMetrics({
1037
+ runState: runResult.state,
1038
+ evalCase,
1039
+ workflow: context.suite.workflow
1040
+ });
1041
+ const metrics = await appendJudgeMetricResults(
1042
+ context.options.judges,
1043
+ evalCase,
1044
+ runResult.state,
1045
+ caseMetrics.metrics
1046
+ );
1047
+ caseResults.push({
1048
+ caseName: caseMetrics.caseName,
1049
+ runId,
1050
+ passed: metrics.every((metric) => metric.skipped || metric.passed),
1051
+ metrics,
1052
+ performance: caseMetrics.performance
1053
+ });
1054
+ }
1055
+ return caseResults;
1056
+ }
1057
+ async function appendJudgeMetricResults(judges, evalCase, runState, metrics) {
1058
+ if (judges === void 0 || judges.length === 0) {
1059
+ return metrics;
1060
+ }
1061
+ const judgeResults = [];
1062
+ for (const definition of judges) {
1063
+ judgeResults.push(
1064
+ await evaluateJudgeMetric({ definition, evalCase, runState })
1065
+ );
1066
+ }
1067
+ return [...metrics, ...judgeResults];
1068
+ }
1069
+ function emitEvalStartedEvent(context) {
1070
+ context.evalTraceBus.emitTraceEvent({
1071
+ id: (0, import_ids.generateTraceEventId)(),
1072
+ runId: context.evalEventsRunId,
1073
+ workflowName: context.suite.workflow.name,
1074
+ timestamp: context.now().toISOString(),
1075
+ parentEventId: null,
1076
+ redacted: false,
1077
+ type: "eval.started",
1078
+ evalRunId: context.evalRunId,
1079
+ suiteName: context.suite.name,
1080
+ caseCount: context.suite.cases.length
1081
+ });
1082
+ }
1083
+ function emitEvalCompletedEvent(context, summary) {
1084
+ context.evalTraceBus.emitTraceEvent({
1085
+ id: (0, import_ids.generateTraceEventId)(),
1086
+ runId: context.evalEventsRunId,
1087
+ workflowName: context.suite.workflow.name,
1088
+ timestamp: context.now().toISOString(),
1089
+ parentEventId: null,
1090
+ redacted: false,
1091
+ type: "eval.completed",
1092
+ evalRunId: context.evalRunId,
1093
+ passedCaseCount: summary.passedCases,
1094
+ failedCaseCount: summary.failedCases,
1095
+ latencyMs: Math.max(0, context.now().getTime() - context.startedAtMs)
1096
+ });
1097
+ }
1098
+ function buildEvalRunFile(context, summary, caseResults) {
1099
+ return {
1100
+ evalRunId: context.evalRunId,
1101
+ suiteName: context.suite.name,
1102
+ workflowName: context.suite.workflow.name,
1103
+ startedAt: context.startedAt,
1104
+ completedAt: context.now().toISOString(),
1105
+ summary,
1106
+ caseResults,
1107
+ events: [...context.evalTraceBus.listTraceEvents()]
1108
+ };
1109
+ }
1110
+ function resolveEvalRunsUploadUrl(baseUrl) {
1111
+ return `${baseUrl}/v1/eval-runs`;
1112
+ }
1113
+ function writeEvalLineToStdout(line) {
1114
+ process.stdout.write(`${line}
1115
+ `);
1116
+ }
1117
+ async function uploadEvalRunFile(context, evalRunFile) {
1118
+ const platformOptions = context.options.platform ?? {};
1119
+ const platformConfig = (0, import_sdk.resolvePlatformConfig)({
1120
+ apiKey: platformOptions.apiKey,
1121
+ baseUrl: platformOptions.baseUrl,
1122
+ environmentVariables: platformOptions.environmentVariables,
1123
+ homeDirectory: platformOptions.homeDirectory
1124
+ });
1125
+ if (platformConfig.apiKey === null) {
1126
+ const writeLine = context.options.writeLine ?? writeEvalLineToStdout;
1127
+ writeLine((0, import_sdk.buildMissingKeyMessage)());
1128
+ return "skipped-no-key";
1129
+ }
1130
+ return await postEvalRunFileToPlatform(
1131
+ platformConfig.apiKey,
1132
+ platformConfig.baseUrl,
1133
+ evalRunFile,
1134
+ platformOptions.fetchImplementation
1135
+ );
1136
+ }
1137
+ async function postEvalRunFileToPlatform(apiKey, baseUrl, evalRunFile, fetchImplementation) {
1138
+ const uploadFetch = fetchImplementation ?? fetch;
1139
+ try {
1140
+ const response = await uploadFetch(resolveEvalRunsUploadUrl(baseUrl), {
1141
+ method: "POST",
1142
+ headers: {
1143
+ authorization: `Bearer ${apiKey}`,
1144
+ "content-type": "application/json"
1145
+ },
1146
+ body: JSON.stringify(evalRunFile)
1147
+ });
1148
+ return response.ok ? "uploaded" : "failed";
1149
+ } catch {
1150
+ return "failed";
1151
+ }
1152
+ }
1153
+ // Annotate the CommonJS export names for ESM import in node:
1154
+ 0 && (module.exports = {
1155
+ EvalCasePerformanceSchema,
1156
+ EvalCaseResultSchema,
1157
+ EvalMetricBreakdownSchema,
1158
+ EvalMetricResultSchema,
1159
+ EvalRunFileSchema,
1160
+ EvalSuitePerformanceSummarySchema,
1161
+ EvalSuiteSummarySchema,
1162
+ EvalWebhookCaseSchema,
1163
+ EvalWebhookPayloadSchema,
1164
+ ExpectedApprovalDecisionSchema,
1165
+ ExpectedToolCallSchema,
1166
+ JudgeVerdictSchema,
1167
+ SerializedEvalCaseExpectationSchema,
1168
+ SerializedEvalCaseSchema,
1169
+ aggregateSuiteMetrics,
1170
+ approvalCorrectnessMetricName,
1171
+ areJsonValuesDeeplyEqual,
1172
+ buildEvalWebhookPayload,
1173
+ collectRunPerformance,
1174
+ createFailedMetricResult,
1175
+ createPassedMetricResult,
1176
+ createScoredMetricResult,
1177
+ createSkippedMetricResult,
1178
+ defaultEvalResultsDirectory,
1179
+ defaultJudgeMetricName,
1180
+ defaultJudgePassThreshold,
1181
+ defineJudgeMetric,
1182
+ determineEvalSuiteStatus,
1183
+ evalSuite,
1184
+ evaluateApprovalCorrectnessMetric,
1185
+ evaluateCaseMetrics,
1186
+ evaluateJudgeMetric,
1187
+ evaluateSchemaValidityMetric,
1188
+ evaluateTaskSuccessMetric,
1189
+ evaluateToolAccuracyMetric,
1190
+ isJsonObjectValue,
1191
+ loadEvalCases,
1192
+ loadEvalCasesFromJsonlFile,
1193
+ loadEvalRunFile,
1194
+ postEvalResultsToWebhook,
1195
+ resolveEvalRunFilePath,
1196
+ resolveEvalRunsUploadUrl,
1197
+ runEvalSuite,
1198
+ runStatusMetricName,
1199
+ saveEvalRunFile,
1200
+ schemaValidityMetricName,
1201
+ taskSuccessMetricName,
1202
+ toolAccuracyMetricName
1203
+ });