import { readJsonFile } from "../core/files"; import type { BackendValidationSettings } from "../core/backendValidation"; import type { FrontendEvalModelConfig } from "../core/models"; import { validateScriptState } from "../core/validators"; import type { BenchmarkArtifactFile, ModeRunner } from "../core/types"; import { BackendPreviewClient } from "../adapters/frontend/backendPreview"; import { runScriptEval } from "../adapters/frontend/core/script/scriptEvalRunner"; import type { ScriptEvalState } from "../adapters/frontend/core/script/fileHelpers"; import { getFrontendApiKey } from "./frontendCommon"; import type { WindmillBackendSettings } from "../core/windmillBackendSettings"; export function createScriptModeRunner( modelConfig: FrontendEvalModelConfig, backendValidation: BackendValidationSettings | undefined, backendSettings: WindmillBackendSettings, ): ModeRunner { return { mode: "script", concurrency: 5, judgeThreshold: 80, async loadInitial(path) { return path ? await readJsonFile(path) : undefined; }, async loadExpected(path) { return path ? await readJsonFile(path) : undefined; }, async run(prompt, initial, context) { if (!initial) { throw new Error("Script evals require an initial script fixture"); } const result = await runScriptEval( prompt, getFrontendApiKey(modelConfig.provider), { initialScript: initial, maxIterations: context.evalCase?.runtime?.maxTurns, provider: modelConfig.provider, model: modelConfig.model, backend: backendSettings, runContext: context, }, ); return { success: result.success, actual: result.script, error: result.error, assistantMessageCount: result.assistantMessageCount, toolCallCount: result.toolCallCount, toolsUsed: result.toolsUsed, toolCallDetails: result.toolCallDetails, skillsInvoked: [], tokenUsage: result.tokenUsage, finalContextTokens: result.finalContextTokens, }; }, validate({ actual, initial, expected }) { return validateScriptState({ actual, initial, expected }); }, async backendValidate({ evalCase, initial, actual, context }) { if (backendValidation?.mode !== "preview") { return null; } const previewClient = new BackendPreviewClient(backendValidation); return await previewClient.withWorkspace( evalCase.id, context.attempt, async (workspaceId) => { const completedJob = await previewClient.runScriptPreview({ workspaceId, content: actual.code, args: (evalCase.runtime?.backendPreview?.args as | Record | undefined) ?? actual.args ?? initial?.args ?? {}, language: normalizePreviewLanguage(actual.lang), path: toPreviewScriptPath(actual.path), timeoutSeconds: evalCase.runtime?.backendPreview?.timeoutSeconds, }); return { checks: [ { name: "backend script preview succeeded", passed: completedJob.success, details: completedJob.success ? `workspace=${workspaceId}` : `workspace=${workspaceId}; job=${completedJob.id}`, }, ], artifactFiles: [ { path: "backend-preview.json", content: JSON.stringify( { workspaceId, jobId: completedJob.id, success: completedJob.success, result: completedJob.result, logs: completedJob.logs, completedJob: completedJob.raw, }, null, 2, ) + "\n", }, ], }; }, ); }, buildArtifacts(actual): BenchmarkArtifactFile[] { return [ { path: "script.json", content: JSON.stringify(actual, null, 2) + "\n", }, { path: actual.path, content: actual.code, }, ]; }, }; } function normalizePreviewLanguage(language: ScriptEvalState["lang"]): string { return language === "bunnative" ? "bun" : language; } function toPreviewScriptPath(filePath: string): string { return filePath.replace(/\.[^.\/]+$/, ""); }