@agentv/core 0.5.0 → 0.5.1

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/dist/index.js CHANGED
@@ -953,1323 +953,1323 @@ function formatTimeoutSuffix(timeoutMs) {
953
953
  return ` after ${seconds}s`;
954
954
  }
955
955
 
956
- // src/evaluation/providers/mock.ts
957
- var DEFAULT_MOCK_RESPONSE = '{"answer":"Mock provider response. Configure targets.yaml to supply a custom value."}';
958
- var MockProvider = class {
959
- id;
960
- kind = "mock";
961
- targetName;
962
- cannedResponse;
963
- delayMs;
964
- delayMinMs;
965
- delayMaxMs;
966
- constructor(targetName, config) {
967
- this.id = `mock:${targetName}`;
968
- this.targetName = targetName;
969
- this.cannedResponse = config.response ?? DEFAULT_MOCK_RESPONSE;
970
- this.delayMs = config.delayMs ?? 0;
971
- this.delayMinMs = config.delayMinMs ?? 0;
972
- this.delayMaxMs = config.delayMaxMs ?? 0;
956
+ // src/evaluation/providers/codex.ts
957
+ import { exec as execCallback, spawn } from "node:child_process";
958
+ import { constants as constants2 } from "node:fs";
959
+ import { access as access2, copyFile, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
960
+ import { tmpdir } from "node:os";
961
+ import path4 from "node:path";
962
+ import { promisify as promisify2 } from "node:util";
963
+
964
+ // src/evaluation/providers/preread.ts
965
+ import path3 from "node:path";
966
+ function buildPromptDocument(request, inputFiles, options) {
967
+ const parts = [];
968
+ const guidelineFiles = collectGuidelineFiles(
969
+ inputFiles,
970
+ options?.guidelinePatterns ?? request.guideline_patterns,
971
+ options?.guidelineOverrides
972
+ );
973
+ const inputFilesList = collectInputFiles(inputFiles);
974
+ const nonGuidelineInputFiles = inputFilesList.filter(
975
+ (file) => !guidelineFiles.includes(file)
976
+ );
977
+ const prereadBlock = buildMandatoryPrereadBlock(guidelineFiles, nonGuidelineInputFiles);
978
+ if (prereadBlock.length > 0) {
979
+ parts.push("\n", prereadBlock);
973
980
  }
974
- async invoke(request) {
975
- const delay = this.calculateDelay();
976
- if (delay > 0) {
977
- await new Promise((resolve) => setTimeout(resolve, delay));
978
- }
979
- return {
980
- text: this.cannedResponse,
981
- raw: {
982
- prompt: request.prompt,
983
- guidelines: request.guidelines
984
- }
985
- };
981
+ parts.push("\n[[ ## user_query ## ]]\n", request.prompt.trim());
982
+ return parts.join("\n").trim();
983
+ }
984
+ function normalizeInputFiles2(inputFiles) {
985
+ if (!inputFiles || inputFiles.length === 0) {
986
+ return void 0;
986
987
  }
987
- calculateDelay() {
988
- if (this.delayMinMs > 0 || this.delayMaxMs > 0) {
989
- const min = Math.max(0, this.delayMinMs);
990
- const max = Math.max(min, this.delayMaxMs);
991
- return Math.floor(Math.random() * (max - min + 1)) + min;
988
+ const deduped = /* @__PURE__ */ new Map();
989
+ for (const inputFile of inputFiles) {
990
+ const absolutePath = path3.resolve(inputFile);
991
+ if (!deduped.has(absolutePath)) {
992
+ deduped.set(absolutePath, absolutePath);
992
993
  }
993
- return this.delayMs;
994
994
  }
995
- };
996
-
997
- // src/evaluation/providers/targets.ts
998
- import { z } from "zod";
999
- var CLI_PLACEHOLDERS = /* @__PURE__ */ new Set(["PROMPT", "GUIDELINES", "EVAL_ID", "ATTEMPT", "FILES"]);
1000
- var BASE_TARGET_SCHEMA = z.object({
1001
- name: z.string().min(1, "target name is required"),
1002
- provider: z.string().min(1, "provider is required"),
1003
- settings: z.record(z.unknown()).optional(),
1004
- judge_target: z.string().optional(),
1005
- workers: z.number().int().min(1).optional()
1006
- });
1007
- var DEFAULT_AZURE_API_VERSION = "2024-10-01-preview";
1008
- function normalizeAzureApiVersion(value) {
1009
- if (!value) {
1010
- return DEFAULT_AZURE_API_VERSION;
995
+ return Array.from(deduped.values());
996
+ }
997
+ function collectGuidelineFiles(inputFiles, guidelinePatterns, overrides) {
998
+ if (!inputFiles || inputFiles.length === 0) {
999
+ return [];
1011
1000
  }
1012
- const trimmed = value.trim();
1013
- if (trimmed.length === 0) {
1014
- return DEFAULT_AZURE_API_VERSION;
1001
+ const unique = /* @__PURE__ */ new Map();
1002
+ for (const inputFile of inputFiles) {
1003
+ const absolutePath = path3.resolve(inputFile);
1004
+ if (overrides?.has(absolutePath)) {
1005
+ if (!unique.has(absolutePath)) {
1006
+ unique.set(absolutePath, absolutePath);
1007
+ }
1008
+ continue;
1009
+ }
1010
+ const normalized = absolutePath.split(path3.sep).join("/");
1011
+ if (isGuidelineFile(normalized, guidelinePatterns)) {
1012
+ if (!unique.has(absolutePath)) {
1013
+ unique.set(absolutePath, absolutePath);
1014
+ }
1015
+ }
1015
1016
  }
1016
- const withoutPrefix = trimmed.replace(/^api[-_]?version\s*=\s*/i, "").trim();
1017
- return withoutPrefix.length > 0 ? withoutPrefix : DEFAULT_AZURE_API_VERSION;
1017
+ return Array.from(unique.values());
1018
1018
  }
1019
- function resolveTargetDefinition(definition, env = process.env) {
1020
- const parsed = BASE_TARGET_SCHEMA.parse(definition);
1021
- const provider = parsed.provider.toLowerCase();
1022
- const providerBatching = resolveOptionalBoolean(
1023
- parsed.settings?.provider_batching ?? parsed.settings?.providerBatching
1024
- );
1025
- switch (provider) {
1026
- case "azure":
1027
- case "azure-openai":
1028
- return {
1029
- kind: "azure",
1030
- name: parsed.name,
1031
- judgeTarget: parsed.judge_target,
1032
- workers: parsed.workers,
1033
- providerBatching,
1034
- config: resolveAzureConfig(parsed, env)
1035
- };
1036
- case "anthropic":
1037
- return {
1038
- kind: "anthropic",
1039
- name: parsed.name,
1040
- judgeTarget: parsed.judge_target,
1041
- workers: parsed.workers,
1042
- providerBatching,
1043
- config: resolveAnthropicConfig(parsed, env)
1044
- };
1045
- case "gemini":
1046
- case "google":
1047
- case "google-gemini":
1048
- return {
1049
- kind: "gemini",
1050
- name: parsed.name,
1051
- judgeTarget: parsed.judge_target,
1052
- workers: parsed.workers,
1053
- providerBatching,
1054
- config: resolveGeminiConfig(parsed, env)
1055
- };
1056
- case "codex":
1057
- case "codex-cli":
1058
- return {
1059
- kind: "codex",
1060
- name: parsed.name,
1061
- judgeTarget: parsed.judge_target,
1062
- workers: parsed.workers,
1063
- providerBatching,
1064
- config: resolveCodexConfig(parsed, env)
1065
- };
1066
- case "mock":
1067
- return {
1068
- kind: "mock",
1069
- name: parsed.name,
1070
- judgeTarget: parsed.judge_target,
1071
- workers: parsed.workers,
1072
- providerBatching,
1073
- config: resolveMockConfig(parsed)
1074
- };
1075
- case "vscode":
1076
- case "vscode-insiders":
1077
- return {
1078
- kind: provider,
1079
- name: parsed.name,
1080
- judgeTarget: parsed.judge_target,
1081
- workers: parsed.workers,
1082
- providerBatching,
1083
- config: resolveVSCodeConfig(parsed, env, provider === "vscode-insiders")
1084
- };
1085
- case "cli":
1086
- return {
1087
- kind: "cli",
1088
- name: parsed.name,
1089
- judgeTarget: parsed.judge_target,
1090
- workers: parsed.workers,
1091
- providerBatching,
1092
- config: resolveCliConfig(parsed, env)
1093
- };
1094
- default:
1095
- throw new Error(`Unsupported provider '${parsed.provider}' in target '${parsed.name}'`);
1019
+ function collectInputFiles(inputFiles) {
1020
+ if (!inputFiles || inputFiles.length === 0) {
1021
+ return [];
1096
1022
  }
1023
+ const unique = /* @__PURE__ */ new Map();
1024
+ for (const inputFile of inputFiles) {
1025
+ const absolutePath = path3.resolve(inputFile);
1026
+ if (!unique.has(absolutePath)) {
1027
+ unique.set(absolutePath, absolutePath);
1028
+ }
1029
+ }
1030
+ return Array.from(unique.values());
1097
1031
  }
1098
- function resolveAzureConfig(target, env) {
1099
- const settings = target.settings ?? {};
1100
- const endpointSource = settings.endpoint ?? settings.resource ?? settings.resourceName;
1101
- const apiKeySource = settings.api_key ?? settings.apiKey;
1102
- const deploymentSource = settings.deployment ?? settings.deploymentName ?? settings.model;
1103
- const versionSource = settings.version ?? settings.api_version;
1104
- const temperatureSource = settings.temperature;
1105
- const maxTokensSource = settings.max_output_tokens ?? settings.maxTokens;
1106
- const resourceName = resolveString(endpointSource, env, `${target.name} endpoint`);
1107
- const apiKey = resolveString(apiKeySource, env, `${target.name} api key`);
1108
- const deploymentName = resolveString(deploymentSource, env, `${target.name} deployment`);
1109
- const version = normalizeAzureApiVersion(
1110
- resolveOptionalString(versionSource, env, `${target.name} api version`)
1111
- );
1112
- const temperature = resolveOptionalNumber(temperatureSource, `${target.name} temperature`);
1113
- const maxOutputTokens = resolveOptionalNumber(
1114
- maxTokensSource,
1115
- `${target.name} max output tokens`
1116
- );
1117
- return {
1118
- resourceName,
1119
- deploymentName,
1120
- apiKey,
1121
- version,
1122
- temperature,
1123
- maxOutputTokens
1124
- };
1125
- }
1126
- function resolveAnthropicConfig(target, env) {
1127
- const settings = target.settings ?? {};
1128
- const apiKeySource = settings.api_key ?? settings.apiKey;
1129
- const modelSource = settings.model ?? settings.deployment ?? settings.variant;
1130
- const temperatureSource = settings.temperature;
1131
- const maxTokensSource = settings.max_output_tokens ?? settings.maxTokens;
1132
- const thinkingBudgetSource = settings.thinking_budget ?? settings.thinkingBudget;
1133
- const apiKey = resolveString(apiKeySource, env, `${target.name} Anthropic api key`);
1134
- const model = resolveString(modelSource, env, `${target.name} Anthropic model`);
1135
- return {
1136
- apiKey,
1137
- model,
1138
- temperature: resolveOptionalNumber(temperatureSource, `${target.name} temperature`),
1139
- maxOutputTokens: resolveOptionalNumber(maxTokensSource, `${target.name} max output tokens`),
1140
- thinkingBudget: resolveOptionalNumber(thinkingBudgetSource, `${target.name} thinking budget`)
1141
- };
1142
- }
1143
- function resolveGeminiConfig(target, env) {
1144
- const settings = target.settings ?? {};
1145
- const apiKeySource = settings.api_key ?? settings.apiKey;
1146
- const modelSource = settings.model ?? settings.deployment ?? settings.variant;
1147
- const temperatureSource = settings.temperature;
1148
- const maxTokensSource = settings.max_output_tokens ?? settings.maxTokens;
1149
- const apiKey = resolveString(apiKeySource, env, `${target.name} Google API key`);
1150
- const model = resolveOptionalString(modelSource, env, `${target.name} Gemini model`, {
1151
- allowLiteral: true,
1152
- optionalEnv: true
1153
- }) ?? "gemini-2.5-flash";
1154
- return {
1155
- apiKey,
1156
- model,
1157
- temperature: resolveOptionalNumber(temperatureSource, `${target.name} temperature`),
1158
- maxOutputTokens: resolveOptionalNumber(maxTokensSource, `${target.name} max output tokens`)
1159
- };
1160
- }
1161
- function resolveCodexConfig(target, env) {
1162
- const settings = target.settings ?? {};
1163
- const executableSource = settings.executable ?? settings.command ?? settings.binary;
1164
- const argsSource = settings.args ?? settings.arguments;
1165
- const cwdSource = settings.cwd;
1166
- const timeoutSource = settings.timeout_seconds ?? settings.timeoutSeconds;
1167
- const executable = resolveOptionalString(executableSource, env, `${target.name} codex executable`, {
1168
- allowLiteral: true,
1169
- optionalEnv: true
1170
- }) ?? "codex";
1171
- const args = resolveOptionalStringArray(argsSource, env, `${target.name} codex args`);
1172
- const cwd = resolveOptionalString(cwdSource, env, `${target.name} codex cwd`, {
1173
- allowLiteral: true,
1174
- optionalEnv: true
1175
- });
1176
- const timeoutMs = resolveTimeoutMs(timeoutSource, `${target.name} codex timeout`);
1177
- return {
1178
- executable,
1179
- args,
1180
- cwd,
1181
- timeoutMs
1182
- };
1183
- }
1184
- function resolveMockConfig(target) {
1185
- const settings = target.settings ?? {};
1186
- const response = typeof settings.response === "string" ? settings.response : void 0;
1187
- return { response };
1188
- }
1189
- function resolveVSCodeConfig(target, env, insiders) {
1190
- const settings = target.settings ?? {};
1191
- const workspaceTemplateEnvVar = resolveOptionalLiteralString(settings.workspace_template ?? settings.workspaceTemplate);
1192
- const workspaceTemplate = workspaceTemplateEnvVar ? resolveOptionalString(workspaceTemplateEnvVar, env, `${target.name} workspace template path`, {
1193
- allowLiteral: false,
1194
- optionalEnv: true
1195
- }) : void 0;
1196
- const commandSource = settings.vscode_cmd ?? settings.command;
1197
- const waitSource = settings.wait;
1198
- const dryRunSource = settings.dry_run ?? settings.dryRun;
1199
- const subagentRootSource = settings.subagent_root ?? settings.subagentRoot;
1200
- const defaultCommand = insiders ? "code-insiders" : "code";
1201
- const command = resolveOptionalLiteralString(commandSource) ?? defaultCommand;
1202
- return {
1203
- command,
1204
- waitForResponse: resolveOptionalBoolean(waitSource) ?? true,
1205
- dryRun: resolveOptionalBoolean(dryRunSource) ?? false,
1206
- subagentRoot: resolveOptionalString(subagentRootSource, env, `${target.name} subagent root`, {
1207
- allowLiteral: true,
1208
- optionalEnv: true
1209
- }),
1210
- workspaceTemplate
1211
- };
1212
- }
1213
- function resolveCliConfig(target, env) {
1214
- const settings = target.settings ?? {};
1215
- const commandTemplateSource = settings.command_template ?? settings.commandTemplate;
1216
- const filesFormat = resolveOptionalLiteralString(
1217
- settings.files_format ?? settings.filesFormat ?? settings.attachments_format ?? settings.attachmentsFormat
1218
- );
1219
- const cwd = resolveOptionalString(settings.cwd, env, `${target.name} working directory`, {
1220
- allowLiteral: true,
1221
- optionalEnv: true
1032
+ function buildMandatoryPrereadBlock(guidelineFiles, inputFiles) {
1033
+ if (guidelineFiles.length === 0 && inputFiles.length === 0) {
1034
+ return "";
1035
+ }
1036
+ const buildList = (files) => files.map((absolutePath) => {
1037
+ const fileName = path3.basename(absolutePath);
1038
+ const fileUri = pathToFileUri(absolutePath);
1039
+ return `* [${fileName}](${fileUri})`;
1222
1040
  });
1223
- const envOverrides = resolveEnvOverrides(settings.env, env, target.name);
1224
- const timeoutMs = resolveTimeoutMs(settings.timeout_seconds ?? settings.timeoutSeconds, `${target.name} timeout`);
1225
- const healthcheck = resolveCliHealthcheck(settings.healthcheck, env, target.name);
1226
- const commandTemplate = resolveString(
1227
- commandTemplateSource,
1228
- env,
1229
- `${target.name} CLI command template`,
1230
- true
1041
+ const sections = [];
1042
+ if (guidelineFiles.length > 0) {
1043
+ sections.push(`Read all guideline files:
1044
+ ${buildList(guidelineFiles).join("\n")}.`);
1045
+ }
1046
+ if (inputFiles.length > 0) {
1047
+ sections.push(`Read all input files:
1048
+ ${buildList(inputFiles).join("\n")}.`);
1049
+ }
1050
+ sections.push(
1051
+ "If any file is missing, fail with ERROR: missing-file <filename> and stop.",
1052
+ "Then apply system_instructions on the user query below."
1231
1053
  );
1232
- assertSupportedCliPlaceholders(commandTemplate, `${target.name} CLI command template`);
1233
- return {
1234
- commandTemplate,
1235
- filesFormat,
1236
- cwd,
1237
- env: envOverrides,
1238
- timeoutMs,
1239
- healthcheck
1240
- };
1054
+ return sections.join("\n");
1241
1055
  }
1242
- function resolveEnvOverrides(source, env, targetName) {
1243
- if (source === void 0 || source === null) {
1244
- return void 0;
1056
+ function pathToFileUri(filePath) {
1057
+ const absolutePath = path3.isAbsolute(filePath) ? filePath : path3.resolve(filePath);
1058
+ const normalizedPath = absolutePath.replace(/\\/g, "/");
1059
+ if (/^[a-zA-Z]:\//.test(normalizedPath)) {
1060
+ return `file:///${normalizedPath}`;
1061
+ }
1062
+ return `file://${normalizedPath}`;
1063
+ }
1064
+
1065
+ // src/evaluation/providers/codex.ts
1066
+ var execAsync2 = promisify2(execCallback);
1067
+ var WORKSPACE_PREFIX = "agentv-codex-";
1068
+ var PROMPT_FILENAME = "prompt.md";
1069
+ var FILES_DIR = "files";
1070
+ var JSONL_TYPE_ITEM_COMPLETED = "item.completed";
1071
+ var CodexProvider = class {
1072
+ id;
1073
+ kind = "codex";
1074
+ targetName;
1075
+ supportsBatch = false;
1076
+ config;
1077
+ runCodex;
1078
+ environmentCheck;
1079
+ resolvedExecutable;
1080
+ constructor(targetName, config, runner = defaultCodexRunner) {
1081
+ this.id = `codex:${targetName}`;
1082
+ this.targetName = targetName;
1083
+ this.config = config;
1084
+ this.runCodex = runner;
1085
+ }
1086
+ async invoke(request) {
1087
+ if (request.signal?.aborted) {
1088
+ throw new Error("Codex provider request was aborted before execution");
1089
+ }
1090
+ await this.ensureEnvironmentReady();
1091
+ const inputFiles = normalizeInputFiles2(request.inputFiles);
1092
+ const originalGuidelines = new Set(
1093
+ collectGuidelineFiles(inputFiles, request.guideline_patterns).map((file) => path4.resolve(file))
1094
+ );
1095
+ const workspaceRoot = await this.createWorkspace();
1096
+ try {
1097
+ const { mirroredInputFiles, guidelineMirrors } = await this.mirrorInputFiles(
1098
+ inputFiles,
1099
+ workspaceRoot,
1100
+ originalGuidelines
1101
+ );
1102
+ const promptContent = buildPromptDocument(request, mirroredInputFiles, {
1103
+ guidelinePatterns: request.guideline_patterns,
1104
+ guidelineOverrides: guidelineMirrors
1105
+ });
1106
+ const promptFile = path4.join(workspaceRoot, PROMPT_FILENAME);
1107
+ await writeFile(promptFile, promptContent, "utf8");
1108
+ const args = this.buildCodexArgs();
1109
+ const cwd = this.resolveCwd(workspaceRoot);
1110
+ const result = await this.executeCodex(args, cwd, promptContent, request.signal);
1111
+ if (result.timedOut) {
1112
+ throw new Error(
1113
+ `Codex CLI timed out${formatTimeoutSuffix2(this.config.timeoutMs ?? void 0)}`
1114
+ );
1115
+ }
1116
+ if (result.exitCode !== 0) {
1117
+ const detail = pickDetail(result.stderr, result.stdout);
1118
+ const prefix = `Codex CLI exited with code ${result.exitCode}`;
1119
+ throw new Error(detail ? `${prefix}: ${detail}` : prefix);
1120
+ }
1121
+ const parsed = parseCodexJson(result.stdout);
1122
+ const assistantText = extractAssistantText(parsed);
1123
+ return {
1124
+ text: assistantText,
1125
+ raw: {
1126
+ response: parsed,
1127
+ stdout: result.stdout,
1128
+ stderr: result.stderr,
1129
+ exitCode: result.exitCode,
1130
+ args,
1131
+ executable: this.resolvedExecutable ?? this.config.executable,
1132
+ promptFile,
1133
+ workspace: workspaceRoot,
1134
+ inputFiles: mirroredInputFiles
1135
+ }
1136
+ };
1137
+ } finally {
1138
+ await this.cleanupWorkspace(workspaceRoot);
1139
+ }
1140
+ }
1141
+ async ensureEnvironmentReady() {
1142
+ if (!this.environmentCheck) {
1143
+ this.environmentCheck = this.validateEnvironment();
1144
+ }
1145
+ await this.environmentCheck;
1245
1146
  }
1246
- if (typeof source !== "object" || Array.isArray(source)) {
1247
- throw new Error(`${targetName} env overrides must be an object map of strings`);
1147
+ async validateEnvironment() {
1148
+ this.resolvedExecutable = await locateExecutable(this.config.executable);
1248
1149
  }
1249
- const entries = Object.entries(source);
1250
- const resolved = {};
1251
- for (const [key, value] of entries) {
1252
- if (typeof value !== "string") {
1253
- throw new Error(`${targetName} env override '${key}' must be a string`);
1150
+ resolveCwd(workspaceRoot) {
1151
+ if (!this.config.cwd) {
1152
+ return workspaceRoot;
1254
1153
  }
1255
- const resolvedValue = resolveString(value, env, `${targetName} env override '${key}'`);
1256
- resolved[key] = resolvedValue;
1154
+ return path4.resolve(this.config.cwd);
1257
1155
  }
1258
- return Object.keys(resolved).length > 0 ? resolved : void 0;
1156
+ buildCodexArgs() {
1157
+ const args = ["--ask-for-approval", "never", "exec", "--json", "--color", "never", "--skip-git-repo-check"];
1158
+ if (this.config.args && this.config.args.length > 0) {
1159
+ args.push(...this.config.args);
1160
+ }
1161
+ args.push("-");
1162
+ return args;
1163
+ }
1164
+ async executeCodex(args, cwd, promptContent, signal) {
1165
+ try {
1166
+ return await this.runCodex({
1167
+ executable: this.resolvedExecutable ?? this.config.executable,
1168
+ args,
1169
+ cwd,
1170
+ prompt: promptContent,
1171
+ timeoutMs: this.config.timeoutMs,
1172
+ env: process.env,
1173
+ signal
1174
+ });
1175
+ } catch (error) {
1176
+ const err = error;
1177
+ if (err.code === "ENOENT") {
1178
+ throw new Error(
1179
+ `Codex executable '${this.config.executable}' was not found. Update the target settings.executable or add it to PATH.`
1180
+ );
1181
+ }
1182
+ throw error;
1183
+ }
1184
+ }
1185
+ async mirrorInputFiles(inputFiles, workspaceRoot, guidelineOriginals) {
1186
+ if (!inputFiles || inputFiles.length === 0) {
1187
+ return {
1188
+ mirroredInputFiles: void 0,
1189
+ guidelineMirrors: /* @__PURE__ */ new Set()
1190
+ };
1191
+ }
1192
+ const filesRoot = path4.join(workspaceRoot, FILES_DIR);
1193
+ await mkdir(filesRoot, { recursive: true });
1194
+ const mirrored = [];
1195
+ const guidelineMirrors = /* @__PURE__ */ new Set();
1196
+ const nameCounts = /* @__PURE__ */ new Map();
1197
+ for (const inputFile of inputFiles) {
1198
+ const absoluteSource = path4.resolve(inputFile);
1199
+ const baseName = path4.basename(absoluteSource);
1200
+ const count = nameCounts.get(baseName) ?? 0;
1201
+ nameCounts.set(baseName, count + 1);
1202
+ const finalName = count === 0 ? baseName : `${baseName}.${count}`;
1203
+ const destination = path4.join(filesRoot, finalName);
1204
+ await copyFile(absoluteSource, destination);
1205
+ const resolvedDestination = path4.resolve(destination);
1206
+ mirrored.push(resolvedDestination);
1207
+ if (guidelineOriginals.has(absoluteSource)) {
1208
+ guidelineMirrors.add(resolvedDestination);
1209
+ }
1210
+ }
1211
+ return {
1212
+ mirroredInputFiles: mirrored,
1213
+ guidelineMirrors
1214
+ };
1215
+ }
1216
+ async createWorkspace() {
1217
+ return await mkdtemp(path4.join(tmpdir(), WORKSPACE_PREFIX));
1218
+ }
1219
+ async cleanupWorkspace(workspaceRoot) {
1220
+ try {
1221
+ await rm(workspaceRoot, { recursive: true, force: true });
1222
+ } catch {
1223
+ }
1224
+ }
1225
+ };
1226
+ async function locateExecutable(candidate) {
1227
+ const includesPathSeparator = candidate.includes("/") || candidate.includes("\\");
1228
+ if (includesPathSeparator) {
1229
+ const resolved = path4.isAbsolute(candidate) ? candidate : path4.resolve(candidate);
1230
+ const executablePath = await ensureWindowsExecutableVariant(resolved);
1231
+ await access2(executablePath, constants2.F_OK);
1232
+ return executablePath;
1233
+ }
1234
+ const locator = process.platform === "win32" ? "where" : "which";
1235
+ try {
1236
+ const { stdout } = await execAsync2(`${locator} ${candidate}`);
1237
+ const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
1238
+ const preferred = selectExecutableCandidate(lines);
1239
+ if (preferred) {
1240
+ const executablePath = await ensureWindowsExecutableVariant(preferred);
1241
+ await access2(executablePath, constants2.F_OK);
1242
+ return executablePath;
1243
+ }
1244
+ } catch {
1245
+ }
1246
+ throw new Error(`Codex executable '${candidate}' was not found on PATH`);
1259
1247
  }
1260
- function resolveTimeoutMs(source, description) {
1261
- const seconds = resolveOptionalNumber(source, `${description} (seconds)`);
1262
- if (seconds === void 0) {
1248
+ function selectExecutableCandidate(candidates) {
1249
+ if (candidates.length === 0) {
1263
1250
  return void 0;
1264
1251
  }
1265
- if (seconds <= 0) {
1266
- throw new Error(`${description} must be greater than zero seconds`);
1252
+ if (process.platform !== "win32") {
1253
+ return candidates[0];
1267
1254
  }
1268
- return Math.floor(seconds * 1e3);
1255
+ const extensions = getWindowsExecutableExtensions();
1256
+ for (const ext of extensions) {
1257
+ const match = candidates.find((candidate) => candidate.toLowerCase().endsWith(ext));
1258
+ if (match) {
1259
+ return match;
1260
+ }
1261
+ }
1262
+ return candidates[0];
1269
1263
  }
1270
- function resolveCliHealthcheck(source, env, targetName) {
1271
- if (source === void 0 || source === null) {
1272
- return void 0;
1264
+ async function ensureWindowsExecutableVariant(candidate) {
1265
+ if (process.platform !== "win32") {
1266
+ return candidate;
1273
1267
  }
1274
- if (typeof source !== "object" || Array.isArray(source)) {
1275
- throw new Error(`${targetName} healthcheck must be an object`);
1268
+ if (hasExecutableExtension(candidate)) {
1269
+ return candidate;
1276
1270
  }
1277
- const candidate = source;
1278
- const type = candidate.type;
1279
- const timeoutMs = resolveTimeoutMs(
1280
- candidate.timeout_seconds ?? candidate.timeoutSeconds,
1281
- `${targetName} healthcheck timeout`
1282
- );
1283
- if (type === "http") {
1284
- const url = resolveString(candidate.url, env, `${targetName} healthcheck URL`);
1285
- return {
1286
- type: "http",
1287
- url,
1288
- timeoutMs
1289
- };
1271
+ const extensions = getWindowsExecutableExtensions();
1272
+ for (const ext of extensions) {
1273
+ const withExtension = `${candidate}${ext}`;
1274
+ try {
1275
+ await access2(withExtension, constants2.F_OK);
1276
+ return withExtension;
1277
+ } catch {
1278
+ }
1290
1279
  }
1291
- if (type === "command") {
1292
- const commandTemplate = resolveString(
1293
- candidate.command_template ?? candidate.commandTemplate,
1294
- env,
1295
- `${targetName} healthcheck command template`,
1296
- true
1297
- );
1298
- assertSupportedCliPlaceholders(commandTemplate, `${targetName} healthcheck command template`);
1299
- const cwd = resolveOptionalString(candidate.cwd, env, `${targetName} healthcheck cwd`, {
1300
- allowLiteral: true,
1301
- optionalEnv: true
1302
- });
1303
- return {
1304
- type: "command",
1305
- commandTemplate,
1306
- timeoutMs,
1307
- cwd
1308
- };
1280
+ return candidate;
1281
+ }
1282
+ function hasExecutableExtension(candidate) {
1283
+ const lower = candidate.toLowerCase();
1284
+ return getWindowsExecutableExtensions().some((ext) => lower.endsWith(ext));
1285
+ }
1286
+ var DEFAULT_WINDOWS_EXTENSIONS = [".com", ".exe", ".bat", ".cmd", ".ps1"];
1287
+ function getWindowsExecutableExtensions() {
1288
+ if (process.platform !== "win32") {
1289
+ return [];
1309
1290
  }
1310
- throw new Error(`${targetName} healthcheck type must be 'http' or 'command'`);
1291
+ const fromEnv = process.env.PATHEXT?.split(";").map((ext) => ext.trim().toLowerCase()).filter((ext) => ext.length > 0);
1292
+ return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_WINDOWS_EXTENSIONS;
1311
1293
  }
1312
- function assertSupportedCliPlaceholders(template, description) {
1313
- const placeholders = extractCliPlaceholders(template);
1314
- for (const placeholder of placeholders) {
1315
- if (!CLI_PLACEHOLDERS.has(placeholder)) {
1316
- throw new Error(
1317
- `${description} includes unsupported placeholder '{${placeholder}}'. Supported placeholders: ${Array.from(CLI_PLACEHOLDERS).join(", ")}`
1318
- );
1294
+ function parseCodexJson(output) {
1295
+ const trimmed = output.trim();
1296
+ if (trimmed.length === 0) {
1297
+ throw new Error("Codex CLI produced no output in --json mode");
1298
+ }
1299
+ try {
1300
+ return JSON.parse(trimmed);
1301
+ } catch {
1302
+ const lineObjects = parseJsonLines(trimmed);
1303
+ if (lineObjects) {
1304
+ return lineObjects;
1305
+ }
1306
+ const lastBrace = trimmed.lastIndexOf("{");
1307
+ if (lastBrace >= 0) {
1308
+ const candidate = trimmed.slice(lastBrace);
1309
+ try {
1310
+ return JSON.parse(candidate);
1311
+ } catch {
1312
+ }
1319
1313
  }
1314
+ const preview = trimmed.slice(0, 200);
1315
+ throw new Error(`Codex CLI emitted invalid JSON: ${preview}${trimmed.length > 200 ? "\u2026" : ""}`);
1320
1316
  }
1321
1317
  }
1322
- function extractCliPlaceholders(template) {
1323
- const matches = template.matchAll(/\{([A-Z_]+)\}/g);
1324
- const results = [];
1325
- for (const match of matches) {
1326
- if (match[1]) {
1327
- results.push(match[1]);
1318
+ function extractAssistantText(parsed) {
1319
+ if (Array.isArray(parsed)) {
1320
+ const text = extractFromEventStream(parsed);
1321
+ if (text) {
1322
+ return text;
1323
+ }
1324
+ }
1325
+ if (!parsed || typeof parsed !== "object") {
1326
+ throw new Error("Codex CLI JSON response did not include an assistant message");
1327
+ }
1328
+ const record = parsed;
1329
+ const eventText = extractFromEvent(record);
1330
+ if (eventText) {
1331
+ return eventText;
1332
+ }
1333
+ const messages = Array.isArray(record.messages) ? record.messages : void 0;
1334
+ if (messages) {
1335
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
1336
+ const entry = messages[index];
1337
+ if (!entry || typeof entry !== "object") {
1338
+ continue;
1339
+ }
1340
+ const role = entry.role;
1341
+ if (role !== "assistant") {
1342
+ continue;
1343
+ }
1344
+ const content = entry.content;
1345
+ const flattened = flattenContent(content);
1346
+ if (flattened) {
1347
+ return flattened;
1348
+ }
1349
+ }
1350
+ }
1351
+ const response = record.response;
1352
+ if (response && typeof response === "object") {
1353
+ const content = response.content;
1354
+ const flattened = flattenContent(content);
1355
+ if (flattened) {
1356
+ return flattened;
1328
1357
  }
1329
1358
  }
1330
- return results;
1359
+ const output = record.output;
1360
+ const flattenedOutput = flattenContent(output);
1361
+ if (flattenedOutput) {
1362
+ return flattenedOutput;
1363
+ }
1364
+ throw new Error("Codex CLI JSON response did not include an assistant message");
1331
1365
  }
1332
- function resolveString(source, env, description, allowLiteral = false) {
1333
- const value = resolveOptionalString(source, env, description, {
1334
- allowLiteral,
1335
- optionalEnv: false
1336
- });
1337
- if (value === void 0) {
1338
- throw new Error(`${description} is required`);
1366
+ function extractFromEventStream(events) {
1367
+ for (let index = events.length - 1; index >= 0; index -= 1) {
1368
+ const candidate = events[index];
1369
+ const text = extractFromEvent(candidate);
1370
+ if (text) {
1371
+ return text;
1372
+ }
1339
1373
  }
1340
- return value;
1374
+ return void 0;
1341
1375
  }
1342
- function resolveOptionalString(source, env, description, options) {
1343
- if (source === void 0 || source === null) {
1344
- return void 0;
1345
- }
1346
- if (typeof source !== "string") {
1347
- throw new Error(`${description} must be a string`);
1348
- }
1349
- const trimmed = source.trim();
1350
- if (trimmed.length === 0) {
1376
+ function extractFromEvent(event) {
1377
+ if (!event || typeof event !== "object") {
1351
1378
  return void 0;
1352
1379
  }
1353
- const envValue = env[trimmed];
1354
- if (envValue !== void 0) {
1355
- if (envValue.trim().length === 0) {
1356
- throw new Error(`Environment variable '${trimmed}' for ${description} is empty`);
1380
+ const record = event;
1381
+ const type = typeof record.type === "string" ? record.type : void 0;
1382
+ if (type === JSONL_TYPE_ITEM_COMPLETED) {
1383
+ const item = record.item;
1384
+ const text = extractFromItem(item);
1385
+ if (text) {
1386
+ return text;
1357
1387
  }
1358
- return envValue;
1359
1388
  }
1360
- const allowLiteral = options?.allowLiteral ?? false;
1361
- const optionalEnv = options?.optionalEnv ?? false;
1362
- const looksLikeEnv = isLikelyEnvReference(trimmed);
1363
- if (looksLikeEnv) {
1364
- if (optionalEnv) {
1365
- return void 0;
1366
- }
1367
- if (!allowLiteral) {
1368
- throw new Error(`Environment variable '${trimmed}' required for ${description} is not set`);
1369
- }
1389
+ const output = record.output ?? record.content;
1390
+ const flattened = flattenContent(output);
1391
+ if (flattened) {
1392
+ return flattened;
1370
1393
  }
1371
- return trimmed;
1394
+ return void 0;
1372
1395
  }
1373
- function resolveOptionalLiteralString(source) {
1374
- if (source === void 0 || source === null) {
1396
+ function extractFromItem(item) {
1397
+ if (!item || typeof item !== "object") {
1375
1398
  return void 0;
1376
1399
  }
1377
- if (typeof source !== "string") {
1378
- throw new Error("expected string value");
1400
+ const record = item;
1401
+ const itemType = typeof record.type === "string" ? record.type : void 0;
1402
+ if (itemType === "agent_message" || itemType === "response" || itemType === "output") {
1403
+ const text = flattenContent(record.text ?? record.content ?? record.output);
1404
+ if (text) {
1405
+ return text;
1406
+ }
1379
1407
  }
1380
- const trimmed = source.trim();
1381
- return trimmed.length > 0 ? trimmed : void 0;
1408
+ return void 0;
1382
1409
  }
1383
- function resolveOptionalNumber(source, description) {
1384
- if (source === void 0 || source === null || source === "") {
1385
- return void 0;
1410
+ function flattenContent(value) {
1411
+ if (typeof value === "string") {
1412
+ return value;
1386
1413
  }
1387
- if (typeof source === "number") {
1388
- return Number.isFinite(source) ? source : void 0;
1414
+ if (Array.isArray(value)) {
1415
+ const parts = value.map((segment) => {
1416
+ if (typeof segment === "string") {
1417
+ return segment;
1418
+ }
1419
+ if (segment && typeof segment === "object" && "text" in segment) {
1420
+ const text = segment.text;
1421
+ return typeof text === "string" ? text : void 0;
1422
+ }
1423
+ return void 0;
1424
+ }).filter((part) => typeof part === "string" && part.length > 0);
1425
+ return parts.length > 0 ? parts.join(" \n") : void 0;
1389
1426
  }
1390
- if (typeof source === "string") {
1391
- const numeric = Number(source);
1392
- if (Number.isFinite(numeric)) {
1393
- return numeric;
1394
- }
1427
+ if (value && typeof value === "object" && "text" in value) {
1428
+ const text = value.text;
1429
+ return typeof text === "string" ? text : void 0;
1395
1430
  }
1396
- throw new Error(`${description} must be a number`);
1431
+ return void 0;
1397
1432
  }
1398
- function resolveOptionalBoolean(source) {
1399
- if (source === void 0 || source === null || source === "") {
1433
+ function parseJsonLines(output) {
1434
+ const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
1435
+ if (lines.length <= 1) {
1400
1436
  return void 0;
1401
1437
  }
1402
- if (typeof source === "boolean") {
1403
- return source;
1404
- }
1405
- if (typeof source === "string") {
1406
- const lowered = source.trim().toLowerCase();
1407
- if (lowered === "true" || lowered === "1") {
1408
- return true;
1409
- }
1410
- if (lowered === "false" || lowered === "0") {
1411
- return false;
1438
+ const parsed = [];
1439
+ for (const line of lines) {
1440
+ try {
1441
+ parsed.push(JSON.parse(line));
1442
+ } catch {
1443
+ return void 0;
1412
1444
  }
1413
1445
  }
1414
- throw new Error("expected boolean value");
1415
- }
1416
- function isLikelyEnvReference(value) {
1417
- return /^[A-Z0-9_]+$/.test(value);
1446
+ return parsed;
1418
1447
  }
1419
- function resolveOptionalStringArray(source, env, description) {
1420
- if (source === void 0 || source === null) {
1421
- return void 0;
1422
- }
1423
- if (!Array.isArray(source)) {
1424
- throw new Error(`${description} must be an array of strings`);
1448
+ function pickDetail(stderr, stdout) {
1449
+ const errorText = stderr.trim();
1450
+ if (errorText.length > 0) {
1451
+ return errorText;
1425
1452
  }
1426
- if (source.length === 0) {
1427
- return void 0;
1453
+ const stdoutText = stdout.trim();
1454
+ return stdoutText.length > 0 ? stdoutText : void 0;
1455
+ }
1456
+ function formatTimeoutSuffix2(timeoutMs) {
1457
+ if (!timeoutMs || timeoutMs <= 0) {
1458
+ return "";
1428
1459
  }
1429
- const resolved = [];
1430
- for (let i = 0; i < source.length; i++) {
1431
- const item = source[i];
1432
- if (typeof item !== "string") {
1433
- throw new Error(`${description}[${i}] must be a string`);
1460
+ const seconds = Math.ceil(timeoutMs / 1e3);
1461
+ return ` after ${seconds}s`;
1462
+ }
1463
+ async function defaultCodexRunner(options) {
1464
+ return await new Promise((resolve, reject) => {
1465
+ const child = spawn(options.executable, options.args, {
1466
+ cwd: options.cwd,
1467
+ env: options.env,
1468
+ stdio: ["pipe", "pipe", "pipe"],
1469
+ shell: shouldShellExecute(options.executable)
1470
+ });
1471
+ let stdout = "";
1472
+ let stderr = "";
1473
+ let timedOut = false;
1474
+ const onAbort = () => {
1475
+ child.kill("SIGTERM");
1476
+ };
1477
+ if (options.signal) {
1478
+ if (options.signal.aborted) {
1479
+ onAbort();
1480
+ } else {
1481
+ options.signal.addEventListener("abort", onAbort, { once: true });
1482
+ }
1434
1483
  }
1435
- const trimmed = item.trim();
1436
- if (trimmed.length === 0) {
1437
- throw new Error(`${description}[${i}] cannot be empty`);
1484
+ let timeoutHandle;
1485
+ if (options.timeoutMs && options.timeoutMs > 0) {
1486
+ timeoutHandle = setTimeout(() => {
1487
+ timedOut = true;
1488
+ child.kill("SIGTERM");
1489
+ }, options.timeoutMs);
1490
+ timeoutHandle.unref?.();
1438
1491
  }
1439
- const envValue = env[trimmed];
1440
- if (envValue !== void 0) {
1441
- if (envValue.trim().length === 0) {
1442
- throw new Error(`Environment variable '${trimmed}' for ${description}[${i}] is empty`);
1492
+ child.stdout.setEncoding("utf8");
1493
+ child.stdout.on("data", (chunk) => {
1494
+ stdout += chunk;
1495
+ });
1496
+ child.stderr.setEncoding("utf8");
1497
+ child.stderr.on("data", (chunk) => {
1498
+ stderr += chunk;
1499
+ });
1500
+ child.stdin.end(options.prompt);
1501
+ const cleanup = () => {
1502
+ if (timeoutHandle) {
1503
+ clearTimeout(timeoutHandle);
1443
1504
  }
1444
- resolved.push(envValue);
1445
- } else {
1446
- resolved.push(trimmed);
1447
- }
1505
+ if (options.signal) {
1506
+ options.signal.removeEventListener("abort", onAbort);
1507
+ }
1508
+ };
1509
+ child.on("error", (error) => {
1510
+ cleanup();
1511
+ reject(error);
1512
+ });
1513
+ child.on("close", (code) => {
1514
+ cleanup();
1515
+ resolve({
1516
+ stdout,
1517
+ stderr,
1518
+ exitCode: typeof code === "number" ? code : -1,
1519
+ timedOut
1520
+ });
1521
+ });
1522
+ });
1523
+ }
1524
+ function shouldShellExecute(executable) {
1525
+ if (process.platform !== "win32") {
1526
+ return false;
1448
1527
  }
1449
- return resolved.length > 0 ? resolved : void 0;
1528
+ const lower = executable.toLowerCase();
1529
+ return lower.endsWith(".cmd") || lower.endsWith(".bat") || lower.endsWith(".ps1");
1450
1530
  }
1451
1531
 
1452
- // src/evaluation/providers/vscode.ts
1453
- import { readFile as readFile2 } from "node:fs/promises";
1454
- import path3 from "node:path";
1455
- import { dispatchAgentSession, dispatchBatchAgent, getSubagentRoot, provisionSubagents } from "subagent";
1456
- var VSCodeProvider = class {
1532
+ // src/evaluation/providers/mock.ts
1533
+ var DEFAULT_MOCK_RESPONSE = '{"answer":"Mock provider response. Configure targets.yaml to supply a custom value."}';
1534
+ var MockProvider = class {
1457
1535
  id;
1458
- kind;
1536
+ kind = "mock";
1459
1537
  targetName;
1460
- supportsBatch = true;
1461
- config;
1462
- constructor(targetName, config, kind) {
1463
- this.id = `${kind}:${targetName}`;
1464
- this.kind = kind;
1538
+ cannedResponse;
1539
+ delayMs;
1540
+ delayMinMs;
1541
+ delayMaxMs;
1542
+ constructor(targetName, config) {
1543
+ this.id = `mock:${targetName}`;
1465
1544
  this.targetName = targetName;
1466
- this.config = config;
1545
+ this.cannedResponse = config.response ?? DEFAULT_MOCK_RESPONSE;
1546
+ this.delayMs = config.delayMs ?? 0;
1547
+ this.delayMinMs = config.delayMinMs ?? 0;
1548
+ this.delayMaxMs = config.delayMaxMs ?? 0;
1467
1549
  }
1468
1550
  async invoke(request) {
1469
- if (request.signal?.aborted) {
1470
- throw new Error("VS Code provider request was aborted before dispatch");
1471
- }
1472
- const inputFiles = normalizeAttachments(request.inputFiles);
1473
- const promptContent = buildPromptDocument(request, inputFiles, request.guideline_patterns);
1474
- const session = await dispatchAgentSession({
1475
- userQuery: promptContent,
1476
- extraAttachments: inputFiles,
1477
- wait: this.config.waitForResponse,
1478
- dryRun: this.config.dryRun,
1479
- vscodeCmd: this.config.command,
1480
- subagentRoot: this.config.subagentRoot,
1481
- workspaceTemplate: this.config.workspaceTemplate,
1482
- silent: true
1483
- });
1484
- if (session.exitCode !== 0 || !session.responseFile) {
1485
- const failure = session.error ?? "VS Code subagent did not produce a response";
1486
- throw new Error(failure);
1487
- }
1488
- if (this.config.dryRun) {
1489
- return {
1490
- text: "",
1491
- raw: {
1492
- session,
1493
- inputFiles
1494
- }
1495
- };
1551
+ const delay = this.calculateDelay();
1552
+ if (delay > 0) {
1553
+ await new Promise((resolve) => setTimeout(resolve, delay));
1496
1554
  }
1497
- const responseText = await readFile2(session.responseFile, "utf8");
1498
1555
  return {
1499
- text: responseText,
1556
+ text: this.cannedResponse,
1500
1557
  raw: {
1501
- session,
1502
- inputFiles
1558
+ prompt: request.prompt,
1559
+ guidelines: request.guidelines
1503
1560
  }
1504
1561
  };
1505
1562
  }
1506
- async invokeBatch(requests) {
1507
- if (requests.length === 0) {
1508
- return [];
1509
- }
1510
- const normalizedRequests = requests.map((req) => ({
1511
- request: req,
1512
- inputFiles: normalizeAttachments(req.inputFiles)
1513
- }));
1514
- const combinedInputFiles = mergeAttachments(
1515
- normalizedRequests.map(({ inputFiles }) => inputFiles)
1516
- );
1517
- const userQueries = normalizedRequests.map(
1518
- ({ request, inputFiles }) => buildPromptDocument(request, inputFiles, request.guideline_patterns)
1519
- );
1520
- const session = await dispatchBatchAgent({
1521
- userQueries,
1522
- extraAttachments: combinedInputFiles,
1523
- wait: this.config.waitForResponse,
1524
- dryRun: this.config.dryRun,
1525
- vscodeCmd: this.config.command,
1526
- subagentRoot: this.config.subagentRoot,
1527
- workspaceTemplate: this.config.workspaceTemplate,
1528
- silent: true
1529
- });
1530
- if (session.exitCode !== 0 || !session.responseFiles) {
1531
- const failure = session.error ?? "VS Code subagent did not produce batch responses";
1532
- throw new Error(failure);
1533
- }
1534
- if (this.config.dryRun) {
1535
- return normalizedRequests.map(({ inputFiles }) => ({
1536
- text: "",
1537
- raw: {
1538
- session,
1539
- inputFiles,
1540
- allInputFiles: combinedInputFiles
1541
- }
1542
- }));
1543
- }
1544
- if (session.responseFiles.length !== requests.length) {
1545
- throw new Error(
1546
- `VS Code batch returned ${session.responseFiles.length} responses for ${requests.length} requests`
1547
- );
1548
- }
1549
- const responses = [];
1550
- for (const [index, responseFile] of session.responseFiles.entries()) {
1551
- const responseText = await readFile2(responseFile, "utf8");
1552
- responses.push({
1553
- text: responseText,
1554
- raw: {
1555
- session,
1556
- inputFiles: normalizedRequests[index]?.inputFiles,
1557
- allInputFiles: combinedInputFiles,
1558
- responseFile
1559
- }
1560
- });
1563
+ calculateDelay() {
1564
+ if (this.delayMinMs > 0 || this.delayMaxMs > 0) {
1565
+ const min = Math.max(0, this.delayMinMs);
1566
+ const max = Math.max(min, this.delayMaxMs);
1567
+ return Math.floor(Math.random() * (max - min + 1)) + min;
1561
1568
  }
1562
- return responses;
1569
+ return this.delayMs;
1563
1570
  }
1564
1571
  };
1565
- function buildPromptDocument(request, attachments, guidelinePatterns) {
1566
- const parts = [];
1567
- const guidelineFiles = collectGuidelineFiles(attachments, guidelinePatterns);
1568
- const attachmentFiles = collectAttachmentFiles(attachments);
1569
- const nonGuidelineAttachments = attachmentFiles.filter(
1570
- (file) => !guidelineFiles.includes(file)
1571
- );
1572
- const prereadBlock = buildMandatoryPrereadBlock(guidelineFiles, nonGuidelineAttachments);
1573
- if (prereadBlock.length > 0) {
1574
- parts.push("\n", prereadBlock);
1575
- }
1576
- parts.push("\n[[ ## user_query ## ]]\n", request.prompt.trim());
1577
- return parts.join("\n").trim();
1578
- }
1579
- function buildMandatoryPrereadBlock(guidelineFiles, attachmentFiles) {
1580
- if (guidelineFiles.length === 0 && attachmentFiles.length === 0) {
1581
- return "";
1582
- }
1583
- const buildList = (files) => files.map((absolutePath) => {
1584
- const fileName = path3.basename(absolutePath);
1585
- const fileUri = pathToFileUri(absolutePath);
1586
- return `* [${fileName}](${fileUri})`;
1587
- });
1588
- const sections = [];
1589
- if (guidelineFiles.length > 0) {
1590
- sections.push(`Read all guideline files:
1591
- ${buildList(guidelineFiles).join("\n")}.`);
1572
+
1573
+ // src/evaluation/providers/targets.ts
1574
+ import { z } from "zod";
1575
+ var CLI_PLACEHOLDERS = /* @__PURE__ */ new Set(["PROMPT", "GUIDELINES", "EVAL_ID", "ATTEMPT", "FILES"]);
1576
+ var BASE_TARGET_SCHEMA = z.object({
1577
+ name: z.string().min(1, "target name is required"),
1578
+ provider: z.string().min(1, "provider is required"),
1579
+ settings: z.record(z.unknown()).optional(),
1580
+ judge_target: z.string().optional(),
1581
+ workers: z.number().int().min(1).optional()
1582
+ });
1583
+ var DEFAULT_AZURE_API_VERSION = "2024-10-01-preview";
1584
+ function normalizeAzureApiVersion(value) {
1585
+ if (!value) {
1586
+ return DEFAULT_AZURE_API_VERSION;
1592
1587
  }
1593
- if (attachmentFiles.length > 0) {
1594
- sections.push(`Read all attachment files:
1595
- ${buildList(attachmentFiles).join("\n")}.`);
1588
+ const trimmed = value.trim();
1589
+ if (trimmed.length === 0) {
1590
+ return DEFAULT_AZURE_API_VERSION;
1596
1591
  }
1597
- sections.push(
1598
- "If any file is missing, fail with ERROR: missing-file <filename> and stop.",
1599
- "Then apply system_instructions on the user query below."
1600
- );
1601
- return sections.join("\n");
1592
+ const withoutPrefix = trimmed.replace(/^api[-_]?version\s*=\s*/i, "").trim();
1593
+ return withoutPrefix.length > 0 ? withoutPrefix : DEFAULT_AZURE_API_VERSION;
1602
1594
  }
1603
- function collectGuidelineFiles(attachments, guidelinePatterns) {
1604
- if (!attachments || attachments.length === 0) {
1605
- return [];
1606
- }
1607
- const unique = /* @__PURE__ */ new Map();
1608
- for (const attachment of attachments) {
1609
- const absolutePath = path3.resolve(attachment);
1610
- const normalized = absolutePath.split(path3.sep).join("/");
1611
- if (isGuidelineFile(normalized, guidelinePatterns)) {
1612
- if (!unique.has(absolutePath)) {
1613
- unique.set(absolutePath, absolutePath);
1614
- }
1615
- }
1595
+ function resolveTargetDefinition(definition, env = process.env) {
1596
+ const parsed = BASE_TARGET_SCHEMA.parse(definition);
1597
+ const provider = parsed.provider.toLowerCase();
1598
+ const providerBatching = resolveOptionalBoolean(
1599
+ parsed.settings?.provider_batching ?? parsed.settings?.providerBatching
1600
+ );
1601
+ switch (provider) {
1602
+ case "azure":
1603
+ case "azure-openai":
1604
+ return {
1605
+ kind: "azure",
1606
+ name: parsed.name,
1607
+ judgeTarget: parsed.judge_target,
1608
+ workers: parsed.workers,
1609
+ providerBatching,
1610
+ config: resolveAzureConfig(parsed, env)
1611
+ };
1612
+ case "anthropic":
1613
+ return {
1614
+ kind: "anthropic",
1615
+ name: parsed.name,
1616
+ judgeTarget: parsed.judge_target,
1617
+ workers: parsed.workers,
1618
+ providerBatching,
1619
+ config: resolveAnthropicConfig(parsed, env)
1620
+ };
1621
+ case "gemini":
1622
+ case "google":
1623
+ case "google-gemini":
1624
+ return {
1625
+ kind: "gemini",
1626
+ name: parsed.name,
1627
+ judgeTarget: parsed.judge_target,
1628
+ workers: parsed.workers,
1629
+ providerBatching,
1630
+ config: resolveGeminiConfig(parsed, env)
1631
+ };
1632
+ case "codex":
1633
+ case "codex-cli":
1634
+ return {
1635
+ kind: "codex",
1636
+ name: parsed.name,
1637
+ judgeTarget: parsed.judge_target,
1638
+ workers: parsed.workers,
1639
+ providerBatching,
1640
+ config: resolveCodexConfig(parsed, env)
1641
+ };
1642
+ case "mock":
1643
+ return {
1644
+ kind: "mock",
1645
+ name: parsed.name,
1646
+ judgeTarget: parsed.judge_target,
1647
+ workers: parsed.workers,
1648
+ providerBatching,
1649
+ config: resolveMockConfig(parsed)
1650
+ };
1651
+ case "vscode":
1652
+ case "vscode-insiders":
1653
+ return {
1654
+ kind: provider,
1655
+ name: parsed.name,
1656
+ judgeTarget: parsed.judge_target,
1657
+ workers: parsed.workers,
1658
+ providerBatching,
1659
+ config: resolveVSCodeConfig(parsed, env, provider === "vscode-insiders")
1660
+ };
1661
+ case "cli":
1662
+ return {
1663
+ kind: "cli",
1664
+ name: parsed.name,
1665
+ judgeTarget: parsed.judge_target,
1666
+ workers: parsed.workers,
1667
+ providerBatching,
1668
+ config: resolveCliConfig(parsed, env)
1669
+ };
1670
+ default:
1671
+ throw new Error(`Unsupported provider '${parsed.provider}' in target '${parsed.name}'`);
1616
1672
  }
1617
- return Array.from(unique.values());
1618
1673
  }
1619
- function collectAttachmentFiles(attachments) {
1620
- if (!attachments || attachments.length === 0) {
1621
- return [];
1622
- }
1623
- const unique = /* @__PURE__ */ new Map();
1624
- for (const attachment of attachments) {
1625
- const absolutePath = path3.resolve(attachment);
1626
- if (!unique.has(absolutePath)) {
1627
- unique.set(absolutePath, absolutePath);
1628
- }
1629
- }
1630
- return Array.from(unique.values());
1674
+ function resolveAzureConfig(target, env) {
1675
+ const settings = target.settings ?? {};
1676
+ const endpointSource = settings.endpoint ?? settings.resource ?? settings.resourceName;
1677
+ const apiKeySource = settings.api_key ?? settings.apiKey;
1678
+ const deploymentSource = settings.deployment ?? settings.deploymentName ?? settings.model;
1679
+ const versionSource = settings.version ?? settings.api_version;
1680
+ const temperatureSource = settings.temperature;
1681
+ const maxTokensSource = settings.max_output_tokens ?? settings.maxTokens;
1682
+ const resourceName = resolveString(endpointSource, env, `${target.name} endpoint`);
1683
+ const apiKey = resolveString(apiKeySource, env, `${target.name} api key`);
1684
+ const deploymentName = resolveString(deploymentSource, env, `${target.name} deployment`);
1685
+ const version = normalizeAzureApiVersion(
1686
+ resolveOptionalString(versionSource, env, `${target.name} api version`)
1687
+ );
1688
+ const temperature = resolveOptionalNumber(temperatureSource, `${target.name} temperature`);
1689
+ const maxOutputTokens = resolveOptionalNumber(
1690
+ maxTokensSource,
1691
+ `${target.name} max output tokens`
1692
+ );
1693
+ return {
1694
+ resourceName,
1695
+ deploymentName,
1696
+ apiKey,
1697
+ version,
1698
+ temperature,
1699
+ maxOutputTokens
1700
+ };
1701
+ }
1702
+ function resolveAnthropicConfig(target, env) {
1703
+ const settings = target.settings ?? {};
1704
+ const apiKeySource = settings.api_key ?? settings.apiKey;
1705
+ const modelSource = settings.model ?? settings.deployment ?? settings.variant;
1706
+ const temperatureSource = settings.temperature;
1707
+ const maxTokensSource = settings.max_output_tokens ?? settings.maxTokens;
1708
+ const thinkingBudgetSource = settings.thinking_budget ?? settings.thinkingBudget;
1709
+ const apiKey = resolveString(apiKeySource, env, `${target.name} Anthropic api key`);
1710
+ const model = resolveString(modelSource, env, `${target.name} Anthropic model`);
1711
+ return {
1712
+ apiKey,
1713
+ model,
1714
+ temperature: resolveOptionalNumber(temperatureSource, `${target.name} temperature`),
1715
+ maxOutputTokens: resolveOptionalNumber(maxTokensSource, `${target.name} max output tokens`),
1716
+ thinkingBudget: resolveOptionalNumber(thinkingBudgetSource, `${target.name} thinking budget`)
1717
+ };
1631
1718
  }
1632
- function pathToFileUri(filePath) {
1633
- const absolutePath = path3.isAbsolute(filePath) ? filePath : path3.resolve(filePath);
1634
- const normalizedPath = absolutePath.replace(/\\/g, "/");
1635
- if (/^[a-zA-Z]:\//.test(normalizedPath)) {
1636
- return `file:///${normalizedPath}`;
1637
- }
1638
- return `file://${normalizedPath}`;
1719
+ function resolveGeminiConfig(target, env) {
1720
+ const settings = target.settings ?? {};
1721
+ const apiKeySource = settings.api_key ?? settings.apiKey;
1722
+ const modelSource = settings.model ?? settings.deployment ?? settings.variant;
1723
+ const temperatureSource = settings.temperature;
1724
+ const maxTokensSource = settings.max_output_tokens ?? settings.maxTokens;
1725
+ const apiKey = resolveString(apiKeySource, env, `${target.name} Google API key`);
1726
+ const model = resolveOptionalString(modelSource, env, `${target.name} Gemini model`, {
1727
+ allowLiteral: true,
1728
+ optionalEnv: true
1729
+ }) ?? "gemini-2.5-flash";
1730
+ return {
1731
+ apiKey,
1732
+ model,
1733
+ temperature: resolveOptionalNumber(temperatureSource, `${target.name} temperature`),
1734
+ maxOutputTokens: resolveOptionalNumber(maxTokensSource, `${target.name} max output tokens`)
1735
+ };
1639
1736
  }
1640
- function normalizeAttachments(attachments) {
1641
- if (!attachments || attachments.length === 0) {
1642
- return void 0;
1643
- }
1644
- const deduped = /* @__PURE__ */ new Set();
1645
- for (const attachment of attachments) {
1646
- deduped.add(path3.resolve(attachment));
1647
- }
1648
- return Array.from(deduped);
1737
+ function resolveCodexConfig(target, env) {
1738
+ const settings = target.settings ?? {};
1739
+ const executableSource = settings.executable ?? settings.command ?? settings.binary;
1740
+ const argsSource = settings.args ?? settings.arguments;
1741
+ const cwdSource = settings.cwd;
1742
+ const timeoutSource = settings.timeout_seconds ?? settings.timeoutSeconds;
1743
+ const executable = resolveOptionalString(executableSource, env, `${target.name} codex executable`, {
1744
+ allowLiteral: true,
1745
+ optionalEnv: true
1746
+ }) ?? "codex";
1747
+ const args = resolveOptionalStringArray(argsSource, env, `${target.name} codex args`);
1748
+ const cwd = resolveOptionalString(cwdSource, env, `${target.name} codex cwd`, {
1749
+ allowLiteral: true,
1750
+ optionalEnv: true
1751
+ });
1752
+ const timeoutMs = resolveTimeoutMs(timeoutSource, `${target.name} codex timeout`);
1753
+ return {
1754
+ executable,
1755
+ args,
1756
+ cwd,
1757
+ timeoutMs
1758
+ };
1649
1759
  }
1650
- function mergeAttachments(all) {
1651
- const deduped = /* @__PURE__ */ new Set();
1652
- for (const list of all) {
1653
- if (!list) continue;
1654
- for (const inputFile of list) {
1655
- deduped.add(path3.resolve(inputFile));
1656
- }
1657
- }
1658
- return deduped.size > 0 ? Array.from(deduped) : void 0;
1760
+ function resolveMockConfig(target) {
1761
+ const settings = target.settings ?? {};
1762
+ const response = typeof settings.response === "string" ? settings.response : void 0;
1763
+ return { response };
1659
1764
  }
1660
- async function ensureVSCodeSubagents(options) {
1661
- const { kind, count, verbose = false } = options;
1662
- const vscodeCmd = kind === "vscode-insiders" ? "code-insiders" : "code";
1663
- const subagentRoot = getSubagentRoot(vscodeCmd);
1664
- try {
1665
- if (verbose) {
1666
- console.log(`Provisioning ${count} subagent(s) via: subagent ${vscodeCmd} provision`);
1667
- }
1668
- const result = await provisionSubagents({
1669
- targetRoot: subagentRoot,
1670
- subagents: count,
1671
- dryRun: false
1672
- });
1673
- if (verbose) {
1674
- if (result.created.length > 0) {
1675
- console.log(`Created ${result.created.length} new subagent(s)`);
1676
- }
1677
- if (result.skippedExisting.length > 0) {
1678
- console.log(`Reusing ${result.skippedExisting.length} existing unlocked subagent(s)`);
1679
- }
1680
- console.log(`
1681
- total unlocked subagents available: ${result.created.length + result.skippedExisting.length}`);
1682
- }
1683
- return {
1684
- provisioned: true,
1685
- message: `Provisioned ${count} subagent(s): ${result.created.length} created, ${result.skippedExisting.length} reused`
1686
- };
1687
- } catch (error) {
1688
- const errorMessage = error instanceof Error ? error.message : String(error);
1689
- if (verbose) {
1690
- console.warn(`Provisioning failed (continuing anyway): ${errorMessage}`);
1691
- }
1692
- return {
1693
- provisioned: false,
1694
- message: `Provisioning failed: ${errorMessage}`
1695
- };
1696
- }
1765
+ function resolveVSCodeConfig(target, env, insiders) {
1766
+ const settings = target.settings ?? {};
1767
+ const workspaceTemplateEnvVar = resolveOptionalLiteralString(settings.workspace_template ?? settings.workspaceTemplate);
1768
+ const workspaceTemplate = workspaceTemplateEnvVar ? resolveOptionalString(workspaceTemplateEnvVar, env, `${target.name} workspace template path`, {
1769
+ allowLiteral: false,
1770
+ optionalEnv: true
1771
+ }) : void 0;
1772
+ const commandSource = settings.vscode_cmd ?? settings.command;
1773
+ const waitSource = settings.wait;
1774
+ const dryRunSource = settings.dry_run ?? settings.dryRun;
1775
+ const subagentRootSource = settings.subagent_root ?? settings.subagentRoot;
1776
+ const defaultCommand = insiders ? "code-insiders" : "code";
1777
+ const command = resolveOptionalLiteralString(commandSource) ?? defaultCommand;
1778
+ return {
1779
+ command,
1780
+ waitForResponse: resolveOptionalBoolean(waitSource) ?? true,
1781
+ dryRun: resolveOptionalBoolean(dryRunSource) ?? false,
1782
+ subagentRoot: resolveOptionalString(subagentRootSource, env, `${target.name} subagent root`, {
1783
+ allowLiteral: true,
1784
+ optionalEnv: true
1785
+ }),
1786
+ workspaceTemplate
1787
+ };
1697
1788
  }
1698
-
1699
- // src/evaluation/providers/codex.ts
1700
- import { exec as execCallback, spawn } from "node:child_process";
1701
- import { constants as constants2 } from "node:fs";
1702
- import { access as access2, copyFile, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
1703
- import { tmpdir } from "node:os";
1704
- import path5 from "node:path";
1705
- import { promisify as promisify2 } from "node:util";
1706
-
1707
- // src/evaluation/providers/preread.ts
1708
- import path4 from "node:path";
1709
- function buildPromptDocument2(request, inputFiles, options) {
1710
- const parts = [];
1711
- const guidelineFiles = collectGuidelineFiles2(
1712
- inputFiles,
1713
- options?.guidelinePatterns ?? request.guideline_patterns,
1714
- options?.guidelineOverrides
1789
+ function resolveCliConfig(target, env) {
1790
+ const settings = target.settings ?? {};
1791
+ const commandTemplateSource = settings.command_template ?? settings.commandTemplate;
1792
+ const filesFormat = resolveOptionalLiteralString(
1793
+ settings.files_format ?? settings.filesFormat ?? settings.attachments_format ?? settings.attachmentsFormat
1715
1794
  );
1716
- const inputFilesList = collectInputFiles(inputFiles);
1717
- const nonGuidelineInputFiles = inputFilesList.filter(
1718
- (file) => !guidelineFiles.includes(file)
1795
+ const cwd = resolveOptionalString(settings.cwd, env, `${target.name} working directory`, {
1796
+ allowLiteral: true,
1797
+ optionalEnv: true
1798
+ });
1799
+ const envOverrides = resolveEnvOverrides(settings.env, env, target.name);
1800
+ const timeoutMs = resolveTimeoutMs(settings.timeout_seconds ?? settings.timeoutSeconds, `${target.name} timeout`);
1801
+ const healthcheck = resolveCliHealthcheck(settings.healthcheck, env, target.name);
1802
+ const commandTemplate = resolveString(
1803
+ commandTemplateSource,
1804
+ env,
1805
+ `${target.name} CLI command template`,
1806
+ true
1719
1807
  );
1720
- const prereadBlock = buildMandatoryPrereadBlock2(guidelineFiles, nonGuidelineInputFiles);
1721
- if (prereadBlock.length > 0) {
1722
- parts.push("\n", prereadBlock);
1723
- }
1724
- parts.push("\n[[ ## user_query ## ]]\n", request.prompt.trim());
1725
- return parts.join("\n").trim();
1808
+ assertSupportedCliPlaceholders(commandTemplate, `${target.name} CLI command template`);
1809
+ return {
1810
+ commandTemplate,
1811
+ filesFormat,
1812
+ cwd,
1813
+ env: envOverrides,
1814
+ timeoutMs,
1815
+ healthcheck
1816
+ };
1726
1817
  }
1727
- function normalizeInputFiles2(inputFiles) {
1728
- if (!inputFiles || inputFiles.length === 0) {
1818
+ function resolveEnvOverrides(source, env, targetName) {
1819
+ if (source === void 0 || source === null) {
1729
1820
  return void 0;
1730
1821
  }
1731
- const deduped = /* @__PURE__ */ new Map();
1732
- for (const inputFile of inputFiles) {
1733
- const absolutePath = path4.resolve(inputFile);
1734
- if (!deduped.has(absolutePath)) {
1735
- deduped.set(absolutePath, absolutePath);
1736
- }
1737
- }
1738
- return Array.from(deduped.values());
1739
- }
1740
- function collectGuidelineFiles2(inputFiles, guidelinePatterns, overrides) {
1741
- if (!inputFiles || inputFiles.length === 0) {
1742
- return [];
1743
- }
1744
- const unique = /* @__PURE__ */ new Map();
1745
- for (const inputFile of inputFiles) {
1746
- const absolutePath = path4.resolve(inputFile);
1747
- if (overrides?.has(absolutePath)) {
1748
- if (!unique.has(absolutePath)) {
1749
- unique.set(absolutePath, absolutePath);
1750
- }
1751
- continue;
1752
- }
1753
- const normalized = absolutePath.split(path4.sep).join("/");
1754
- if (isGuidelineFile(normalized, guidelinePatterns)) {
1755
- if (!unique.has(absolutePath)) {
1756
- unique.set(absolutePath, absolutePath);
1757
- }
1758
- }
1759
- }
1760
- return Array.from(unique.values());
1761
- }
1762
- function collectInputFiles(inputFiles) {
1763
- if (!inputFiles || inputFiles.length === 0) {
1764
- return [];
1822
+ if (typeof source !== "object" || Array.isArray(source)) {
1823
+ throw new Error(`${targetName} env overrides must be an object map of strings`);
1765
1824
  }
1766
- const unique = /* @__PURE__ */ new Map();
1767
- for (const inputFile of inputFiles) {
1768
- const absolutePath = path4.resolve(inputFile);
1769
- if (!unique.has(absolutePath)) {
1770
- unique.set(absolutePath, absolutePath);
1825
+ const entries = Object.entries(source);
1826
+ const resolved = {};
1827
+ for (const [key, value] of entries) {
1828
+ if (typeof value !== "string") {
1829
+ throw new Error(`${targetName} env override '${key}' must be a string`);
1771
1830
  }
1831
+ const resolvedValue = resolveString(value, env, `${targetName} env override '${key}'`);
1832
+ resolved[key] = resolvedValue;
1772
1833
  }
1773
- return Array.from(unique.values());
1834
+ return Object.keys(resolved).length > 0 ? resolved : void 0;
1774
1835
  }
1775
- function buildMandatoryPrereadBlock2(guidelineFiles, inputFiles) {
1776
- if (guidelineFiles.length === 0 && inputFiles.length === 0) {
1777
- return "";
1778
- }
1779
- const buildList = (files) => files.map((absolutePath) => {
1780
- const fileName = path4.basename(absolutePath);
1781
- const fileUri = pathToFileUri2(absolutePath);
1782
- return `* [${fileName}](${fileUri})`;
1783
- });
1784
- const sections = [];
1785
- if (guidelineFiles.length > 0) {
1786
- sections.push(`Read all guideline files:
1787
- ${buildList(guidelineFiles).join("\n")}.`);
1836
+ function resolveTimeoutMs(source, description) {
1837
+ const seconds = resolveOptionalNumber(source, `${description} (seconds)`);
1838
+ if (seconds === void 0) {
1839
+ return void 0;
1788
1840
  }
1789
- if (inputFiles.length > 0) {
1790
- sections.push(`Read all input files:
1791
- ${buildList(inputFiles).join("\n")}.`);
1841
+ if (seconds <= 0) {
1842
+ throw new Error(`${description} must be greater than zero seconds`);
1792
1843
  }
1793
- sections.push(
1794
- "If any file is missing, fail with ERROR: missing-file <filename> and stop.",
1795
- "Then apply system_instructions on the user query below."
1796
- );
1797
- return sections.join("\n");
1844
+ return Math.floor(seconds * 1e3);
1798
1845
  }
1799
- function pathToFileUri2(filePath) {
1800
- const absolutePath = path4.isAbsolute(filePath) ? filePath : path4.resolve(filePath);
1801
- const normalizedPath = absolutePath.replace(/\\/g, "/");
1802
- if (/^[a-zA-Z]:\//.test(normalizedPath)) {
1803
- return `file:///${normalizedPath}`;
1846
+ function resolveCliHealthcheck(source, env, targetName) {
1847
+ if (source === void 0 || source === null) {
1848
+ return void 0;
1804
1849
  }
1805
- return `file://${normalizedPath}`;
1806
- }
1807
-
1808
- // src/evaluation/providers/codex.ts
1809
- var execAsync2 = promisify2(execCallback);
1810
- var WORKSPACE_PREFIX = "agentv-codex-";
1811
- var PROMPT_FILENAME = "prompt.md";
1812
- var FILES_DIR = "files";
1813
- var JSONL_TYPE_ITEM_COMPLETED = "item.completed";
1814
- var CodexProvider = class {
1815
- id;
1816
- kind = "codex";
1817
- targetName;
1818
- supportsBatch = false;
1819
- config;
1820
- runCodex;
1821
- environmentCheck;
1822
- resolvedExecutable;
1823
- constructor(targetName, config, runner = defaultCodexRunner) {
1824
- this.id = `codex:${targetName}`;
1825
- this.targetName = targetName;
1826
- this.config = config;
1827
- this.runCodex = runner;
1850
+ if (typeof source !== "object" || Array.isArray(source)) {
1851
+ throw new Error(`${targetName} healthcheck must be an object`);
1828
1852
  }
1829
- async invoke(request) {
1830
- if (request.signal?.aborted) {
1831
- throw new Error("Codex provider request was aborted before execution");
1832
- }
1833
- await this.ensureEnvironmentReady();
1834
- const inputFiles = normalizeInputFiles2(request.inputFiles);
1835
- const originalGuidelines = new Set(
1836
- collectGuidelineFiles2(inputFiles, request.guideline_patterns).map((file) => path5.resolve(file))
1853
+ const candidate = source;
1854
+ const type = candidate.type;
1855
+ const timeoutMs = resolveTimeoutMs(
1856
+ candidate.timeout_seconds ?? candidate.timeoutSeconds,
1857
+ `${targetName} healthcheck timeout`
1858
+ );
1859
+ if (type === "http") {
1860
+ const url = resolveString(candidate.url, env, `${targetName} healthcheck URL`);
1861
+ return {
1862
+ type: "http",
1863
+ url,
1864
+ timeoutMs
1865
+ };
1866
+ }
1867
+ if (type === "command") {
1868
+ const commandTemplate = resolveString(
1869
+ candidate.command_template ?? candidate.commandTemplate,
1870
+ env,
1871
+ `${targetName} healthcheck command template`,
1872
+ true
1837
1873
  );
1838
- const workspaceRoot = await this.createWorkspace();
1839
- try {
1840
- const { mirroredInputFiles, guidelineMirrors } = await this.mirrorInputFiles(
1841
- inputFiles,
1842
- workspaceRoot,
1843
- originalGuidelines
1874
+ assertSupportedCliPlaceholders(commandTemplate, `${targetName} healthcheck command template`);
1875
+ const cwd = resolveOptionalString(candidate.cwd, env, `${targetName} healthcheck cwd`, {
1876
+ allowLiteral: true,
1877
+ optionalEnv: true
1878
+ });
1879
+ return {
1880
+ type: "command",
1881
+ commandTemplate,
1882
+ timeoutMs,
1883
+ cwd
1884
+ };
1885
+ }
1886
+ throw new Error(`${targetName} healthcheck type must be 'http' or 'command'`);
1887
+ }
1888
+ function assertSupportedCliPlaceholders(template, description) {
1889
+ const placeholders = extractCliPlaceholders(template);
1890
+ for (const placeholder of placeholders) {
1891
+ if (!CLI_PLACEHOLDERS.has(placeholder)) {
1892
+ throw new Error(
1893
+ `${description} includes unsupported placeholder '{${placeholder}}'. Supported placeholders: ${Array.from(CLI_PLACEHOLDERS).join(", ")}`
1844
1894
  );
1845
- const promptContent = buildPromptDocument2(request, mirroredInputFiles, {
1846
- guidelinePatterns: request.guideline_patterns,
1847
- guidelineOverrides: guidelineMirrors
1848
- });
1849
- const promptFile = path5.join(workspaceRoot, PROMPT_FILENAME);
1850
- await writeFile(promptFile, promptContent, "utf8");
1851
- const args = this.buildCodexArgs();
1852
- const cwd = this.resolveCwd(workspaceRoot);
1853
- const result = await this.executeCodex(args, cwd, promptContent, request.signal);
1854
- if (result.timedOut) {
1855
- throw new Error(
1856
- `Codex CLI timed out${formatTimeoutSuffix2(this.config.timeoutMs ?? void 0)}`
1857
- );
1858
- }
1859
- if (result.exitCode !== 0) {
1860
- const detail = pickDetail(result.stderr, result.stdout);
1861
- const prefix = `Codex CLI exited with code ${result.exitCode}`;
1862
- throw new Error(detail ? `${prefix}: ${detail}` : prefix);
1863
- }
1864
- const parsed = parseCodexJson(result.stdout);
1865
- const assistantText = extractAssistantText(parsed);
1866
- return {
1867
- text: assistantText,
1868
- raw: {
1869
- response: parsed,
1870
- stdout: result.stdout,
1871
- stderr: result.stderr,
1872
- exitCode: result.exitCode,
1873
- args,
1874
- executable: this.resolvedExecutable ?? this.config.executable,
1875
- promptFile,
1876
- workspace: workspaceRoot,
1877
- inputFiles: mirroredInputFiles
1878
- }
1879
- };
1880
- } finally {
1881
- await this.cleanupWorkspace(workspaceRoot);
1882
1895
  }
1883
1896
  }
1884
- async ensureEnvironmentReady() {
1885
- if (!this.environmentCheck) {
1886
- this.environmentCheck = this.validateEnvironment();
1897
+ }
1898
+ function extractCliPlaceholders(template) {
1899
+ const matches = template.matchAll(/\{([A-Z_]+)\}/g);
1900
+ const results = [];
1901
+ for (const match of matches) {
1902
+ if (match[1]) {
1903
+ results.push(match[1]);
1887
1904
  }
1888
- await this.environmentCheck;
1889
1905
  }
1890
- async validateEnvironment() {
1891
- this.resolvedExecutable = await locateExecutable(this.config.executable);
1906
+ return results;
1907
+ }
1908
+ function resolveString(source, env, description, allowLiteral = false) {
1909
+ const value = resolveOptionalString(source, env, description, {
1910
+ allowLiteral,
1911
+ optionalEnv: false
1912
+ });
1913
+ if (value === void 0) {
1914
+ throw new Error(`${description} is required`);
1892
1915
  }
1893
- resolveCwd(workspaceRoot) {
1894
- if (!this.config.cwd) {
1895
- return workspaceRoot;
1896
- }
1897
- return path5.resolve(this.config.cwd);
1916
+ return value;
1917
+ }
1918
+ function resolveOptionalString(source, env, description, options) {
1919
+ if (source === void 0 || source === null) {
1920
+ return void 0;
1898
1921
  }
1899
- buildCodexArgs() {
1900
- const args = ["--ask-for-approval", "never", "exec", "--json", "--color", "never", "--skip-git-repo-check"];
1901
- if (this.config.args && this.config.args.length > 0) {
1902
- args.push(...this.config.args);
1903
- }
1904
- args.push("-");
1905
- return args;
1922
+ if (typeof source !== "string") {
1923
+ throw new Error(`${description} must be a string`);
1906
1924
  }
1907
- async executeCodex(args, cwd, promptContent, signal) {
1908
- try {
1909
- return await this.runCodex({
1910
- executable: this.resolvedExecutable ?? this.config.executable,
1911
- args,
1912
- cwd,
1913
- prompt: promptContent,
1914
- timeoutMs: this.config.timeoutMs,
1915
- env: process.env,
1916
- signal
1917
- });
1918
- } catch (error) {
1919
- const err = error;
1920
- if (err.code === "ENOENT") {
1921
- throw new Error(
1922
- `Codex executable '${this.config.executable}' was not found. Update the target settings.executable or add it to PATH.`
1923
- );
1924
- }
1925
- throw error;
1926
- }
1925
+ const trimmed = source.trim();
1926
+ if (trimmed.length === 0) {
1927
+ return void 0;
1927
1928
  }
1928
- async mirrorInputFiles(inputFiles, workspaceRoot, guidelineOriginals) {
1929
- if (!inputFiles || inputFiles.length === 0) {
1930
- return {
1931
- mirroredInputFiles: void 0,
1932
- guidelineMirrors: /* @__PURE__ */ new Set()
1933
- };
1934
- }
1935
- const filesRoot = path5.join(workspaceRoot, FILES_DIR);
1936
- await mkdir(filesRoot, { recursive: true });
1937
- const mirrored = [];
1938
- const guidelineMirrors = /* @__PURE__ */ new Set();
1939
- const nameCounts = /* @__PURE__ */ new Map();
1940
- for (const inputFile of inputFiles) {
1941
- const absoluteSource = path5.resolve(inputFile);
1942
- const baseName = path5.basename(absoluteSource);
1943
- const count = nameCounts.get(baseName) ?? 0;
1944
- nameCounts.set(baseName, count + 1);
1945
- const finalName = count === 0 ? baseName : `${baseName}.${count}`;
1946
- const destination = path5.join(filesRoot, finalName);
1947
- await copyFile(absoluteSource, destination);
1948
- const resolvedDestination = path5.resolve(destination);
1949
- mirrored.push(resolvedDestination);
1950
- if (guidelineOriginals.has(absoluteSource)) {
1951
- guidelineMirrors.add(resolvedDestination);
1952
- }
1929
+ const envValue = env[trimmed];
1930
+ if (envValue !== void 0) {
1931
+ if (envValue.trim().length === 0) {
1932
+ throw new Error(`Environment variable '${trimmed}' for ${description} is empty`);
1953
1933
  }
1954
- return {
1955
- mirroredInputFiles: mirrored,
1956
- guidelineMirrors
1957
- };
1958
- }
1959
- async createWorkspace() {
1960
- return await mkdtemp(path5.join(tmpdir(), WORKSPACE_PREFIX));
1934
+ return envValue;
1961
1935
  }
1962
- async cleanupWorkspace(workspaceRoot) {
1963
- try {
1964
- await rm(workspaceRoot, { recursive: true, force: true });
1965
- } catch {
1936
+ const allowLiteral = options?.allowLiteral ?? false;
1937
+ const optionalEnv = options?.optionalEnv ?? false;
1938
+ const looksLikeEnv = isLikelyEnvReference(trimmed);
1939
+ if (looksLikeEnv) {
1940
+ if (optionalEnv) {
1941
+ return void 0;
1942
+ }
1943
+ if (!allowLiteral) {
1944
+ throw new Error(`Environment variable '${trimmed}' required for ${description} is not set`);
1966
1945
  }
1967
1946
  }
1968
- };
1969
- async function locateExecutable(candidate) {
1970
- const includesPathSeparator = candidate.includes("/") || candidate.includes("\\");
1971
- if (includesPathSeparator) {
1972
- const resolved = path5.isAbsolute(candidate) ? candidate : path5.resolve(candidate);
1973
- const executablePath = await ensureWindowsExecutableVariant(resolved);
1974
- await access2(executablePath, constants2.F_OK);
1975
- return executablePath;
1947
+ return trimmed;
1948
+ }
1949
+ function resolveOptionalLiteralString(source) {
1950
+ if (source === void 0 || source === null) {
1951
+ return void 0;
1976
1952
  }
1977
- const locator = process.platform === "win32" ? "where" : "which";
1978
- try {
1979
- const { stdout } = await execAsync2(`${locator} ${candidate}`);
1980
- const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
1981
- const preferred = selectExecutableCandidate(lines);
1982
- if (preferred) {
1983
- const executablePath = await ensureWindowsExecutableVariant(preferred);
1984
- await access2(executablePath, constants2.F_OK);
1985
- return executablePath;
1986
- }
1987
- } catch {
1953
+ if (typeof source !== "string") {
1954
+ throw new Error("expected string value");
1988
1955
  }
1989
- throw new Error(`Codex executable '${candidate}' was not found on PATH`);
1956
+ const trimmed = source.trim();
1957
+ return trimmed.length > 0 ? trimmed : void 0;
1990
1958
  }
1991
- function selectExecutableCandidate(candidates) {
1992
- if (candidates.length === 0) {
1959
+ function resolveOptionalNumber(source, description) {
1960
+ if (source === void 0 || source === null || source === "") {
1993
1961
  return void 0;
1994
1962
  }
1995
- if (process.platform !== "win32") {
1996
- return candidates[0];
1963
+ if (typeof source === "number") {
1964
+ return Number.isFinite(source) ? source : void 0;
1997
1965
  }
1998
- const extensions = getWindowsExecutableExtensions();
1999
- for (const ext of extensions) {
2000
- const match = candidates.find((candidate) => candidate.toLowerCase().endsWith(ext));
2001
- if (match) {
2002
- return match;
1966
+ if (typeof source === "string") {
1967
+ const numeric = Number(source);
1968
+ if (Number.isFinite(numeric)) {
1969
+ return numeric;
2003
1970
  }
2004
1971
  }
2005
- return candidates[0];
1972
+ throw new Error(`${description} must be a number`);
2006
1973
  }
2007
- async function ensureWindowsExecutableVariant(candidate) {
2008
- if (process.platform !== "win32") {
2009
- return candidate;
1974
+ function resolveOptionalBoolean(source) {
1975
+ if (source === void 0 || source === null || source === "") {
1976
+ return void 0;
2010
1977
  }
2011
- if (hasExecutableExtension(candidate)) {
2012
- return candidate;
1978
+ if (typeof source === "boolean") {
1979
+ return source;
2013
1980
  }
2014
- const extensions = getWindowsExecutableExtensions();
2015
- for (const ext of extensions) {
2016
- const withExtension = `${candidate}${ext}`;
2017
- try {
2018
- await access2(withExtension, constants2.F_OK);
2019
- return withExtension;
2020
- } catch {
1981
+ if (typeof source === "string") {
1982
+ const lowered = source.trim().toLowerCase();
1983
+ if (lowered === "true" || lowered === "1") {
1984
+ return true;
1985
+ }
1986
+ if (lowered === "false" || lowered === "0") {
1987
+ return false;
2021
1988
  }
2022
1989
  }
2023
- return candidate;
1990
+ throw new Error("expected boolean value");
2024
1991
  }
2025
- function hasExecutableExtension(candidate) {
2026
- const lower = candidate.toLowerCase();
2027
- return getWindowsExecutableExtensions().some((ext) => lower.endsWith(ext));
1992
+ function isLikelyEnvReference(value) {
1993
+ return /^[A-Z0-9_]+$/.test(value);
2028
1994
  }
2029
- var DEFAULT_WINDOWS_EXTENSIONS = [".com", ".exe", ".bat", ".cmd", ".ps1"];
2030
- function getWindowsExecutableExtensions() {
2031
- if (process.platform !== "win32") {
2032
- return [];
1995
+ function resolveOptionalStringArray(source, env, description) {
1996
+ if (source === void 0 || source === null) {
1997
+ return void 0;
2033
1998
  }
2034
- const fromEnv = process.env.PATHEXT?.split(";").map((ext) => ext.trim().toLowerCase()).filter((ext) => ext.length > 0);
2035
- return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_WINDOWS_EXTENSIONS;
2036
- }
2037
- function parseCodexJson(output) {
2038
- const trimmed = output.trim();
2039
- if (trimmed.length === 0) {
2040
- throw new Error("Codex CLI produced no output in --json mode");
1999
+ if (!Array.isArray(source)) {
2000
+ throw new Error(`${description} must be an array of strings`);
2041
2001
  }
2042
- try {
2043
- return JSON.parse(trimmed);
2044
- } catch {
2045
- const lineObjects = parseJsonLines(trimmed);
2046
- if (lineObjects) {
2047
- return lineObjects;
2002
+ if (source.length === 0) {
2003
+ return void 0;
2004
+ }
2005
+ const resolved = [];
2006
+ for (let i = 0; i < source.length; i++) {
2007
+ const item = source[i];
2008
+ if (typeof item !== "string") {
2009
+ throw new Error(`${description}[${i}] must be a string`);
2048
2010
  }
2049
- const lastBrace = trimmed.lastIndexOf("{");
2050
- if (lastBrace >= 0) {
2051
- const candidate = trimmed.slice(lastBrace);
2052
- try {
2053
- return JSON.parse(candidate);
2054
- } catch {
2011
+ const trimmed = item.trim();
2012
+ if (trimmed.length === 0) {
2013
+ throw new Error(`${description}[${i}] cannot be empty`);
2014
+ }
2015
+ const envValue = env[trimmed];
2016
+ if (envValue !== void 0) {
2017
+ if (envValue.trim().length === 0) {
2018
+ throw new Error(`Environment variable '${trimmed}' for ${description}[${i}] is empty`);
2055
2019
  }
2020
+ resolved.push(envValue);
2021
+ } else {
2022
+ resolved.push(trimmed);
2056
2023
  }
2057
- const preview = trimmed.slice(0, 200);
2058
- throw new Error(`Codex CLI emitted invalid JSON: ${preview}${trimmed.length > 200 ? "\u2026" : ""}`);
2059
2024
  }
2025
+ return resolved.length > 0 ? resolved : void 0;
2060
2026
  }
2061
- function extractAssistantText(parsed) {
2062
- if (Array.isArray(parsed)) {
2063
- const text = extractFromEventStream(parsed);
2064
- if (text) {
2065
- return text;
2066
- }
2067
- }
2068
- if (!parsed || typeof parsed !== "object") {
2069
- throw new Error("Codex CLI JSON response did not include an assistant message");
2070
- }
2071
- const record = parsed;
2072
- const eventText = extractFromEvent(record);
2073
- if (eventText) {
2074
- return eventText;
2027
+
2028
+ // src/evaluation/providers/vscode.ts
2029
+ import { readFile as readFile2 } from "node:fs/promises";
2030
+ import path5 from "node:path";
2031
+ import { dispatchAgentSession, dispatchBatchAgent, getSubagentRoot, provisionSubagents } from "subagent";
2032
+ var VSCodeProvider = class {
2033
+ id;
2034
+ kind;
2035
+ targetName;
2036
+ supportsBatch = true;
2037
+ config;
2038
+ constructor(targetName, config, kind) {
2039
+ this.id = `${kind}:${targetName}`;
2040
+ this.kind = kind;
2041
+ this.targetName = targetName;
2042
+ this.config = config;
2075
2043
  }
2076
- const messages = Array.isArray(record.messages) ? record.messages : void 0;
2077
- if (messages) {
2078
- for (let index = messages.length - 1; index >= 0; index -= 1) {
2079
- const entry = messages[index];
2080
- if (!entry || typeof entry !== "object") {
2081
- continue;
2082
- }
2083
- const role = entry.role;
2084
- if (role !== "assistant") {
2085
- continue;
2086
- }
2087
- const content = entry.content;
2088
- const flattened = flattenContent(content);
2089
- if (flattened) {
2090
- return flattened;
2091
- }
2044
+ async invoke(request) {
2045
+ if (request.signal?.aborted) {
2046
+ throw new Error("VS Code provider request was aborted before dispatch");
2047
+ }
2048
+ const inputFiles = normalizeAttachments(request.inputFiles);
2049
+ const promptContent = buildPromptDocument2(request, inputFiles, request.guideline_patterns);
2050
+ const session = await dispatchAgentSession({
2051
+ userQuery: promptContent,
2052
+ extraAttachments: inputFiles,
2053
+ wait: this.config.waitForResponse,
2054
+ dryRun: this.config.dryRun,
2055
+ vscodeCmd: this.config.command,
2056
+ subagentRoot: this.config.subagentRoot,
2057
+ workspaceTemplate: this.config.workspaceTemplate,
2058
+ silent: true
2059
+ });
2060
+ if (session.exitCode !== 0 || !session.responseFile) {
2061
+ const failure = session.error ?? "VS Code subagent did not produce a response";
2062
+ throw new Error(failure);
2063
+ }
2064
+ if (this.config.dryRun) {
2065
+ return {
2066
+ text: "",
2067
+ raw: {
2068
+ session,
2069
+ inputFiles
2070
+ }
2071
+ };
2092
2072
  }
2073
+ const responseText = await readFile2(session.responseFile, "utf8");
2074
+ return {
2075
+ text: responseText,
2076
+ raw: {
2077
+ session,
2078
+ inputFiles
2079
+ }
2080
+ };
2093
2081
  }
2094
- const response = record.response;
2095
- if (response && typeof response === "object") {
2096
- const content = response.content;
2097
- const flattened = flattenContent(content);
2098
- if (flattened) {
2099
- return flattened;
2082
+ async invokeBatch(requests) {
2083
+ if (requests.length === 0) {
2084
+ return [];
2085
+ }
2086
+ const normalizedRequests = requests.map((req) => ({
2087
+ request: req,
2088
+ inputFiles: normalizeAttachments(req.inputFiles)
2089
+ }));
2090
+ const combinedInputFiles = mergeAttachments(
2091
+ normalizedRequests.map(({ inputFiles }) => inputFiles)
2092
+ );
2093
+ const userQueries = normalizedRequests.map(
2094
+ ({ request, inputFiles }) => buildPromptDocument2(request, inputFiles, request.guideline_patterns)
2095
+ );
2096
+ const session = await dispatchBatchAgent({
2097
+ userQueries,
2098
+ extraAttachments: combinedInputFiles,
2099
+ wait: this.config.waitForResponse,
2100
+ dryRun: this.config.dryRun,
2101
+ vscodeCmd: this.config.command,
2102
+ subagentRoot: this.config.subagentRoot,
2103
+ workspaceTemplate: this.config.workspaceTemplate,
2104
+ silent: true
2105
+ });
2106
+ if (session.exitCode !== 0 || !session.responseFiles) {
2107
+ const failure = session.error ?? "VS Code subagent did not produce batch responses";
2108
+ throw new Error(failure);
2109
+ }
2110
+ if (this.config.dryRun) {
2111
+ return normalizedRequests.map(({ inputFiles }) => ({
2112
+ text: "",
2113
+ raw: {
2114
+ session,
2115
+ inputFiles,
2116
+ allInputFiles: combinedInputFiles
2117
+ }
2118
+ }));
2119
+ }
2120
+ if (session.responseFiles.length !== requests.length) {
2121
+ throw new Error(
2122
+ `VS Code batch returned ${session.responseFiles.length} responses for ${requests.length} requests`
2123
+ );
2100
2124
  }
2101
- }
2102
- const output = record.output;
2103
- const flattenedOutput = flattenContent(output);
2104
- if (flattenedOutput) {
2105
- return flattenedOutput;
2106
- }
2107
- throw new Error("Codex CLI JSON response did not include an assistant message");
2108
- }
2109
- function extractFromEventStream(events) {
2110
- for (let index = events.length - 1; index >= 0; index -= 1) {
2111
- const candidate = events[index];
2112
- const text = extractFromEvent(candidate);
2113
- if (text) {
2114
- return text;
2125
+ const responses = [];
2126
+ for (const [index, responseFile] of session.responseFiles.entries()) {
2127
+ const responseText = await readFile2(responseFile, "utf8");
2128
+ responses.push({
2129
+ text: responseText,
2130
+ raw: {
2131
+ session,
2132
+ inputFiles: normalizedRequests[index]?.inputFiles,
2133
+ allInputFiles: combinedInputFiles,
2134
+ responseFile
2135
+ }
2136
+ });
2115
2137
  }
2138
+ return responses;
2116
2139
  }
2117
- return void 0;
2140
+ };
2141
+ function buildPromptDocument2(request, attachments, guidelinePatterns) {
2142
+ const parts = [];
2143
+ const guidelineFiles = collectGuidelineFiles2(attachments, guidelinePatterns);
2144
+ const attachmentFiles = collectAttachmentFiles(attachments);
2145
+ const nonGuidelineAttachments = attachmentFiles.filter(
2146
+ (file) => !guidelineFiles.includes(file)
2147
+ );
2148
+ const prereadBlock = buildMandatoryPrereadBlock2(guidelineFiles, nonGuidelineAttachments);
2149
+ if (prereadBlock.length > 0) {
2150
+ parts.push("\n", prereadBlock);
2151
+ }
2152
+ parts.push("\n[[ ## user_query ## ]]\n", request.prompt.trim());
2153
+ return parts.join("\n").trim();
2118
2154
  }
2119
- function extractFromEvent(event) {
2120
- if (!event || typeof event !== "object") {
2121
- return void 0;
2155
+ function buildMandatoryPrereadBlock2(guidelineFiles, attachmentFiles) {
2156
+ if (guidelineFiles.length === 0 && attachmentFiles.length === 0) {
2157
+ return "";
2122
2158
  }
2123
- const record = event;
2124
- const type = typeof record.type === "string" ? record.type : void 0;
2125
- if (type === JSONL_TYPE_ITEM_COMPLETED) {
2126
- const item = record.item;
2127
- const text = extractFromItem(item);
2128
- if (text) {
2129
- return text;
2130
- }
2159
+ const buildList = (files) => files.map((absolutePath) => {
2160
+ const fileName = path5.basename(absolutePath);
2161
+ const fileUri = pathToFileUri2(absolutePath);
2162
+ return `* [${fileName}](${fileUri})`;
2163
+ });
2164
+ const sections = [];
2165
+ if (guidelineFiles.length > 0) {
2166
+ sections.push(`Read all guideline files:
2167
+ ${buildList(guidelineFiles).join("\n")}.`);
2131
2168
  }
2132
- const output = record.output ?? record.content;
2133
- const flattened = flattenContent(output);
2134
- if (flattened) {
2135
- return flattened;
2169
+ if (attachmentFiles.length > 0) {
2170
+ sections.push(`Read all attachment files:
2171
+ ${buildList(attachmentFiles).join("\n")}.`);
2136
2172
  }
2137
- return void 0;
2173
+ sections.push(
2174
+ "If any file is missing, fail with ERROR: missing-file <filename> and stop.",
2175
+ "Then apply system_instructions on the user query below."
2176
+ );
2177
+ return sections.join("\n");
2138
2178
  }
2139
- function extractFromItem(item) {
2140
- if (!item || typeof item !== "object") {
2141
- return void 0;
2179
+ function collectGuidelineFiles2(attachments, guidelinePatterns) {
2180
+ if (!attachments || attachments.length === 0) {
2181
+ return [];
2142
2182
  }
2143
- const record = item;
2144
- const itemType = typeof record.type === "string" ? record.type : void 0;
2145
- if (itemType === "agent_message" || itemType === "response" || itemType === "output") {
2146
- const text = flattenContent(record.text ?? record.content ?? record.output);
2147
- if (text) {
2148
- return text;
2183
+ const unique = /* @__PURE__ */ new Map();
2184
+ for (const attachment of attachments) {
2185
+ const absolutePath = path5.resolve(attachment);
2186
+ const normalized = absolutePath.split(path5.sep).join("/");
2187
+ if (isGuidelineFile(normalized, guidelinePatterns)) {
2188
+ if (!unique.has(absolutePath)) {
2189
+ unique.set(absolutePath, absolutePath);
2190
+ }
2149
2191
  }
2150
2192
  }
2151
- return void 0;
2193
+ return Array.from(unique.values());
2152
2194
  }
2153
- function flattenContent(value) {
2154
- if (typeof value === "string") {
2155
- return value;
2195
+ function collectAttachmentFiles(attachments) {
2196
+ if (!attachments || attachments.length === 0) {
2197
+ return [];
2156
2198
  }
2157
- if (Array.isArray(value)) {
2158
- const parts = value.map((segment) => {
2159
- if (typeof segment === "string") {
2160
- return segment;
2161
- }
2162
- if (segment && typeof segment === "object" && "text" in segment) {
2163
- const text = segment.text;
2164
- return typeof text === "string" ? text : void 0;
2165
- }
2166
- return void 0;
2167
- }).filter((part) => typeof part === "string" && part.length > 0);
2168
- return parts.length > 0 ? parts.join(" \n") : void 0;
2199
+ const unique = /* @__PURE__ */ new Map();
2200
+ for (const attachment of attachments) {
2201
+ const absolutePath = path5.resolve(attachment);
2202
+ if (!unique.has(absolutePath)) {
2203
+ unique.set(absolutePath, absolutePath);
2204
+ }
2169
2205
  }
2170
- if (value && typeof value === "object" && "text" in value) {
2171
- const text = value.text;
2172
- return typeof text === "string" ? text : void 0;
2206
+ return Array.from(unique.values());
2207
+ }
2208
+ function pathToFileUri2(filePath) {
2209
+ const absolutePath = path5.isAbsolute(filePath) ? filePath : path5.resolve(filePath);
2210
+ const normalizedPath = absolutePath.replace(/\\/g, "/");
2211
+ if (/^[a-zA-Z]:\//.test(normalizedPath)) {
2212
+ return `file:///${normalizedPath}`;
2173
2213
  }
2174
- return void 0;
2214
+ return `file://${normalizedPath}`;
2175
2215
  }
2176
- function parseJsonLines(output) {
2177
- const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
2178
- if (lines.length <= 1) {
2216
+ function normalizeAttachments(attachments) {
2217
+ if (!attachments || attachments.length === 0) {
2179
2218
  return void 0;
2180
2219
  }
2181
- const parsed = [];
2182
- for (const line of lines) {
2183
- try {
2184
- parsed.push(JSON.parse(line));
2185
- } catch {
2186
- return void 0;
2187
- }
2188
- }
2189
- return parsed;
2190
- }
2191
- function pickDetail(stderr, stdout) {
2192
- const errorText = stderr.trim();
2193
- if (errorText.length > 0) {
2194
- return errorText;
2220
+ const deduped = /* @__PURE__ */ new Set();
2221
+ for (const attachment of attachments) {
2222
+ deduped.add(path5.resolve(attachment));
2195
2223
  }
2196
- const stdoutText = stdout.trim();
2197
- return stdoutText.length > 0 ? stdoutText : void 0;
2224
+ return Array.from(deduped);
2198
2225
  }
2199
- function formatTimeoutSuffix2(timeoutMs) {
2200
- if (!timeoutMs || timeoutMs <= 0) {
2201
- return "";
2226
+ function mergeAttachments(all) {
2227
+ const deduped = /* @__PURE__ */ new Set();
2228
+ for (const list of all) {
2229
+ if (!list) continue;
2230
+ for (const inputFile of list) {
2231
+ deduped.add(path5.resolve(inputFile));
2232
+ }
2202
2233
  }
2203
- const seconds = Math.ceil(timeoutMs / 1e3);
2204
- return ` after ${seconds}s`;
2234
+ return deduped.size > 0 ? Array.from(deduped) : void 0;
2205
2235
  }
2206
- async function defaultCodexRunner(options) {
2207
- return await new Promise((resolve, reject) => {
2208
- const child = spawn(options.executable, options.args, {
2209
- cwd: options.cwd,
2210
- env: options.env,
2211
- stdio: ["pipe", "pipe", "pipe"],
2212
- shell: shouldShellExecute(options.executable)
2213
- });
2214
- let stdout = "";
2215
- let stderr = "";
2216
- let timedOut = false;
2217
- const onAbort = () => {
2218
- child.kill("SIGTERM");
2219
- };
2220
- if (options.signal) {
2221
- if (options.signal.aborted) {
2222
- onAbort();
2223
- } else {
2224
- options.signal.addEventListener("abort", onAbort, { once: true });
2225
- }
2226
- }
2227
- let timeoutHandle;
2228
- if (options.timeoutMs && options.timeoutMs > 0) {
2229
- timeoutHandle = setTimeout(() => {
2230
- timedOut = true;
2231
- child.kill("SIGTERM");
2232
- }, options.timeoutMs);
2233
- timeoutHandle.unref?.();
2236
+ async function ensureVSCodeSubagents(options) {
2237
+ const { kind, count, verbose = false } = options;
2238
+ const vscodeCmd = kind === "vscode-insiders" ? "code-insiders" : "code";
2239
+ const subagentRoot = getSubagentRoot(vscodeCmd);
2240
+ try {
2241
+ if (verbose) {
2242
+ console.log(`Provisioning ${count} subagent(s) via: subagent ${vscodeCmd} provision`);
2234
2243
  }
2235
- child.stdout.setEncoding("utf8");
2236
- child.stdout.on("data", (chunk) => {
2237
- stdout += chunk;
2238
- });
2239
- child.stderr.setEncoding("utf8");
2240
- child.stderr.on("data", (chunk) => {
2241
- stderr += chunk;
2244
+ const result = await provisionSubagents({
2245
+ targetRoot: subagentRoot,
2246
+ subagents: count,
2247
+ dryRun: false
2242
2248
  });
2243
- child.stdin.end(options.prompt);
2244
- const cleanup = () => {
2245
- if (timeoutHandle) {
2246
- clearTimeout(timeoutHandle);
2249
+ if (verbose) {
2250
+ if (result.created.length > 0) {
2251
+ console.log(`Created ${result.created.length} new subagent(s)`);
2247
2252
  }
2248
- if (options.signal) {
2249
- options.signal.removeEventListener("abort", onAbort);
2253
+ if (result.skippedExisting.length > 0) {
2254
+ console.log(`Reusing ${result.skippedExisting.length} existing unlocked subagent(s)`);
2250
2255
  }
2256
+ console.log(`
2257
+ total unlocked subagents available: ${result.created.length + result.skippedExisting.length}`);
2258
+ }
2259
+ return {
2260
+ provisioned: true,
2261
+ message: `Provisioned ${count} subagent(s): ${result.created.length} created, ${result.skippedExisting.length} reused`
2262
+ };
2263
+ } catch (error) {
2264
+ const errorMessage = error instanceof Error ? error.message : String(error);
2265
+ if (verbose) {
2266
+ console.warn(`Provisioning failed (continuing anyway): ${errorMessage}`);
2267
+ }
2268
+ return {
2269
+ provisioned: false,
2270
+ message: `Provisioning failed: ${errorMessage}`
2251
2271
  };
2252
- child.on("error", (error) => {
2253
- cleanup();
2254
- reject(error);
2255
- });
2256
- child.on("close", (code) => {
2257
- cleanup();
2258
- resolve({
2259
- stdout,
2260
- stderr,
2261
- exitCode: typeof code === "number" ? code : -1,
2262
- timedOut
2263
- });
2264
- });
2265
- });
2266
- }
2267
- function shouldShellExecute(executable) {
2268
- if (process.platform !== "win32") {
2269
- return false;
2270
2272
  }
2271
- const lower = executable.toLowerCase();
2272
- return lower.endsWith(".cmd") || lower.endsWith(".bat") || lower.endsWith(".ps1");
2273
2273
  }
2274
2274
 
2275
2275
  // src/evaluation/providers/targets-file.ts