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