/** * The eval app's commander program: `run ` executes a * benchmark and writes its JSON report; `report ` renders one as * markdown. Parsing follows the cmdline contract: the action publishes an * ordinary service, and help or a rejection publishes nothing. * * @module dsh-eval/command */ import { Command } from 'commander' import type { Context } from '@deepseek-ai/cordis' import { parseCmdline } from '@deepseek-ai/dsh-cmdline' import type { ImportFormat } from './import.ts' /** What the eval app resolved from its command line. */ export type EvalStartupValues = | { kind: 'run' benchmarkPath: string outPath: string trials?: number concurrency?: number dshCommand?: readonly string[] profile?: string } | { kind: 'report' runPath: string } | { kind: 'compare' runPathA: string runPathB: string } | { kind: 'import' format: ImportFormat path: string outPath: string caseId: string } /** Options accepted by `eval run`. */ interface RunCommandOptions { out: string trials?: number concurrency?: number dsh?: string profile?: string } /** Options accepted by `eval import`. */ interface ImportCommandOptions { out: string caseId: string } /** Split a single `--dsh` value into argv on whitespace. */ function splitCommand(value: string | undefined): readonly string[] | undefined { if (value === undefined) return undefined const command = value.trim().split(/\s+/u).filter(part => part !== '') return command.length === 0 ? undefined : command } /** * Build a commander option parser for a positive-integer flag, so `--trials` * and `--concurrency` reject the same inputs with the same message shape. * @param program - the program to error through. * @param flag - the flag name, for the error text. * @returns the parser. */ function positiveInteger(program: Command, flag: string): (value: string) => number { return (value) => { const parsed = Number(value) if (!Number.isInteger(parsed) || parsed <= 0) program.error(`error: ${flag} must be a positive integer`) return parsed } } /** * Build a fresh eval commander program. The root action rejects a bare * invocation; the `run` and `report` actions hand the resolved values to the * publish callback. The callback, not a Cordis service, carries the values * because the plugin's own fiber is not yet active when the parse action runs * and a strict `ctx.get` would miss a just-provided service. * @param publish - receives the resolved invocation once a subcommand runs. * @returns the configured program. */ export function evalCommand(publish: (values: EvalStartupValues) => void): Command { const program = new Command() .name('dsh --profile eval') .description('Run agent evaluations over headless dsh profiles and report trace-based metrics.') .helpOption('-h, --help', 'show this help') .action(() => { program.error('error: eval needs a subcommand: run or report ') }) const run = program.command('run').description('run a benchmark and write its JSON report') run .argument('', 'path to the benchmark document') .option('--out ', 'output run JSON path', 'eval-run.json') .option('--trials ', 'override the benchmark trials count', positiveInteger(program, '--trials')) .option('--concurrency ', 'override the in-flight trial limit', positiveInteger(program, '--concurrency')) .option('--dsh ', 'override the dsh launcher command, space-separated') .option('--profile ', 'override the spawned dsh profile') .action((benchmarkPath: string, options: RunCommandOptions) => { const dshCommand = splitCommand(options.dsh) publish({ kind: 'run', benchmarkPath, outPath: options.out, ...(options.trials !== undefined ? { trials: options.trials } : {}), ...(options.concurrency !== undefined ? { concurrency: options.concurrency } : {}), ...(dshCommand !== undefined ? { dshCommand } : {}), ...(options.profile !== undefined ? { profile: options.profile } : {}), }) }) const report = program.command('report').description('render a persisted run JSON as markdown') report .argument('', 'path to a run report') .action((runPath: string) => { publish({ kind: 'report', runPath }) }) const compare = program.command('compare').description('compare two persisted run reports as a markdown table') compare .argument('', 'baseline run report') .argument('', 'candidate run report') .action((runPathA: string, runPathB: string) => { publish({ kind: 'compare', runPathA, runPathB }) }) const importer = program.command('import').description('import an external harness session log as a run report') importer .argument('', 'external harness format: codex or claude-code') .argument('', 'path to the external session log') .option('--out ', 'output run JSON path', 'imported-run.json') .option('--case-id ', 'case id for the imported trial', 'imported') .action((format: string, path: string, options: ImportCommandOptions) => { if (format !== 'codex' && format !== 'claude-code') { program.error('error: import format must be codex or claude-code') return } publish({ kind: 'import', format, path, outPath: options.out, caseId: options.caseId }) }) return program } /** * Parse the launcher's inner arguments with the eval program and return what * the invoked command published. * @param ctx - plugin context carrying `cmdlineArgs` and `appExit`. * @returns the resolved invocation, or undefined when help or an error exited. */ export function applyEval(ctx: Context): EvalStartupValues | undefined { let resolved: EvalStartupValues | undefined const program = evalCommand((values) => { resolved = values }) parseCmdline(ctx, program) return resolved }