@plumloom/cli 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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +71 -0
- package/dist/actions/configured-runs.d.ts +25 -0
- package/dist/actions/configured-runs.d.ts.map +1 -0
- package/dist/actions/configured-runs.js +35 -0
- package/dist/actions/configured-runs.js.map +1 -0
- package/dist/actions/context.d.ts +5 -0
- package/dist/actions/context.d.ts.map +1 -0
- package/dist/actions/context.js +2 -0
- package/dist/actions/context.js.map +1 -0
- package/dist/actions/doctor.d.ts +46 -0
- package/dist/actions/doctor.d.ts.map +1 -0
- package/dist/actions/doctor.js +130 -0
- package/dist/actions/doctor.js.map +1 -0
- package/dist/actions/evaluations.d.ts +87 -0
- package/dist/actions/evaluations.d.ts.map +1 -0
- package/dist/actions/evaluations.js +307 -0
- package/dist/actions/evaluations.js.map +1 -0
- package/dist/actions/gate.d.ts +63 -0
- package/dist/actions/gate.d.ts.map +1 -0
- package/dist/actions/gate.js +272 -0
- package/dist/actions/gate.js.map +1 -0
- package/dist/actions/identity.d.ts +4 -0
- package/dist/actions/identity.d.ts.map +1 -0
- package/dist/actions/identity.js +4 -0
- package/dist/actions/identity.js.map +1 -0
- package/dist/actions/models.d.ts +4 -0
- package/dist/actions/models.d.ts.map +1 -0
- package/dist/actions/models.js +4 -0
- package/dist/actions/models.js.map +1 -0
- package/dist/actions/quality-standards.d.ts +30 -0
- package/dist/actions/quality-standards.d.ts.map +1 -0
- package/dist/actions/quality-standards.js +86 -0
- package/dist/actions/quality-standards.js.map +1 -0
- package/dist/actions/results.d.ts +12 -0
- package/dist/actions/results.d.ts.map +1 -0
- package/dist/actions/results.js +65 -0
- package/dist/actions/results.js.map +1 -0
- package/dist/actions/runs.d.ts +22 -0
- package/dist/actions/runs.d.ts.map +1 -0
- package/dist/actions/runs.js +59 -0
- package/dist/actions/runs.js.map +1 -0
- package/dist/actions/suite-gate.d.ts +24 -0
- package/dist/actions/suite-gate.d.ts.map +1 -0
- package/dist/actions/suite-gate.js +40 -0
- package/dist/actions/suite-gate.js.map +1 -0
- package/dist/actions/suite.d.ts +49 -0
- package/dist/actions/suite.d.ts.map +1 -0
- package/dist/actions/suite.js +68 -0
- package/dist/actions/suite.js.map +1 -0
- package/dist/actions/validation.d.ts +3 -0
- package/dist/actions/validation.d.ts.map +1 -0
- package/dist/actions/validation.js +26 -0
- package/dist/actions/validation.js.map +1 -0
- package/dist/actions/workspaces.d.ts +13 -0
- package/dist/actions/workspaces.d.ts.map +1 -0
- package/dist/actions/workspaces.js +18 -0
- package/dist/actions/workspaces.js.map +1 -0
- package/dist/api/api.d.ts +237 -0
- package/dist/api/api.d.ts.map +1 -0
- package/dist/api/api.js +396 -0
- package/dist/api/api.js.map +1 -0
- package/dist/api/client.d.ts +38 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +295 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/endpoints.d.ts +31 -0
- package/dist/api/endpoints.d.ts.map +1 -0
- package/dist/api/endpoints.js +32 -0
- package/dist/api/endpoints.js.map +1 -0
- package/dist/api/errors.d.ts +22 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +84 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/schemas.d.ts +169 -0
- package/dist/api/schemas.d.ts.map +1 -0
- package/dist/api/schemas.js +194 -0
- package/dist/api/schemas.js.map +1 -0
- package/dist/auth/credential-store.d.ts +9 -0
- package/dist/auth/credential-store.d.ts.map +1 -0
- package/dist/auth/credential-store.js +2 -0
- package/dist/auth/credential-store.js.map +1 -0
- package/dist/auth/credentials.d.ts +27 -0
- package/dist/auth/credentials.d.ts.map +1 -0
- package/dist/auth/credentials.js +81 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/keyring-store.d.ts +9 -0
- package/dist/auth/keyring-store.d.ts.map +1 -0
- package/dist/auth/keyring-store.js +57 -0
- package/dist/auth/keyring-store.js.map +1 -0
- package/dist/auth/login.d.ts +28 -0
- package/dist/auth/login.d.ts.map +1 -0
- package/dist/auth/login.js +27 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/password-prompt.d.ts +5 -0
- package/dist/auth/password-prompt.d.ts.map +1 -0
- package/dist/auth/password-prompt.js +10 -0
- package/dist/auth/password-prompt.js.map +1 -0
- package/dist/auth/redact.d.ts +3 -0
- package/dist/auth/redact.d.ts.map +1 -0
- package/dist/auth/redact.js +69 -0
- package/dist/auth/redact.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/executor.d.ts +36 -0
- package/dist/commands/executor.d.ts.map +1 -0
- package/dist/commands/executor.js +1218 -0
- package/dist/commands/executor.js.map +1 -0
- package/dist/commands/program.d.ts +21 -0
- package/dist/commands/program.d.ts.map +1 -0
- package/dist/commands/program.js +588 -0
- package/dist/commands/program.js.map +1 -0
- package/dist/commands/types.d.ts +139 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +57 -0
- package/dist/config.js.map +1 -0
- package/dist/configured-run/file-inputs.d.ts +8 -0
- package/dist/configured-run/file-inputs.d.ts.map +1 -0
- package/dist/configured-run/file-inputs.js +160 -0
- package/dist/configured-run/file-inputs.js.map +1 -0
- package/dist/configured-run/validation.d.ts +103 -0
- package/dist/configured-run/validation.d.ts.map +1 -0
- package/dist/configured-run/validation.js +327 -0
- package/dist/configured-run/validation.js.map +1 -0
- package/dist/domain/common.d.ts +6 -0
- package/dist/domain/common.d.ts.map +1 -0
- package/dist/domain/common.js +10 -0
- package/dist/domain/common.js.map +1 -0
- package/dist/domain/types.d.ts +117 -0
- package/dist/domain/types.d.ts.map +1 -0
- package/dist/domain/types.js +2 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/errors/autoeval-error.d.ts +13 -0
- package/dist/errors/autoeval-error.d.ts.map +1 -0
- package/dist/errors/autoeval-error.js +36 -0
- package/dist/errors/autoeval-error.js.map +1 -0
- package/dist/errors/hints.d.ts +3 -0
- package/dist/errors/hints.d.ts.map +1 -0
- package/dist/errors/hints.js +43 -0
- package/dist/errors/hints.js.map +1 -0
- package/dist/gate/decision.d.ts +97 -0
- package/dist/gate/decision.d.ts.map +1 -0
- package/dist/gate/decision.js +436 -0
- package/dist/gate/decision.js.map +1 -0
- package/dist/gate/policy.d.ts +31 -0
- package/dist/gate/policy.d.ts.map +1 -0
- package/dist/gate/policy.js +26 -0
- package/dist/gate/policy.js.map +1 -0
- package/dist/guide/eval-creation-backend.d.ts +20 -0
- package/dist/guide/eval-creation-backend.d.ts.map +1 -0
- package/dist/guide/eval-creation-backend.js +16 -0
- package/dist/guide/eval-creation-backend.js.map +1 -0
- package/dist/guide/eval-guide.d.ts +24 -0
- package/dist/guide/eval-guide.d.ts.map +1 -0
- package/dist/guide/eval-guide.js +219 -0
- package/dist/guide/eval-guide.js.map +1 -0
- package/dist/mcp/context.d.ts +21 -0
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +19 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/schemas.d.ts +206 -0
- package/dist/mcp/schemas.d.ts.map +1 -0
- package/dist/mcp/schemas.js +80 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +10 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +42 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +287 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +32 -0
- package/dist/mcp.js.map +1 -0
- package/dist/output/big-digits.d.ts +7 -0
- package/dist/output/big-digits.d.ts.map +1 -0
- package/dist/output/big-digits.js +43 -0
- package/dist/output/big-digits.js.map +1 -0
- package/dist/output/clipboard.d.ts +29 -0
- package/dist/output/clipboard.d.ts.map +1 -0
- package/dist/output/clipboard.js +82 -0
- package/dist/output/clipboard.js.map +1 -0
- package/dist/output/colors.d.ts +21 -0
- package/dist/output/colors.d.ts.map +1 -0
- package/dist/output/colors.js +21 -0
- package/dist/output/colors.js.map +1 -0
- package/dist/output/digest.d.ts +165 -0
- package/dist/output/digest.d.ts.map +1 -0
- package/dist/output/digest.js +771 -0
- package/dist/output/digest.js.map +1 -0
- package/dist/output/human.d.ts +95 -0
- package/dist/output/human.d.ts.map +1 -0
- package/dist/output/human.js +643 -0
- package/dist/output/human.js.map +1 -0
- package/dist/output/json.d.ts +2 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +5 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/layout.d.ts +18 -0
- package/dist/output/layout.d.ts.map +1 -0
- package/dist/output/layout.js +64 -0
- package/dist/output/layout.js.map +1 -0
- package/dist/output/markdown.d.ts +16 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +38 -0
- package/dist/output/markdown.js.map +1 -0
- package/dist/output/progress.d.ts +29 -0
- package/dist/output/progress.d.ts.map +1 -0
- package/dist/output/progress.js +136 -0
- package/dist/output/progress.js.map +1 -0
- package/dist/output/safe-text.d.ts +11 -0
- package/dist/output/safe-text.d.ts.map +1 -0
- package/dist/output/safe-text.js +44 -0
- package/dist/output/safe-text.js.map +1 -0
- package/dist/output/scorecard.d.ts +23 -0
- package/dist/output/scorecard.d.ts.map +1 -0
- package/dist/output/scorecard.js +506 -0
- package/dist/output/scorecard.js.map +1 -0
- package/dist/output/section.d.ts +15 -0
- package/dist/output/section.d.ts.map +1 -0
- package/dist/output/section.js +44 -0
- package/dist/output/section.js.map +1 -0
- package/dist/output/spinner.d.ts +24 -0
- package/dist/output/spinner.d.ts.map +1 -0
- package/dist/output/spinner.js +34 -0
- package/dist/output/spinner.js.map +1 -0
- package/dist/output/splash.d.ts +37 -0
- package/dist/output/splash.d.ts.map +1 -0
- package/dist/output/splash.js +169 -0
- package/dist/output/splash.js.map +1 -0
- package/dist/output/table.d.ts +33 -0
- package/dist/output/table.d.ts.map +1 -0
- package/dist/output/table.js +105 -0
- package/dist/output/table.js.map +1 -0
- package/dist/output/trend.d.ts +19 -0
- package/dist/output/trend.d.ts.map +1 -0
- package/dist/output/trend.js +38 -0
- package/dist/output/trend.js.map +1 -0
- package/dist/output/writer.d.ts +14 -0
- package/dist/output/writer.d.ts.map +1 -0
- package/dist/output/writer.js +33 -0
- package/dist/output/writer.js.map +1 -0
- package/dist/polling/clock.d.ts +9 -0
- package/dist/polling/clock.d.ts.map +1 -0
- package/dist/polling/clock.js +23 -0
- package/dist/polling/clock.js.map +1 -0
- package/dist/polling/wait-for-result.d.ts +16 -0
- package/dist/polling/wait-for-result.d.ts.map +1 -0
- package/dist/polling/wait-for-result.js +29 -0
- package/dist/polling/wait-for-result.js.map +1 -0
- package/dist/polling/wait-for-run.d.ts +14 -0
- package/dist/polling/wait-for-run.d.ts.map +1 -0
- package/dist/polling/wait-for-run.js +36 -0
- package/dist/polling/wait-for-run.js.map +1 -0
- package/dist/quickstart/prompt.d.ts +18 -0
- package/dist/quickstart/prompt.d.ts.map +1 -0
- package/dist/quickstart/prompt.js +37 -0
- package/dist/quickstart/prompt.js.map +1 -0
- package/dist/quickstart/sample.d.ts +28 -0
- package/dist/quickstart/sample.d.ts.map +1 -0
- package/dist/quickstart/sample.js +235 -0
- package/dist/quickstart/sample.js.map +1 -0
- package/dist/quickstart/selection.d.ts +53 -0
- package/dist/quickstart/selection.d.ts.map +1 -0
- package/dist/quickstart/selection.js +97 -0
- package/dist/quickstart/selection.js.map +1 -0
- package/dist/run.d.ts +28 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +134 -0
- package/dist/run.js.map +1 -0
- package/dist/runtime/action-context.d.ts +19 -0
- package/dist/runtime/action-context.d.ts.map +1 -0
- package/dist/runtime/action-context.js +40 -0
- package/dist/runtime/action-context.js.map +1 -0
- package/dist/suite/execution.d.ts +46 -0
- package/dist/suite/execution.d.ts.map +1 -0
- package/dist/suite/execution.js +97 -0
- package/dist/suite/execution.js.map +1 -0
- package/dist/suite/manifest.d.ts +16 -0
- package/dist/suite/manifest.d.ts.map +1 -0
- package/dist/suite/manifest.js +92 -0
- package/dist/suite/manifest.js.map +1 -0
- package/dist/suite/reporting.d.ts +35 -0
- package/dist/suite/reporting.d.ts.map +1 -0
- package/dist/suite/reporting.js +131 -0
- package/dist/suite/reporting.js.map +1 -0
- package/dist/trace/deepseek-harness.d.ts +31 -0
- package/dist/trace/deepseek-harness.d.ts.map +1 -0
- package/dist/trace/deepseek-harness.js +412 -0
- package/dist/trace/deepseek-harness.js.map +1 -0
- package/dist/trace/import.d.ts +9 -0
- package/dist/trace/import.d.ts.map +1 -0
- package/dist/trace/import.js +45 -0
- package/dist/trace/import.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,1218 @@
|
|
|
1
|
+
import { open, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createEvaluation, getEvaluation, listEvaluationVersions, listEvaluations, updateEvaluationTitleAndRefreshList, } from '../actions/evaluations.js';
|
|
5
|
+
import { runConfiguredEvaluation, validateConfiguredEvaluation, } from '../actions/configured-runs.js';
|
|
6
|
+
import { assignQualityStandardToWorkspace, getWorkspaceQualityStandard, createQualityStandard, getQualityStandard, updateQualityStandard, } from '../actions/quality-standards.js';
|
|
7
|
+
import { whoAmI } from '../actions/identity.js';
|
|
8
|
+
import { getModels } from '../actions/models.js';
|
|
9
|
+
import { getResults } from '../actions/results.js';
|
|
10
|
+
import { runSuite } from '../actions/suite.js';
|
|
11
|
+
import { gateSuite, suiteGateExitError } from '../actions/suite-gate.js';
|
|
12
|
+
import { doctorExitError, runDoctor } from '../actions/doctor.js';
|
|
13
|
+
import { clusterExecutionFailures, clusterSuiteFailures } from '../suite/reporting.js';
|
|
14
|
+
import { evaluateGate } from '../actions/gate.js';
|
|
15
|
+
import { getRunStatus, runEvaluation } from '../actions/runs.js';
|
|
16
|
+
import { createWorkspace, listWorkspaces } from '../actions/workspaces.js';
|
|
17
|
+
import { resolveCredential } from '../auth/credentials.js';
|
|
18
|
+
import { KeyringCredentialStore } from '../auth/keyring-store.js';
|
|
19
|
+
import { login, logoutWithEnvironment } from '../auth/login.js';
|
|
20
|
+
import { MaskedSecretPrompt } from '../auth/password-prompt.js';
|
|
21
|
+
import { applyModelOverrides, parseConfiguredRunInput, requireModelOverridesForPublicPlaceholders, } from '../configured-run/validation.js';
|
|
22
|
+
import { resolveEvalFileInputs } from '../configured-run/file-inputs.js';
|
|
23
|
+
import { ReadlineChoicePrompt } from '../quickstart/prompt.js';
|
|
24
|
+
import { enabledModels, selectQuickstartModel, selectQuickstartWorkspace, } from '../quickstart/selection.js';
|
|
25
|
+
import { quickstartEvaluationName, quickstartSample } from '../quickstart/sample.js';
|
|
26
|
+
import { AutoevalError } from '../errors/autoeval-error.js';
|
|
27
|
+
import { SystemPollClock } from '../polling/clock.js';
|
|
28
|
+
import { createApiActionContext } from '../runtime/action-context.js';
|
|
29
|
+
import { readSuiteManifest } from '../suite/manifest.js';
|
|
30
|
+
import { convertHarnessSession } from '../trace/deepseek-harness.js';
|
|
31
|
+
import { buildAgentTraceInput } from '../trace/import.js';
|
|
32
|
+
import { renderEvaluation, renderEvaluationVersions, renderEvaluations, renderCreatedWorkspace, renderCreatedEvaluation, renderIdentity, renderLogin, renderLoginGuidance, renderLogout, renderModels, renderResults, renderGateReport, renderRunExecution, renderQuickstartPlan, renderQuickstartNextSteps, renderRunStatus, renderConfiguredRunValidation, renderQualityStandard, renderWorkspaceQualityStandard, renderDoctorReport, renderSuiteSummary, renderSuiteGateReport, renderTraceImport, renderWorkspaces, } from '../output/human.js';
|
|
33
|
+
import { createProgressReporter, formatStatusProgress, } from '../output/progress.js';
|
|
34
|
+
import { resolveColorMode, supportsUnicode } from '../output/splash.js';
|
|
35
|
+
import { OutputWriter } from '../output/writer.js';
|
|
36
|
+
const DEFAULT_SUITE_RUN_CONCURRENCY = 3;
|
|
37
|
+
const DEFAULT_SUITE_RUN_START_STAGGER_MS = 250;
|
|
38
|
+
const SUITE_TIMELINE_DEBUG_ENV = 'AUTOEVAL_SUITE_TIMELINE_DEBUG';
|
|
39
|
+
const MAX_TRACE_SESSION_BYTES = 32 * 1024 * 1024;
|
|
40
|
+
const MAX_TRACE_TEMPLATE_BYTES = 1024 * 1024;
|
|
41
|
+
const MAX_TRACE_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
42
|
+
const NULL_STATISTIC_KEYS = new Set([
|
|
43
|
+
'ci95',
|
|
44
|
+
'ci95_lower',
|
|
45
|
+
'ci95_upper',
|
|
46
|
+
'std_dev',
|
|
47
|
+
'overall_ci',
|
|
48
|
+
'overall_ci_lower',
|
|
49
|
+
'overall_ci_upper',
|
|
50
|
+
]);
|
|
51
|
+
async function readBoundedTextFile(inputFile, maxBytes, label, code) {
|
|
52
|
+
let handle;
|
|
53
|
+
try {
|
|
54
|
+
handle = await open(inputFile, 'r');
|
|
55
|
+
const metadata = await handle.stat();
|
|
56
|
+
if (!metadata.isFile()) {
|
|
57
|
+
throw new AutoevalError(`${label} must be a regular file.`, {
|
|
58
|
+
kind: 'usage',
|
|
59
|
+
code,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (metadata.size > maxBytes) {
|
|
63
|
+
throw new AutoevalError(`${label} exceeds the ${Math.floor(maxBytes / 1024 / 1024)} MiB limit.`, {
|
|
64
|
+
kind: 'usage',
|
|
65
|
+
code,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return await handle.readFile('utf8');
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof AutoevalError)
|
|
72
|
+
throw error;
|
|
73
|
+
throw new AutoevalError(`${label} was not found or could not be read.`, {
|
|
74
|
+
kind: 'usage',
|
|
75
|
+
code,
|
|
76
|
+
cause: error,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await handle?.close();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function formatTimelineTimestamp(epochMs) {
|
|
84
|
+
const date = new Date(epochMs);
|
|
85
|
+
const timezoneLabel = new Intl.DateTimeFormat(undefined, { timeZoneName: 'short' })
|
|
86
|
+
.formatToParts(date)
|
|
87
|
+
.find((part) => part.type === 'timeZoneName')?.value ?? 'local';
|
|
88
|
+
return `${date.getHours().toString().padStart(2, '0')}:${date
|
|
89
|
+
.getMinutes()
|
|
90
|
+
.toString()
|
|
91
|
+
.padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}.${date
|
|
92
|
+
.getMilliseconds()
|
|
93
|
+
.toString()
|
|
94
|
+
.padStart(3, '0')} ${timezoneLabel}`;
|
|
95
|
+
}
|
|
96
|
+
const qualityStandardFileSchema = z
|
|
97
|
+
.object({
|
|
98
|
+
name: z.string().trim().min(1),
|
|
99
|
+
judge_model: z.uuid(),
|
|
100
|
+
rubric: z.string().min(1),
|
|
101
|
+
anchors: z
|
|
102
|
+
.array(z
|
|
103
|
+
.object({
|
|
104
|
+
input: z.string().min(1),
|
|
105
|
+
response: z.string().min(1),
|
|
106
|
+
reference: z.string().min(1),
|
|
107
|
+
score: z.union([z.literal(1), z.literal(5)]),
|
|
108
|
+
reasoning: z.string().min(1),
|
|
109
|
+
})
|
|
110
|
+
.strict())
|
|
111
|
+
.min(1)
|
|
112
|
+
.max(5),
|
|
113
|
+
})
|
|
114
|
+
.loose();
|
|
115
|
+
const qualityStandardDraftFileSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
qs_name: z.string().trim().min(1),
|
|
118
|
+
judgeModel: z.uuid(),
|
|
119
|
+
rubric: z.string().min(1),
|
|
120
|
+
anchors: z
|
|
121
|
+
.array(z
|
|
122
|
+
.object({
|
|
123
|
+
user_question: z.string().min(1),
|
|
124
|
+
example_response: z.string().min(1),
|
|
125
|
+
ideal_answer: z.string().min(1),
|
|
126
|
+
why: z.string().min(1),
|
|
127
|
+
score: z.union([z.literal(1), z.literal(5)]),
|
|
128
|
+
rating: z.string().trim().min(1).optional(),
|
|
129
|
+
})
|
|
130
|
+
.loose())
|
|
131
|
+
.min(1)
|
|
132
|
+
.max(5),
|
|
133
|
+
})
|
|
134
|
+
.loose();
|
|
135
|
+
function deriveDraftAnchorScore(score) {
|
|
136
|
+
return score;
|
|
137
|
+
}
|
|
138
|
+
const REQUIRED_ANCHOR_FIELDS = ['input', 'score', 'response', 'reasoning', 'reference'];
|
|
139
|
+
function isCanonicalQualityStandardPayload(parsedJson) {
|
|
140
|
+
if (typeof parsedJson !== 'object' || parsedJson === null) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const record = parsedJson;
|
|
144
|
+
if ('name' in record || 'judge_model' in record) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
const anchors = record.anchors;
|
|
148
|
+
if (!Array.isArray(anchors)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return anchors.some((anchor) => {
|
|
152
|
+
if (typeof anchor !== 'object' || anchor === null) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const anchorRecord = anchor;
|
|
156
|
+
return ('input' in anchorRecord ||
|
|
157
|
+
'response' in anchorRecord ||
|
|
158
|
+
'reference' in anchorRecord ||
|
|
159
|
+
'reasoning' in anchorRecord);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function listMissingCanonicalAnchorFields(parsedJson) {
|
|
163
|
+
if (typeof parsedJson !== 'object' || parsedJson === null || !('anchors' in parsedJson)) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
const anchors = parsedJson.anchors;
|
|
167
|
+
if (!Array.isArray(anchors)) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
const missing = [];
|
|
171
|
+
anchors.forEach((anchor, index) => {
|
|
172
|
+
if (typeof anchor !== 'object' || anchor === null) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const record = anchor;
|
|
176
|
+
for (const field of REQUIRED_ANCHOR_FIELDS) {
|
|
177
|
+
if (!(field in record)) {
|
|
178
|
+
missing.push(`anchors[${index}].${field}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return missing;
|
|
183
|
+
}
|
|
184
|
+
function parseQualityStandardInput(parsedJson) {
|
|
185
|
+
const canonical = qualityStandardFileSchema.safeParse(parsedJson);
|
|
186
|
+
if (canonical.success) {
|
|
187
|
+
const anchors = canonical.data.anchors.map((anchor) => ({
|
|
188
|
+
input: anchor.input,
|
|
189
|
+
score: anchor.score,
|
|
190
|
+
response: anchor.response,
|
|
191
|
+
reasoning: anchor.reasoning,
|
|
192
|
+
reference: anchor.reference,
|
|
193
|
+
}));
|
|
194
|
+
return {
|
|
195
|
+
name: canonical.data.name,
|
|
196
|
+
judge_model: canonical.data.judge_model,
|
|
197
|
+
rubric: canonical.data.rubric,
|
|
198
|
+
anchors,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const missingCanonicalFields = isCanonicalQualityStandardPayload(parsedJson)
|
|
202
|
+
? listMissingCanonicalAnchorFields(parsedJson)
|
|
203
|
+
: [];
|
|
204
|
+
if (missingCanonicalFields.length > 0) {
|
|
205
|
+
throw new AutoevalError(`Quality standard anchors are missing required fields: ${missingCanonicalFields.join(', ')}`, {
|
|
206
|
+
kind: 'usage',
|
|
207
|
+
code: 'INVALID_QUALITY_STANDARD_SCHEMA',
|
|
208
|
+
cause: canonical.error,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const draft = qualityStandardDraftFileSchema.safeParse(parsedJson);
|
|
212
|
+
if (draft.success) {
|
|
213
|
+
return {
|
|
214
|
+
name: draft.data.qs_name,
|
|
215
|
+
judge_model: draft.data.judgeModel,
|
|
216
|
+
rubric: draft.data.rubric,
|
|
217
|
+
anchors: draft.data.anchors.map((anchor) => ({
|
|
218
|
+
input: anchor.user_question,
|
|
219
|
+
response: anchor.example_response,
|
|
220
|
+
reference: anchor.ideal_answer,
|
|
221
|
+
score: deriveDraftAnchorScore(anchor.score),
|
|
222
|
+
reasoning: anchor.why,
|
|
223
|
+
})),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
throw new AutoevalError('Quality standard input does not match a supported schema (canonical or draft format).', {
|
|
227
|
+
kind: 'usage',
|
|
228
|
+
code: 'INVALID_QUALITY_STANDARD_SCHEMA',
|
|
229
|
+
cause: canonical.error,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function qualityStandardIdFromRecord(input) {
|
|
233
|
+
const idCandidate = input.quality_standard_id ?? input.qualityStandardId ?? input.id;
|
|
234
|
+
return typeof idCandidate === 'string' && idCandidate.trim() !== '' ? idCandidate : undefined;
|
|
235
|
+
}
|
|
236
|
+
const assignedWorkspaceQualityStandardSchema = qualityStandardFileSchema
|
|
237
|
+
.extend({
|
|
238
|
+
quality_standard_id: z.uuid().optional(),
|
|
239
|
+
qualityStandardId: z.uuid().optional(),
|
|
240
|
+
id: z.uuid().optional(),
|
|
241
|
+
})
|
|
242
|
+
.loose();
|
|
243
|
+
function assertAssignedWorkspaceQualityStandard(workspaceQualityStandard) {
|
|
244
|
+
const parsed = assignedWorkspaceQualityStandardSchema.safeParse(workspaceQualityStandard);
|
|
245
|
+
if (!parsed.success) {
|
|
246
|
+
throw new AutoevalError('Workspace quality standard lookup returned an invalid quality standard payload.', {
|
|
247
|
+
kind: 'upstream',
|
|
248
|
+
code: 'INVALID_WORKSPACE_QUALITY_STANDARD',
|
|
249
|
+
cause: parsed.error,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return parsed.data;
|
|
253
|
+
}
|
|
254
|
+
async function ensureWorkspaceQualityStandardUnassigned(context, workspaceId, signal) {
|
|
255
|
+
const existing = await getWorkspaceQualityStandard(context, workspaceId, signal);
|
|
256
|
+
if (existing === null) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const validatedExisting = assertAssignedWorkspaceQualityStandard(existing);
|
|
260
|
+
const existingId = qualityStandardIdFromRecord(validatedExisting);
|
|
261
|
+
throw new AutoevalError(existingId
|
|
262
|
+
? `Workspace ${workspaceId} already has a quality standard assigned (${existingId}). Delete the existing quality standard first, then create a new one for this workspace.`
|
|
263
|
+
: `Workspace ${workspaceId} already has a quality standard assigned. Delete the existing quality standard first, then create a new one for this workspace.`, {
|
|
264
|
+
kind: 'validation',
|
|
265
|
+
code: 'WORKSPACE_QUALITY_STANDARD_ALREADY_ASSIGNED',
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
async function readQualityStandardInput(inputFile) {
|
|
269
|
+
let inputFileContent;
|
|
270
|
+
try {
|
|
271
|
+
inputFileContent = await readFile(inputFile, 'utf8');
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
throw new AutoevalError(`Quality standard input file was not found or could not be read: ${inputFile}`, {
|
|
275
|
+
kind: 'usage',
|
|
276
|
+
code: 'QUALITY_STANDARD_INPUT_READ_FAILED',
|
|
277
|
+
cause: error,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
let parsedJson;
|
|
281
|
+
try {
|
|
282
|
+
parsedJson = JSON.parse(inputFileContent);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
throw new AutoevalError('Quality standard input file must contain valid JSON.', {
|
|
286
|
+
kind: 'usage',
|
|
287
|
+
code: 'INVALID_QUALITY_STANDARD_JSON',
|
|
288
|
+
cause: error,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return parseQualityStandardInput(parsedJson);
|
|
292
|
+
}
|
|
293
|
+
export { parseConfiguredRunInput } from '../configured-run/validation.js';
|
|
294
|
+
async function readConfiguredRunInput(inputFile) {
|
|
295
|
+
let inputFileContent;
|
|
296
|
+
try {
|
|
297
|
+
inputFileContent = await readFile(inputFile, 'utf8');
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
throw new AutoevalError(`Configured run input file was not found or could not be read: ${inputFile}`, {
|
|
301
|
+
kind: 'usage',
|
|
302
|
+
code: 'CONFIGURED_RUN_INPUT_READ_FAILED',
|
|
303
|
+
cause: error,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
let parsedJson;
|
|
307
|
+
try {
|
|
308
|
+
parsedJson = JSON.parse(inputFileContent);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
throw new AutoevalError('Configured run input file must contain valid JSON', {
|
|
312
|
+
kind: 'usage',
|
|
313
|
+
code: 'INVALID_CONFIGURED_RUN_JSON',
|
|
314
|
+
cause: error,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return resolveEvalFileInputs(parsedJson, dirname(resolve(inputFile)));
|
|
318
|
+
}
|
|
319
|
+
const gateThresholdsFileSchema = z
|
|
320
|
+
.object({
|
|
321
|
+
minOverall: z.number().optional(),
|
|
322
|
+
minScenario: z.number().optional(),
|
|
323
|
+
minJudgeAgreement: z.number().min(0).max(1).optional(),
|
|
324
|
+
metrics: z.record(z.string().min(1), z.number()).optional(),
|
|
325
|
+
})
|
|
326
|
+
.strict();
|
|
327
|
+
async function readGateThresholdsFile(inputFile) {
|
|
328
|
+
let content;
|
|
329
|
+
try {
|
|
330
|
+
content = await readFile(inputFile, 'utf8');
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
throw new AutoevalError(`Gate thresholds file was not found or could not be read: ${inputFile}`, { kind: 'usage', code: 'GATE_THRESHOLDS_READ_FAILED', cause: error });
|
|
334
|
+
}
|
|
335
|
+
let parsedJson;
|
|
336
|
+
try {
|
|
337
|
+
parsedJson = JSON.parse(content);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
throw new AutoevalError('Gate thresholds file must contain valid JSON.', {
|
|
341
|
+
kind: 'usage',
|
|
342
|
+
code: 'INVALID_GATE_THRESHOLDS_JSON',
|
|
343
|
+
cause: error,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
const parsed = gateThresholdsFileSchema.safeParse(parsedJson);
|
|
347
|
+
if (!parsed.success) {
|
|
348
|
+
throw new AutoevalError('Gate thresholds file must contain only numeric minOverall, minScenario, minJudgeAgreement, or a numeric metrics object.', { kind: 'usage', code: 'INVALID_GATE_THRESHOLD', cause: parsed.error });
|
|
349
|
+
}
|
|
350
|
+
const data = parsed.data;
|
|
351
|
+
return {
|
|
352
|
+
...(data.minOverall !== undefined ? { minOverall: data.minOverall } : {}),
|
|
353
|
+
...(data.minScenario !== undefined ? { minScenario: data.minScenario } : {}),
|
|
354
|
+
...(data.minJudgeAgreement !== undefined ? { minJudgeAgreement: data.minJudgeAgreement } : {}),
|
|
355
|
+
...(data.metrics !== undefined ? { metrics: data.metrics } : {}),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
export class RuntimeCommandExecutor {
|
|
359
|
+
#configuration;
|
|
360
|
+
#environment;
|
|
361
|
+
#store;
|
|
362
|
+
#prompt;
|
|
363
|
+
#choicePrompt;
|
|
364
|
+
#now;
|
|
365
|
+
#clock;
|
|
366
|
+
#fetchImplementation;
|
|
367
|
+
#writer;
|
|
368
|
+
#stdin;
|
|
369
|
+
#stderr;
|
|
370
|
+
#signal;
|
|
371
|
+
#lastCreateFromRuns = [];
|
|
372
|
+
constructor(dependencies) {
|
|
373
|
+
this.#configuration = dependencies.configuration;
|
|
374
|
+
this.#environment = dependencies.environment ?? process.env;
|
|
375
|
+
this.#store =
|
|
376
|
+
dependencies.store ??
|
|
377
|
+
new KeyringCredentialStore(dependencies.configuration.apiBaseUrl.origin);
|
|
378
|
+
this.#prompt = dependencies.prompt ?? new MaskedSecretPrompt();
|
|
379
|
+
this.#choicePrompt = dependencies.choicePrompt ?? new ReadlineChoicePrompt();
|
|
380
|
+
this.#now = dependencies.now ?? (() => new Date());
|
|
381
|
+
this.#clock = dependencies.clock ?? new SystemPollClock();
|
|
382
|
+
this.#fetchImplementation = dependencies.fetchImplementation;
|
|
383
|
+
const stdout = dependencies.stdout ?? process.stdout;
|
|
384
|
+
this.#stderr = dependencies.stderr ?? process.stderr;
|
|
385
|
+
this.#writer = new OutputWriter(stdout, this.#stderr);
|
|
386
|
+
this.#stdin = dependencies.stdin ?? process.stdin;
|
|
387
|
+
this.#signal = dependencies.signal;
|
|
388
|
+
}
|
|
389
|
+
async execute(command, options) {
|
|
390
|
+
this.#lastCreateFromRuns = [];
|
|
391
|
+
if (command.kind === 'logout') {
|
|
392
|
+
const result = await logoutWithEnvironment({
|
|
393
|
+
store: this.#store,
|
|
394
|
+
environment: this.#environment,
|
|
395
|
+
});
|
|
396
|
+
this.#writer.writeResult(result, renderLogout(result.wasRemoved, result.hasEnvironmentCredential), options.json);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (command.kind === 'login') {
|
|
400
|
+
const allowPrompt = command.key === undefined &&
|
|
401
|
+
this.#stdin.isTTY === true &&
|
|
402
|
+
this.#stderr.isTTY === true &&
|
|
403
|
+
!options.json;
|
|
404
|
+
if (allowPrompt) {
|
|
405
|
+
const environmentKey = this.#environment.AUTOEVAL_API_KEY;
|
|
406
|
+
const hasEnvironmentKey = environmentKey !== undefined && environmentKey.trim() !== '';
|
|
407
|
+
let hasStoredKey = false;
|
|
408
|
+
if (!hasEnvironmentKey) {
|
|
409
|
+
try {
|
|
410
|
+
const storedKey = await this.#store.read();
|
|
411
|
+
hasStoredKey = storedKey !== undefined && storedKey.trim() !== '';
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
hasStoredKey = false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (!hasEnvironmentKey && !hasStoredKey) {
|
|
418
|
+
this.#stderr.write(renderLoginGuidance());
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const result = await login({
|
|
422
|
+
environment: this.#environment,
|
|
423
|
+
store: this.#store,
|
|
424
|
+
prompt: this.#prompt,
|
|
425
|
+
allowPrompt,
|
|
426
|
+
...(command.key !== undefined ? { explicitKey: command.key } : {}),
|
|
427
|
+
validate: async (apiKey) => this.#createContext(apiKey, options).api.getCurrentUser(this.#signal),
|
|
428
|
+
});
|
|
429
|
+
this.#writer.writeResult(result, renderLogin(result), options.json);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
// Trace conversion is local-only. Resolve credentials only if the command
|
|
433
|
+
// subsequently submits the generated evaluation.
|
|
434
|
+
if (command.kind === 'trace-import') {
|
|
435
|
+
const report = await this.#importTrace(command, options);
|
|
436
|
+
if (command.run) {
|
|
437
|
+
await this.execute({
|
|
438
|
+
kind: 'evaluation-create-from',
|
|
439
|
+
workspaceId: report.workspaceId,
|
|
440
|
+
inputFiles: [command.outputFile],
|
|
441
|
+
run: true,
|
|
442
|
+
}, options);
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const credential = await resolveCredential({
|
|
447
|
+
environment: this.#environment,
|
|
448
|
+
store: this.#store,
|
|
449
|
+
prompt: this.#prompt,
|
|
450
|
+
allowPrompt: false,
|
|
451
|
+
});
|
|
452
|
+
const context = this.#createContext(credential.apiKey, options);
|
|
453
|
+
switch (command.kind) {
|
|
454
|
+
case 'whoami': {
|
|
455
|
+
const result = await whoAmI(context, this.#signal);
|
|
456
|
+
this.#writer.writeResult(result, renderIdentity(result), options.json);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
case 'workspace-list': {
|
|
460
|
+
const result = await listWorkspaces(context, {}, this.#signal);
|
|
461
|
+
this.#writer.writeResult(result, renderWorkspaces(result), options.json);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
case 'workspace-create': {
|
|
465
|
+
const result = await createWorkspace(context, {
|
|
466
|
+
name: command.name,
|
|
467
|
+
...(command.description ? { description: command.description } : {}),
|
|
468
|
+
}, this.#signal);
|
|
469
|
+
this.#writer.writeResult(result, renderCreatedWorkspace(result), options.json);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
case 'evaluation-list': {
|
|
473
|
+
const result = await listEvaluations(context, { workspaceId: command.workspaceId }, this.#signal);
|
|
474
|
+
this.#writer.writeResult(result, renderEvaluations(result), options.json);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
case 'evaluation-create': {
|
|
478
|
+
const result = await createEvaluation(context, {
|
|
479
|
+
workspaceId: command.workspaceId,
|
|
480
|
+
...(command.name ? { evaluationName: command.name } : {}),
|
|
481
|
+
}, this.#signal);
|
|
482
|
+
this.#writer.writeResult(result, renderCreatedEvaluation(result), options.json);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
case 'evaluation-update-title': {
|
|
486
|
+
const result = await updateEvaluationTitleAndRefreshList(context, {
|
|
487
|
+
workspaceId: command.workspaceId,
|
|
488
|
+
evaluationId: command.evaluationId,
|
|
489
|
+
evaluationName: command.name,
|
|
490
|
+
userSystemId: command.userSystemId,
|
|
491
|
+
...(command.description !== undefined ? { description: command.description } : {}),
|
|
492
|
+
...(command.page !== undefined ? { page: command.page } : {}),
|
|
493
|
+
...(command.size !== undefined ? { size: command.size } : {}),
|
|
494
|
+
}, this.#signal);
|
|
495
|
+
this.#writer.writeResult(result, renderEvaluations(result.page), options.json);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
case 'quality-standard-create': {
|
|
499
|
+
const payload = await readQualityStandardInput(command.inputFile);
|
|
500
|
+
if (command.workspaceId !== undefined) {
|
|
501
|
+
await ensureWorkspaceQualityStandardUnassigned(context, command.workspaceId, this.#signal);
|
|
502
|
+
}
|
|
503
|
+
const result = await createQualityStandard(context, payload, this.#signal);
|
|
504
|
+
if (command.workspaceId !== undefined) {
|
|
505
|
+
if (result.qualityStandardId === undefined) {
|
|
506
|
+
throw new AutoevalError('The Autoeval API did not return a quality standard ID, so workspace association could not be completed.', {
|
|
507
|
+
kind: 'upstream',
|
|
508
|
+
code: 'MISSING_QUALITY_STANDARD_ID',
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
await assignQualityStandardToWorkspace(context, {
|
|
512
|
+
workspaceId: command.workspaceId,
|
|
513
|
+
qualityStandardId: result.qualityStandardId,
|
|
514
|
+
}, this.#signal);
|
|
515
|
+
}
|
|
516
|
+
const rendered = result.qualityStandardId !== undefined
|
|
517
|
+
? command.workspaceId !== undefined
|
|
518
|
+
? `Created quality standard ${payload.name} (${result.qualityStandardId}) and associated it with workspace ${command.workspaceId}\n`
|
|
519
|
+
: `Created quality standard ${payload.name} (${result.qualityStandardId})\n`
|
|
520
|
+
: `Created quality standard ${payload.name}\n`;
|
|
521
|
+
this.#writer.writeResult(result, rendered, options.json);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
case 'quality-standard-update': {
|
|
525
|
+
const payload = await readQualityStandardInput(command.inputFile);
|
|
526
|
+
const result = await updateQualityStandard(context, {
|
|
527
|
+
qualityStandardId: command.qualityStandardId,
|
|
528
|
+
name: payload.name,
|
|
529
|
+
judge_model: payload.judge_model,
|
|
530
|
+
rubric: payload.rubric,
|
|
531
|
+
anchors: payload.anchors,
|
|
532
|
+
}, this.#signal);
|
|
533
|
+
this.#writer.writeResult(result, `Updated quality standard ${payload.name} (${command.qualityStandardId})\n`, options.json);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
case 'quality-standard-assign': {
|
|
537
|
+
const result = await assignQualityStandardToWorkspace(context, {
|
|
538
|
+
workspaceId: command.workspaceId,
|
|
539
|
+
qualityStandardId: command.qualityStandardId,
|
|
540
|
+
}, this.#signal);
|
|
541
|
+
this.#writer.writeResult(result, `Associated quality standard ${command.qualityStandardId} with workspace ${command.workspaceId}\n`, options.json);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
case 'quality-standard-workspace': {
|
|
545
|
+
const workspaceQualityStandard = await getWorkspaceQualityStandard(context, command.workspaceId, this.#signal);
|
|
546
|
+
const result = workspaceQualityStandard === null
|
|
547
|
+
? null
|
|
548
|
+
: assertAssignedWorkspaceQualityStandard(workspaceQualityStandard);
|
|
549
|
+
this.#writer.writeResult(result, renderWorkspaceQualityStandard(result, command.workspaceId), options.json);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
case 'quality-standard-show': {
|
|
553
|
+
const result = await getQualityStandard(context, command.qualityStandardId, this.#signal);
|
|
554
|
+
this.#writer.writeResult(result, renderQualityStandard(result, command.qualityStandardId), options.json);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
case 'evaluation-validate': {
|
|
558
|
+
const parsedJson = await readConfiguredRunInput(command.inputFile);
|
|
559
|
+
const input = parseConfiguredRunInput(parsedJson);
|
|
560
|
+
const result = await validateConfiguredEvaluation(context, input, this.#signal);
|
|
561
|
+
this.#writer.writeResult(result, renderConfiguredRunValidation(result), options.json);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
case 'suite-run': {
|
|
565
|
+
const manifest = await readSuiteManifest(command.manifestFile);
|
|
566
|
+
const summary = await this.#runSuiteFromManifest(context, manifest, command, options);
|
|
567
|
+
const failureClusters = clusterExecutionFailures(summary);
|
|
568
|
+
this.#writer.writeResult({ ...summary, failureClusters }, renderSuiteSummary(summary, failureClusters), options.json);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
case 'suite-gate': {
|
|
572
|
+
const manifest = await readSuiteManifest(command.manifestFile);
|
|
573
|
+
const summary = await this.#runSuiteFromManifest(context, manifest, command, options);
|
|
574
|
+
const policyByFile = new Map(manifest.entries.map((entry) => [entry.file, entry.gate]));
|
|
575
|
+
const gateResult = gateSuite({ summary, policyByFile });
|
|
576
|
+
const failureClusters = clusterSuiteFailures(gateResult);
|
|
577
|
+
this.#writer.writeResult({ ...gateResult, failureClusters }, renderSuiteGateReport(gateResult, failureClusters), options.json);
|
|
578
|
+
const gateError = suiteGateExitError(gateResult);
|
|
579
|
+
if (gateError !== undefined)
|
|
580
|
+
throw gateError;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
case 'evaluation-create-from': {
|
|
584
|
+
const created = [];
|
|
585
|
+
const elapsedByEvaluationId = new Map();
|
|
586
|
+
const createAndMaybeRun = async (inputFile) => {
|
|
587
|
+
const parsedConfiguredRun = parseConfiguredRunInput(await readConfiguredRunInput(inputFile));
|
|
588
|
+
const configuredRun = applyModelOverrides(parsedConfiguredRun, {
|
|
589
|
+
...(command.judgeModelId === undefined ? {} : { judgeModelId: command.judgeModelId }),
|
|
590
|
+
...(command.primaryModelId === undefined
|
|
591
|
+
? {}
|
|
592
|
+
: { primaryModelId: command.primaryModelId }),
|
|
593
|
+
});
|
|
594
|
+
if (command.run) {
|
|
595
|
+
requireModelOverridesForPublicPlaceholders(configuredRun);
|
|
596
|
+
await validateConfiguredEvaluation(context, configuredRun, this.#signal);
|
|
597
|
+
}
|
|
598
|
+
const evaluation = await createEvaluation(context, {
|
|
599
|
+
workspaceId: command.workspaceId,
|
|
600
|
+
evaluationName: configuredRun.evaluationName ?? configuredRun.configuration.contextName,
|
|
601
|
+
}, this.#signal);
|
|
602
|
+
const createdEntry = {
|
|
603
|
+
evaluationId: evaluation.evaluationId,
|
|
604
|
+
name: configuredRun.evaluationName ?? configuredRun.configuration.contextName,
|
|
605
|
+
input: inputFile,
|
|
606
|
+
};
|
|
607
|
+
if (!command.run) {
|
|
608
|
+
return createdEntry;
|
|
609
|
+
}
|
|
610
|
+
const progress = this.#createProgressReporter(`Configured run (${configuredRun.configuration.contextName})`, options);
|
|
611
|
+
try {
|
|
612
|
+
progress.update('starting run');
|
|
613
|
+
const run = await runConfiguredEvaluation(context, {
|
|
614
|
+
evaluationId: evaluation.evaluationId,
|
|
615
|
+
configuredRun,
|
|
616
|
+
clock: this.#clock,
|
|
617
|
+
pollIntervalMs: this.#configuration.pollIntervalMs,
|
|
618
|
+
pollTimeoutMs: this.#configuration.pollTimeoutMs,
|
|
619
|
+
prevalidated: true,
|
|
620
|
+
onStatus: (status) => progress.reportStatus(status),
|
|
621
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
622
|
+
});
|
|
623
|
+
createdEntry.runId = run.run.runId;
|
|
624
|
+
elapsedByEvaluationId.set(evaluation.evaluationId, run.outcome.elapsedMs);
|
|
625
|
+
return createdEntry;
|
|
626
|
+
}
|
|
627
|
+
finally {
|
|
628
|
+
progress.stop();
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
if (!command.run) {
|
|
632
|
+
for (const inputFile of command.inputFiles) {
|
|
633
|
+
created.push(await createAndMaybeRun(inputFile));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
const runConcurrency = command.runConcurrency ?? command.inputFiles.length;
|
|
638
|
+
const runStartStaggerMs = command.runStartStaggerMs ?? 0;
|
|
639
|
+
const timelineDebug = this.#environment[SUITE_TIMELINE_DEBUG_ENV] === '1';
|
|
640
|
+
const maxWorkers = Math.max(1, Math.min(runConcurrency, command.inputFiles.length));
|
|
641
|
+
const orderedEntries = new Array(command.inputFiles.length);
|
|
642
|
+
let nextIndex = 0;
|
|
643
|
+
let nextStartAtMs = this.#clock.now();
|
|
644
|
+
const claimNext = async () => {
|
|
645
|
+
if (nextIndex >= command.inputFiles.length)
|
|
646
|
+
return undefined;
|
|
647
|
+
const index = nextIndex;
|
|
648
|
+
nextIndex += 1;
|
|
649
|
+
const inputFile = command.inputFiles[index];
|
|
650
|
+
if (inputFile === undefined)
|
|
651
|
+
return undefined;
|
|
652
|
+
const scheduledStartMs = nextStartAtMs;
|
|
653
|
+
nextStartAtMs += runStartStaggerMs;
|
|
654
|
+
const delayMs = Math.max(0, scheduledStartMs - this.#clock.now());
|
|
655
|
+
if (delayMs > 0) {
|
|
656
|
+
await this.#clock.sleep(delayMs, this.#signal);
|
|
657
|
+
}
|
|
658
|
+
return { index, inputFile };
|
|
659
|
+
};
|
|
660
|
+
const worker = async () => {
|
|
661
|
+
while (true) {
|
|
662
|
+
const next = await claimNext();
|
|
663
|
+
if (next === undefined)
|
|
664
|
+
return;
|
|
665
|
+
const evalNumber = next.index + 1;
|
|
666
|
+
if (timelineDebug) {
|
|
667
|
+
const startedAt = formatTimelineTimestamp(this.#clock.now());
|
|
668
|
+
this.#stderr.write(`[${startedAt}] START Eval ${evalNumber}\n`);
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
const createdEntry = await createAndMaybeRun(next.inputFile);
|
|
672
|
+
orderedEntries[next.index] = createdEntry;
|
|
673
|
+
if (timelineDebug) {
|
|
674
|
+
const finishedAt = formatTimelineTimestamp(this.#clock.now());
|
|
675
|
+
this.#stderr.write(`[${finishedAt}] FINISH Eval ${evalNumber}\n`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
if (timelineDebug) {
|
|
680
|
+
const failedAt = formatTimelineTimestamp(this.#clock.now());
|
|
681
|
+
this.#stderr.write(`[${failedAt}] FAIL Eval ${evalNumber}\n`);
|
|
682
|
+
}
|
|
683
|
+
throw error;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
await Promise.all(Array.from({ length: maxWorkers }, () => worker()));
|
|
688
|
+
created.push(...orderedEntries.filter((entry) => entry !== undefined));
|
|
689
|
+
}
|
|
690
|
+
const rendered = created
|
|
691
|
+
.map((entry) => {
|
|
692
|
+
const header = `Created "${entry.name}" (${entry.evaluationId}) from ${entry.input}\n`;
|
|
693
|
+
if (!command.run) {
|
|
694
|
+
return `${header} Run it with: autoeval eval run-configured --evaluation ${entry.evaluationId} --input ${entry.input}\n`;
|
|
695
|
+
}
|
|
696
|
+
if (entry.runId === undefined)
|
|
697
|
+
return header;
|
|
698
|
+
const seconds = Math.round((elapsedByEvaluationId.get(entry.evaluationId) ?? 0) / 1_000);
|
|
699
|
+
return [
|
|
700
|
+
header,
|
|
701
|
+
` Run ${entry.runId} finished in ${seconds}s\n`,
|
|
702
|
+
'\n',
|
|
703
|
+
' Fetch the results with:\n',
|
|
704
|
+
` autoeval results ${entry.evaluationId} ${entry.runId}\n`,
|
|
705
|
+
'\n',
|
|
706
|
+
' Add --json for machine-readable output.\n',
|
|
707
|
+
].join('');
|
|
708
|
+
})
|
|
709
|
+
.join('\n');
|
|
710
|
+
this.#lastCreateFromRuns = created
|
|
711
|
+
.filter((entry) => typeof entry.runId === 'string')
|
|
712
|
+
.map((entry) => ({ evaluationId: entry.evaluationId, runId: entry.runId }));
|
|
713
|
+
this.#writer.writeResult({ created, ran: command.run }, rendered, options.json);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
case 'evaluation-run-configured': {
|
|
717
|
+
const parsedJson = await readConfiguredRunInput(command.inputFile);
|
|
718
|
+
const configuredRun = parseConfiguredRunInput(parsedJson);
|
|
719
|
+
const progress = this.#createProgressReporter('Configured run', options);
|
|
720
|
+
try {
|
|
721
|
+
progress.update('validating input and creating versions');
|
|
722
|
+
const result = await runConfiguredEvaluation(context, {
|
|
723
|
+
evaluationId: command.evaluationId,
|
|
724
|
+
configuredRun,
|
|
725
|
+
clock: this.#clock,
|
|
726
|
+
pollIntervalMs: this.#configuration.pollIntervalMs,
|
|
727
|
+
pollTimeoutMs: this.#configuration.pollTimeoutMs,
|
|
728
|
+
onStatus: (status) => progress.reportStatus(status),
|
|
729
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
730
|
+
});
|
|
731
|
+
progress.stop();
|
|
732
|
+
this.#writer.writeResult(result, renderRunExecution(result), options.json);
|
|
733
|
+
}
|
|
734
|
+
finally {
|
|
735
|
+
progress.stop();
|
|
736
|
+
}
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
case 'evaluation-show': {
|
|
740
|
+
const result = await getEvaluation(context, command.evaluationId, command.version, this.#signal);
|
|
741
|
+
this.#writer.writeResult(result, renderEvaluation(result), options.json);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
case 'evaluation-versions': {
|
|
745
|
+
const result = await listEvaluationVersions(context, command.evaluationId, this.#signal);
|
|
746
|
+
this.#writer.writeResult(result, renderEvaluationVersions(result), options.json);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
case 'doctor': {
|
|
750
|
+
const manifestFile = command.manifestFile;
|
|
751
|
+
const report = await runDoctor(context, {
|
|
752
|
+
loadEvalFile: async (inputFile) => parseConfiguredRunInput(await readConfiguredRunInput(inputFile)),
|
|
753
|
+
evalFiles: command.inputFiles,
|
|
754
|
+
...(command.workspaceId === undefined ? {} : { workspaceId: command.workspaceId }),
|
|
755
|
+
...(manifestFile === undefined
|
|
756
|
+
? {}
|
|
757
|
+
: {
|
|
758
|
+
manifestFile,
|
|
759
|
+
loadManifest: () => readSuiteManifest(manifestFile),
|
|
760
|
+
}),
|
|
761
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
762
|
+
});
|
|
763
|
+
this.#writer.writeResult(report, renderDoctorReport(report), options.json);
|
|
764
|
+
const doctorError = doctorExitError(report);
|
|
765
|
+
if (doctorError !== undefined)
|
|
766
|
+
throw doctorError;
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
case 'models': {
|
|
770
|
+
const result = await getModels(context, this.#signal);
|
|
771
|
+
this.#writer.writeResult(result, renderModels(result), options.json);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
case 'quickstart': {
|
|
775
|
+
await this.#runQuickstart(context, command, options);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
case 'run': {
|
|
779
|
+
const progress = this.#createProgressReporter('Run', options);
|
|
780
|
+
try {
|
|
781
|
+
progress.update('starting');
|
|
782
|
+
const result = await runEvaluation(context, {
|
|
783
|
+
evaluationId: command.evaluationId,
|
|
784
|
+
clock: this.#clock,
|
|
785
|
+
pollIntervalMs: this.#configuration.pollIntervalMs,
|
|
786
|
+
pollTimeoutMs: this.#configuration.pollTimeoutMs,
|
|
787
|
+
onStatus: (status) => progress.reportStatus(status),
|
|
788
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
789
|
+
});
|
|
790
|
+
progress.stop();
|
|
791
|
+
this.#writer.writeResult(result, renderRunExecution(result), options.json);
|
|
792
|
+
}
|
|
793
|
+
finally {
|
|
794
|
+
progress.stop();
|
|
795
|
+
}
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
case 'status': {
|
|
799
|
+
const result = await getRunStatus(context, command, this.#signal);
|
|
800
|
+
this.#writer.writeResult(result, renderRunStatus(result), options.json);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
case 'gate': {
|
|
804
|
+
const fileThresholds = command.thresholdsFile === undefined
|
|
805
|
+
? {}
|
|
806
|
+
: await readGateThresholdsFile(command.thresholdsFile);
|
|
807
|
+
const thresholds = {
|
|
808
|
+
...fileThresholds,
|
|
809
|
+
...command.thresholds,
|
|
810
|
+
...(fileThresholds.metrics === undefined && command.thresholds.metrics === undefined
|
|
811
|
+
? {}
|
|
812
|
+
: { metrics: { ...fileThresholds.metrics, ...command.thresholds.metrics } }),
|
|
813
|
+
};
|
|
814
|
+
if (thresholds.minOverall === undefined &&
|
|
815
|
+
thresholds.minScenario === undefined &&
|
|
816
|
+
thresholds.minJudgeAgreement === undefined &&
|
|
817
|
+
Object.keys(thresholds.metrics ?? {}).length === 0) {
|
|
818
|
+
throw new AutoevalError('The gate needs at least one configured threshold.', {
|
|
819
|
+
kind: 'usage',
|
|
820
|
+
code: 'NO_GATE_THRESHOLD',
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
const progress = this.#createProgressReporter('Gate', options);
|
|
824
|
+
let execution;
|
|
825
|
+
let results;
|
|
826
|
+
try {
|
|
827
|
+
progress.update('starting run');
|
|
828
|
+
execution = await runEvaluation(context, {
|
|
829
|
+
evaluationId: command.evaluationId,
|
|
830
|
+
clock: this.#clock,
|
|
831
|
+
pollIntervalMs: this.#configuration.pollIntervalMs,
|
|
832
|
+
pollTimeoutMs: this.#configuration.pollTimeoutMs,
|
|
833
|
+
onStatus: (status) => progress.reportStatus(status),
|
|
834
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
835
|
+
});
|
|
836
|
+
progress.update('reading results');
|
|
837
|
+
results = await getResults(context, {
|
|
838
|
+
evaluationId: execution.run.evaluationId,
|
|
839
|
+
runId: execution.run.runId,
|
|
840
|
+
clock: this.#clock,
|
|
841
|
+
resultReadyIntervalMs: this.#configuration.resultReadyIntervalMs,
|
|
842
|
+
resultReadyTimeoutMs: this.#configuration.resultReadyTimeoutMs,
|
|
843
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
finally {
|
|
847
|
+
progress.stop();
|
|
848
|
+
}
|
|
849
|
+
const report = evaluateGate(results, thresholds);
|
|
850
|
+
const summary = {
|
|
851
|
+
report,
|
|
852
|
+
run: { ...execution.run, status: execution.outcome.status.state },
|
|
853
|
+
elapsedMs: execution.outcome.elapsedMs,
|
|
854
|
+
};
|
|
855
|
+
this.#writer.writeResult(summary, renderGateReport(summary), options.json);
|
|
856
|
+
if (report.decision === 'FAIL') {
|
|
857
|
+
const failed = report.checks
|
|
858
|
+
.filter((check) => check.decision === 'FAIL')
|
|
859
|
+
.map((check) => check.label)
|
|
860
|
+
.join(', ');
|
|
861
|
+
throw new AutoevalError(`Gate failed: ${failed} did not meet the configured threshold.`, {
|
|
862
|
+
kind: 'gate_failed',
|
|
863
|
+
code: 'GATE_THRESHOLD_NOT_MET',
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
if (report.decision === 'INCONCLUSIVE') {
|
|
867
|
+
const blocked = report.checks
|
|
868
|
+
.filter((check) => check.decision === 'INCONCLUSIVE')
|
|
869
|
+
.map((check) => check.label)
|
|
870
|
+
.join(', ');
|
|
871
|
+
throw new AutoevalError(report.advisory
|
|
872
|
+
? 'Gate inconclusive: no metric thresholds are configured for this context type.'
|
|
873
|
+
: `Gate inconclusive: ${blocked} did not report usable evidence.`, { kind: 'gate_failed', code: 'GATE_INCONCLUSIVE' });
|
|
874
|
+
}
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
case 'results': {
|
|
878
|
+
const progress = this.#createProgressReporter('Results', options);
|
|
879
|
+
try {
|
|
880
|
+
progress.update('reading results');
|
|
881
|
+
const result = await getResults(context, {
|
|
882
|
+
evaluationId: command.evaluationId,
|
|
883
|
+
runId: command.runId,
|
|
884
|
+
clock: this.#clock,
|
|
885
|
+
resultReadyIntervalMs: this.#configuration.resultReadyIntervalMs,
|
|
886
|
+
resultReadyTimeoutMs: this.#configuration.resultReadyTimeoutMs,
|
|
887
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
888
|
+
});
|
|
889
|
+
const scenarioStatus = result.contextType === 'scenario' && !options.json
|
|
890
|
+
? await getRunStatus(context, { evaluationId: command.evaluationId, runId: command.runId }, this.#signal)
|
|
891
|
+
.then((status) => status.raw)
|
|
892
|
+
.catch(() => undefined)
|
|
893
|
+
: undefined;
|
|
894
|
+
progress.stop();
|
|
895
|
+
const humanResult = this.#omitNullStatisticFields(result);
|
|
896
|
+
const output = options.json ? result : humanResult;
|
|
897
|
+
this.#writer.writeResult(output, renderResults(humanResult, {
|
|
898
|
+
showOutputs: command.showOutputs === true,
|
|
899
|
+
...(scenarioStatus === undefined ? {} : { scenarioStatus }),
|
|
900
|
+
}), options.json);
|
|
901
|
+
}
|
|
902
|
+
finally {
|
|
903
|
+
progress.stop();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
/** True when a numbered picker may be shown. */
|
|
909
|
+
#canPrompt(options, assumeYes) {
|
|
910
|
+
return !assumeYes && !options.json && this.#stdin.isTTY === true && this.#stderr.isTTY === true;
|
|
911
|
+
}
|
|
912
|
+
async #runQuickstart(context, command, options) {
|
|
913
|
+
const interactive = this.#canPrompt(options, command.assumeYes);
|
|
914
|
+
const workspacePage = await listWorkspaces(context, {}, this.#signal);
|
|
915
|
+
const workspaceResolution = selectQuickstartWorkspace(workspacePage.items, command.workspaceId);
|
|
916
|
+
let workspace;
|
|
917
|
+
if (workspaceResolution.kind === 'resolved') {
|
|
918
|
+
workspace = workspaceResolution.workspace;
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
if (!interactive) {
|
|
922
|
+
throw new AutoevalError('This account has more than one workspace. Pass --workspace <workspace-id>; run "autoeval workspace list" to see them.', { kind: 'usage', code: 'QUICKSTART_WORKSPACE_REQUIRED' });
|
|
923
|
+
}
|
|
924
|
+
const index = await this.#choicePrompt.choose('Which workspace should this evaluation go in?', workspaceResolution.candidates.map((candidate) => ({
|
|
925
|
+
label: candidate.name,
|
|
926
|
+
detail: `${candidate.evaluationCount} evaluations`,
|
|
927
|
+
})));
|
|
928
|
+
const chosen = workspaceResolution.candidates[index];
|
|
929
|
+
if (!chosen) {
|
|
930
|
+
throw new AutoevalError('No workspace was chosen.', {
|
|
931
|
+
kind: 'usage',
|
|
932
|
+
code: 'QUICKSTART_WORKSPACE_REQUIRED',
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
workspace = chosen;
|
|
936
|
+
}
|
|
937
|
+
const models = await getModels(context, this.#signal);
|
|
938
|
+
const candidates = enabledModels(models);
|
|
939
|
+
const resolveModel = async (role, override) => {
|
|
940
|
+
const resolution = selectQuickstartModel(candidates, role, override);
|
|
941
|
+
if (resolution.kind === 'resolved')
|
|
942
|
+
return resolution.model;
|
|
943
|
+
if (!interactive) {
|
|
944
|
+
throw new AutoevalError('More than one model is enabled and none matches the quickstart preference list. Pass --judge-model-id and --primary-model-id; run "autoeval models" to see the IDs.', { kind: 'usage', code: 'QUICKSTART_MODEL_REQUIRED' });
|
|
945
|
+
}
|
|
946
|
+
const index = await this.#choicePrompt.choose(role === 'judge' ? 'Which model should judge?' : 'Which model is under test?', resolution.candidates.map((model) => ({
|
|
947
|
+
label: model.displayName,
|
|
948
|
+
detail: model.provider,
|
|
949
|
+
})));
|
|
950
|
+
const chosen = resolution.candidates[index];
|
|
951
|
+
if (!chosen) {
|
|
952
|
+
throw new AutoevalError('No model was chosen.', {
|
|
953
|
+
kind: 'usage',
|
|
954
|
+
code: 'QUICKSTART_MODEL_REQUIRED',
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
return chosen;
|
|
958
|
+
};
|
|
959
|
+
const bundled = quickstartSample(command.sample);
|
|
960
|
+
const sampleSource = command.inputFile === undefined
|
|
961
|
+
? bundled.data
|
|
962
|
+
: await readConfiguredRunInput(command.inputFile);
|
|
963
|
+
const sampleLabel = command.inputFile ?? `${bundled.name} (bundled)`;
|
|
964
|
+
const parsed = parseConfiguredRunInput(sampleSource);
|
|
965
|
+
const judge = await resolveModel('judge', command.judgeModelId);
|
|
966
|
+
const primary = parsed.configuration.contextType === 'scenario'
|
|
967
|
+
? await resolveModel('primary', command.primaryModelId)
|
|
968
|
+
: undefined;
|
|
969
|
+
const overridden = applyModelOverrides(parsed, {
|
|
970
|
+
judgeModelId: judge.id,
|
|
971
|
+
...(primary === undefined ? {} : { primaryModelId: primary.id }),
|
|
972
|
+
});
|
|
973
|
+
const quickstartAgentTrace = overridden.configuration.contextType === 'agent_trace';
|
|
974
|
+
const configuredRun = {
|
|
975
|
+
...overridden,
|
|
976
|
+
methodology: {
|
|
977
|
+
...overridden.methodology,
|
|
978
|
+
judgeModel: quickstartAgentTrace ? judge.id : judge.displayName,
|
|
979
|
+
...(quickstartAgentTrace ? { runsPerScenario: 1 } : {}),
|
|
980
|
+
},
|
|
981
|
+
};
|
|
982
|
+
const evaluationName = quickstartEvaluationName(this.#now());
|
|
983
|
+
await validateConfiguredEvaluation(context, configuredRun, this.#signal);
|
|
984
|
+
const evaluation = await createEvaluation(context, { workspaceId: workspace.id, evaluationName }, this.#signal);
|
|
985
|
+
if (!options.json) {
|
|
986
|
+
this.#stderr.write(renderQuickstartPlan({
|
|
987
|
+
workspace: { id: workspace.id, name: workspace.name },
|
|
988
|
+
judge,
|
|
989
|
+
...(primary === undefined ? {} : { primary }),
|
|
990
|
+
sample: sampleLabel,
|
|
991
|
+
evaluationName,
|
|
992
|
+
evaluationId: evaluation.evaluationId,
|
|
993
|
+
}));
|
|
994
|
+
}
|
|
995
|
+
const progress = this.#createProgressReporter('Quickstart', options);
|
|
996
|
+
try {
|
|
997
|
+
progress.update('starting run');
|
|
998
|
+
const run = await runConfiguredEvaluation(context, {
|
|
999
|
+
evaluationId: evaluation.evaluationId,
|
|
1000
|
+
configuredRun,
|
|
1001
|
+
clock: this.#clock,
|
|
1002
|
+
pollIntervalMs: this.#configuration.pollIntervalMs,
|
|
1003
|
+
pollTimeoutMs: this.#configuration.pollTimeoutMs,
|
|
1004
|
+
prevalidated: true,
|
|
1005
|
+
onStatus: (status) => progress.reportStatus(status),
|
|
1006
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
1007
|
+
});
|
|
1008
|
+
progress.update('reading results');
|
|
1009
|
+
const results = await getResults(context, {
|
|
1010
|
+
evaluationId: evaluation.evaluationId,
|
|
1011
|
+
runId: run.run.runId,
|
|
1012
|
+
clock: this.#clock,
|
|
1013
|
+
resultReadyIntervalMs: this.#configuration.resultReadyIntervalMs,
|
|
1014
|
+
resultReadyTimeoutMs: this.#configuration.resultReadyTimeoutMs,
|
|
1015
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
1016
|
+
});
|
|
1017
|
+
this.#lastCreateFromRuns = [{ evaluationId: evaluation.evaluationId, runId: run.run.runId }];
|
|
1018
|
+
progress.stop();
|
|
1019
|
+
const humanResults = this.#omitNullStatisticFields(results);
|
|
1020
|
+
this.#writer.writeResult({
|
|
1021
|
+
workspaceId: workspace.id,
|
|
1022
|
+
evaluationId: evaluation.evaluationId,
|
|
1023
|
+
evaluationName,
|
|
1024
|
+
runId: run.run.runId,
|
|
1025
|
+
judgeModelId: judge.id,
|
|
1026
|
+
...(primary === undefined ? {} : { primaryModelId: primary.id }),
|
|
1027
|
+
results: this.#redactResultsJson(results),
|
|
1028
|
+
}, `${renderResults(humanResults, {
|
|
1029
|
+
...(results.contextType === 'scenario' ? { scenarioStatus: run.outcome.status.raw } : {}),
|
|
1030
|
+
})}${renderQuickstartNextSteps(evaluation.evaluationId, run.run.runId)}`, options.json);
|
|
1031
|
+
}
|
|
1032
|
+
finally {
|
|
1033
|
+
progress.stop();
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async #runSuiteFromManifest(context, manifest, command, options) {
|
|
1037
|
+
const progress = this.#createProgressReporter('Suite', options);
|
|
1038
|
+
try {
|
|
1039
|
+
progress.update('starting evaluations');
|
|
1040
|
+
const summary = await runSuite(context, {
|
|
1041
|
+
workspaceId: command.workspaceId ?? manifest.workspaceId,
|
|
1042
|
+
inputFiles: manifest.evalFiles,
|
|
1043
|
+
loadEvalFile: async (inputFile) => parseConfiguredRunInput(await readConfiguredRunInput(inputFile)),
|
|
1044
|
+
clock: this.#clock,
|
|
1045
|
+
pollIntervalMs: this.#configuration.pollIntervalMs,
|
|
1046
|
+
pollTimeoutMs: this.#configuration.pollTimeoutMs,
|
|
1047
|
+
resultReadyIntervalMs: this.#configuration.resultReadyIntervalMs,
|
|
1048
|
+
resultReadyTimeoutMs: this.#configuration.resultReadyTimeoutMs,
|
|
1049
|
+
concurrency: command.concurrency ?? DEFAULT_SUITE_RUN_CONCURRENCY,
|
|
1050
|
+
staggerMs: command.staggerMs ?? DEFAULT_SUITE_RUN_START_STAGGER_MS,
|
|
1051
|
+
modelOverrides: {
|
|
1052
|
+
...(command.judgeModelId === undefined ? {} : { judgeModelId: command.judgeModelId }),
|
|
1053
|
+
...(command.primaryModelId === undefined
|
|
1054
|
+
? {}
|
|
1055
|
+
: { primaryModelId: command.primaryModelId }),
|
|
1056
|
+
},
|
|
1057
|
+
onStatus: (item, status) => progress.update(`eval ${item.index + 1}: ${formatStatusProgress(status)}`),
|
|
1058
|
+
...(this.#signal ? { signal: this.#signal } : {}),
|
|
1059
|
+
});
|
|
1060
|
+
this.#lastCreateFromRuns = summary.evals
|
|
1061
|
+
.map((entry) => entry.execution)
|
|
1062
|
+
.filter((execution) => execution !== undefined)
|
|
1063
|
+
.map((execution) => ({
|
|
1064
|
+
evaluationId: execution.evaluationId,
|
|
1065
|
+
runId: execution.runId,
|
|
1066
|
+
}));
|
|
1067
|
+
return summary;
|
|
1068
|
+
}
|
|
1069
|
+
finally {
|
|
1070
|
+
progress.stop();
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
async #importTrace(command, options) {
|
|
1074
|
+
if (command.run && command.workspaceId === undefined) {
|
|
1075
|
+
throw new AutoevalError('--run requires --workspace <workspace-id>.', {
|
|
1076
|
+
kind: 'usage',
|
|
1077
|
+
code: 'TRACE_IMPORT_WORKSPACE_REQUIRED',
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
const sessionContent = await readBoundedTextFile(command.sessionFile, MAX_TRACE_SESSION_BYTES, 'Session log', 'HARNESS_SESSION_READ_FAILED');
|
|
1081
|
+
const templateContent = await readBoundedTextFile(command.templateFile, MAX_TRACE_TEMPLATE_BYTES, 'Template file', 'TRACE_TEMPLATE_READ_FAILED');
|
|
1082
|
+
let template;
|
|
1083
|
+
try {
|
|
1084
|
+
template = JSON.parse(templateContent);
|
|
1085
|
+
}
|
|
1086
|
+
catch (error) {
|
|
1087
|
+
throw new AutoevalError('Template file must contain valid JSON.', {
|
|
1088
|
+
kind: 'usage',
|
|
1089
|
+
code: 'TRACE_TEMPLATE_INVALID_JSON',
|
|
1090
|
+
cause: error,
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
const conversion = convertHarnessSession({
|
|
1094
|
+
content: sessionContent,
|
|
1095
|
+
...(command.serviceName ? { serviceName: command.serviceName } : {}),
|
|
1096
|
+
});
|
|
1097
|
+
const evaluationInput = buildAgentTraceInput({
|
|
1098
|
+
template,
|
|
1099
|
+
conversion,
|
|
1100
|
+
...(command.evaluationName ? { evaluationName: command.evaluationName } : {}),
|
|
1101
|
+
});
|
|
1102
|
+
const serialized = `${JSON.stringify(evaluationInput, null, 2)}\n`;
|
|
1103
|
+
if (Buffer.byteLength(serialized, 'utf8') > MAX_TRACE_OUTPUT_BYTES) {
|
|
1104
|
+
throw new AutoevalError('Imported trace exceeds the 64 MiB output limit.', {
|
|
1105
|
+
kind: 'usage',
|
|
1106
|
+
code: 'TRACE_OUTPUT_TOO_LARGE',
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
try {
|
|
1110
|
+
await writeFile(command.outputFile, serialized, { encoding: 'utf8', flag: 'wx' });
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
const code = typeof error === 'object' && error !== null && 'code' in error
|
|
1114
|
+
? String(error.code)
|
|
1115
|
+
: undefined;
|
|
1116
|
+
throw new AutoevalError(code === 'EEXIST'
|
|
1117
|
+
? 'Output file already exists; choose a new --out path.'
|
|
1118
|
+
: 'Output file could not be written.', {
|
|
1119
|
+
kind: 'usage',
|
|
1120
|
+
code: code === 'EEXIST' ? 'TRACE_OUTPUT_EXISTS' : 'TRACE_OUTPUT_WRITE_FAILED',
|
|
1121
|
+
cause: error,
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
const report = { outputFile: command.outputFile, ...conversion.stats };
|
|
1125
|
+
this.#writer.writeResult(report, renderTraceImport(report), options.json);
|
|
1126
|
+
return { workspaceId: command.workspaceId ?? '' };
|
|
1127
|
+
}
|
|
1128
|
+
#createProgressReporter(label, options) {
|
|
1129
|
+
return createProgressReporter({
|
|
1130
|
+
stream: this.#stderr,
|
|
1131
|
+
enabled: this.#stderr.isTTY === true && !options.json,
|
|
1132
|
+
label,
|
|
1133
|
+
unicode: supportsUnicode(this.#environment),
|
|
1134
|
+
color: resolveColorMode(this.#environment) !== 'none',
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
#createContext(apiKey, options) {
|
|
1138
|
+
const onDiagnostic = options.debug
|
|
1139
|
+
? (diagnostic) => this.#writer.writeDiagnostic(diagnostic)
|
|
1140
|
+
: undefined;
|
|
1141
|
+
return createApiActionContext({
|
|
1142
|
+
configuration: this.#configuration,
|
|
1143
|
+
apiKey,
|
|
1144
|
+
...(this.#fetchImplementation ? { fetchImplementation: this.#fetchImplementation } : {}),
|
|
1145
|
+
...(onDiagnostic ? { onDiagnostic } : {}),
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
takeLastCreateFromRuns() {
|
|
1149
|
+
const runs = this.#lastCreateFromRuns;
|
|
1150
|
+
this.#lastCreateFromRuns = [];
|
|
1151
|
+
return runs;
|
|
1152
|
+
}
|
|
1153
|
+
#redactResultsJson(value) {
|
|
1154
|
+
const hiddenKeys = new Set([
|
|
1155
|
+
'evaluation_id',
|
|
1156
|
+
'generated_at',
|
|
1157
|
+
'_meta',
|
|
1158
|
+
'provider_key',
|
|
1159
|
+
'scenario_id',
|
|
1160
|
+
'schema_version',
|
|
1161
|
+
'view_details_url',
|
|
1162
|
+
'view_details_label',
|
|
1163
|
+
'execution_id',
|
|
1164
|
+
'pagination',
|
|
1165
|
+
'actions',
|
|
1166
|
+
'metadata',
|
|
1167
|
+
'color_code',
|
|
1168
|
+
'model_name',
|
|
1169
|
+
]);
|
|
1170
|
+
if (Array.isArray(value)) {
|
|
1171
|
+
return value.map((entry) => this.#redactResultsJson(entry));
|
|
1172
|
+
}
|
|
1173
|
+
if (typeof value !== 'object' || value === null) {
|
|
1174
|
+
return value;
|
|
1175
|
+
}
|
|
1176
|
+
const source = value;
|
|
1177
|
+
const result = {};
|
|
1178
|
+
for (const [key, nested] of Object.entries(source)) {
|
|
1179
|
+
if (hiddenKeys.has(key))
|
|
1180
|
+
continue;
|
|
1181
|
+
if (key === 'step_id' || key === 'parent_step_id') {
|
|
1182
|
+
result[key] = this.#normalizeStepId(nested);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
if (key === 'step_ids' && Array.isArray(nested)) {
|
|
1186
|
+
result[key] = nested.map((entry) => this.#normalizeStepId(entry));
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
result[key] = this.#redactResultsJson(nested);
|
|
1190
|
+
}
|
|
1191
|
+
return result;
|
|
1192
|
+
}
|
|
1193
|
+
#normalizeStepId(value) {
|
|
1194
|
+
if (typeof value !== 'string')
|
|
1195
|
+
return value;
|
|
1196
|
+
if (!/^\d+$/u.test(value))
|
|
1197
|
+
return value;
|
|
1198
|
+
const normalized = value.replace(/^0+/u, '');
|
|
1199
|
+
return normalized === '' ? '0' : normalized;
|
|
1200
|
+
}
|
|
1201
|
+
#omitNullStatisticFields(value) {
|
|
1202
|
+
if (Array.isArray(value)) {
|
|
1203
|
+
return value.map((entry) => this.#omitNullStatisticFields(entry));
|
|
1204
|
+
}
|
|
1205
|
+
if (typeof value !== 'object' || value === null) {
|
|
1206
|
+
return value;
|
|
1207
|
+
}
|
|
1208
|
+
const source = value;
|
|
1209
|
+
const result = {};
|
|
1210
|
+
for (const [key, nested] of Object.entries(source)) {
|
|
1211
|
+
if (nested === null && NULL_STATISTIC_KEYS.has(key))
|
|
1212
|
+
continue;
|
|
1213
|
+
result[key] = this.#omitNullStatisticFields(nested);
|
|
1214
|
+
}
|
|
1215
|
+
return result;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
//# sourceMappingURL=executor.js.map
|