@opensearch-project/agent-health 0.6.0 → 0.7.0
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/cli/dist/index.js +722 -330
- package/dist/assets/index-D-Np_l_T.js +246 -0
- package/dist/assets/index-vZt9QZKf.css +1 -0
- package/dist/index.html +2 -2
- package/docs/CLI.md +138 -1
- package/docs/CONNECTORS.md +1 -1
- package/docs/SDK.md +126 -2
- package/docs/skills/add-connector/SKILL.md +5 -1
- package/lib/dist/lib/agentTrends.d.ts +210 -0
- package/lib/dist/lib/agentTrends.d.ts.map +1 -0
- package/lib/dist/lib/agentTrends.js +360 -0
- package/lib/dist/lib/agentTrends.js.map +1 -0
- package/lib/dist/lib/benchmarkCaseReview.d.ts +114 -0
- package/lib/dist/lib/benchmarkCaseReview.d.ts.map +1 -0
- package/lib/dist/lib/benchmarkCaseReview.js +177 -0
- package/lib/dist/lib/benchmarkCaseReview.js.map +1 -0
- package/lib/dist/lib/benchmarkRunsTable.d.ts +109 -0
- package/lib/dist/lib/benchmarkRunsTable.d.ts.map +1 -0
- package/lib/dist/lib/benchmarkRunsTable.js +212 -0
- package/lib/dist/lib/benchmarkRunsTable.js.map +1 -0
- package/lib/dist/lib/comparisonInsights.d.ts +49 -2
- package/lib/dist/lib/comparisonInsights.d.ts.map +1 -1
- package/lib/dist/lib/comparisonInsights.js +65 -7
- package/lib/dist/lib/comparisonInsights.js.map +1 -1
- package/lib/dist/lib/config/loader.d.ts.map +1 -1
- package/lib/dist/lib/config/loader.js +11 -1
- package/lib/dist/lib/config/loader.js.map +1 -1
- package/lib/dist/lib/dashboardMetrics.d.ts +11 -2
- package/lib/dist/lib/dashboardMetrics.d.ts.map +1 -1
- package/lib/dist/lib/dashboardMetrics.js +38 -3
- package/lib/dist/lib/dashboardMetrics.js.map +1 -1
- package/lib/dist/lib/evaluationRerun.d.ts +39 -0
- package/lib/dist/lib/evaluationRerun.d.ts.map +1 -1
- package/lib/dist/lib/evaluationRerun.js +49 -0
- package/lib/dist/lib/evaluationRerun.js.map +1 -1
- package/lib/dist/lib/judgeFailureSummary.d.ts +66 -0
- package/lib/dist/lib/judgeFailureSummary.d.ts.map +1 -0
- package/lib/dist/lib/judgeFailureSummary.js +68 -0
- package/lib/dist/lib/judgeFailureSummary.js.map +1 -0
- package/lib/dist/lib/judgeStrategies.d.ts +108 -0
- package/lib/dist/lib/judgeStrategies.d.ts.map +1 -0
- package/lib/dist/lib/judgeStrategies.js +135 -0
- package/lib/dist/lib/judgeStrategies.js.map +1 -0
- package/lib/dist/lib/matchers/expect.d.ts +21 -1
- package/lib/dist/lib/matchers/expect.d.ts.map +1 -1
- package/lib/dist/lib/matchers/expect.js +51 -0
- package/lib/dist/lib/matchers/expect.js.map +1 -1
- package/lib/dist/lib/matchers/judgeAccessor.d.ts +4 -0
- package/lib/dist/lib/matchers/judgeAccessor.d.ts.map +1 -1
- package/lib/dist/lib/matchers/judgeAccessor.js +13 -2
- package/lib/dist/lib/matchers/judgeAccessor.js.map +1 -1
- package/lib/dist/lib/matchers/judgeReasoningParse.d.ts +49 -0
- package/lib/dist/lib/matchers/judgeReasoningParse.d.ts.map +1 -0
- package/lib/dist/lib/matchers/judgeReasoningParse.js +128 -0
- package/lib/dist/lib/matchers/judgeReasoningParse.js.map +1 -0
- package/lib/dist/lib/matchers/types.d.ts +25 -0
- package/lib/dist/lib/matchers/types.d.ts.map +1 -1
- package/lib/dist/lib/resolveCanonicalRun.d.ts +23 -0
- package/lib/dist/lib/resolveCanonicalRun.d.ts.map +1 -0
- package/lib/dist/lib/resolveCanonicalRun.js +26 -0
- package/lib/dist/lib/resolveCanonicalRun.js.map +1 -0
- package/lib/dist/lib/runActions.d.ts +120 -0
- package/lib/dist/lib/runActions.d.ts.map +1 -0
- package/lib/dist/lib/runActions.js +130 -0
- package/lib/dist/lib/runActions.js.map +1 -0
- package/lib/dist/lib/runInsights.d.ts +86 -0
- package/lib/dist/lib/runInsights.d.ts.map +1 -0
- package/lib/dist/lib/runInsights.js +185 -0
- package/lib/dist/lib/runInsights.js.map +1 -0
- package/lib/dist/lib/runName.d.ts +29 -0
- package/lib/dist/lib/runName.d.ts.map +1 -0
- package/lib/dist/lib/runName.js +38 -0
- package/lib/dist/lib/runName.js.map +1 -0
- package/lib/dist/lib/runReportPath.d.ts +16 -0
- package/lib/dist/lib/runReportPath.d.ts.map +1 -0
- package/lib/dist/lib/runReportPath.js +22 -0
- package/lib/dist/lib/runReportPath.js.map +1 -0
- package/lib/dist/lib/runSort.d.ts +27 -0
- package/lib/dist/lib/runSort.d.ts.map +1 -0
- package/lib/dist/lib/runSort.js +31 -0
- package/lib/dist/lib/runSort.js.map +1 -0
- package/lib/dist/lib/runStats.d.ts +86 -6
- package/lib/dist/lib/runStats.d.ts.map +1 -1
- package/lib/dist/lib/runStats.js +170 -19
- package/lib/dist/lib/runStats.js.map +1 -1
- package/lib/dist/lib/testCases/define.d.ts.map +1 -1
- package/lib/dist/lib/testCases/define.js +93 -46
- package/lib/dist/lib/testCases/define.js.map +1 -1
- package/lib/dist/lib/testCases/judge.d.ts.map +1 -1
- package/lib/dist/lib/testCases/judge.js +22 -4
- package/lib/dist/lib/testCases/judge.js.map +1 -1
- package/lib/dist/lib/testCases/loader.d.ts +24 -0
- package/lib/dist/lib/testCases/loader.d.ts.map +1 -1
- package/lib/dist/lib/testCases/loader.js +253 -36
- package/lib/dist/lib/testCases/loader.js.map +1 -1
- package/lib/dist/lib/trajectoryStepDisplay.d.ts +25 -0
- package/lib/dist/lib/trajectoryStepDisplay.d.ts.map +1 -0
- package/lib/dist/lib/trajectoryStepDisplay.js +42 -0
- package/lib/dist/lib/trajectoryStepDisplay.js.map +1 -0
- package/lib/dist/lib/utils.d.ts +19 -0
- package/lib/dist/lib/utils.d.ts.map +1 -1
- package/lib/dist/lib/utils.js +27 -0
- package/lib/dist/lib/utils.js.map +1 -1
- package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.d.ts +68 -32
- package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.d.ts.map +1 -1
- package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.js +148 -125
- package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.js.map +1 -1
- package/lib/dist/services/connectors/kiro/KiroConnector.d.ts +21 -13
- package/lib/dist/services/connectors/kiro/KiroConnector.d.ts.map +1 -1
- package/lib/dist/services/connectors/kiro/KiroConnector.js +22 -25
- package/lib/dist/services/connectors/kiro/KiroConnector.js.map +1 -1
- package/lib/dist/services/connectors/pi/PiConnector.d.ts +49 -10
- package/lib/dist/services/connectors/pi/PiConnector.d.ts.map +1 -1
- package/lib/dist/services/connectors/pi/PiConnector.js +102 -86
- package/lib/dist/services/connectors/pi/PiConnector.js.map +1 -1
- package/lib/dist/services/connectors/subprocess/SubprocessConnector.d.ts +59 -14
- package/lib/dist/services/connectors/subprocess/SubprocessConnector.d.ts.map +1 -1
- package/lib/dist/services/connectors/subprocess/SubprocessConnector.js +87 -61
- package/lib/dist/services/connectors/subprocess/SubprocessConnector.js.map +1 -1
- package/lib/dist/services/evaluation/bedrockJudge.d.ts +11 -0
- package/lib/dist/services/evaluation/bedrockJudge.d.ts.map +1 -1
- package/lib/dist/services/evaluation/bedrockJudge.js +2 -0
- package/lib/dist/services/evaluation/bedrockJudge.js.map +1 -1
- package/lib/dist/services/evaluation/index.d.ts +18 -1
- package/lib/dist/services/evaluation/index.d.ts.map +1 -1
- package/lib/dist/services/evaluation/index.js +156 -19
- package/lib/dist/services/evaluation/index.js.map +1 -1
- package/lib/dist/services/metrics.d.ts +55 -0
- package/lib/dist/services/metrics.d.ts.map +1 -0
- package/lib/dist/services/metrics.js +89 -0
- package/lib/dist/services/metrics.js.map +1 -0
- package/lib/dist/services/storage/asyncBenchmarkStorage.d.ts.map +1 -1
- package/lib/dist/services/storage/asyncBenchmarkStorage.js +16 -0
- package/lib/dist/services/storage/asyncBenchmarkStorage.js.map +1 -1
- package/lib/dist/services/storage/asyncRunStorage.d.ts +11 -0
- package/lib/dist/services/storage/asyncRunStorage.d.ts.map +1 -1
- package/lib/dist/services/storage/asyncRunStorage.js +49 -2
- package/lib/dist/services/storage/asyncRunStorage.js.map +1 -1
- package/lib/dist/services/storage/asyncTestCaseStorage.d.ts +1 -1
- package/lib/dist/services/storage/asyncTestCaseStorage.d.ts.map +1 -1
- package/lib/dist/services/storage/asyncTestCaseStorage.js +2 -1
- package/lib/dist/services/storage/asyncTestCaseStorage.js.map +1 -1
- package/lib/dist/services/storage/opensearchClient.d.ts +12 -0
- package/lib/dist/services/storage/opensearchClient.d.ts.map +1 -1
- package/lib/dist/services/storage/opensearchClient.js.map +1 -1
- package/lib/dist/services/traces/browserRecovery.d.ts.map +1 -1
- package/lib/dist/services/traces/browserRecovery.js +3 -0
- package/lib/dist/services/traces/browserRecovery.js.map +1 -1
- package/lib/dist/services/traces/index.d.ts +8 -1
- package/lib/dist/services/traces/index.d.ts.map +1 -1
- package/lib/dist/services/traces/index.js +33 -12
- package/lib/dist/services/traces/index.js.map +1 -1
- package/lib/dist/services/traces/judgeAgentsHints.d.ts +98 -3
- package/lib/dist/services/traces/judgeAgentsHints.d.ts.map +1 -1
- package/lib/dist/services/traces/judgeAgentsHints.js +144 -3
- package/lib/dist/services/traces/judgeAgentsHints.js.map +1 -1
- package/lib/dist/services/traces/spansToTrajectory.js +4 -4
- package/lib/dist/services/traces/spansToTrajectory.js.map +1 -1
- package/lib/dist/services/traces/tracePoller.d.ts.map +1 -1
- package/lib/dist/services/traces/tracePoller.js +17 -20
- package/lib/dist/services/traces/tracePoller.js.map +1 -1
- package/lib/dist/services/traces/trajectoryMerge.d.ts +79 -0
- package/lib/dist/services/traces/trajectoryMerge.d.ts.map +1 -0
- package/lib/dist/services/traces/trajectoryMerge.js +109 -0
- package/lib/dist/services/traces/trajectoryMerge.js.map +1 -0
- package/lib/dist/types/index.d.ts +195 -2
- package/lib/dist/types/index.d.ts.map +1 -1
- package/lib/dist/types/index.js +24 -0
- package/lib/dist/types/index.js.map +1 -1
- package/package.json +6 -5
- package/server/dist/app.js +3398 -926
- package/server/dist/index.js +3401 -929
- package/dist/assets/index-BfxtxmKc.css +0 -1
- package/dist/assets/index-CrjAfDHu.js +0 -243
package/cli/dist/index.js
CHANGED
|
@@ -560,7 +560,19 @@ async function loadUserConfig(configPath) {
|
|
|
560
560
|
return module2.default ?? module2;
|
|
561
561
|
} catch (error) {
|
|
562
562
|
const message = error instanceof Error ? error.message : String(error);
|
|
563
|
-
|
|
563
|
+
let helpText = "";
|
|
564
|
+
if (message.includes("ERR_MODULE_NOT_FOUND") || message.includes("Cannot find")) {
|
|
565
|
+
helpText = `
|
|
566
|
+
|
|
567
|
+
Ensure your cwd package.json has: {"type":"module"}`;
|
|
568
|
+
}
|
|
569
|
+
if (message.includes("tsconfig")) {
|
|
570
|
+
helpText = `
|
|
571
|
+
|
|
572
|
+
Set TSX_TSCONFIG_PATH to your tsconfig.json location, e.g.:
|
|
573
|
+
TSX_TSCONFIG_PATH=/path/to/agent-health/tsconfig.json npx @opensearch-project/agent-health`;
|
|
574
|
+
}
|
|
575
|
+
throw new Error(`Failed to load config file ${configPath}: ${message}${helpText}`);
|
|
564
576
|
}
|
|
565
577
|
}
|
|
566
578
|
async function loadConfig(cwd = process.cwd(), force = false) {
|
|
@@ -2308,9 +2320,6 @@ var init_SubprocessConnector = __esm({
|
|
|
2308
2320
|
* `serviceName` to point at their OpenSearch service.name for Strategy C.
|
|
2309
2321
|
*/
|
|
2310
2322
|
this.traceContext = { propagateEnv: true };
|
|
2311
|
-
/** Buffer of clean stdout lines accumulated during streaming.
|
|
2312
|
-
* Used by onBeforeStreamEnd() to emit a consolidated `response` step. */
|
|
2313
|
-
this.streamBuffer = [];
|
|
2314
2323
|
this.config = { ...DEFAULT_SUBPROCESS_CONFIG, ...config };
|
|
2315
2324
|
}
|
|
2316
2325
|
/**
|
|
@@ -2332,31 +2341,24 @@ Question: ${prompt}`;
|
|
|
2332
2341
|
*/
|
|
2333
2342
|
async execute(endpoint, request, auth, onProgress, onRawEvent) {
|
|
2334
2343
|
this.debug("========== execute() STARTED ==========");
|
|
2335
|
-
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
if (cfgOverride.env !== void 0) this.config.env = { ...this.config.env || {}, ...cfgOverride.env };
|
|
2340
|
-
if (cfgOverride.inputMode !== void 0) this.config.inputMode = cfgOverride.inputMode;
|
|
2341
|
-
if (cfgOverride.outputParser !== void 0) this.config.outputParser = cfgOverride.outputParser;
|
|
2342
|
-
if (cfgOverride.timeout !== void 0) this.config.timeout = cfgOverride.timeout;
|
|
2343
|
-
if (cfgOverride.workingDir !== void 0) this.config.workingDir = cfgOverride.workingDir;
|
|
2344
|
-
const command = endpoint || this.config.command;
|
|
2345
|
-
const args = this.config.args || [];
|
|
2344
|
+
const state = this.createExecutionState();
|
|
2345
|
+
const config = this.resolveExecutionConfig(request);
|
|
2346
|
+
const command = endpoint || config.command;
|
|
2347
|
+
const args = config.args || [];
|
|
2346
2348
|
const input = request.payload || this.buildPayload(request);
|
|
2347
2349
|
const runId = `subprocess-${Date.now()}`;
|
|
2348
2350
|
this.debug("Command:", command);
|
|
2349
2351
|
this.debug("Args:", args);
|
|
2350
|
-
this.debug("Input mode:",
|
|
2351
|
-
this.debug("Output parser:",
|
|
2352
|
-
this.debug("Timeout:",
|
|
2352
|
+
this.debug("Input mode:", config.inputMode);
|
|
2353
|
+
this.debug("Output parser:", config.outputParser);
|
|
2354
|
+
this.debug("Timeout:", config.timeout);
|
|
2353
2355
|
this.debug("Input (first 500 chars):", input.substring(0, 500));
|
|
2354
|
-
this.debug("Working dir:",
|
|
2356
|
+
this.debug("Working dir:", config.workingDir || process.cwd());
|
|
2355
2357
|
this.debug("Run ID:", runId);
|
|
2356
2358
|
const env = {
|
|
2357
2359
|
...process.env,
|
|
2358
2360
|
...this.buildAuthEnv(auth),
|
|
2359
|
-
...
|
|
2361
|
+
...config.env,
|
|
2360
2362
|
// W3C trace context (Strategy A): TRACEPARENT/TRACESTATE from the active
|
|
2361
2363
|
// eval `test_case` span. Agents whose OTel SDK honors TRACEPARENT (pi —
|
|
2362
2364
|
// verified) emit their spans under the eval span's traceId, giving the
|
|
@@ -2373,12 +2375,12 @@ Question: ${prompt}`;
|
|
|
2373
2375
|
let stdout = "";
|
|
2374
2376
|
let stderr = "";
|
|
2375
2377
|
let settled = false;
|
|
2376
|
-
const finalArgs =
|
|
2378
|
+
const finalArgs = config.inputMode === "arg" ? [...args, input] : args;
|
|
2377
2379
|
this.debug("Spawning process...");
|
|
2378
2380
|
this.debug("Full command:", command, finalArgs.join(" "));
|
|
2379
2381
|
const proc = spawn(command, finalArgs, {
|
|
2380
2382
|
env,
|
|
2381
|
-
cwd:
|
|
2383
|
+
cwd: config.workingDir,
|
|
2382
2384
|
shell: false
|
|
2383
2385
|
});
|
|
2384
2386
|
this.debug("Process spawned, PID:", proc.pid);
|
|
@@ -2387,9 +2389,9 @@ Question: ${prompt}`;
|
|
|
2387
2389
|
settled = true;
|
|
2388
2390
|
this.debug("TIMEOUT reached, killing process");
|
|
2389
2391
|
proc.kill("SIGTERM");
|
|
2390
|
-
reject(new Error(`Subprocess timed out after ${
|
|
2391
|
-
},
|
|
2392
|
-
if (
|
|
2392
|
+
reject(new Error(`Subprocess timed out after ${config.timeout}ms`));
|
|
2393
|
+
}, config.timeout);
|
|
2394
|
+
if (config.inputMode === "stdin") {
|
|
2393
2395
|
this.debug("Writing input to stdin...");
|
|
2394
2396
|
proc.stdin.write(input);
|
|
2395
2397
|
proc.stdin.end();
|
|
@@ -2402,8 +2404,8 @@ Question: ${prompt}`;
|
|
|
2402
2404
|
stdout += chunk;
|
|
2403
2405
|
rawOutput.push({ type: "stdout", data: chunk, timestamp: Date.now() });
|
|
2404
2406
|
onRawEvent?.({ type: "stdout", data: chunk });
|
|
2405
|
-
if (
|
|
2406
|
-
this.parseStreamingOutput(chunk, trajectory, onProgress);
|
|
2407
|
+
if (config.outputParser === "streaming") {
|
|
2408
|
+
this.parseStreamingOutput(chunk, trajectory, onProgress, state);
|
|
2407
2409
|
}
|
|
2408
2410
|
});
|
|
2409
2411
|
proc.stderr.on("data", (data) => {
|
|
@@ -2413,8 +2415,8 @@ Question: ${prompt}`;
|
|
|
2413
2415
|
stderr += chunk;
|
|
2414
2416
|
rawOutput.push({ type: "stderr", data: chunk, timestamp: Date.now() });
|
|
2415
2417
|
onRawEvent?.({ type: "stderr", data: chunk });
|
|
2416
|
-
if (
|
|
2417
|
-
this.parseStderrChunk(chunk, trajectory, onProgress);
|
|
2418
|
+
if (config.outputParser === "streaming") {
|
|
2419
|
+
this.parseStderrChunk(chunk, trajectory, onProgress, state);
|
|
2418
2420
|
}
|
|
2419
2421
|
});
|
|
2420
2422
|
proc.on("close", (code2, signal) => {
|
|
@@ -2427,8 +2429,8 @@ Question: ${prompt}`;
|
|
|
2427
2429
|
this.error(`Process exited with code ${code2}`);
|
|
2428
2430
|
this.error("stderr:", stderr);
|
|
2429
2431
|
}
|
|
2430
|
-
if (
|
|
2431
|
-
this.onBeforeStreamEnd(trajectory, onProgress);
|
|
2432
|
+
if (config.outputParser === "streaming") {
|
|
2433
|
+
this.onBeforeStreamEnd(trajectory, onProgress, state);
|
|
2432
2434
|
if (code2 !== 0 && trajectory.length === 0) {
|
|
2433
2435
|
const errorContent = stderr.trim() ? `Error: Process exited with code ${code2}. ${stderr.trim()}` : `Error: Process exited with code ${code2}`;
|
|
2434
2436
|
const errorStep = this.createStep("tool_result", errorContent, {
|
|
@@ -2438,8 +2440,8 @@ Question: ${prompt}`;
|
|
|
2438
2440
|
onProgress?.(errorStep);
|
|
2439
2441
|
}
|
|
2440
2442
|
}
|
|
2441
|
-
const finalTrajectory =
|
|
2442
|
-
if (
|
|
2443
|
+
const finalTrajectory = config.outputParser === "streaming" ? trajectory : this.parseResponse({ stdout, stderr, exitCode: code2 }, config);
|
|
2444
|
+
if (config.outputParser !== "streaming") {
|
|
2443
2445
|
finalTrajectory.forEach((step) => onProgress?.(step));
|
|
2444
2446
|
}
|
|
2445
2447
|
this.debug("Resolving with trajectory of", finalTrajectory.length, "steps");
|
|
@@ -2452,7 +2454,7 @@ Question: ${prompt}`;
|
|
|
2452
2454
|
args: finalArgs,
|
|
2453
2455
|
exitCode: code2,
|
|
2454
2456
|
stderr: stderr || void 0,
|
|
2455
|
-
...this.extraResultMetadata()
|
|
2457
|
+
...this.extraResultMetadata(state)
|
|
2456
2458
|
}
|
|
2457
2459
|
});
|
|
2458
2460
|
});
|
|
@@ -2477,22 +2479,55 @@ Question: ${prompt}`;
|
|
|
2477
2479
|
});
|
|
2478
2480
|
this.debug("========== execute() COMPLETED ==========");
|
|
2479
2481
|
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Create the per-invocation parse state for one `execute()` call.
|
|
2484
|
+
* Subclasses that buffer anything while streaming (partial NDJSON lines,
|
|
2485
|
+
* pending tool names, captured ids) override this to add their fields —
|
|
2486
|
+
* and must NOT keep that data on the instance.
|
|
2487
|
+
*/
|
|
2488
|
+
createExecutionState() {
|
|
2489
|
+
return { streamBuffer: [] };
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Resolve the effective config for one `execute()` call: constructor
|
|
2493
|
+
* defaults overlaid with the request's `connectorConfig` (so any agent
|
|
2494
|
+
* registered with `connectorType: 'subprocess'` can specify command / args /
|
|
2495
|
+
* inputMode / outputParser / timeout / workingDir / env per agent). Pure —
|
|
2496
|
+
* MUST NOT write to `this.config`. Subclasses override to translate their
|
|
2497
|
+
* own `connectorConfig` shape (Claude Code flags, Pi package/model) into the
|
|
2498
|
+
* base fields, typically by calling `super.resolveExecutionConfig()` on a
|
|
2499
|
+
* request whose `connectorConfig` has been rewritten into base shape.
|
|
2500
|
+
*/
|
|
2501
|
+
resolveExecutionConfig(request) {
|
|
2502
|
+
const o = request.connectorConfig || {};
|
|
2503
|
+
return {
|
|
2504
|
+
...this.config,
|
|
2505
|
+
...o.command !== void 0 ? { command: o.command } : {},
|
|
2506
|
+
...o.args !== void 0 ? { args: [...o.args] } : { args: [...this.config.args || []] },
|
|
2507
|
+
env: { ...this.config.env || {}, ...o.env || {} },
|
|
2508
|
+
...o.inputMode !== void 0 ? { inputMode: o.inputMode } : {},
|
|
2509
|
+
...o.outputParser !== void 0 ? { outputParser: o.outputParser } : {},
|
|
2510
|
+
...o.timeout !== void 0 ? { timeout: o.timeout } : {},
|
|
2511
|
+
...o.workingDir !== void 0 ? { workingDir: o.workingDir } : {}
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2480
2514
|
/**
|
|
2481
2515
|
* Parse a stderr chunk in streaming mode. Default is a no-op.
|
|
2482
2516
|
*
|
|
2483
2517
|
* Override in subclasses for CLIs that carry tool-event markers on stderr.
|
|
2484
2518
|
* Implementations should buffer partial lines (chunks rarely align with
|
|
2485
|
-
* line boundaries) and emit steps via `onProgress` AND push them
|
|
2486
|
-
* `trajectory` so they appear in the final response.
|
|
2519
|
+
* line boundaries) in `state` and emit steps via `onProgress` AND push them
|
|
2520
|
+
* onto `trajectory` so they appear in the final response.
|
|
2487
2521
|
*/
|
|
2488
|
-
parseStderrChunk(_chunk, _trajectory, _onProgress) {
|
|
2522
|
+
parseStderrChunk(_chunk, _trajectory, _onProgress, _state) {
|
|
2489
2523
|
}
|
|
2490
2524
|
/**
|
|
2491
2525
|
* Subclass hook: extra protocol-specific fields to merge into the
|
|
2492
|
-
* connector result `metadata
|
|
2493
|
-
*
|
|
2526
|
+
* connector result `metadata`, read from the per-invocation `state` (Claude
|
|
2527
|
+
* Code surfaces its captured `sessionId` for Strategy D trace correlation).
|
|
2528
|
+
* Default: none.
|
|
2494
2529
|
*/
|
|
2495
|
-
extraResultMetadata() {
|
|
2530
|
+
extraResultMetadata(_state) {
|
|
2496
2531
|
return {};
|
|
2497
2532
|
}
|
|
2498
2533
|
/**
|
|
@@ -2506,7 +2541,8 @@ Question: ${prompt}`;
|
|
|
2506
2541
|
* `response` step containing the full coherent answer (good for the
|
|
2507
2542
|
* judge) — without losing the live stream.
|
|
2508
2543
|
*/
|
|
2509
|
-
parseStreamingOutput(chunk, trajectory, onProgress) {
|
|
2544
|
+
parseStreamingOutput(chunk, trajectory, onProgress, state) {
|
|
2545
|
+
const buf = state?.streamBuffer ?? [];
|
|
2510
2546
|
const stripped = chunk.replace(/\x1b\][^\x07\x1b]*(\x07|\x1b\\)/g, "").replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "").replace(/\x1b[=>NOP\\]/g, "").replace(/\r/g, "\n");
|
|
2511
2547
|
const lines = stripped.split("\n");
|
|
2512
2548
|
for (const raw of lines) {
|
|
@@ -2514,7 +2550,7 @@ Question: ${prompt}`;
|
|
|
2514
2550
|
if (!line) continue;
|
|
2515
2551
|
if (/^[⠁-⣿\s]+$/.test(line)) continue;
|
|
2516
2552
|
if (line.length < 2 && !/[A-Za-z0-9]/.test(line)) continue;
|
|
2517
|
-
|
|
2553
|
+
buf.push(line);
|
|
2518
2554
|
const step = this.createStep("assistant", line);
|
|
2519
2555
|
trajectory.push(step);
|
|
2520
2556
|
onProgress?.(step);
|
|
@@ -2525,10 +2561,10 @@ Question: ${prompt}`;
|
|
|
2525
2561
|
* clean output. Streaming gave the user real-time visibility; this final
|
|
2526
2562
|
* step gives the judge a single coherent answer to grade against.
|
|
2527
2563
|
*/
|
|
2528
|
-
onBeforeStreamEnd(trajectory, onProgress) {
|
|
2529
|
-
if (
|
|
2530
|
-
const finalText =
|
|
2531
|
-
|
|
2564
|
+
onBeforeStreamEnd(trajectory, onProgress, state) {
|
|
2565
|
+
if (state?.streamBuffer?.length) {
|
|
2566
|
+
const finalText = state.streamBuffer.join("\n").trim();
|
|
2567
|
+
state.streamBuffer = [];
|
|
2532
2568
|
if (finalText) {
|
|
2533
2569
|
const step = this.createStep("response", finalText);
|
|
2534
2570
|
trajectory.push(step);
|
|
@@ -2539,9 +2575,9 @@ Question: ${prompt}`;
|
|
|
2539
2575
|
/**
|
|
2540
2576
|
* Parse final subprocess output
|
|
2541
2577
|
*/
|
|
2542
|
-
parseResponse(data) {
|
|
2578
|
+
parseResponse(data, config = this.config) {
|
|
2543
2579
|
const steps = [];
|
|
2544
|
-
if (
|
|
2580
|
+
if (config.outputParser === "json") {
|
|
2545
2581
|
try {
|
|
2546
2582
|
const parsed = JSON.parse(data.stdout);
|
|
2547
2583
|
return this.parseJsonOutput(parsed);
|
|
@@ -2599,6 +2635,24 @@ Question: ${prompt}`;
|
|
|
2599
2635
|
});
|
|
2600
2636
|
|
|
2601
2637
|
// services/connectors/claude-code/ClaudeCodeConnector.ts
|
|
2638
|
+
function toolResultText(block, event) {
|
|
2639
|
+
const c = block?.content;
|
|
2640
|
+
if (typeof c === "string") return c;
|
|
2641
|
+
if (Array.isArray(c)) {
|
|
2642
|
+
return c.map(
|
|
2643
|
+
(part) => part && part.type === "text" && typeof part.text === "string" ? part.text : JSON.stringify(part)
|
|
2644
|
+
).join("\n");
|
|
2645
|
+
}
|
|
2646
|
+
if (c != null) return JSON.stringify(c);
|
|
2647
|
+
const mirror = event?.tool_use_result;
|
|
2648
|
+
if (mirror === void 0 || mirror === null) return "";
|
|
2649
|
+
return typeof mirror === "string" ? mirror : JSON.stringify(mirror);
|
|
2650
|
+
}
|
|
2651
|
+
function boundToolOutput(text, max = CLAUDE_CODE_MAX_TOOL_OUTPUT_CHARS) {
|
|
2652
|
+
if (text.length <= max) return text;
|
|
2653
|
+
return `${text.slice(0, max)}
|
|
2654
|
+
\u2026 [tool output truncated: showing ${max} of ${text.length} chars]`;
|
|
2655
|
+
}
|
|
2602
2656
|
function createBedrockClaudeCodeConnector() {
|
|
2603
2657
|
const env = {
|
|
2604
2658
|
AWS_PROFILE: process.env.AWS_PROFILE || "Bedrock",
|
|
@@ -2626,11 +2680,12 @@ function createBedrockClaudeCodeConnector() {
|
|
|
2626
2680
|
}
|
|
2627
2681
|
return new ClaudeCodeConnector({ env });
|
|
2628
2682
|
}
|
|
2629
|
-
var CLAUDE_CODE_DEFAULT_CONFIG, ClaudeCodeConnector, claudeCodeConnector;
|
|
2683
|
+
var CLAUDE_CODE_MAX_TOOL_OUTPUT_CHARS, CLAUDE_CODE_DEFAULT_CONFIG, ClaudeCodeConnector, claudeCodeConnector;
|
|
2630
2684
|
var init_ClaudeCodeConnector = __esm({
|
|
2631
2685
|
"services/connectors/claude-code/ClaudeCodeConnector.ts"() {
|
|
2632
2686
|
init_types();
|
|
2633
2687
|
init_SubprocessConnector();
|
|
2688
|
+
CLAUDE_CODE_MAX_TOOL_OUTPUT_CHARS = 32 * 1024;
|
|
2634
2689
|
CLAUDE_CODE_DEFAULT_CONFIG = {
|
|
2635
2690
|
command: "claude",
|
|
2636
2691
|
args: ["--print", "--verbose", "--output-format", "stream-json"],
|
|
@@ -2653,10 +2708,6 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2653
2708
|
this.type = "claude-code";
|
|
2654
2709
|
this.name = "Claude Code CLI";
|
|
2655
2710
|
this.traceContext = { propagateEnv: true, serviceName: "claude-code-agent" };
|
|
2656
|
-
this.outputBuffer = "";
|
|
2657
|
-
this.thinkingBuffer = "";
|
|
2658
|
-
this.textBuffer = "";
|
|
2659
|
-
this.isInThinking = false;
|
|
2660
2711
|
}
|
|
2661
2712
|
/**
|
|
2662
2713
|
* Build prompt for Claude Code
|
|
@@ -2676,20 +2727,29 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2676
2727
|
parts.push(request.testCase.initialPrompt);
|
|
2677
2728
|
return parts.join("\n");
|
|
2678
2729
|
}
|
|
2730
|
+
createExecutionState() {
|
|
2731
|
+
return {
|
|
2732
|
+
...super.createExecutionState(),
|
|
2733
|
+
outputBuffer: "",
|
|
2734
|
+
thinkingBuffer: "",
|
|
2735
|
+
textBuffer: "",
|
|
2736
|
+
pendingToolUses: /* @__PURE__ */ new Map()
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2679
2739
|
/**
|
|
2680
2740
|
* Parse Claude Code streaming output (stream-json format)
|
|
2681
2741
|
* Each line is a JSON object with type and content
|
|
2682
2742
|
*/
|
|
2683
|
-
parseStreamingOutput(chunk, trajectory, onProgress) {
|
|
2684
|
-
|
|
2685
|
-
const lines =
|
|
2686
|
-
|
|
2743
|
+
parseStreamingOutput(chunk, trajectory, onProgress, state) {
|
|
2744
|
+
state.outputBuffer += chunk;
|
|
2745
|
+
const lines = state.outputBuffer.split("\n");
|
|
2746
|
+
state.outputBuffer = lines.pop() || "";
|
|
2687
2747
|
for (const line of lines) {
|
|
2688
2748
|
const trimmed = line.trim();
|
|
2689
2749
|
if (!trimmed) continue;
|
|
2690
2750
|
try {
|
|
2691
2751
|
const event = JSON.parse(trimmed);
|
|
2692
|
-
const steps = this.parseJsonEvent(event);
|
|
2752
|
+
const steps = this.parseJsonEvent(event, state);
|
|
2693
2753
|
for (const step of steps) {
|
|
2694
2754
|
trajectory.push(step);
|
|
2695
2755
|
onProgress?.(step);
|
|
@@ -2706,10 +2766,10 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2706
2766
|
/**
|
|
2707
2767
|
* Parse a single JSON event from stream-json output
|
|
2708
2768
|
*/
|
|
2709
|
-
parseJsonEvent(event) {
|
|
2769
|
+
parseJsonEvent(event, state) {
|
|
2710
2770
|
const steps = [];
|
|
2711
2771
|
if (typeof event.session_id === "string" && event.session_id) {
|
|
2712
|
-
|
|
2772
|
+
state.sessionId = event.session_id;
|
|
2713
2773
|
}
|
|
2714
2774
|
if (event.type === "assistant" && event.message?.content) {
|
|
2715
2775
|
for (const block of event.message.content) {
|
|
@@ -2718,6 +2778,9 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2718
2778
|
} else if (block.type === "text" && block.text) {
|
|
2719
2779
|
steps.push(this.createStep("assistant", block.text));
|
|
2720
2780
|
} else if (block.type === "tool_use") {
|
|
2781
|
+
if (typeof block.id === "string" && block.id) {
|
|
2782
|
+
state.pendingToolUses.set(block.id, { name: block.name, input: block.input });
|
|
2783
|
+
}
|
|
2721
2784
|
steps.push(this.createStep("action", JSON.stringify(block.input || {}), {
|
|
2722
2785
|
toolName: block.name,
|
|
2723
2786
|
toolArgs: block.input
|
|
@@ -2727,10 +2790,14 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2727
2790
|
} else if (event.type === "user" && event.message?.content) {
|
|
2728
2791
|
for (const block of event.message.content) {
|
|
2729
2792
|
if (block.type === "tool_result") {
|
|
2730
|
-
const
|
|
2793
|
+
const paired = typeof block.tool_use_id === "string" ? state.pendingToolUses.get(block.tool_use_id) : void 0;
|
|
2794
|
+
if (paired && typeof block.tool_use_id === "string") state.pendingToolUses.delete(block.tool_use_id);
|
|
2795
|
+
const output = boundToolOutput(toolResultText(block, event));
|
|
2731
2796
|
steps.push(
|
|
2732
|
-
this.createStep("tool_result",
|
|
2733
|
-
status: block.is_error ? "FAILURE" /* FAILURE */ : "SUCCESS" /* SUCCESS
|
|
2797
|
+
this.createStep("tool_result", output, {
|
|
2798
|
+
status: block.is_error ? "FAILURE" /* FAILURE */ : "SUCCESS" /* SUCCESS */,
|
|
2799
|
+
...paired?.name ? { toolName: paired.name } : {},
|
|
2800
|
+
...output ? { toolOutput: output } : {}
|
|
2734
2801
|
})
|
|
2735
2802
|
);
|
|
2736
2803
|
} else if (block.type === "text" && block.text) {
|
|
@@ -2739,18 +2806,18 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2739
2806
|
}
|
|
2740
2807
|
} else if (event.type === "content_block_delta") {
|
|
2741
2808
|
if (event.delta?.type === "thinking_delta" && event.delta.thinking) {
|
|
2742
|
-
|
|
2809
|
+
state.thinkingBuffer += event.delta.thinking;
|
|
2743
2810
|
} else if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
2744
|
-
|
|
2811
|
+
state.textBuffer += event.delta.text;
|
|
2745
2812
|
}
|
|
2746
2813
|
} else if (event.type === "content_block_stop") {
|
|
2747
|
-
if (
|
|
2748
|
-
steps.push(this.createStep("thinking",
|
|
2749
|
-
|
|
2814
|
+
if (state.thinkingBuffer) {
|
|
2815
|
+
steps.push(this.createStep("thinking", state.thinkingBuffer));
|
|
2816
|
+
state.thinkingBuffer = "";
|
|
2750
2817
|
}
|
|
2751
|
-
if (
|
|
2752
|
-
steps.push(this.createStep("assistant",
|
|
2753
|
-
|
|
2818
|
+
if (state.textBuffer) {
|
|
2819
|
+
steps.push(this.createStep("assistant", state.textBuffer));
|
|
2820
|
+
state.textBuffer = "";
|
|
2754
2821
|
}
|
|
2755
2822
|
} else if (event.type === "result" && event.result) {
|
|
2756
2823
|
steps.push(this.createStep(
|
|
@@ -2785,45 +2852,36 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2785
2852
|
}
|
|
2786
2853
|
return steps;
|
|
2787
2854
|
}
|
|
2788
|
-
/**
|
|
2789
|
-
* Reset state for new execution
|
|
2790
|
-
*/
|
|
2791
|
-
resetState() {
|
|
2792
|
-
this.outputBuffer = "";
|
|
2793
|
-
this.thinkingBuffer = "";
|
|
2794
|
-
this.textBuffer = "";
|
|
2795
|
-
this.isInThinking = false;
|
|
2796
|
-
}
|
|
2797
2855
|
/**
|
|
2798
2856
|
* Flush remaining buffers when the subprocess stream ends.
|
|
2799
2857
|
*/
|
|
2800
|
-
onBeforeStreamEnd(trajectory, onProgress) {
|
|
2801
|
-
if (
|
|
2858
|
+
onBeforeStreamEnd(trajectory, onProgress, state) {
|
|
2859
|
+
if (state.outputBuffer.trim()) {
|
|
2802
2860
|
try {
|
|
2803
|
-
const event = JSON.parse(
|
|
2804
|
-
const steps = this.parseJsonEvent(event);
|
|
2861
|
+
const event = JSON.parse(state.outputBuffer.trim());
|
|
2862
|
+
const steps = this.parseJsonEvent(event, state);
|
|
2805
2863
|
for (const step of steps) {
|
|
2806
2864
|
trajectory.push(step);
|
|
2807
2865
|
onProgress?.(step);
|
|
2808
2866
|
}
|
|
2809
2867
|
} catch {
|
|
2810
|
-
const step = this.createStep("assistant",
|
|
2868
|
+
const step = this.createStep("assistant", state.outputBuffer.trim());
|
|
2811
2869
|
trajectory.push(step);
|
|
2812
2870
|
onProgress?.(step);
|
|
2813
2871
|
}
|
|
2814
|
-
|
|
2872
|
+
state.outputBuffer = "";
|
|
2815
2873
|
}
|
|
2816
|
-
if (
|
|
2817
|
-
const step = this.createStep("thinking",
|
|
2874
|
+
if (state.thinkingBuffer) {
|
|
2875
|
+
const step = this.createStep("thinking", state.thinkingBuffer);
|
|
2818
2876
|
trajectory.push(step);
|
|
2819
2877
|
onProgress?.(step);
|
|
2820
|
-
|
|
2878
|
+
state.thinkingBuffer = "";
|
|
2821
2879
|
}
|
|
2822
|
-
if (
|
|
2823
|
-
const step = this.createStep("response",
|
|
2880
|
+
if (state.textBuffer) {
|
|
2881
|
+
const step = this.createStep("response", state.textBuffer);
|
|
2824
2882
|
trajectory.push(step);
|
|
2825
2883
|
onProgress?.(step);
|
|
2826
|
-
|
|
2884
|
+
state.textBuffer = "";
|
|
2827
2885
|
}
|
|
2828
2886
|
}
|
|
2829
2887
|
/**
|
|
@@ -2859,61 +2917,52 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2859
2917
|
return args;
|
|
2860
2918
|
}
|
|
2861
2919
|
/**
|
|
2862
|
-
*
|
|
2920
|
+
* Translate `ClaudeCodeConnectorConfig` (the agent's `connectorConfig`) into
|
|
2921
|
+
* the effective per-execution subprocess config. Pure: builds a fresh
|
|
2922
|
+
* snapshot from the constructor defaults — `this.config` is never written,
|
|
2923
|
+
* so concurrent executions on the shared singleton cannot compound each
|
|
2924
|
+
* other's flags (spawns were once observed with --append-system-prompt /
|
|
2925
|
+
* --allowed-tools duplicated up to 5× at concurrency 3) or swap env /
|
|
2926
|
+
* timeout / cwd.
|
|
2927
|
+
*/
|
|
2928
|
+
resolveExecutionConfig(request) {
|
|
2929
|
+
const ccConfig = request.connectorConfig || {};
|
|
2930
|
+
const base = super.resolveExecutionConfig({
|
|
2931
|
+
...request,
|
|
2932
|
+
// Only `env` / `timeout` / `workingDir` are base-shaped on the Claude
|
|
2933
|
+
// Code config; everything else is translated to CLI flags below.
|
|
2934
|
+
connectorConfig: {
|
|
2935
|
+
...ccConfig.env ? { env: ccConfig.env } : {},
|
|
2936
|
+
...ccConfig.timeout !== void 0 ? { timeout: ccConfig.timeout } : {},
|
|
2937
|
+
...ccConfig.workingDir ? { workingDir: ccConfig.workingDir } : {}
|
|
2938
|
+
}
|
|
2939
|
+
});
|
|
2940
|
+
const configArgs = this.buildConfigArgs(ccConfig);
|
|
2941
|
+
if (configArgs.length > 0) this.debug("Config args added:", configArgs);
|
|
2942
|
+
let env = base.env || {};
|
|
2943
|
+
if (env.CLAUDE_CODE_USE_BEDROCK === "1") {
|
|
2944
|
+
env = { ...env, ANTHROPIC_API_KEY: "" };
|
|
2945
|
+
this.debug("Bedrock mode: cleared ANTHROPIC_API_KEY to bypass credit check");
|
|
2946
|
+
}
|
|
2947
|
+
return {
|
|
2948
|
+
...base,
|
|
2949
|
+
env,
|
|
2950
|
+
args: [...this.config.args || [], ...configArgs],
|
|
2951
|
+
...ccConfig.usePromptArg ? { inputMode: "arg" } : {}
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
/**
|
|
2955
|
+
* Override execute for debug logging around the base implementation.
|
|
2863
2956
|
*/
|
|
2864
2957
|
async execute(endpoint, request, auth, onProgress, onRawEvent) {
|
|
2865
2958
|
this.debug("========== execute() STARTED ==========");
|
|
2866
2959
|
this.debug("Endpoint:", endpoint);
|
|
2867
2960
|
this.debug("Test case:", request.testCase.name);
|
|
2868
|
-
this.debug("
|
|
2869
|
-
|
|
2870
|
-
this.
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
const originalInputMode = this.config.inputMode;
|
|
2874
|
-
const originalTimeout = this.config.timeout;
|
|
2875
|
-
const originalWorkingDir = this.config.workingDir;
|
|
2876
|
-
const ccConfig = request.connectorConfig;
|
|
2877
|
-
if (ccConfig) {
|
|
2878
|
-
this.debug("Applying connectorConfig:", Object.keys(ccConfig));
|
|
2879
|
-
if (ccConfig.env) {
|
|
2880
|
-
this.config.env = { ...this.config.env, ...ccConfig.env };
|
|
2881
|
-
}
|
|
2882
|
-
if (ccConfig.usePromptArg) {
|
|
2883
|
-
this.config.inputMode = "arg";
|
|
2884
|
-
}
|
|
2885
|
-
if (ccConfig.timeout !== void 0) {
|
|
2886
|
-
this.config.timeout = ccConfig.timeout;
|
|
2887
|
-
}
|
|
2888
|
-
if (ccConfig.workingDir) {
|
|
2889
|
-
this.config.workingDir = ccConfig.workingDir;
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
if (this.config.env?.CLAUDE_CODE_USE_BEDROCK === "1") {
|
|
2893
|
-
this.config.env = { ...this.config.env, ANTHROPIC_API_KEY: "" };
|
|
2894
|
-
this.debug("Bedrock mode: cleared ANTHROPIC_API_KEY to bypass credit check");
|
|
2895
|
-
}
|
|
2896
|
-
if (ccConfig) {
|
|
2897
|
-
const configArgs = this.buildConfigArgs(ccConfig);
|
|
2898
|
-
this.config.args = [...this.pristineArgs, ...configArgs];
|
|
2899
|
-
if (configArgs.length > 0) {
|
|
2900
|
-
this.debug("Config args added:", configArgs);
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
try {
|
|
2904
|
-
this.debug("State reset, calling super.execute()...");
|
|
2905
|
-
this.sessionId = void 0;
|
|
2906
|
-
const result = await super.execute(endpoint, request, auth, onProgress, onRawEvent);
|
|
2907
|
-
this.debug("super.execute() returned with", result.trajectory.length, "steps");
|
|
2908
|
-
this.debug("========== execute() COMPLETED ==========");
|
|
2909
|
-
return result;
|
|
2910
|
-
} finally {
|
|
2911
|
-
this.config.env = originalEnv;
|
|
2912
|
-
this.config.args = originalArgs;
|
|
2913
|
-
this.config.inputMode = originalInputMode;
|
|
2914
|
-
this.config.timeout = originalTimeout;
|
|
2915
|
-
this.config.workingDir = originalWorkingDir;
|
|
2916
|
-
}
|
|
2961
|
+
if (request.connectorConfig) this.debug("Applying connectorConfig:", Object.keys(request.connectorConfig));
|
|
2962
|
+
const result = await super.execute(endpoint, request, auth, onProgress, onRawEvent);
|
|
2963
|
+
this.debug("super.execute() returned with", result.trajectory.length, "steps");
|
|
2964
|
+
this.debug("========== execute() COMPLETED ==========");
|
|
2965
|
+
return result;
|
|
2917
2966
|
}
|
|
2918
2967
|
/**
|
|
2919
2968
|
* Health check - verify claude command exists
|
|
@@ -2923,10 +2972,15 @@ var init_ClaudeCodeConnector = __esm({
|
|
|
2923
2972
|
}
|
|
2924
2973
|
/**
|
|
2925
2974
|
* Surface the captured Claude Code `session_id` so the runner can persist it
|
|
2926
|
-
* as `report.sessionId` for Strategy D trace correlation.
|
|
2975
|
+
* as `report.sessionId` for Strategy D trace correlation. Read from the
|
|
2976
|
+
* per-invocation state — never from the instance — so concurrent runs on
|
|
2977
|
+
* the shared singleton can't swap session ids.
|
|
2927
2978
|
*/
|
|
2928
|
-
extraResultMetadata() {
|
|
2929
|
-
return
|
|
2979
|
+
extraResultMetadata(state) {
|
|
2980
|
+
return {
|
|
2981
|
+
...super.extraResultMetadata(state),
|
|
2982
|
+
...state.sessionId ? { sessionId: state.sessionId } : {}
|
|
2983
|
+
};
|
|
2930
2984
|
}
|
|
2931
2985
|
};
|
|
2932
2986
|
claudeCodeConnector = new ClaudeCodeConnector();
|
|
@@ -2960,39 +3014,36 @@ var init_KiroConnector = __esm({
|
|
|
2960
3014
|
this.type = "kiro";
|
|
2961
3015
|
this.name = "Kiro CLI";
|
|
2962
3016
|
this.traceContext = { propagateEnv: true, serviceName: "kiro-agent" };
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
* attach the same toolName to the corresponding `[tool] status:` row. */
|
|
2967
|
-
this.pendingToolName = null;
|
|
3017
|
+
}
|
|
3018
|
+
createExecutionState() {
|
|
3019
|
+
return { ...super.createExecutionState(), stderrLineBuffer: "", pendingToolName: null };
|
|
2968
3020
|
}
|
|
2969
3021
|
/**
|
|
2970
3022
|
* Convert kiro-cli's stderr `[tool]` markers into trajectory steps.
|
|
2971
3023
|
*
|
|
2972
3024
|
* Each chunk may straddle line boundaries, so we accumulate into
|
|
2973
|
-
* `stderrLineBuffer` and only consume complete lines (the trailing
|
|
3025
|
+
* `state.stderrLineBuffer` and only consume complete lines (the trailing
|
|
2974
3026
|
* partial line is held until the next chunk or `onBeforeStreamEnd`).
|
|
2975
3027
|
*/
|
|
2976
|
-
parseStderrChunk(chunk, trajectory, onProgress) {
|
|
2977
|
-
|
|
2978
|
-
const lines =
|
|
2979
|
-
|
|
3028
|
+
parseStderrChunk(chunk, trajectory, onProgress, state) {
|
|
3029
|
+
state.stderrLineBuffer += chunk;
|
|
3030
|
+
const lines = state.stderrLineBuffer.split("\n");
|
|
3031
|
+
state.stderrLineBuffer = lines.pop() || "";
|
|
2980
3032
|
for (const raw of lines) {
|
|
2981
|
-
this.processStderrLine(raw, trajectory, onProgress);
|
|
3033
|
+
this.processStderrLine(raw, trajectory, onProgress, state);
|
|
2982
3034
|
}
|
|
2983
3035
|
}
|
|
2984
3036
|
/**
|
|
2985
|
-
* Drain any held partial line from the stderr buffer
|
|
2986
|
-
*
|
|
2987
|
-
* consolidated stdout response step.
|
|
3037
|
+
* Drain any held partial line from the stderr buffer, then defer to the
|
|
3038
|
+
* base class to flush the consolidated stdout response step.
|
|
2988
3039
|
*/
|
|
2989
|
-
onBeforeStreamEnd(trajectory, onProgress) {
|
|
2990
|
-
if (
|
|
2991
|
-
this.processStderrLine(
|
|
3040
|
+
onBeforeStreamEnd(trajectory, onProgress, state) {
|
|
3041
|
+
if (state.stderrLineBuffer.trim()) {
|
|
3042
|
+
this.processStderrLine(state.stderrLineBuffer, trajectory, onProgress, state);
|
|
2992
3043
|
}
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
super.onBeforeStreamEnd(trajectory, onProgress);
|
|
3044
|
+
state.stderrLineBuffer = "";
|
|
3045
|
+
state.pendingToolName = null;
|
|
3046
|
+
super.onBeforeStreamEnd(trajectory, onProgress, state);
|
|
2996
3047
|
}
|
|
2997
3048
|
/**
|
|
2998
3049
|
* Parse a single stderr line. Recognised forms:
|
|
@@ -3004,7 +3055,7 @@ var init_KiroConnector = __esm({
|
|
|
3004
3055
|
*
|
|
3005
3056
|
* Other stderr lines (AWS SDK warnings, telemetry info, etc.) are ignored.
|
|
3006
3057
|
*/
|
|
3007
|
-
processStderrLine(raw, trajectory, onProgress) {
|
|
3058
|
+
processStderrLine(raw, trajectory, onProgress, state) {
|
|
3008
3059
|
const line = raw.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "").trim();
|
|
3009
3060
|
if (!line.startsWith("[tool]")) return;
|
|
3010
3061
|
const m2 = line.match(/^\[tool\]\s+(Running|status):\s*(.*)$/i);
|
|
@@ -3014,7 +3065,7 @@ var init_KiroConnector = __esm({
|
|
|
3014
3065
|
if (kind === "running") {
|
|
3015
3066
|
const toolName = payload.split(/\s+/)[0] || "unknown";
|
|
3016
3067
|
const toolArgs = { command: payload };
|
|
3017
|
-
|
|
3068
|
+
state.pendingToolName = toolName;
|
|
3018
3069
|
const step = this.createStep("action", JSON.stringify(toolArgs), {
|
|
3019
3070
|
toolName,
|
|
3020
3071
|
toolArgs
|
|
@@ -3030,12 +3081,12 @@ var init_KiroConnector = __esm({
|
|
|
3030
3081
|
`status: ${payload}`,
|
|
3031
3082
|
{
|
|
3032
3083
|
status: success ? "SUCCESS" /* SUCCESS */ : "FAILURE" /* FAILURE */,
|
|
3033
|
-
toolName:
|
|
3084
|
+
toolName: state.pendingToolName ?? void 0
|
|
3034
3085
|
}
|
|
3035
3086
|
);
|
|
3036
3087
|
trajectory.push(step);
|
|
3037
3088
|
onProgress?.(step);
|
|
3038
|
-
|
|
3089
|
+
state.pendingToolName = null;
|
|
3039
3090
|
}
|
|
3040
3091
|
}
|
|
3041
3092
|
async healthCheck(endpoint, auth) {
|
|
@@ -3076,9 +3127,9 @@ var init_PiConnector = __esm({
|
|
|
3076
3127
|
this.type = "pi";
|
|
3077
3128
|
this.name = "Pi (pi.dev)";
|
|
3078
3129
|
this.traceContext = { propagateEnv: true, serviceName: "pi-agent" };
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3130
|
+
}
|
|
3131
|
+
createExecutionState() {
|
|
3132
|
+
return { ...super.createExecutionState(), piOutputBuffer: "", piThinkingBuffer: "", piTextBuffer: "" };
|
|
3082
3133
|
}
|
|
3083
3134
|
/**
|
|
3084
3135
|
* Build prompt for Pi
|
|
@@ -3101,16 +3152,16 @@ var init_PiConnector = __esm({
|
|
|
3101
3152
|
* Parse Pi streaming output (JSON format)
|
|
3102
3153
|
* Pi's --mode json produces NDJSON lines
|
|
3103
3154
|
*/
|
|
3104
|
-
parseStreamingOutput(chunk, trajectory, onProgress) {
|
|
3105
|
-
|
|
3106
|
-
const lines =
|
|
3107
|
-
|
|
3155
|
+
parseStreamingOutput(chunk, trajectory, onProgress, state) {
|
|
3156
|
+
state.piOutputBuffer += chunk;
|
|
3157
|
+
const lines = state.piOutputBuffer.split("\n");
|
|
3158
|
+
state.piOutputBuffer = lines.pop() || "";
|
|
3108
3159
|
for (const line of lines) {
|
|
3109
3160
|
const trimmed = line.trim();
|
|
3110
3161
|
if (!trimmed) continue;
|
|
3111
3162
|
try {
|
|
3112
3163
|
const event = JSON.parse(trimmed);
|
|
3113
|
-
const steps = this.parsePiEvent(event);
|
|
3164
|
+
const steps = this.parsePiEvent(event, state);
|
|
3114
3165
|
for (const step of steps) {
|
|
3115
3166
|
trajectory.push(step);
|
|
3116
3167
|
onProgress?.(step);
|
|
@@ -3133,7 +3184,7 @@ var init_PiConnector = __esm({
|
|
|
3133
3184
|
* - message_start / message_end — full message with content blocks
|
|
3134
3185
|
* - message_update — streaming deltas with assistantMessageEvent
|
|
3135
3186
|
*/
|
|
3136
|
-
parsePiEvent(event) {
|
|
3187
|
+
parsePiEvent(event, state) {
|
|
3137
3188
|
const steps = [];
|
|
3138
3189
|
if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
3139
3190
|
const content = event.message.content;
|
|
@@ -3154,9 +3205,9 @@ var init_PiConnector = __esm({
|
|
|
3154
3205
|
} else if (event.type === "message_update") {
|
|
3155
3206
|
const assistantEvent = event.assistantMessageEvent;
|
|
3156
3207
|
if (assistantEvent?.type === "text_delta" && assistantEvent.delta) {
|
|
3157
|
-
|
|
3208
|
+
state.piTextBuffer += assistantEvent.delta;
|
|
3158
3209
|
} else if (assistantEvent?.type === "thinking_delta" && assistantEvent.delta) {
|
|
3159
|
-
|
|
3210
|
+
state.piThinkingBuffer += assistantEvent.delta;
|
|
3160
3211
|
}
|
|
3161
3212
|
} else if (event.type === "tool_result") {
|
|
3162
3213
|
const content = event.content || event.output || JSON.stringify(event);
|
|
@@ -3166,13 +3217,13 @@ var init_PiConnector = __esm({
|
|
|
3166
3217
|
{ status: event.is_error ? "FAILURE" /* FAILURE */ : "SUCCESS" /* SUCCESS */ }
|
|
3167
3218
|
));
|
|
3168
3219
|
} else if (event.type === "agent_end") {
|
|
3169
|
-
if (
|
|
3170
|
-
steps.push(this.createStep("thinking",
|
|
3171
|
-
|
|
3220
|
+
if (state.piThinkingBuffer) {
|
|
3221
|
+
steps.push(this.createStep("thinking", state.piThinkingBuffer));
|
|
3222
|
+
state.piThinkingBuffer = "";
|
|
3172
3223
|
}
|
|
3173
|
-
if (
|
|
3174
|
-
steps.push(this.createStep("response",
|
|
3175
|
-
|
|
3224
|
+
if (state.piTextBuffer) {
|
|
3225
|
+
steps.push(this.createStep("response", state.piTextBuffer));
|
|
3226
|
+
state.piTextBuffer = "";
|
|
3176
3227
|
}
|
|
3177
3228
|
}
|
|
3178
3229
|
return steps;
|
|
@@ -3196,90 +3247,105 @@ var init_PiConnector = __esm({
|
|
|
3196
3247
|
/**
|
|
3197
3248
|
* Flush remaining buffers when the subprocess stream ends.
|
|
3198
3249
|
*/
|
|
3199
|
-
onBeforeStreamEnd(trajectory, onProgress) {
|
|
3200
|
-
if (
|
|
3250
|
+
onBeforeStreamEnd(trajectory, onProgress, state) {
|
|
3251
|
+
if (state.piOutputBuffer.trim()) {
|
|
3201
3252
|
try {
|
|
3202
|
-
const event = JSON.parse(
|
|
3203
|
-
const steps = this.parsePiEvent(event);
|
|
3253
|
+
const event = JSON.parse(state.piOutputBuffer.trim());
|
|
3254
|
+
const steps = this.parsePiEvent(event, state);
|
|
3204
3255
|
for (const step of steps) {
|
|
3205
3256
|
trajectory.push(step);
|
|
3206
3257
|
onProgress?.(step);
|
|
3207
3258
|
}
|
|
3208
3259
|
} catch {
|
|
3209
|
-
const step = this.createStep("assistant",
|
|
3260
|
+
const step = this.createStep("assistant", state.piOutputBuffer.trim());
|
|
3210
3261
|
trajectory.push(step);
|
|
3211
3262
|
onProgress?.(step);
|
|
3212
3263
|
}
|
|
3213
|
-
|
|
3264
|
+
state.piOutputBuffer = "";
|
|
3214
3265
|
}
|
|
3215
|
-
if (
|
|
3216
|
-
const step = this.createStep("thinking",
|
|
3266
|
+
if (state.piThinkingBuffer) {
|
|
3267
|
+
const step = this.createStep("thinking", state.piThinkingBuffer);
|
|
3217
3268
|
trajectory.push(step);
|
|
3218
3269
|
onProgress?.(step);
|
|
3219
|
-
|
|
3270
|
+
state.piThinkingBuffer = "";
|
|
3220
3271
|
}
|
|
3221
|
-
if (
|
|
3222
|
-
const step = this.createStep("response",
|
|
3272
|
+
if (state.piTextBuffer) {
|
|
3273
|
+
const step = this.createStep("response", state.piTextBuffer);
|
|
3223
3274
|
trajectory.push(step);
|
|
3224
3275
|
onProgress?.(step);
|
|
3225
|
-
|
|
3276
|
+
state.piTextBuffer = "";
|
|
3226
3277
|
}
|
|
3227
3278
|
}
|
|
3228
3279
|
/**
|
|
3229
|
-
*
|
|
3280
|
+
* Translate `PiConnectorConfig` into the effective per-execution subprocess
|
|
3281
|
+
* config. Pure — `this.config` is never written, so concurrent executions
|
|
3282
|
+
* on the shared singleton cannot see each other's args / env / timeout.
|
|
3230
3283
|
*/
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
const piConfig = request.connectorConfig;
|
|
3240
|
-
if (piConfig) {
|
|
3241
|
-
if (piConfig.env) {
|
|
3242
|
-
this.config.env = { ...this.config.env, ...piConfig.env };
|
|
3243
|
-
}
|
|
3244
|
-
if (piConfig.timeout !== void 0) {
|
|
3245
|
-
this.config.timeout = piConfig.timeout;
|
|
3246
|
-
}
|
|
3247
|
-
if (piConfig.workingDir) {
|
|
3248
|
-
this.config.workingDir = piConfig.workingDir;
|
|
3249
|
-
}
|
|
3250
|
-
const extraArgs = [];
|
|
3251
|
-
if (piConfig.packagePath) {
|
|
3252
|
-
extraArgs.push("--skill", `${piConfig.packagePath}/skills/*`);
|
|
3253
|
-
extraArgs.push("--extension", `${piConfig.packagePath}/extensions/agent-health.ts`);
|
|
3254
|
-
extraArgs.push("--append-system-prompt", `${piConfig.packagePath}/prompts/agent-health.md`);
|
|
3255
|
-
}
|
|
3256
|
-
if (piConfig.model) {
|
|
3257
|
-
extraArgs.push("--model", piConfig.model);
|
|
3258
|
-
}
|
|
3259
|
-
if (piConfig.additionalArgs) {
|
|
3260
|
-
extraArgs.push(...piConfig.additionalArgs);
|
|
3261
|
-
}
|
|
3262
|
-
if (extraArgs.length > 0) {
|
|
3263
|
-
this.config.args = [...this.config.args || [], ...extraArgs];
|
|
3284
|
+
resolveExecutionConfig(request) {
|
|
3285
|
+
const piConfig = request.connectorConfig || {};
|
|
3286
|
+
const base = super.resolveExecutionConfig({
|
|
3287
|
+
...request,
|
|
3288
|
+
connectorConfig: {
|
|
3289
|
+
...piConfig.env ? { env: piConfig.env } : {},
|
|
3290
|
+
...piConfig.timeout !== void 0 ? { timeout: piConfig.timeout } : {},
|
|
3291
|
+
...piConfig.workingDir ? { workingDir: piConfig.workingDir } : {}
|
|
3264
3292
|
}
|
|
3293
|
+
});
|
|
3294
|
+
const extraArgs = [];
|
|
3295
|
+
if (piConfig.packagePath) {
|
|
3296
|
+
extraArgs.push("--skill", `${piConfig.packagePath}/skills/*`);
|
|
3297
|
+
extraArgs.push("--extension", `${piConfig.packagePath}/extensions/agent-health.ts`);
|
|
3298
|
+
extraArgs.push("--append-system-prompt", `${piConfig.packagePath}/prompts/agent-health.md`);
|
|
3265
3299
|
}
|
|
3266
|
-
if (
|
|
3267
|
-
|
|
3300
|
+
if (piConfig.model) {
|
|
3301
|
+
extraArgs.push("--model", piConfig.model);
|
|
3268
3302
|
}
|
|
3269
|
-
if (
|
|
3270
|
-
|
|
3303
|
+
if (piConfig.additionalArgs) {
|
|
3304
|
+
extraArgs.push(...piConfig.additionalArgs);
|
|
3271
3305
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3306
|
+
let args = [...this.config.args || [], ...extraArgs];
|
|
3307
|
+
if (request.modelId) {
|
|
3308
|
+
args = [...this.stripModelFlag(args), "--model", request.modelId];
|
|
3274
3309
|
}
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3310
|
+
const env = { ...base.env || {} };
|
|
3311
|
+
if (process.env.AWS_PROFILE) env.AWS_PROFILE = process.env.AWS_PROFILE;
|
|
3312
|
+
if (process.env.AWS_REGION) env.AWS_REGION = process.env.AWS_REGION;
|
|
3313
|
+
return { ...base, args, env };
|
|
3314
|
+
}
|
|
3315
|
+
/**
|
|
3316
|
+
* Remove any `--model <value>` (two-token) or `--model=<value>` (single-
|
|
3317
|
+
* token) occurrence from an argv array. Used so `request.modelId` can
|
|
3318
|
+
* override `connectorConfig.model` without leaving a stale second
|
|
3319
|
+
* `--model` flag in the final argv.
|
|
3320
|
+
*
|
|
3321
|
+
* This connector only ever generates the two-token form itself, but
|
|
3322
|
+
* `connectorConfig.additionalArgs` is user-supplied and could plausibly
|
|
3323
|
+
* contain the `--model=value` form, so both are handled (codex_review
|
|
3324
|
+
* finding: a stray `--model=value` from `additionalArgs` would otherwise
|
|
3325
|
+
* survive alongside the newly-appended two-token flag, recreating the
|
|
3326
|
+
* duplicate-flag bug). If a bare `--model` has no following value (or is
|
|
3327
|
+
* the last element), only the bare flag is removed — the next token is
|
|
3328
|
+
* left alone rather than being swallowed as a "value" when it's actually
|
|
3329
|
+
* another flag (codex_review finding: guards against corrupting unrelated
|
|
3330
|
+
* malformed argv, e.g. `['--foo', '--model', '--bar']`).
|
|
3331
|
+
*/
|
|
3332
|
+
stripModelFlag(args) {
|
|
3333
|
+
const result = [];
|
|
3334
|
+
for (let i = 0; i < args.length; i++) {
|
|
3335
|
+
const arg = args[i];
|
|
3336
|
+
if (arg === "--model") {
|
|
3337
|
+
const next = args[i + 1];
|
|
3338
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
3339
|
+
i++;
|
|
3340
|
+
}
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
if (arg.startsWith("--model=")) {
|
|
3344
|
+
continue;
|
|
3345
|
+
}
|
|
3346
|
+
result.push(arg);
|
|
3282
3347
|
}
|
|
3348
|
+
return result;
|
|
3283
3349
|
}
|
|
3284
3350
|
/**
|
|
3285
3351
|
* Health check - verify pi command exists
|
|
@@ -3557,21 +3623,38 @@ var init_server = __esm({
|
|
|
3557
3623
|
});
|
|
3558
3624
|
|
|
3559
3625
|
// lib/testCases/define.ts
|
|
3626
|
+
function getSharedState() {
|
|
3627
|
+
const g = globalThis;
|
|
3628
|
+
let state = g[REGISTRY_KEY];
|
|
3629
|
+
if (!state) {
|
|
3630
|
+
state = {
|
|
3631
|
+
registries: /* @__PURE__ */ new Map(),
|
|
3632
|
+
hookRegistries: /* @__PURE__ */ new Map(),
|
|
3633
|
+
activeFile: null,
|
|
3634
|
+
describeStack: [],
|
|
3635
|
+
experimentalWarningEmitted: false
|
|
3636
|
+
};
|
|
3637
|
+
g[REGISTRY_KEY] = state;
|
|
3638
|
+
}
|
|
3639
|
+
return state;
|
|
3640
|
+
}
|
|
3560
3641
|
function emitExperimentalWarningOnce() {
|
|
3561
|
-
|
|
3562
|
-
experimentalWarningEmitted
|
|
3642
|
+
const state = getSharedState();
|
|
3643
|
+
if (state.experimentalWarningEmitted) return;
|
|
3644
|
+
state.experimentalWarningEmitted = true;
|
|
3563
3645
|
if (readEnv("AH_SUPPRESS_EXPERIMENTAL", "AGENT_HEALTH_SUPPRESS_EXPERIMENTAL") === "1") return;
|
|
3564
3646
|
console.warn(
|
|
3565
3647
|
"[agent-health] The code-based test SDK (test()/judge()/expect()) is experimental. The API may change in a minor release without a deprecation cycle. Pin your @opensearch-project/agent-health version, or set AH_SUPPRESS_EXPERIMENTAL=1 to silence this notice."
|
|
3566
3648
|
);
|
|
3567
3649
|
}
|
|
3568
3650
|
function setActiveFile(filePath2) {
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3651
|
+
const state = getSharedState();
|
|
3652
|
+
state.activeFile = filePath2;
|
|
3653
|
+
if (!state.registries.has(filePath2)) {
|
|
3654
|
+
state.registries.set(filePath2, []);
|
|
3572
3655
|
}
|
|
3573
|
-
if (!hookRegistries.has(filePath2)) {
|
|
3574
|
-
hookRegistries.set(filePath2, []);
|
|
3656
|
+
if (!state.hookRegistries.has(filePath2)) {
|
|
3657
|
+
state.hookRegistries.set(filePath2, []);
|
|
3575
3658
|
}
|
|
3576
3659
|
}
|
|
3577
3660
|
function test(name, optionsOrBody, maybeBody) {
|
|
@@ -3591,14 +3674,15 @@ function test(name, optionsOrBody, maybeBody) {
|
|
|
3591
3674
|
if (typeof evaluate2 !== "function") {
|
|
3592
3675
|
throw new Error(`test("${name}") requires a body function`);
|
|
3593
3676
|
}
|
|
3594
|
-
const
|
|
3595
|
-
|
|
3596
|
-
|
|
3677
|
+
const state = getSharedState();
|
|
3678
|
+
const key = state.activeFile ?? DEFAULT_KEY;
|
|
3679
|
+
if (!state.registries.has(key)) {
|
|
3680
|
+
state.registries.set(key, []);
|
|
3597
3681
|
}
|
|
3598
|
-
const registry2 = registries.get(key);
|
|
3599
|
-
const benchmarkPath = describeStack.length > 0 ? describeStack.join(" > ") : void 0;
|
|
3682
|
+
const registry2 = state.registries.get(key);
|
|
3683
|
+
const benchmarkPath = state.describeStack.length > 0 ? state.describeStack.join(" > ") : void 0;
|
|
3600
3684
|
if (registry2.some((t) => t.name === name && t.benchmarkPath === benchmarkPath)) {
|
|
3601
|
-
const fileLabel = activeFile ? ` in ${activeFile}` : "";
|
|
3685
|
+
const fileLabel = state.activeFile ? ` in ${state.activeFile}` : "";
|
|
3602
3686
|
const groupLabel = benchmarkPath ? ` (in describe "${benchmarkPath}")` : "";
|
|
3603
3687
|
throw new Error(
|
|
3604
3688
|
`Duplicate test name "${name}"${groupLabel}${fileLabel}. Test names must be unique within their describe block. Move one of the tests to a different describe() or rename it.`
|
|
@@ -3608,7 +3692,7 @@ function test(name, optionsOrBody, maybeBody) {
|
|
|
3608
3692
|
name,
|
|
3609
3693
|
options,
|
|
3610
3694
|
evaluate: evaluate2,
|
|
3611
|
-
sourceFile: activeFile ?? void 0,
|
|
3695
|
+
sourceFile: state.activeFile ?? void 0,
|
|
3612
3696
|
benchmarkPath
|
|
3613
3697
|
});
|
|
3614
3698
|
}
|
|
@@ -3620,7 +3704,8 @@ function describe(name, fn) {
|
|
|
3620
3704
|
if (typeof fn !== "function") {
|
|
3621
3705
|
throw new Error(`describe("${name}") requires a body function`);
|
|
3622
3706
|
}
|
|
3623
|
-
|
|
3707
|
+
const state = getSharedState();
|
|
3708
|
+
state.describeStack.push(name);
|
|
3624
3709
|
try {
|
|
3625
3710
|
const result = fn();
|
|
3626
3711
|
if (result && typeof result.then === "function") {
|
|
@@ -3629,41 +3714,45 @@ function describe(name, fn) {
|
|
|
3629
3714
|
);
|
|
3630
3715
|
}
|
|
3631
3716
|
} finally {
|
|
3632
|
-
describeStack.pop();
|
|
3717
|
+
state.describeStack.pop();
|
|
3633
3718
|
}
|
|
3634
3719
|
}
|
|
3635
3720
|
function getRegisteredTests(filePath2) {
|
|
3636
|
-
|
|
3637
|
-
return [...registries.
|
|
3721
|
+
const state = getSharedState();
|
|
3722
|
+
if (filePath2) return [...state.registries.get(filePath2) ?? []];
|
|
3723
|
+
return [...state.registries.values()].flatMap((r) => [...r]);
|
|
3638
3724
|
}
|
|
3639
3725
|
function getRegisteredHooks(filePath2) {
|
|
3640
|
-
|
|
3641
|
-
return [...hookRegistries.
|
|
3726
|
+
const state = getSharedState();
|
|
3727
|
+
if (filePath2) return [...state.hookRegistries.get(filePath2) ?? []];
|
|
3728
|
+
return [...state.hookRegistries.values()].flatMap((r) => [...r]);
|
|
3642
3729
|
}
|
|
3643
3730
|
function clearRegistry(filePath2) {
|
|
3731
|
+
const state = getSharedState();
|
|
3644
3732
|
if (filePath2) {
|
|
3645
|
-
registries.delete(filePath2);
|
|
3646
|
-
hookRegistries.delete(filePath2);
|
|
3733
|
+
state.registries.delete(filePath2);
|
|
3734
|
+
state.hookRegistries.delete(filePath2);
|
|
3647
3735
|
} else {
|
|
3648
|
-
registries.clear();
|
|
3649
|
-
hookRegistries.clear();
|
|
3736
|
+
state.registries.clear();
|
|
3737
|
+
state.hookRegistries.clear();
|
|
3650
3738
|
}
|
|
3651
|
-
activeFile = null;
|
|
3739
|
+
state.activeFile = null;
|
|
3652
3740
|
}
|
|
3653
3741
|
function registerHook(kind, fn) {
|
|
3654
3742
|
emitExperimentalWarningOnce();
|
|
3655
3743
|
if (typeof fn !== "function") {
|
|
3656
3744
|
throw new Error(`${kind}() requires a function as its first argument`);
|
|
3657
3745
|
}
|
|
3658
|
-
const
|
|
3659
|
-
|
|
3660
|
-
|
|
3746
|
+
const state = getSharedState();
|
|
3747
|
+
const key = state.activeFile ?? DEFAULT_KEY;
|
|
3748
|
+
if (!state.hookRegistries.has(key)) {
|
|
3749
|
+
state.hookRegistries.set(key, []);
|
|
3661
3750
|
}
|
|
3662
|
-
const describePath = describeStack.length > 0 ? describeStack.join(" > ") : void 0;
|
|
3663
|
-
hookRegistries.get(key).push({
|
|
3751
|
+
const describePath = state.describeStack.length > 0 ? state.describeStack.join(" > ") : void 0;
|
|
3752
|
+
state.hookRegistries.get(key).push({
|
|
3664
3753
|
kind,
|
|
3665
3754
|
fn,
|
|
3666
|
-
sourceFile: activeFile ?? void 0,
|
|
3755
|
+
sourceFile: state.activeFile ?? void 0,
|
|
3667
3756
|
describePath
|
|
3668
3757
|
});
|
|
3669
3758
|
}
|
|
@@ -3679,16 +3768,12 @@ function beforeEach(fn) {
|
|
|
3679
3768
|
function afterEach(fn) {
|
|
3680
3769
|
registerHook("afterEach", fn);
|
|
3681
3770
|
}
|
|
3682
|
-
var
|
|
3771
|
+
var DEFAULT_KEY, REGISTRY_KEY;
|
|
3683
3772
|
var init_define = __esm({
|
|
3684
3773
|
"lib/testCases/define.ts"() {
|
|
3685
3774
|
init_envCompat();
|
|
3686
|
-
registries = /* @__PURE__ */ new Map();
|
|
3687
|
-
hookRegistries = /* @__PURE__ */ new Map();
|
|
3688
|
-
activeFile = null;
|
|
3689
3775
|
DEFAULT_KEY = "__default__";
|
|
3690
|
-
|
|
3691
|
-
experimentalWarningEmitted = false;
|
|
3776
|
+
REGISTRY_KEY = /* @__PURE__ */ Symbol.for("agent-health.test-registry.v1");
|
|
3692
3777
|
((test2) => {
|
|
3693
3778
|
test2.beforeAll = (fn) => registerHook("beforeAll", fn);
|
|
3694
3779
|
test2.afterAll = (fn) => registerHook("afterAll", fn);
|
|
@@ -3790,10 +3875,10 @@ async function runJudge(resultOrTrajectory, claimOrClaims, options, role) {
|
|
|
3790
3875
|
if (runId) requestBody.runId = runId;
|
|
3791
3876
|
if (Array.isArray(judgeAgents) && judgeAgents.length > 0) requestBody.agents = judgeAgents;
|
|
3792
3877
|
const finalize = (raw2, durationMs) => {
|
|
3793
|
-
const
|
|
3878
|
+
const headline = typeof raw2.metrics?.accuracy === "number" ? raw2.metrics.accuracy : typeof raw2.overallScore === "number" ? raw2.overallScore : void 0;
|
|
3794
3879
|
const verdict = makeVerdict({
|
|
3795
3880
|
passFailStatus: raw2.passFailStatus ?? "failed",
|
|
3796
|
-
accuracy,
|
|
3881
|
+
accuracy: headline ?? 0,
|
|
3797
3882
|
reasoning: raw2.llmJudgeReasoning ?? "",
|
|
3798
3883
|
role,
|
|
3799
3884
|
skipped: false,
|
|
@@ -3805,15 +3890,22 @@ async function runJudge(resultOrTrajectory, claimOrClaims, options, role) {
|
|
|
3805
3890
|
method: "llm-judge",
|
|
3806
3891
|
role,
|
|
3807
3892
|
durationMs,
|
|
3808
|
-
score
|
|
3893
|
+
// Only record a score when the judge actually produced a headline
|
|
3894
|
+
// number — a fabricated 0 renders as a misleading "score 0%".
|
|
3895
|
+
...headline !== void 0 ? { score: headline / 100 } : {},
|
|
3809
3896
|
reasoning: verdict.reasoning,
|
|
3810
3897
|
model: options?.model,
|
|
3811
|
-
|
|
3898
|
+
// NOTE: no `errorMessage` mirror. It used to copy `reasoning` verbatim
|
|
3899
|
+
// on failure, which persisted the same multi-KB string twice and made
|
|
3900
|
+
// the UI render it twice (once red as "error", once as "reasoning").
|
|
3901
|
+
// `reasoning` is the single source of truth; consumers that need a
|
|
3902
|
+
// failure detail fall back to it (see expect.toPass()).
|
|
3812
3903
|
// Preserve the rest of the judge payload — these were silently
|
|
3813
3904
|
// dropped before, which made SDK `judge()` calls strictly less
|
|
3814
3905
|
// informative than the legacy auto-judge path. See MatcherResult.
|
|
3815
3906
|
...Array.isArray(raw2.improvementStrategies) && raw2.improvementStrategies.length > 0 ? { improvementStrategies: raw2.improvementStrategies } : {},
|
|
3816
|
-
...raw2.metrics && typeof raw2.metrics === "object" ? { judgeMetrics: { ...raw2.metrics } } : {}
|
|
3907
|
+
...raw2.metrics && typeof raw2.metrics === "object" ? { judgeMetrics: { ...raw2.metrics } } : {},
|
|
3908
|
+
...raw2.extraFields && typeof raw2.extraFields === "object" && Object.keys(raw2.extraFields).length > 0 ? { judgeExtraFields: raw2.extraFields } : {}
|
|
3817
3909
|
});
|
|
3818
3910
|
return verdict;
|
|
3819
3911
|
};
|
|
@@ -4076,6 +4168,9 @@ function ensurePluginsInstalled() {
|
|
|
4076
4168
|
errorMessage: err?.message || String(err)
|
|
4077
4169
|
});
|
|
4078
4170
|
}
|
|
4171
|
+
if (utils.flag(this, "ahSoft")) {
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4079
4174
|
throw err;
|
|
4080
4175
|
}
|
|
4081
4176
|
};
|
|
@@ -4147,6 +4242,11 @@ function ensurePluginsInstalled() {
|
|
|
4147
4242
|
);
|
|
4148
4243
|
});
|
|
4149
4244
|
}
|
|
4245
|
+
function softExpect(val, msg) {
|
|
4246
|
+
const assertion = chai.expect(val, msg);
|
|
4247
|
+
chai.util.flag(assertion, "ahSoft", true);
|
|
4248
|
+
return assertion;
|
|
4249
|
+
}
|
|
4150
4250
|
function describeAssertion(ctx, _expr, msg, negateMsg, expected, actual, utils) {
|
|
4151
4251
|
const rawMsg = typeof msg === "function" ? msg() : msg;
|
|
4152
4252
|
const rawNeg = typeof negateMsg === "function" ? negateMsg() : negateMsg;
|
|
@@ -4202,6 +4302,7 @@ var init_expect = __esm({
|
|
|
4202
4302
|
pluginsInstalled = false;
|
|
4203
4303
|
ensurePluginsInstalled();
|
|
4204
4304
|
expect2 = chai.expect;
|
|
4305
|
+
expect2.soft = softExpect;
|
|
4205
4306
|
}
|
|
4206
4307
|
});
|
|
4207
4308
|
|
|
@@ -4627,8 +4728,8 @@ function claudeNativeTrajectory(spans) {
|
|
|
4627
4728
|
const redacted = !prompt || prompt === "<REDACTED>";
|
|
4628
4729
|
steps.push({
|
|
4629
4730
|
...base,
|
|
4630
|
-
type: "
|
|
4631
|
-
content: redacted ? `
|
|
4731
|
+
type: "user",
|
|
4732
|
+
content: redacted ? `[prompt redacted \u2014 set OTEL_LOG_USER_PROMPTS=1 to capture] (${a["user_prompt_length"] ?? "?"} chars)` : String(prompt)
|
|
4632
4733
|
});
|
|
4633
4734
|
} else if (t === "llm_request") {
|
|
4634
4735
|
const model = a["model"] || a["gen_ai.request.model"] || "";
|
|
@@ -4839,7 +4940,7 @@ function genericTrajectory(spans, serviceName) {
|
|
|
4839
4940
|
break;
|
|
4840
4941
|
}
|
|
4841
4942
|
case "user":
|
|
4842
|
-
steps.push({ ...base, type: "
|
|
4943
|
+
steps.push({ ...base, type: "user", content: m2.content });
|
|
4843
4944
|
break;
|
|
4844
4945
|
default:
|
|
4845
4946
|
steps.push({ ...base, type: "thinking", content: m2.content });
|
|
@@ -5260,6 +5361,8 @@ var init_utils = __esm({
|
|
|
5260
5361
|
// lib/testCases/loader.ts
|
|
5261
5362
|
var loader_exports = {};
|
|
5262
5363
|
__export(loader_exports, {
|
|
5364
|
+
DEFINITION_BODY_SOURCE_MAX_CHARS: () => DEFINITION_BODY_SOURCE_MAX_CHARS,
|
|
5365
|
+
captureTestDefinition: () => captureTestDefinition,
|
|
5263
5366
|
computeTestCaseHash: () => computeTestCaseHash,
|
|
5264
5367
|
detectSourceLanguage: () => detectSourceLanguage,
|
|
5265
5368
|
isCodeFile: () => isCodeFile,
|
|
@@ -5300,58 +5403,144 @@ function computeTestCaseHash(tc, fileSource2) {
|
|
|
5300
5403
|
});
|
|
5301
5404
|
return createHash("sha256").update(content).digest("hex");
|
|
5302
5405
|
}
|
|
5406
|
+
function captureTestDefinition(tc) {
|
|
5407
|
+
let options = {};
|
|
5408
|
+
try {
|
|
5409
|
+
const parsed = JSON.parse(JSON.stringify(tc.options ?? {}));
|
|
5410
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) options = parsed;
|
|
5411
|
+
} catch {
|
|
5412
|
+
}
|
|
5413
|
+
let bodySource = "";
|
|
5414
|
+
try {
|
|
5415
|
+
bodySource = typeof tc.evaluate === "function" ? Function.prototype.toString.call(tc.evaluate) : "";
|
|
5416
|
+
} catch {
|
|
5417
|
+
bodySource = "";
|
|
5418
|
+
}
|
|
5419
|
+
let bodyTruncated = false;
|
|
5420
|
+
if (bodySource.length > DEFINITION_BODY_SOURCE_MAX_CHARS) {
|
|
5421
|
+
bodySource = bodySource.slice(0, DEFINITION_BODY_SOURCE_MAX_CHARS) + BODY_TRUNCATION_MARKER;
|
|
5422
|
+
bodyTruncated = true;
|
|
5423
|
+
}
|
|
5424
|
+
return {
|
|
5425
|
+
registeredAs: "sdk",
|
|
5426
|
+
options,
|
|
5427
|
+
bodySource,
|
|
5428
|
+
...bodyTruncated ? { bodyTruncated: true } : {}
|
|
5429
|
+
};
|
|
5430
|
+
}
|
|
5431
|
+
function getEsbuildTransformSync() {
|
|
5432
|
+
if (!esbuildTransformSyncPromise) {
|
|
5433
|
+
esbuildTransformSyncPromise = import("esbuild").then((m2) => m2.transformSync);
|
|
5434
|
+
}
|
|
5435
|
+
return esbuildTransformSyncPromise;
|
|
5436
|
+
}
|
|
5303
5437
|
async function loadTestCasesFromModule(filePath) {
|
|
5304
5438
|
const absPath = resolve4(filePath);
|
|
5305
5439
|
const fileSource = readFileSync2(absPath, "utf-8");
|
|
5306
5440
|
clearRegistry(absPath);
|
|
5307
5441
|
setActiveFile(absPath);
|
|
5442
|
+
const transformSync = absPath.endsWith(".mjs") ? void 0 : await getEsbuildTransformSync();
|
|
5443
|
+
const tsModuleCache = /* @__PURE__ */ new Map();
|
|
5308
5444
|
let module;
|
|
5309
|
-
|
|
5310
|
-
const
|
|
5311
|
-
const fileDir = dirname3(absPath);
|
|
5445
|
+
const runAsSyntheticCjs = (code, modulePath) => {
|
|
5446
|
+
const fileDir = dirname3(modulePath);
|
|
5312
5447
|
const Module = NodeModule;
|
|
5313
|
-
const m = new Module(
|
|
5314
|
-
m.filename =
|
|
5448
|
+
const m = new Module(modulePath);
|
|
5449
|
+
m.filename = modulePath;
|
|
5315
5450
|
m.paths = Module._nodeModulePaths(fileDir);
|
|
5316
|
-
const fileRequire = createRequire(
|
|
5451
|
+
const fileRequire = createRequire(modulePath);
|
|
5317
5452
|
const isDefineId = (id) => {
|
|
5318
5453
|
const normalized = id.replace(/\\/g, "/").replace(/\.js$/, "");
|
|
5319
5454
|
return normalized === "lib/testCases/define" || normalized.endsWith("/lib/testCases/define");
|
|
5320
5455
|
};
|
|
5321
5456
|
const isPackageName = (id) => id === "@opensearch-project/agent-health" || id === "@opensearch/agent-health" || id === "agent-health";
|
|
5322
5457
|
const sdkExports = getAuthoringSurface();
|
|
5458
|
+
const requireLocalTsModule = (resolvedTsPath) => {
|
|
5459
|
+
const cached = tsModuleCache.get(resolvedTsPath);
|
|
5460
|
+
if (cached !== void 0) return cached;
|
|
5461
|
+
if (!transformSync) {
|
|
5462
|
+
throw new Error(
|
|
5463
|
+
`Cannot import TypeScript file: ${resolvedTsPath}
|
|
5464
|
+
esbuild (required to load .ts files) was not resolved for this load.`
|
|
5465
|
+
);
|
|
5466
|
+
}
|
|
5467
|
+
const helperSource = readFileSync2(resolvedTsPath, "utf-8");
|
|
5468
|
+
const helperCjs = transformSync(helperSource, {
|
|
5469
|
+
loader: "ts",
|
|
5470
|
+
format: "cjs",
|
|
5471
|
+
target: "node18",
|
|
5472
|
+
sourcefile: resolvedTsPath
|
|
5473
|
+
}).code;
|
|
5474
|
+
const helperExports = runAsSyntheticCjs(helperCjs, resolvedTsPath);
|
|
5475
|
+
tsModuleCache.set(resolvedTsPath, helperExports);
|
|
5476
|
+
return helperExports;
|
|
5477
|
+
};
|
|
5323
5478
|
const wrappedRequire = (id) => {
|
|
5324
5479
|
if (isDefineId(id) || isPackageName(id)) {
|
|
5325
5480
|
return sdkExports;
|
|
5326
5481
|
}
|
|
5482
|
+
let resolved;
|
|
5327
5483
|
try {
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5484
|
+
resolved = fileRequire.resolve(id);
|
|
5485
|
+
} catch {
|
|
5486
|
+
}
|
|
5487
|
+
if (resolved) {
|
|
5488
|
+
const normalizedResolved = resolved.replace(/\\/g, "/");
|
|
5489
|
+
if (normalizedResolved.endsWith("/lib/testCases/define.js") || normalizedResolved.endsWith("/lib/testCases/define")) {
|
|
5331
5490
|
return sdkExports;
|
|
5332
5491
|
}
|
|
5333
|
-
|
|
5492
|
+
if (normalizedResolved.endsWith(".ts")) {
|
|
5493
|
+
return requireLocalTsModule(resolved);
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
try {
|
|
5497
|
+
return fileRequire(id);
|
|
5498
|
+
} catch (err) {
|
|
5499
|
+
if (err.code === "ERR_REQUIRE_ESM") {
|
|
5500
|
+
throw new Error(
|
|
5501
|
+
`.eval.ts fixtures cannot import ESM-only packages via require() (attempted: '${id}').
|
|
5502
|
+
'${id}' has no CommonJS entry point, and .eval.ts files execute as synthetic CJS.
|
|
5503
|
+
Use .eval.mjs (real ESM -- can import ESM-only packages) or pre-compile to .eval.js instead.`
|
|
5504
|
+
);
|
|
5505
|
+
}
|
|
5506
|
+
throw err;
|
|
5334
5507
|
}
|
|
5335
|
-
return fileRequire(id);
|
|
5336
5508
|
};
|
|
5337
5509
|
wrappedRequire.resolve = fileRequire.resolve;
|
|
5338
5510
|
const wrapper = `(function(exports, require, module, __filename, __dirname) { ${code}
|
|
5339
5511
|
});`;
|
|
5340
5512
|
const compiledFn = eval(wrapper);
|
|
5341
|
-
compiledFn(m.exports, wrappedRequire, m,
|
|
5342
|
-
|
|
5513
|
+
compiledFn(m.exports, wrappedRequire, m, modulePath, fileDir);
|
|
5514
|
+
return m.exports;
|
|
5515
|
+
};
|
|
5516
|
+
if (absPath.endsWith(".js")) {
|
|
5517
|
+
module = runAsSyntheticCjs(fileSource, absPath);
|
|
5518
|
+
} else if (absPath.endsWith(".ts")) {
|
|
5519
|
+
if (!transformSync) {
|
|
5520
|
+
throw new Error(`Cannot import TypeScript file: ${filePath}
|
|
5521
|
+
esbuild was not resolved.`);
|
|
5522
|
+
}
|
|
5523
|
+
let cjsSource;
|
|
5524
|
+
try {
|
|
5525
|
+
cjsSource = transformSync(fileSource, {
|
|
5526
|
+
loader: "ts",
|
|
5527
|
+
format: "cjs",
|
|
5528
|
+
target: "node18",
|
|
5529
|
+
sourcefile: absPath
|
|
5530
|
+
}).code;
|
|
5531
|
+
} catch (esbuildErr) {
|
|
5532
|
+
throw new Error(
|
|
5533
|
+
`Cannot import TypeScript file: ${filePath}
|
|
5534
|
+
esbuild (required to load .eval.ts files) failed to transpile it: ${esbuildErr.message}
|
|
5535
|
+
Or pre-compile .eval.ts to .eval.js before running.`
|
|
5536
|
+
);
|
|
5537
|
+
}
|
|
5538
|
+
module = runAsSyntheticCjs(cjsSource, absPath);
|
|
5343
5539
|
} else {
|
|
5344
5540
|
try {
|
|
5345
|
-
const fileUrl = pathToFileURL2(absPath).href
|
|
5541
|
+
const fileUrl = `${pathToFileURL2(absPath).href}?ah-reload=${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
5346
5542
|
module = await import(fileUrl);
|
|
5347
5543
|
} catch (err) {
|
|
5348
|
-
if (err.code === "ERR_UNKNOWN_FILE_EXTENSION" && absPath.endsWith(".ts")) {
|
|
5349
|
-
throw new Error(
|
|
5350
|
-
`Cannot import TypeScript file: ${filePath}
|
|
5351
|
-
Install tsx as a dependency: npm install tsx
|
|
5352
|
-
Or pre-compile .eval.ts to .eval.js before running.`
|
|
5353
|
-
);
|
|
5354
|
-
}
|
|
5355
5544
|
throw new Error(`Failed to import module: ${filePath}
|
|
5356
5545
|
${err.message}`);
|
|
5357
5546
|
}
|
|
@@ -5364,7 +5553,8 @@ ${err.message}`);
|
|
|
5364
5553
|
}
|
|
5365
5554
|
const loaded = testCases.map((tc) => ({
|
|
5366
5555
|
...tc,
|
|
5367
|
-
hash: computeTestCaseHash(tc, fileSource)
|
|
5556
|
+
hash: computeTestCaseHash(tc, fileSource),
|
|
5557
|
+
definition: captureTestDefinition(tc)
|
|
5368
5558
|
}));
|
|
5369
5559
|
const benchmarks = /* @__PURE__ */ new Map();
|
|
5370
5560
|
for (const tc of loaded) {
|
|
@@ -5377,13 +5567,15 @@ ${err.message}`);
|
|
|
5377
5567
|
const hooks = getRegisteredHooks(absPath);
|
|
5378
5568
|
return { testCases: loaded, filePath: absPath, benchmarks, hooks, fileSource };
|
|
5379
5569
|
}
|
|
5380
|
-
var CODE_EXTENSIONS;
|
|
5570
|
+
var CODE_EXTENSIONS, DEFINITION_BODY_SOURCE_MAX_CHARS, BODY_TRUNCATION_MARKER, esbuildTransformSyncPromise;
|
|
5381
5571
|
var init_loader2 = __esm({
|
|
5382
5572
|
"lib/testCases/loader.ts"() {
|
|
5383
5573
|
init_define();
|
|
5384
5574
|
init_authoringSurface();
|
|
5385
5575
|
init_utils();
|
|
5386
5576
|
CODE_EXTENSIONS = [".ts", ".js", ".mjs"];
|
|
5577
|
+
DEFINITION_BODY_SOURCE_MAX_CHARS = 32 * 1024;
|
|
5578
|
+
BODY_TRUNCATION_MARKER = "\n/* \u2026 truncated \u2014 see whole file \u2026 */";
|
|
5387
5579
|
}
|
|
5388
5580
|
});
|
|
5389
5581
|
|
|
@@ -5619,6 +5811,10 @@ async function startServer(options) {
|
|
|
5619
5811
|
if (err.code === "EADDRINUSE" && port <= options.port + MAX_PORT_ATTEMPTS) {
|
|
5620
5812
|
console.log(` Port ${port} is in use, trying ${port + 1}...`);
|
|
5621
5813
|
resolve10(tryListen(port + 1));
|
|
5814
|
+
} else if (err.code === "EADDRINUSE") {
|
|
5815
|
+
reject(new Error(
|
|
5816
|
+
`Port ${port} is in use and all fallback ports (${options.port}-${options.port + MAX_PORT_ATTEMPTS}) are occupied. Set AH_PORT=<available-port> to use a different port: \`AH_PORT=8001 npx @opensearch-project/agent-health\``
|
|
5817
|
+
));
|
|
5622
5818
|
} else {
|
|
5623
5819
|
reject(err);
|
|
5624
5820
|
}
|
|
@@ -6579,6 +6775,25 @@ var ApiClient = class {
|
|
|
6579
6775
|
}
|
|
6580
6776
|
return res.json();
|
|
6581
6777
|
}
|
|
6778
|
+
/**
|
|
6779
|
+
* Union test case ids into a benchmark's top-level testCaseIds AND its
|
|
6780
|
+
* current version's testCaseIds, in place (no version bump). Server-side
|
|
6781
|
+
* counterpart of services/benchmarkPromotion.ts:linkTestCaseIdsToBenchmark
|
|
6782
|
+
* — used by `benchmark repair-links --apply` to backfill benchmarks whose
|
|
6783
|
+
* current version's testCaseIds fell behind the top level.
|
|
6784
|
+
*/
|
|
6785
|
+
async linkBenchmarkTestCaseIds(id, testCaseIds) {
|
|
6786
|
+
const res = await fetch(`${this.baseUrl}/api/storage/benchmarks/${id}/link-test-case-ids`, {
|
|
6787
|
+
method: "POST",
|
|
6788
|
+
headers: { "Content-Type": "application/json" },
|
|
6789
|
+
body: JSON.stringify({ testCaseIds })
|
|
6790
|
+
});
|
|
6791
|
+
if (!res.ok) {
|
|
6792
|
+
const errorBody = await res.text();
|
|
6793
|
+
throw new Error(`Failed to link test case ids to benchmark: ${errorBody}`);
|
|
6794
|
+
}
|
|
6795
|
+
return res.json();
|
|
6796
|
+
}
|
|
6582
6797
|
/**
|
|
6583
6798
|
* Fetch traces from OpenSearch with optional filters
|
|
6584
6799
|
*/
|
|
@@ -7510,6 +7725,7 @@ function calculateRunStats(run, reports) {
|
|
|
7510
7725
|
let failed = 0;
|
|
7511
7726
|
let pending = 0;
|
|
7512
7727
|
let errored = 0;
|
|
7728
|
+
let notRun = 0;
|
|
7513
7729
|
let total = 0;
|
|
7514
7730
|
Object.entries(run.results || {}).forEach(([testCaseId, result]) => {
|
|
7515
7731
|
total++;
|
|
@@ -7517,7 +7733,11 @@ function calculateRunStats(run, reports) {
|
|
|
7517
7733
|
pending++;
|
|
7518
7734
|
return;
|
|
7519
7735
|
}
|
|
7520
|
-
if (result.status === "
|
|
7736
|
+
if (result.status === "cancelled") {
|
|
7737
|
+
notRun++;
|
|
7738
|
+
return;
|
|
7739
|
+
}
|
|
7740
|
+
if (result.status === "failed") {
|
|
7521
7741
|
failed++;
|
|
7522
7742
|
return;
|
|
7523
7743
|
}
|
|
@@ -7544,13 +7764,14 @@ function calculateRunStats(run, reports) {
|
|
|
7544
7764
|
pending++;
|
|
7545
7765
|
}
|
|
7546
7766
|
});
|
|
7547
|
-
const evaluable = Math.max(0, total - errored);
|
|
7767
|
+
const evaluable = Math.max(0, total - errored - notRun);
|
|
7548
7768
|
const passRate = evaluable > 0 ? Math.round(passed / evaluable * 100) : 0;
|
|
7549
7769
|
return {
|
|
7550
7770
|
passed,
|
|
7551
7771
|
failed,
|
|
7552
7772
|
pending,
|
|
7553
7773
|
errored,
|
|
7774
|
+
notRun,
|
|
7554
7775
|
total,
|
|
7555
7776
|
passRate
|
|
7556
7777
|
};
|
|
@@ -7734,7 +7955,8 @@ async function applyDoctorPlan(api, plan) {
|
|
|
7734
7955
|
return result;
|
|
7735
7956
|
}
|
|
7736
7957
|
async function migrateBenchmarksToImages(api, baseUrl, opts = {}) {
|
|
7737
|
-
const
|
|
7958
|
+
const dryRun = opts.dryRun !== false;
|
|
7959
|
+
const result = { dryRun, migrated: [], skipped: [], errors: [] };
|
|
7738
7960
|
let benchmarks2 = await api.listBenchmarks();
|
|
7739
7961
|
if (opts.benchmarkIds) {
|
|
7740
7962
|
const allow = new Set(opts.benchmarkIds);
|
|
@@ -7753,7 +7975,7 @@ async function migrateBenchmarksToImages(api, baseUrl, opts = {}) {
|
|
|
7753
7975
|
const res = await fetch(`${baseUrl}/api/storage/images`, {
|
|
7754
7976
|
method: "POST",
|
|
7755
7977
|
headers: { "Content-Type": "application/json" },
|
|
7756
|
-
body: JSON.stringify({ testCaseIds: b.testCaseIds, tags: [b.name] })
|
|
7978
|
+
body: JSON.stringify({ testCaseIds: b.testCaseIds, tags: [b.name], dryRun })
|
|
7757
7979
|
});
|
|
7758
7980
|
if (!res.ok) {
|
|
7759
7981
|
result.errors.push(`${b.name}: ${await res.text()}`);
|
|
@@ -7765,11 +7987,13 @@ async function migrateBenchmarksToImages(api, baseUrl, opts = {}) {
|
|
|
7765
7987
|
benchmarkId: b.id,
|
|
7766
7988
|
name: b.name,
|
|
7767
7989
|
digest: body.image.digest,
|
|
7990
|
+
...dryRun ? { alreadyExists: body.alreadyExists === true } : {},
|
|
7991
|
+
...dryRun && Array.isArray(body.wouldAddTags) && body.wouldAddTags.length > 0 ? { wouldAddTags: body.wouldAddTags } : {},
|
|
7768
7992
|
...missingTestCaseIds && missingTestCaseIds.length > 0 ? { missingTestCaseIds } : {}
|
|
7769
7993
|
});
|
|
7770
7994
|
if (missingTestCaseIds && missingTestCaseIds.length > 0) {
|
|
7771
7995
|
result.errors.push(
|
|
7772
|
-
`${b.name}: migrated from a PARTIAL test-case set \u2014 missing ${missingTestCaseIds.length} id(s): ${missingTestCaseIds.join(", ")}`
|
|
7996
|
+
`${b.name}: ${dryRun ? "would migrate" : "migrated"} from a PARTIAL test-case set \u2014 missing ${missingTestCaseIds.length} id(s): ${missingTestCaseIds.join(", ")}`
|
|
7773
7997
|
);
|
|
7774
7998
|
}
|
|
7775
7999
|
} catch (e) {
|
|
@@ -7816,14 +8040,14 @@ function printPlan(plan) {
|
|
|
7816
8040
|
console.log(chalk4.gray(" Runs and reports are never deleted.\n"));
|
|
7817
8041
|
}
|
|
7818
8042
|
function createBenchmarkDoctorCommand() {
|
|
7819
|
-
return new Command3("doctor").description("Detect and clean up duplicated / debris benchmarks (dry-run by default)").option("--dry-run", "Preview only \u2014 this is already the default; use --apply to execute").option("--apply", "Execute the plan (default: dry-run report only)").option("--migrate-images", "
|
|
8043
|
+
return new Command3("doctor").description("Detect and clean up duplicated / debris benchmarks (dry-run by default)").option("--dry-run", "Preview only \u2014 this is already the default; use --apply to execute").option("--apply", "Execute the plan (default: dry-run report only)").option("--migrate-images", "Preview (or, with --apply, execute) converting remaining benchmarks into tagged benchmark images").option("--json", "Output as JSON instead of the human-readable report", false).addHelpText("after", "\n Dry-run by default. Nothing is changed without --apply.\n").action(async (options) => {
|
|
7820
8044
|
if (options.dryRun && options.apply) {
|
|
7821
8045
|
console.error(chalk4.red("\n Error: --dry-run and --apply are mutually exclusive."));
|
|
7822
8046
|
console.error(chalk4.gray(" Dry-run is the default. Use --apply to execute changes.\n"));
|
|
7823
8047
|
process.exit(1);
|
|
7824
8048
|
}
|
|
7825
8049
|
const config = await loadConfig();
|
|
7826
|
-
const isReadOnly = !options.apply
|
|
8050
|
+
const isReadOnly = !options.apply;
|
|
7827
8051
|
config.server.readOnly = isReadOnly;
|
|
7828
8052
|
const serverResult = await ensureServer(config.server);
|
|
7829
8053
|
const cleanup = createServerCleanup(serverResult, false);
|
|
@@ -7854,18 +8078,23 @@ function createBenchmarkDoctorCommand() {
|
|
|
7854
8078
|
console.log(chalk4.gray(" Dry-run only. Re-run with --apply to execute.\n"));
|
|
7855
8079
|
}
|
|
7856
8080
|
if (options.migrateImages) {
|
|
7857
|
-
const migration = await migrateBenchmarksToImages(api, serverResult.baseUrl);
|
|
8081
|
+
const migration = await migrateBenchmarksToImages(api, serverResult.baseUrl, { dryRun: !options.apply });
|
|
7858
8082
|
if (isJson) {
|
|
7859
8083
|
jsonOutput.migration = migration;
|
|
7860
8084
|
} else {
|
|
7861
|
-
console.log(chalk4.bold(" Image migration:"));
|
|
8085
|
+
console.log(chalk4.bold(migration.dryRun ? " Image migration plan (dry-run):" : " Image migration:"));
|
|
7862
8086
|
for (const m2 of migration.migrated) {
|
|
7863
|
-
|
|
8087
|
+
const verb = migration.dryRun ? m2.alreadyExists ? m2.wouldAddTags?.length ? "= already an image, would add tag(s)" : "= already an image, no change" : "+ would create" : "\u2713";
|
|
8088
|
+
const tagSuffix = migration.dryRun && m2.wouldAddTags?.length ? ` [${m2.wouldAddTags.join(", ")}]` : "";
|
|
8089
|
+
console.log(chalk4.green(` ${verb} ${m2.name} \u2192 ${m2.digest.slice(0, 12)}${tagSuffix}`));
|
|
7864
8090
|
}
|
|
7865
8091
|
for (const s of migration.skipped) {
|
|
7866
8092
|
console.log(chalk4.gray(` - ${s.name}: skipped (${s.reason})`));
|
|
7867
8093
|
}
|
|
7868
8094
|
for (const err of migration.errors) console.log(chalk4.red(` ! ${err}`));
|
|
8095
|
+
if (migration.dryRun && migration.migrated.length > 0) {
|
|
8096
|
+
console.log(chalk4.gray(" Dry-run only. Re-run with --migrate-images --apply to execute."));
|
|
8097
|
+
}
|
|
7869
8098
|
console.log();
|
|
7870
8099
|
}
|
|
7871
8100
|
}
|
|
@@ -7882,6 +8111,60 @@ function createBenchmarkDoctorCommand() {
|
|
|
7882
8111
|
});
|
|
7883
8112
|
}
|
|
7884
8113
|
|
|
8114
|
+
// cli/utils/benchmarkDoctor.ts
|
|
8115
|
+
function computeBenchmarkRepairPlan(benchmark, evaluationRuns) {
|
|
8116
|
+
const existing = new Set(benchmark.testCaseIds || []);
|
|
8117
|
+
const missing = /* @__PURE__ */ new Set();
|
|
8118
|
+
const affectedRunIds = [];
|
|
8119
|
+
for (const run of evaluationRuns) {
|
|
8120
|
+
if (run.benchmarkId !== benchmark.id) continue;
|
|
8121
|
+
let runHasMissing = false;
|
|
8122
|
+
for (const snapshot of run.testCaseSnapshots || []) {
|
|
8123
|
+
if (snapshot?.id && !existing.has(snapshot.id)) {
|
|
8124
|
+
missing.add(snapshot.id);
|
|
8125
|
+
runHasMissing = true;
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
if (runHasMissing) affectedRunIds.push(run.id);
|
|
8129
|
+
}
|
|
8130
|
+
if (missing.size === 0) return null;
|
|
8131
|
+
return {
|
|
8132
|
+
benchmarkId: benchmark.id,
|
|
8133
|
+
benchmarkName: benchmark.name,
|
|
8134
|
+
missingTestCaseIds: Array.from(missing),
|
|
8135
|
+
affectedRunIds
|
|
8136
|
+
};
|
|
8137
|
+
}
|
|
8138
|
+
function applyRepairPlan(existingTestCaseIds, plan) {
|
|
8139
|
+
const existing = new Set(existingTestCaseIds || []);
|
|
8140
|
+
const result = [...existingTestCaseIds];
|
|
8141
|
+
for (const id of plan.missingTestCaseIds) {
|
|
8142
|
+
if (!existing.has(id)) {
|
|
8143
|
+
existing.add(id);
|
|
8144
|
+
result.push(id);
|
|
8145
|
+
}
|
|
8146
|
+
}
|
|
8147
|
+
return result;
|
|
8148
|
+
}
|
|
8149
|
+
function computeVersionLinkRepairPlan(benchmark) {
|
|
8150
|
+
const topLevelIds = benchmark.testCaseIds || [];
|
|
8151
|
+
if (topLevelIds.length === 0) return null;
|
|
8152
|
+
const versions = benchmark.versions || [];
|
|
8153
|
+
if (versions.length === 0) return null;
|
|
8154
|
+
const currentVersion = benchmark.currentVersion ?? 1;
|
|
8155
|
+
const currentEntry = versions.find((v) => v.version === currentVersion) || versions[versions.length - 1];
|
|
8156
|
+
const existing = new Set(currentEntry?.testCaseIds || []);
|
|
8157
|
+
const missing = topLevelIds.filter((id) => !existing.has(id));
|
|
8158
|
+
if (missing.length === 0) return null;
|
|
8159
|
+
return {
|
|
8160
|
+
benchmarkId: benchmark.id,
|
|
8161
|
+
benchmarkName: benchmark.name,
|
|
8162
|
+
currentVersion: currentEntry?.version ?? currentVersion,
|
|
8163
|
+
missingTestCaseIds: missing,
|
|
8164
|
+
needsManualReview: versions.length > 1
|
|
8165
|
+
};
|
|
8166
|
+
}
|
|
8167
|
+
|
|
7885
8168
|
// cli/commands/benchmark.ts
|
|
7886
8169
|
function findAgent2(identifier, config) {
|
|
7887
8170
|
return config.agents.find(
|
|
@@ -7891,6 +8174,16 @@ function findAgent2(identifier, config) {
|
|
|
7891
8174
|
function getDefaultModel2(config) {
|
|
7892
8175
|
return Object.keys(config.models)[0] || "claude-sonnet";
|
|
7893
8176
|
}
|
|
8177
|
+
function resolveDefaultAgentKey(config) {
|
|
8178
|
+
const enabledAgent = config.agents.find((a) => a.enabled !== false);
|
|
8179
|
+
if (!enabledAgent) {
|
|
8180
|
+
console.error(chalk5.red(" Error: No enabled agents found."));
|
|
8181
|
+
process.exit(1);
|
|
8182
|
+
throw new Error("No enabled agents found (process.exit(1) did not terminate the process).");
|
|
8183
|
+
}
|
|
8184
|
+
console.log(chalk5.gray(` Agent: ${enabledAgent.name} (default)`));
|
|
8185
|
+
return enabledAgent.key;
|
|
8186
|
+
}
|
|
7894
8187
|
function isFilePath(value) {
|
|
7895
8188
|
return value.toLowerCase().endsWith(".json") || isCodeFile(value);
|
|
7896
8189
|
}
|
|
@@ -8215,13 +8508,7 @@ async function runUnifiedMode(options, config, serverConfig, isCI, fileArray) {
|
|
|
8215
8508
|
const api = new ApiClient(serverResult.baseUrl);
|
|
8216
8509
|
let agentKey;
|
|
8217
8510
|
if (options.agent.length === 0) {
|
|
8218
|
-
|
|
8219
|
-
if (!enabledAgent) {
|
|
8220
|
-
console.error(chalk5.red(" Error: No enabled agents found."));
|
|
8221
|
-
process.exit(1);
|
|
8222
|
-
}
|
|
8223
|
-
agentKey = enabledAgent.key;
|
|
8224
|
-
console.log(chalk5.gray(` Agent: ${enabledAgent.name} (default)`));
|
|
8511
|
+
agentKey = resolveDefaultAgentKey(config);
|
|
8225
8512
|
} else {
|
|
8226
8513
|
agentKey = options.agent[0];
|
|
8227
8514
|
console.log(chalk5.gray(` Agent: ${agentKey}`));
|
|
@@ -8488,6 +8775,10 @@ function createBenchmarkCommand() {
|
|
|
8488
8775
|
sourceCode: loaded2.fileSource,
|
|
8489
8776
|
sourceFileName,
|
|
8490
8777
|
sourceLanguage,
|
|
8778
|
+
// Per-test slice of the above: THIS test's resolved
|
|
8779
|
+
// options + evaluate body. Lets the run report show one
|
|
8780
|
+
// test's definition instead of the whole file.
|
|
8781
|
+
definition: tc.definition,
|
|
8491
8782
|
description: tc.options.description,
|
|
8492
8783
|
// Forward expectedOutcomes / expectedTrajectory — see
|
|
8493
8784
|
// services/sourceResolver.ts for rationale. Without
|
|
@@ -8755,8 +9046,106 @@ function createBenchmarkCommand() {
|
|
|
8755
9046
|
cleanup();
|
|
8756
9047
|
}
|
|
8757
9048
|
});
|
|
9049
|
+
command.addCommand(createBenchmarkLinkRepairCommand());
|
|
8758
9050
|
return command;
|
|
8759
9051
|
}
|
|
9052
|
+
function createBenchmarkLinkRepairCommand() {
|
|
9053
|
+
return new Command4("repair-links").description("Detect benchmarks whose testCaseIds are out of sync (run-referenced ids missing from testCaseIds, or testCaseIds missing from the current version) \u2014 use --apply to fix").option("--apply", "Apply the repair (default: dry-run, report only)").option("--stop-server", "Stop the server after the check completes (default: keep running)").action(async (options) => {
|
|
9054
|
+
console.log(chalk5.bold("\nAgent Health - Benchmark Link Repair\n"));
|
|
9055
|
+
const config = await loadConfig();
|
|
9056
|
+
const serverConfig = { ...DEFAULT_SERVER_CONFIG, ...config.server };
|
|
9057
|
+
const isCI = !!process.env.CI;
|
|
9058
|
+
const connectSpinner = ora2("Connecting to server...").start();
|
|
9059
|
+
let serverResult;
|
|
9060
|
+
let cleanup;
|
|
9061
|
+
try {
|
|
9062
|
+
serverResult = await ensureServer(serverConfig);
|
|
9063
|
+
cleanup = createServerCleanup(serverResult, isCI || options.stopServer);
|
|
9064
|
+
connectSpinner.succeed(serverResult.wasStarted ? `Started server on port ${serverConfig.port}` : `Connected to existing server on port ${serverConfig.port}`);
|
|
9065
|
+
} catch (error) {
|
|
9066
|
+
connectSpinner.fail(`Failed to connect: ${error instanceof Error ? error.message : error}`);
|
|
9067
|
+
process.exit(1);
|
|
9068
|
+
}
|
|
9069
|
+
const api = new ApiClient(serverResult.baseUrl);
|
|
9070
|
+
try {
|
|
9071
|
+
const benchmarks2 = await api.listBenchmarks();
|
|
9072
|
+
let shellFlagged = 0;
|
|
9073
|
+
let shellRepaired = 0;
|
|
9074
|
+
let versionFlagged = 0;
|
|
9075
|
+
let versionRepaired = 0;
|
|
9076
|
+
let versionNeedsReview = 0;
|
|
9077
|
+
for (let bm of benchmarks2) {
|
|
9078
|
+
let evaluationRuns = [];
|
|
9079
|
+
try {
|
|
9080
|
+
const res = await fetch(
|
|
9081
|
+
`${serverResult.baseUrl}/api/storage/evaluation-runs?benchmarkId=${encodeURIComponent(bm.id)}&size=500`
|
|
9082
|
+
);
|
|
9083
|
+
if (res.ok) {
|
|
9084
|
+
const data = await res.json();
|
|
9085
|
+
evaluationRuns = data.evaluationRuns || [];
|
|
9086
|
+
}
|
|
9087
|
+
} catch {
|
|
9088
|
+
}
|
|
9089
|
+
if (evaluationRuns.length > 0) {
|
|
9090
|
+
const shellPlan = computeBenchmarkRepairPlan(bm, evaluationRuns);
|
|
9091
|
+
if (shellPlan) {
|
|
9092
|
+
shellFlagged++;
|
|
9093
|
+
console.log("");
|
|
9094
|
+
console.log(chalk5.yellow(` Stale shell: ${shellPlan.benchmarkName} (${shellPlan.benchmarkId})`));
|
|
9095
|
+
console.log(chalk5.gray(` ${shellPlan.missingTestCaseIds.length} test case id(s) missing from testCaseIds, referenced by ${shellPlan.affectedRunIds.length} run(s)`));
|
|
9096
|
+
console.log(chalk5.gray(` Missing: ${shellPlan.missingTestCaseIds.join(", ")}`));
|
|
9097
|
+
if (options.apply) {
|
|
9098
|
+
const newTestCaseIds = applyRepairPlan(bm.testCaseIds || [], shellPlan);
|
|
9099
|
+
await api.updateBenchmark(bm.id, { testCaseIds: newTestCaseIds });
|
|
9100
|
+
shellRepaired++;
|
|
9101
|
+
console.log(chalk5.green(` \u2713 Repaired: testCaseIds now has ${newTestCaseIds.length} id(s)`));
|
|
9102
|
+
const refreshed = await api.getBenchmark(bm.id);
|
|
9103
|
+
if (refreshed) bm = refreshed;
|
|
9104
|
+
}
|
|
9105
|
+
}
|
|
9106
|
+
}
|
|
9107
|
+
const versionPlan = computeVersionLinkRepairPlan(bm);
|
|
9108
|
+
if (versionPlan) {
|
|
9109
|
+
versionFlagged++;
|
|
9110
|
+
console.log("");
|
|
9111
|
+
console.log(chalk5.yellow(` Stale version: ${versionPlan.benchmarkName} (${versionPlan.benchmarkId})`));
|
|
9112
|
+
console.log(chalk5.gray(` v${versionPlan.currentVersion}'s testCaseIds is missing ${versionPlan.missingTestCaseIds.length} id(s) present at the top level`));
|
|
9113
|
+
console.log(chalk5.gray(` Missing: ${versionPlan.missingTestCaseIds.join(", ")}`));
|
|
9114
|
+
if (versionPlan.needsManualReview) {
|
|
9115
|
+
versionNeedsReview++;
|
|
9116
|
+
console.log(chalk5.red(" \u26A0 Skipped: this benchmark has more than one version. The top-level"));
|
|
9117
|
+
console.log(chalk5.red(" testCaseIds can contain ids from an OLDER version's runs (see"));
|
|
9118
|
+
console.log(chalk5.red(" cli/utils/benchmarkDoctor.ts) -- copying them into the CURRENT"));
|
|
9119
|
+
console.log(chalk5.red(" version could mix in test cases that do not belong to it. Review"));
|
|
9120
|
+
console.log(chalk5.red(` manually: POST /api/storage/benchmarks/${versionPlan.benchmarkId}/link-test-case-ids`));
|
|
9121
|
+
console.log(chalk5.red(" with the exact ids you intend for the current version, or edit the"));
|
|
9122
|
+
console.log(chalk5.red(" benchmark in the UI."));
|
|
9123
|
+
} else if (options.apply) {
|
|
9124
|
+
const { benchmark: repaired } = await api.linkBenchmarkTestCaseIds(bm.id, bm.testCaseIds || []);
|
|
9125
|
+
versionRepaired++;
|
|
9126
|
+
const fixedVersion = (repaired.versions || []).find((v) => v.version === (repaired.currentVersion ?? versionPlan.currentVersion));
|
|
9127
|
+
console.log(chalk5.green(` \u2713 Repaired: v${versionPlan.currentVersion}'s testCaseIds now has ${fixedVersion?.testCaseIds?.length ?? "?"} id(s)`));
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
9130
|
+
}
|
|
9131
|
+
const flagged = shellFlagged + versionFlagged;
|
|
9132
|
+
console.log("");
|
|
9133
|
+
if (flagged === 0) {
|
|
9134
|
+
console.log(chalk5.green(" All benchmarks healthy \u2014 no missing test case links found."));
|
|
9135
|
+
} else if (options.apply) {
|
|
9136
|
+
console.log(chalk5.green(` Repaired ${shellRepaired}/${shellFlagged} stale shell benchmark(s), ${versionRepaired}/${versionFlagged - versionNeedsReview} stale version benchmark(s).`));
|
|
9137
|
+
if (versionNeedsReview > 0) {
|
|
9138
|
+
console.log(chalk5.red(` ${versionNeedsReview} multi-version benchmark(s) need manual review (not auto-repaired) -- see above.`));
|
|
9139
|
+
}
|
|
9140
|
+
} else {
|
|
9141
|
+
console.log(chalk5.yellow(` Found ${shellFlagged} stale shell benchmark(s), ${versionFlagged} stale version benchmark(s) (${versionNeedsReview} of which need manual review, not auto-fixable).`));
|
|
9142
|
+
console.log(chalk5.gray(" Re-run with --apply to fix the auto-fixable ones."));
|
|
9143
|
+
}
|
|
9144
|
+
} finally {
|
|
9145
|
+
cleanup();
|
|
9146
|
+
}
|
|
9147
|
+
});
|
|
9148
|
+
}
|
|
8760
9149
|
|
|
8761
9150
|
// cli/commands/export.ts
|
|
8762
9151
|
init_config2();
|
|
@@ -8834,6 +9223,7 @@ function createReportCommand() {
|
|
|
8834
9223
|
`Failed to connect to server: ${error instanceof Error ? error.message : error}`
|
|
8835
9224
|
);
|
|
8836
9225
|
process.exit(1);
|
|
9226
|
+
return;
|
|
8837
9227
|
}
|
|
8838
9228
|
const api = new ApiClient(serverResult.baseUrl);
|
|
8839
9229
|
try {
|
|
@@ -8846,6 +9236,7 @@ function createReportCommand() {
|
|
|
8846
9236
|
console.log(chalk7.gray(" npx agent-health list benchmarks"));
|
|
8847
9237
|
console.log("");
|
|
8848
9238
|
process.exit(1);
|
|
9239
|
+
return;
|
|
8849
9240
|
}
|
|
8850
9241
|
spinner.succeed(`Found benchmark: ${benchmark.name} (${benchmark.id})`);
|
|
8851
9242
|
const params = new URLSearchParams({ format: options.format });
|
|
@@ -8859,6 +9250,7 @@ function createReportCommand() {
|
|
|
8859
9250
|
const errorBody = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
8860
9251
|
reportSpinner.fail(`Report generation failed: ${errorBody.error}`);
|
|
8861
9252
|
process.exit(1);
|
|
9253
|
+
return;
|
|
8862
9254
|
}
|
|
8863
9255
|
const contentDisposition = response.headers.get("content-disposition") || "";
|
|
8864
9256
|
const filenameMatch = contentDisposition.match(/filename="([^"]+)"/);
|