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