import { loadSelectedCases } from "../../core/cases"; import { resolveBackendValidationSettings } from "../../core/backendValidation"; import { formatRunModelLabel, getFrontendEvalModel, resolveEvalModel, } from "../../core/models"; import { buildRunResult } from "../../core/results"; import { runSuite } from "../../core/runSuite"; import type { BenchmarkRunResult, ModeRunner } from "../../core/types"; import { resolveWindmillBackendSettings } from "../../core/windmillBackendSettings"; import { emitFrontendBenchmarkProgress } from "./progress"; import { DEFAULT_JUDGE_MODEL } from "../../core/judge"; export type FrontendBenchmarkMode = "flow" | "app" | "script" | "global"; export async function runFrontendBenchmarkFromEnv(): Promise { const mode = parseMode(process.env.WMILL_FRONTEND_AI_EVAL_MODE); const caseIds = parseOptionalJsonStringArray( process.env.WMILL_FRONTEND_AI_EVAL_CASE_IDS, ); const runs = parsePositiveInteger( process.env.WMILL_FRONTEND_AI_EVAL_RUNS, "WMILL_FRONTEND_AI_EVAL_RUNS", ); const emitProgress = process.env.WMILL_FRONTEND_AI_EVAL_PROGRESS === "1"; const verbose = process.env.WMILL_FRONTEND_AI_EVAL_VERBOSE === "1"; const executionOnly = process.env.WMILL_FRONTEND_AI_EVAL_EXECUTION_ONLY === "1"; const judgeModel = process.env.WMILL_FRONTEND_AI_EVAL_SKIP_JUDGE === "1" || executionOnly ? null : DEFAULT_JUDGE_MODEL; const model = resolveEvalModel( mode, process.env.WMILL_FRONTEND_AI_EVAL_MODEL, ); const backendValidation = resolveBackendValidationSettings({ evalMode: mode, requestedMode: process.env.WMILL_FRONTEND_AI_EVAL_BACKEND_VALIDATION, }); const backendSettings = resolveWindmillBackendSettings(); const selectedCases = await loadSelectedCases(mode, caseIds); const modeRunner = await getModeRunner( mode, getFrontendEvalModel(model), backendValidation, backendSettings, ); const runModel = formatRunModelLabel(mode, model); const caseResults = await runSuite({ modeRunner, cases: selectedCases, runs, runModel, judgeModel, executionOnly, concurrency: verbose ? 1 : undefined, verbose, onProgress: emitProgress ? (event) => emitFrontendBenchmarkProgress(event) : undefined, }); return buildRunResult({ mode, runs, runModel, judgeModel, caseResults, }); } async function getModeRunner( mode: FrontendBenchmarkMode, model: ReturnType, backendValidation: ReturnType, backendSettings: ReturnType, ): Promise> { switch (mode) { case "flow": { const { createFlowModeRunner } = await import("../../modes/flow"); return createFlowModeRunner(model, backendValidation, backendSettings); } case "app": { const { createAppModeRunner } = await import("../../modes/app"); return createAppModeRunner(model, backendSettings); } case "script": { const { createScriptModeRunner } = await import("../../modes/script"); return createScriptModeRunner( model, backendValidation, backendSettings, ); } case "global": { const { createGlobalModeRunner } = await import("../../modes/global"); return createGlobalModeRunner(model, backendSettings); } } } function parseMode(value: string | undefined): FrontendBenchmarkMode { if ( value === "flow" || value === "app" || value === "script" || value === "global" ) { return value; } throw new Error(`Unsupported frontend benchmark mode: ${String(value)}`); } function parseOptionalJsonStringArray(value: string | undefined): string[] { if (!value) { return []; } const parsed = JSON.parse(value) as unknown; if ( !Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string") ) { throw new Error( "WMILL_FRONTEND_AI_EVAL_CASE_IDS must be a JSON string array", ); } return parsed; } function parsePositiveInteger( value: string | undefined, envName: string, ): number { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed <= 0) { throw new Error(`${envName} must be a positive integer`); } return parsed; }