/**
* Edit benchmark runner.
*
* Orchestrates benchmark runs by launching RPC clients, sending prompts,
* and verifying results. Supports parallel runs for reliability measurement.
*/
///
import * as fs from "node:fs/promises";
import { join } from "node:path";
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import { RpcClient } from "@oh-my-pi/pi-coding-agent";
import { TempDir } from "@oh-my-pi/pi-utils";
import { renderPromptTemplate } from "../coding-agent/src/config/prompt-templates";
import { computeLineHash } from "../coding-agent/src/patch/hashline";
import { diffLines } from "diff";
import { formatDirectory } from "./formatter";
import { type EditTask, extractTaskFiles } from "./tasks";
import { verifyExpectedFileSubset, verifyExpectedFiles } from "./verify";
import benchmarkTaskPrompt from "./prompts/benchmark-task.md" with { type: "text" };
const TMP_DIR = await TempDir.create("@reach-benchmark-");
const TMP = TMP_DIR.path();
export interface BenchmarkConfig {
provider: string;
model: string;
thinkingLevel?: ThinkingLevel;
runsPerTask: number;
timeout: number;
taskConcurrency: number;
requireEditToolCall?: boolean;
requireReadToolCall?: boolean;
noEditRequired?: boolean;
autoFormat?: boolean;
editVariant?: "replace" | "patch" | "hashline" | "auto";
editFuzzy?: boolean | "auto";
editFuzzyThreshold?: number | "auto";
guided?: boolean;
maxAttempts?: number;
timeoutRetryCount?: number;
}
function splitLines(value: string): string[] {
return value.split("\n").filter((line, idx, arr) => idx < arr.length - 1 || line);
}
function getEditPathFromArgs(args: unknown): string | null {
if (!args || typeof args !== "object") return null;
const pathValue = (args as { path?: unknown }).path;
return typeof pathValue === "string" && pathValue.length > 0 ? pathValue : null;
}
const HASHLINE_SUBTYPES = ["replaceLine", "replaceLines", "insertAfter"] as const;
function countHashlineEditSubtypes(args: unknown): Record {
const counts: Record = Object.fromEntries(HASHLINE_SUBTYPES.map((k) => [k, 0]));
if (!args || typeof args !== "object") return counts;
const edits = (args as { edits?: unknown[] }).edits;
if (!Array.isArray(edits)) return counts;
for (const edit of edits) {
if (!edit || typeof edit !== "object") continue;
for (const key of HASHLINE_SUBTYPES) {
if (key in edit) {
counts[key]++;
break;
}
}
}
return counts;
}
async function collectOriginalFileContents(cwd: string, files: string[]): Promise