@opensearch-project/agent-health 0.3.0 → 0.4.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/README.md +43 -2
- package/cli/dist/index.js +1300 -112
- package/deployment/cloudformation/agent-health-observability.yaml +571 -0
- package/dist/assets/index-BOIP5L7h.js +246 -0
- package/dist/assets/index-CU9YKpAL.css +1 -0
- package/dist/index.html +2 -2
- package/lib/dist/config/index.js +50 -2
- package/lib/dist/index.js +66 -6
- package/package.json +25 -8
- package/server/dist/app.js +1276 -101
- package/server/dist/index.js +1429 -130
- package/dist/assets/index-EvPLSTAS.js +0 -267
- package/dist/assets/index-RXasQKUs.css +0 -1
package/server/dist/index.js
CHANGED
|
@@ -593,7 +593,8 @@ var init_claudeCode = __esm({
|
|
|
593
593
|
if (s.session_completed) completedSessions++;
|
|
594
594
|
const toolCallCount = Object.values(s.tool_counts).reduce((a, b) => a + b, 0);
|
|
595
595
|
totalToolCalls += toolCallCount;
|
|
596
|
-
const
|
|
596
|
+
const _d = new Date(s.start_time);
|
|
597
|
+
const date = `${_d.getFullYear()}-${String(_d.getMonth() + 1).padStart(2, "0")}-${String(_d.getDate()).padStart(2, "0")}`;
|
|
597
598
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
598
599
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
599
600
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -1487,7 +1488,8 @@ var init_kiro = __esm({
|
|
|
1487
1488
|
if (s.session_completed) completedSessions++;
|
|
1488
1489
|
const toolCallCount = Object.values(s.tool_counts).reduce((a, b) => a + b, 0);
|
|
1489
1490
|
totalToolCalls += toolCallCount;
|
|
1490
|
-
const
|
|
1491
|
+
const _d = new Date(s.start_time);
|
|
1492
|
+
const date = `${_d.getFullYear()}-${String(_d.getMonth() + 1).padStart(2, "0")}-${String(_d.getDate()).padStart(2, "0")}`;
|
|
1491
1493
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
1492
1494
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
1493
1495
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -1837,7 +1839,8 @@ var init_codex = __esm({
|
|
|
1837
1839
|
if (s.session_completed) completedSessions++;
|
|
1838
1840
|
const toolCallCount = Object.values(s.tool_counts).reduce((a, b) => a + b, 0);
|
|
1839
1841
|
totalToolCalls += toolCallCount;
|
|
1840
|
-
const
|
|
1842
|
+
const _d = new Date(s.start_time);
|
|
1843
|
+
const date = `${_d.getFullYear()}-${String(_d.getMonth() + 1).padStart(2, "0")}-${String(_d.getDate()).padStart(2, "0")}`;
|
|
1841
1844
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
1842
1845
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
1843
1846
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -2292,7 +2295,7 @@ function localDateStr(d) {
|
|
|
2292
2295
|
function filterByDate(sessions, range) {
|
|
2293
2296
|
if (!range?.from && !range?.to) return sessions;
|
|
2294
2297
|
return sessions.filter((s) => {
|
|
2295
|
-
const date = s.start_time
|
|
2298
|
+
const date = localDateStr(new Date(s.start_time));
|
|
2296
2299
|
if (range.from && date < range.from) return false;
|
|
2297
2300
|
if (range.to && date > range.to) return false;
|
|
2298
2301
|
return true;
|
|
@@ -2318,7 +2321,7 @@ function computeStatsFromSessions(sessions, agent) {
|
|
|
2318
2321
|
if (s.session_completed) completedSessions++;
|
|
2319
2322
|
const toolCallCount = Object.values(s.tool_counts).reduce((a, b) => a + b, 0);
|
|
2320
2323
|
totalToolCalls += toolCallCount;
|
|
2321
|
-
const date = s.start_time
|
|
2324
|
+
const date = localDateStr(new Date(s.start_time));
|
|
2322
2325
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
2323
2326
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
2324
2327
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -2496,7 +2499,7 @@ var init_registry = __esm({
|
|
|
2496
2499
|
}
|
|
2497
2500
|
const dailyCostMap = /* @__PURE__ */ new Map();
|
|
2498
2501
|
for (const s of sessions) {
|
|
2499
|
-
const date = s.start_time
|
|
2502
|
+
const date = localDateStr(new Date(s.start_time));
|
|
2500
2503
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue;
|
|
2501
2504
|
const key = `${date}:${s.agent}`;
|
|
2502
2505
|
const existing = dailyCostMap.get(key) ?? { date, cost: 0, agent: s.agent };
|
|
@@ -2524,7 +2527,7 @@ var init_registry = __esm({
|
|
|
2524
2527
|
if (!s.start_time) continue;
|
|
2525
2528
|
const d = new Date(s.start_time);
|
|
2526
2529
|
if (isNaN(d.getTime())) continue;
|
|
2527
|
-
activeDates.add(s.start_time
|
|
2530
|
+
activeDates.add(localDateStr(new Date(s.start_time)));
|
|
2528
2531
|
dowCounts[d.getDay()]++;
|
|
2529
2532
|
hourCounts[d.getHours()]++;
|
|
2530
2533
|
}
|
|
@@ -2551,7 +2554,7 @@ var init_registry = __esm({
|
|
|
2551
2554
|
}
|
|
2552
2555
|
const dailyMap = /* @__PURE__ */ new Map();
|
|
2553
2556
|
for (const s of sessions) {
|
|
2554
|
-
const date = s.start_time
|
|
2557
|
+
const date = localDateStr(new Date(s.start_time));
|
|
2555
2558
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue;
|
|
2556
2559
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
2557
2560
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -2670,7 +2673,7 @@ var init_registry = __esm({
|
|
|
2670
2673
|
const totalDuration = data.sessions.reduce((s, sess) => s + sess.duration_minutes, 0);
|
|
2671
2674
|
const dailyCostMap = /* @__PURE__ */ new Map();
|
|
2672
2675
|
for (const s of data.sessions) {
|
|
2673
|
-
const date = s.start_time
|
|
2676
|
+
const date = localDateStr(new Date(s.start_time));
|
|
2674
2677
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue;
|
|
2675
2678
|
const key = `${date}:${s.agent}`;
|
|
2676
2679
|
const existing = dailyCostMap.get(key) ?? { date, cost: 0, agent: s.agent };
|
|
@@ -2879,7 +2882,7 @@ var init_registry = __esm({
|
|
|
2879
2882
|
const totalToolErrors = userSessions.reduce((s, sess) => s + sess.total_tool_errors, 0);
|
|
2880
2883
|
const totalDuration = userSessions.reduce((s, sess) => s + sess.duration_minutes, 0);
|
|
2881
2884
|
const agents = [...new Set(userSessions.map((s) => s.agent))];
|
|
2882
|
-
const activeDates = new Set(userSessions.map((s) => s.start_time
|
|
2885
|
+
const activeDates = new Set(userSessions.map((s) => localDateStr(new Date(s.start_time))));
|
|
2883
2886
|
const projectCounts = /* @__PURE__ */ new Map();
|
|
2884
2887
|
for (const s of userSessions) {
|
|
2885
2888
|
projectCounts.set(s.project_path, (projectCounts.get(s.project_path) ?? 0) + 1);
|
|
@@ -3011,9 +3014,10 @@ var init_remoteAggregator = __esm({
|
|
|
3011
3014
|
filterByRange(sessions, range) {
|
|
3012
3015
|
if (!range?.from && !range?.to) return sessions;
|
|
3013
3016
|
return sessions.filter((s) => {
|
|
3014
|
-
const
|
|
3015
|
-
|
|
3016
|
-
if (range.
|
|
3017
|
+
const y = new Date(s.start_time);
|
|
3018
|
+
const date = `${y.getFullYear()}-${String(y.getMonth() + 1).padStart(2, "0")}-${String(y.getDate()).padStart(2, "0")}`;
|
|
3019
|
+
if (range.from && date < range.from) return false;
|
|
3020
|
+
if (range.to && date > range.to) return false;
|
|
3017
3021
|
return true;
|
|
3018
3022
|
});
|
|
3019
3023
|
}
|
|
@@ -3161,6 +3165,8 @@ var init_config = __esm({
|
|
|
3161
3165
|
// OpenAI-compatible (optional - for OpenAI-compatible judge/agent endpoints)
|
|
3162
3166
|
openaiCompatibleApiKey: getEnvVar("OPENAI_COMPATIBLE_API_KEY", ""),
|
|
3163
3167
|
openaiCompatibleEndpoint: getEnvVar("OPENAI_COMPATIBLE_ENDPOINT", "http://localhost:4000/v1/chat/completions"),
|
|
3168
|
+
// Observio Sample Agent endpoint
|
|
3169
|
+
observioEndpoint: getEnvVar("OBSERVIO_ENDPOINT", "http://localhost:3001/run-agent"),
|
|
3164
3170
|
// Claude Code Telemetry (optional - for OTEL traces from Claude Code)
|
|
3165
3171
|
claudeCodeTelemetryEnabled: getEnvVar("CLAUDE_CODE_TELEMETRY_ENABLED", "false") === "true",
|
|
3166
3172
|
otelExporterEndpoint: getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT", ""),
|
|
@@ -3225,6 +3231,16 @@ var init_constants = __esm({
|
|
|
3225
3231
|
description: "Invokes the Claude Code CLI. Server-only \u2014 use the CLI or benchmark runner.",
|
|
3226
3232
|
serverOnly: true
|
|
3227
3233
|
},
|
|
3234
|
+
"strands": {
|
|
3235
|
+
label: "Amazon Strands",
|
|
3236
|
+
description: "Amazon Strands agent framework via Bedrock Agent Runtime API. Server-only \u2014 requires AWS SDK.",
|
|
3237
|
+
serverOnly: true
|
|
3238
|
+
},
|
|
3239
|
+
"langgraph": {
|
|
3240
|
+
label: "LangGraph (REST)",
|
|
3241
|
+
description: "LangGraph agent via direct REST API. Use for non-AG-UI LangGraph instances.",
|
|
3242
|
+
serverOnly: false
|
|
3243
|
+
},
|
|
3228
3244
|
"mock": {
|
|
3229
3245
|
label: "Mock",
|
|
3230
3246
|
description: "Built-in demo agent for testing. No real endpoint needed.",
|
|
@@ -3244,6 +3260,15 @@ var init_constants = __esm({
|
|
|
3244
3260
|
headers: {},
|
|
3245
3261
|
useTraces: false
|
|
3246
3262
|
},
|
|
3263
|
+
{
|
|
3264
|
+
key: "observio",
|
|
3265
|
+
name: "Observio Sample Agent",
|
|
3266
|
+
endpoint: ENV_CONFIG.observioEndpoint || "http://localhost:3001/run-agent",
|
|
3267
|
+
description: "Observio sample agent \u2014 ReAct pattern with LangGraph and Bedrock. Start with: cd observio-sample-agent && npm run start:ag-ui",
|
|
3268
|
+
connectorType: "agui-streaming",
|
|
3269
|
+
headers: {},
|
|
3270
|
+
useTraces: true
|
|
3271
|
+
},
|
|
3247
3272
|
{
|
|
3248
3273
|
key: "claude-code",
|
|
3249
3274
|
name: "Claude Code",
|
|
@@ -3253,6 +3278,30 @@ var init_constants = __esm({
|
|
|
3253
3278
|
headers: {},
|
|
3254
3279
|
useTraces: ENV_CONFIG.claudeCodeTelemetryEnabled && !!ENV_CONFIG.otelExporterEndpoint,
|
|
3255
3280
|
connectorConfig: { env: getClaudeCodeConnectorEnv() }
|
|
3281
|
+
},
|
|
3282
|
+
{
|
|
3283
|
+
key: "strands",
|
|
3284
|
+
name: "Amazon Strands",
|
|
3285
|
+
endpoint: "${STRANDS_AGENT_ID}",
|
|
3286
|
+
description: "Amazon Strands agent framework (Bedrock Agent Runtime)",
|
|
3287
|
+
connectorType: "strands",
|
|
3288
|
+
headers: {},
|
|
3289
|
+
useTraces: false,
|
|
3290
|
+
connectorConfig: {
|
|
3291
|
+
agentAliasId: "${STRANDS_ALIAS_ID:-TSTALIASID}",
|
|
3292
|
+
region: "${AWS_REGION:-us-east-1}"
|
|
3293
|
+
},
|
|
3294
|
+
enabled: false
|
|
3295
|
+
},
|
|
3296
|
+
{
|
|
3297
|
+
key: "langgraph-rest",
|
|
3298
|
+
name: "LangGraph (REST)",
|
|
3299
|
+
endpoint: "${LANGGRAPH_API_ENDPOINT:-http://localhost:8000}",
|
|
3300
|
+
description: "LangGraph agent via direct REST API",
|
|
3301
|
+
connectorType: "langgraph",
|
|
3302
|
+
headers: {},
|
|
3303
|
+
useTraces: false,
|
|
3304
|
+
enabled: false
|
|
3256
3305
|
}
|
|
3257
3306
|
],
|
|
3258
3307
|
models: {
|
|
@@ -3436,6 +3485,7 @@ function mergeConfigs(userConfig, defaultConfig) {
|
|
|
3436
3485
|
...DEFAULT_SERVER_CONFIG,
|
|
3437
3486
|
...userConfig.server
|
|
3438
3487
|
};
|
|
3488
|
+
const telemetry = userConfig.telemetry ?? {};
|
|
3439
3489
|
return {
|
|
3440
3490
|
server,
|
|
3441
3491
|
agents,
|
|
@@ -3443,7 +3493,8 @@ function mergeConfigs(userConfig, defaultConfig) {
|
|
|
3443
3493
|
connectors,
|
|
3444
3494
|
testCases,
|
|
3445
3495
|
reporters,
|
|
3446
|
-
judge
|
|
3496
|
+
judge,
|
|
3497
|
+
telemetry
|
|
3447
3498
|
};
|
|
3448
3499
|
}
|
|
3449
3500
|
async function loadUserConfig(configPath) {
|
|
@@ -3485,7 +3536,8 @@ function loadConfigSync(cwd = process.cwd()) {
|
|
|
3485
3536
|
connectors: [],
|
|
3486
3537
|
testCases: [],
|
|
3487
3538
|
reporters: [["console"]],
|
|
3488
|
-
judge: { provider: "bedrock", model: "claude-sonnet-4" }
|
|
3539
|
+
judge: { provider: "bedrock", model: "claude-sonnet-4" },
|
|
3540
|
+
telemetry: {}
|
|
3489
3541
|
};
|
|
3490
3542
|
}
|
|
3491
3543
|
function clearConfigCache() {
|
|
@@ -4186,15 +4238,15 @@ router2.post("/api/judge", async (req, res) => {
|
|
|
4186
4238
|
if (!modelConfig) {
|
|
4187
4239
|
modelConfig = Object.values(config2.models).find((m) => m.model_id === modelId);
|
|
4188
4240
|
}
|
|
4189
|
-
const
|
|
4241
|
+
const provider2 = modelConfig?.provider || "bedrock";
|
|
4190
4242
|
const resolvedModelId = modelConfig?.model_id || modelId;
|
|
4191
|
-
debug("JudgeAPI", "Using provider:",
|
|
4192
|
-
if (
|
|
4243
|
+
debug("JudgeAPI", "Using provider:", provider2, "model:", resolvedModelId);
|
|
4244
|
+
if (provider2 === "demo") {
|
|
4193
4245
|
debug("JudgeAPI", "Demo provider - returning mock evaluation");
|
|
4194
4246
|
const mockResult = generateMockEvaluation(trajectory, expectedOutcomes);
|
|
4195
4247
|
return res.json(mockResult);
|
|
4196
4248
|
}
|
|
4197
|
-
if (
|
|
4249
|
+
if (provider2 === "openai-compatible") {
|
|
4198
4250
|
debug("JudgeAPI", "OpenAI-compatible provider - calling endpoint");
|
|
4199
4251
|
const result2 = await evaluateWithOpenAICompatible(
|
|
4200
4252
|
{ trajectory, expectedOutcomes, expectedTrajectory, logs },
|
|
@@ -4211,7 +4263,7 @@ router2.post("/api/judge", async (req, res) => {
|
|
|
4211
4263
|
res.json(result);
|
|
4212
4264
|
} catch (error) {
|
|
4213
4265
|
console.error("[JudgeAPI] Error during evaluation:", error);
|
|
4214
|
-
const
|
|
4266
|
+
const provider2 = (() => {
|
|
4215
4267
|
try {
|
|
4216
4268
|
const config2 = loadConfigSync();
|
|
4217
4269
|
const { modelId } = req.body;
|
|
@@ -4221,7 +4273,7 @@ router2.post("/api/judge", async (req, res) => {
|
|
|
4221
4273
|
return "bedrock";
|
|
4222
4274
|
}
|
|
4223
4275
|
})();
|
|
4224
|
-
const errorMessage =
|
|
4276
|
+
const errorMessage = provider2 === "openai-compatible" ? parseOpenAICompatibleError(error) : parseBedrockError(error);
|
|
4225
4277
|
res.status(500).json({
|
|
4226
4278
|
error: `Judge evaluation failed: ${errorMessage}`,
|
|
4227
4279
|
details: error.message
|
|
@@ -4512,13 +4564,13 @@ function getCustomAgents() {
|
|
|
4512
4564
|
}
|
|
4513
4565
|
|
|
4514
4566
|
// lib/hooks.ts
|
|
4515
|
-
async function executeBeforeRequestHook(hooks,
|
|
4567
|
+
async function executeBeforeRequestHook(hooks, context2, agentKey) {
|
|
4516
4568
|
if (!hooks?.beforeRequest) {
|
|
4517
|
-
return
|
|
4569
|
+
return context2;
|
|
4518
4570
|
}
|
|
4519
4571
|
debug("Hooks", `Executing beforeRequest hook for agent "${agentKey}"`);
|
|
4520
4572
|
try {
|
|
4521
|
-
const result = await hooks.beforeRequest(
|
|
4573
|
+
const result = await hooks.beforeRequest(context2);
|
|
4522
4574
|
if (!result || typeof result !== "object") {
|
|
4523
4575
|
throw new Error("beforeRequest hook must return an object with endpoint, payload, and headers");
|
|
4524
4576
|
}
|
|
@@ -4538,12 +4590,12 @@ async function executeBeforeRequestHook(hooks, context, agentKey) {
|
|
|
4538
4590
|
throw new Error(`beforeRequest hook failed for agent "${agentKey}": ${message}`);
|
|
4539
4591
|
}
|
|
4540
4592
|
}
|
|
4541
|
-
async function executeAfterResponseHook(hooks,
|
|
4593
|
+
async function executeAfterResponseHook(hooks, context2, agentKey) {
|
|
4542
4594
|
if (!hooks?.afterResponse) {
|
|
4543
|
-
return
|
|
4595
|
+
return context2;
|
|
4544
4596
|
}
|
|
4545
4597
|
try {
|
|
4546
|
-
const result = await hooks.afterResponse(
|
|
4598
|
+
const result = await hooks.afterResponse(context2);
|
|
4547
4599
|
if (!result || typeof result !== "object") {
|
|
4548
4600
|
throw new Error("afterResponse hook must return an object with response, trajectory, and optional runId");
|
|
4549
4601
|
}
|
|
@@ -4559,12 +4611,12 @@ async function executeAfterResponseHook(hooks, context, agentKey) {
|
|
|
4559
4611
|
throw new Error(`afterResponse hook failed for agent "${agentKey}": ${message}`);
|
|
4560
4612
|
}
|
|
4561
4613
|
}
|
|
4562
|
-
async function executeBuildTrajectoryHook(hooks,
|
|
4614
|
+
async function executeBuildTrajectoryHook(hooks, context2, agentKey) {
|
|
4563
4615
|
if (!hooks?.buildTrajectory) {
|
|
4564
4616
|
return null;
|
|
4565
4617
|
}
|
|
4566
4618
|
try {
|
|
4567
|
-
const result = await hooks.buildTrajectory(
|
|
4619
|
+
const result = await hooks.buildTrajectory(context2);
|
|
4568
4620
|
if (result !== null && !Array.isArray(result)) {
|
|
4569
4621
|
throw new Error("buildTrajectory hook must return an array of TrajectoryStep objects or null");
|
|
4570
4622
|
}
|
|
@@ -4615,6 +4667,36 @@ var agent_default = router3;
|
|
|
4615
4667
|
import { Router as Router4 } from "express";
|
|
4616
4668
|
|
|
4617
4669
|
// server/services/tracesService.ts
|
|
4670
|
+
function classifyOpenSearchError(error) {
|
|
4671
|
+
const statusCode = error.meta?.statusCode;
|
|
4672
|
+
const message = error.message || "Unknown error";
|
|
4673
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
4674
|
+
return {
|
|
4675
|
+
category: "auth",
|
|
4676
|
+
message: `Authentication failed (HTTP ${statusCode})`,
|
|
4677
|
+
suggestion: "AWS credentials may be expired or invalid. Run `aws sts get-caller-identity` to verify, then refresh with `aws sso login` or update your credentials."
|
|
4678
|
+
};
|
|
4679
|
+
}
|
|
4680
|
+
if (statusCode === 404) {
|
|
4681
|
+
return {
|
|
4682
|
+
category: "index_not_found",
|
|
4683
|
+
message: "Trace index not found",
|
|
4684
|
+
suggestion: "The OpenSearch traces index does not exist yet. Ensure your agents are configured to emit OpenTelemetry spans."
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
if (message.includes("ECONNREFUSED") || message.includes("ENOTFOUND") || message.includes("getaddrinfo") || message.includes("Connection refused")) {
|
|
4688
|
+
return {
|
|
4689
|
+
category: "connection",
|
|
4690
|
+
message: `Cannot connect to OpenSearch: ${message}`,
|
|
4691
|
+
suggestion: "Check that the OpenSearch endpoint is correct and the cluster is reachable from your network."
|
|
4692
|
+
};
|
|
4693
|
+
}
|
|
4694
|
+
return {
|
|
4695
|
+
category: "unknown",
|
|
4696
|
+
message,
|
|
4697
|
+
suggestion: "Check the server logs for more details."
|
|
4698
|
+
};
|
|
4699
|
+
}
|
|
4618
4700
|
function transformSpan(source) {
|
|
4619
4701
|
const attributes = {};
|
|
4620
4702
|
for (const [key, value] of Object.entries(source)) {
|
|
@@ -4669,11 +4751,11 @@ function transformSpan(source) {
|
|
|
4669
4751
|
};
|
|
4670
4752
|
}
|
|
4671
4753
|
async function fetchTraces(options, client, indexPattern = "otel-v1-apm-span-*") {
|
|
4672
|
-
const { traceId, runIds, startTime, endTime, size = 100, serviceName, textSearch, cursor } = options;
|
|
4754
|
+
const { traceId, runIds, sessionId, startTime, endTime, size = 100, serviceName, textSearch, cursor } = options;
|
|
4673
4755
|
const hasTimeRange = startTime || endTime;
|
|
4674
|
-
const hasIdFilter = traceId || runIds && runIds.length > 0;
|
|
4756
|
+
const hasIdFilter = traceId || runIds && runIds.length > 0 || sessionId;
|
|
4675
4757
|
if (!hasIdFilter && !hasTimeRange) {
|
|
4676
|
-
throw new Error("Either traceId, runIds, or time range is required");
|
|
4758
|
+
throw new Error("Either traceId, runIds, sessionId, or time range is required");
|
|
4677
4759
|
}
|
|
4678
4760
|
debug("TracesService", "Fetching traces:", { traceId, runIds: runIds?.length, serviceName, textSearch, size, cursor: cursor ? "present" : "none" });
|
|
4679
4761
|
const must = [];
|
|
@@ -4685,6 +4767,9 @@ async function fetchTraces(options, client, indexPattern = "otel-v1-apm-span-*")
|
|
|
4685
4767
|
terms: { "span.attributes.gen_ai@request@id": runIds }
|
|
4686
4768
|
});
|
|
4687
4769
|
}
|
|
4770
|
+
if (sessionId) {
|
|
4771
|
+
must.push({ term: { "span.attributes.session@id": sessionId } });
|
|
4772
|
+
}
|
|
4688
4773
|
if (startTime || endTime) {
|
|
4689
4774
|
const range = { "startTime": {} };
|
|
4690
4775
|
if (startTime) range["startTime"].gte = new Date(startTime).toISOString();
|
|
@@ -4756,7 +4841,13 @@ async function checkTracesHealth(client, indexPattern = "otel-v1-apm-span-*") {
|
|
|
4756
4841
|
return { status: "error", index: indexPattern };
|
|
4757
4842
|
}
|
|
4758
4843
|
} catch (error) {
|
|
4759
|
-
|
|
4844
|
+
const classified = classifyOpenSearchError(error);
|
|
4845
|
+
return {
|
|
4846
|
+
status: "error",
|
|
4847
|
+
error: classified.message,
|
|
4848
|
+
errorCategory: classified.category,
|
|
4849
|
+
suggestion: classified.suggestion
|
|
4850
|
+
};
|
|
4760
4851
|
}
|
|
4761
4852
|
}
|
|
4762
4853
|
|
|
@@ -6410,10 +6501,10 @@ function createOpenSearchClient(config2) {
|
|
|
6410
6501
|
region: config2.awsRegion,
|
|
6411
6502
|
service: config2.awsService || "es",
|
|
6412
6503
|
getCredentials: () => {
|
|
6413
|
-
const
|
|
6504
|
+
const provider2 = fromNodeProviderChain2({
|
|
6414
6505
|
...config2.awsProfile && { profile: config2.awsProfile }
|
|
6415
6506
|
});
|
|
6416
|
-
return
|
|
6507
|
+
return provider2();
|
|
6417
6508
|
}
|
|
6418
6509
|
});
|
|
6419
6510
|
return new Client({
|
|
@@ -7334,15 +7425,19 @@ var OpenSearchRunOperations = class {
|
|
|
7334
7425
|
async getAll(options) {
|
|
7335
7426
|
const size = options?.size ?? 100;
|
|
7336
7427
|
const from = options?.from ?? 0;
|
|
7428
|
+
const body = {
|
|
7429
|
+
size,
|
|
7430
|
+
from,
|
|
7431
|
+
sort: [{ createdAt: { order: "desc" } }],
|
|
7432
|
+
query: { match_all: {} }
|
|
7433
|
+
};
|
|
7434
|
+
if (options?._source && options._source.length > 0) {
|
|
7435
|
+
body._source = options._source;
|
|
7436
|
+
}
|
|
7337
7437
|
try {
|
|
7338
7438
|
const result = await this.client.search({
|
|
7339
7439
|
index: this.index,
|
|
7340
|
-
body
|
|
7341
|
-
size,
|
|
7342
|
-
from,
|
|
7343
|
-
sort: [{ createdAt: { order: "desc" } }],
|
|
7344
|
-
query: { match_all: {} }
|
|
7345
|
-
}
|
|
7440
|
+
body
|
|
7346
7441
|
});
|
|
7347
7442
|
const items = hitsToSources(result.body.hits?.hits || []);
|
|
7348
7443
|
const total = typeof result.body.hits?.total === "object" ? result.body.hits.total.value : result.body.hits?.total ?? 0;
|
|
@@ -8046,8 +8141,12 @@ function resolveObservabilityConfig(req) {
|
|
|
8046
8141
|
if (fileConfig) {
|
|
8047
8142
|
return {
|
|
8048
8143
|
endpoint: fileConfig.endpoint,
|
|
8144
|
+
authType: fileConfig.authType,
|
|
8049
8145
|
username: fileConfig.username,
|
|
8050
8146
|
password: fileConfig.password,
|
|
8147
|
+
awsProfile: fileConfig.awsProfile,
|
|
8148
|
+
awsRegion: fileConfig.awsRegion,
|
|
8149
|
+
awsService: fileConfig.awsService,
|
|
8051
8150
|
tlsSkipVerify: fileConfig.tlsSkipVerify,
|
|
8052
8151
|
indexes: {
|
|
8053
8152
|
traces: fileConfig.indexes?.traces || DEFAULT_OTEL_INDEXES.traces,
|
|
@@ -8091,14 +8190,34 @@ function getStorageConfigFromEnv() {
|
|
|
8091
8190
|
awsService: process.env.OPENSEARCH_STORAGE_AWS_SERVICE || void 0
|
|
8092
8191
|
};
|
|
8093
8192
|
}
|
|
8193
|
+
function getObservabilityConfigFromEnv() {
|
|
8194
|
+
const endpoint = process.env.OPENSEARCH_LOGS_ENDPOINT;
|
|
8195
|
+
if (!endpoint) {
|
|
8196
|
+
return null;
|
|
8197
|
+
}
|
|
8198
|
+
return {
|
|
8199
|
+
endpoint,
|
|
8200
|
+
authType: process.env.OPENSEARCH_LOGS_AUTH_TYPE || void 0,
|
|
8201
|
+
username: process.env.OPENSEARCH_LOGS_USERNAME,
|
|
8202
|
+
password: process.env.OPENSEARCH_LOGS_PASSWORD,
|
|
8203
|
+
awsProfile: process.env.OPENSEARCH_LOGS_AWS_PROFILE,
|
|
8204
|
+
awsRegion: process.env.OPENSEARCH_LOGS_AWS_REGION,
|
|
8205
|
+
awsService: process.env.OPENSEARCH_LOGS_AWS_SERVICE || void 0,
|
|
8206
|
+
indexes: {
|
|
8207
|
+
traces: process.env.OPENSEARCH_LOGS_TRACES_INDEX || DEFAULT_OTEL_INDEXES.traces,
|
|
8208
|
+
logs: process.env.OPENSEARCH_LOGS_INDEX || DEFAULT_OTEL_INDEXES.logs,
|
|
8209
|
+
metrics: DEFAULT_OTEL_INDEXES.metrics
|
|
8210
|
+
}
|
|
8211
|
+
};
|
|
8212
|
+
}
|
|
8094
8213
|
|
|
8095
8214
|
// server/routes/traces.ts
|
|
8096
8215
|
var router4 = Router4();
|
|
8097
8216
|
router4.post("/api/traces", async (req, res) => {
|
|
8098
8217
|
try {
|
|
8099
|
-
const { traceId, runIds, startTime, endTime, size = 100, serviceName, textSearch, cursor } = req.body;
|
|
8218
|
+
const { traceId, runIds, sessionId, startTime, endTime, size = 100, serviceName, textSearch, cursor } = req.body;
|
|
8100
8219
|
const hasTimeRange = startTime || endTime;
|
|
8101
|
-
const hasIdFilter = traceId || runIds && runIds.length > 0;
|
|
8220
|
+
const hasIdFilter = traceId || runIds && runIds.length > 0 || sessionId;
|
|
8102
8221
|
if (!hasIdFilter && !hasTimeRange) {
|
|
8103
8222
|
return res.status(400).json({
|
|
8104
8223
|
error: "Either traceId, runIds, or time range is required"
|
|
@@ -8112,16 +8231,18 @@ router4.post("/api/traces", async (req, res) => {
|
|
|
8112
8231
|
}
|
|
8113
8232
|
let realSpans = [];
|
|
8114
8233
|
let warning;
|
|
8234
|
+
let warningCategory;
|
|
8235
|
+
let suggestion;
|
|
8115
8236
|
let nextCursor = null;
|
|
8116
8237
|
let hasMore = false;
|
|
8117
8238
|
const config2 = resolveObservabilityConfig(req);
|
|
8118
|
-
if (config2 && (traceId || runIds && runIds.length > 0 || startTime || endTime)) {
|
|
8239
|
+
if (config2 && (traceId || runIds && runIds.length > 0 || sessionId || startTime || endTime)) {
|
|
8119
8240
|
let client;
|
|
8120
8241
|
try {
|
|
8121
8242
|
client = createOpenSearchClient(config2);
|
|
8122
8243
|
const indexPattern = config2.indexes?.traces || DEFAULT_OTEL_INDEXES.traces;
|
|
8123
8244
|
const result = await fetchTraces(
|
|
8124
|
-
{ traceId, runIds, startTime, endTime, size, serviceName, textSearch, cursor },
|
|
8245
|
+
{ traceId, runIds, sessionId, startTime, endTime, size, serviceName, textSearch, cursor },
|
|
8125
8246
|
client,
|
|
8126
8247
|
indexPattern
|
|
8127
8248
|
);
|
|
@@ -8129,8 +8250,11 @@ router4.post("/api/traces", async (req, res) => {
|
|
|
8129
8250
|
nextCursor = result.nextCursor || null;
|
|
8130
8251
|
hasMore = result.hasMore || false;
|
|
8131
8252
|
} catch (e) {
|
|
8132
|
-
|
|
8133
|
-
|
|
8253
|
+
const classified = classifyOpenSearchError(e);
|
|
8254
|
+
console.warn(`[TracesAPI] OpenSearch query failed (${classified.category}):`, classified.message);
|
|
8255
|
+
warning = classified.message;
|
|
8256
|
+
warningCategory = classified.category;
|
|
8257
|
+
suggestion = classified.suggestion;
|
|
8134
8258
|
} finally {
|
|
8135
8259
|
if (client) {
|
|
8136
8260
|
await client.close().catch(() => {
|
|
@@ -8156,6 +8280,8 @@ router4.post("/api/traces", async (req, res) => {
|
|
|
8156
8280
|
}
|
|
8157
8281
|
}
|
|
8158
8282
|
warning = "Observability data source not configured";
|
|
8283
|
+
warningCategory = "not_configured";
|
|
8284
|
+
suggestion = "Configure OPENSEARCH_LOGS_ENDPOINT or observabilityStorage in agent-health.config.ts to connect to your traces cluster.";
|
|
8159
8285
|
}
|
|
8160
8286
|
const allSpans = [...sampleSpans, ...realSpans];
|
|
8161
8287
|
res.json({
|
|
@@ -8163,7 +8289,9 @@ router4.post("/api/traces", async (req, res) => {
|
|
|
8163
8289
|
total: allSpans.length,
|
|
8164
8290
|
nextCursor,
|
|
8165
8291
|
hasMore,
|
|
8166
|
-
warning
|
|
8292
|
+
warning,
|
|
8293
|
+
warningCategory,
|
|
8294
|
+
suggestion
|
|
8167
8295
|
});
|
|
8168
8296
|
} catch (error) {
|
|
8169
8297
|
console.error("[TracesAPI] Error:", error);
|
|
@@ -8280,33 +8408,21 @@ function computeMetricsFromSampleSpans(runId) {
|
|
|
8280
8408
|
status: "success"
|
|
8281
8409
|
};
|
|
8282
8410
|
}
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
headers: {
|
|
8299
|
-
"Content-Type": "application/json",
|
|
8300
|
-
"Authorization": `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
|
8301
|
-
},
|
|
8302
|
-
body: JSON.stringify(query)
|
|
8303
|
-
});
|
|
8304
|
-
if (!response.ok) {
|
|
8305
|
-
const errorText = await response.text();
|
|
8306
|
-
throw new Error(`OpenSearch query failed: ${response.status} - ${errorText}`);
|
|
8307
|
-
}
|
|
8308
|
-
const data = await response.json();
|
|
8309
|
-
const spans = data.hits?.hits?.map((h) => h._source) || [];
|
|
8411
|
+
var METRICS_SOURCE_FIELDS = [
|
|
8412
|
+
"span.attributes.gen_ai@request@id",
|
|
8413
|
+
"span.attributes.gen_ai@usage@input_tokens",
|
|
8414
|
+
"span.attributes.gen_ai@usage@output_tokens",
|
|
8415
|
+
"span.attributes.gen_ai@request@model",
|
|
8416
|
+
"span.attributes.gen_ai@tool@name",
|
|
8417
|
+
"span.attributes.tool.name",
|
|
8418
|
+
"name",
|
|
8419
|
+
"traceId",
|
|
8420
|
+
"startTime",
|
|
8421
|
+
"endTime",
|
|
8422
|
+
"durationInNanos",
|
|
8423
|
+
"status.code"
|
|
8424
|
+
];
|
|
8425
|
+
function computeMetricsFromSpans(runId, spans) {
|
|
8310
8426
|
if (spans.length === 0) {
|
|
8311
8427
|
return {
|
|
8312
8428
|
runId,
|
|
@@ -8378,6 +8494,95 @@ async function computeMetrics(runId, osConfig) {
|
|
|
8378
8494
|
status
|
|
8379
8495
|
};
|
|
8380
8496
|
}
|
|
8497
|
+
async function computeMetrics(runId, osConfig) {
|
|
8498
|
+
const { endpoint, username, password, indexPattern = "otel-v1-apm-span-*" } = osConfig;
|
|
8499
|
+
const query = {
|
|
8500
|
+
size: 500,
|
|
8501
|
+
sort: [{ startTime: { order: "asc" } }],
|
|
8502
|
+
query: {
|
|
8503
|
+
bool: {
|
|
8504
|
+
must: [
|
|
8505
|
+
{ term: { "span.attributes.gen_ai@request@id": runId } }
|
|
8506
|
+
]
|
|
8507
|
+
}
|
|
8508
|
+
}
|
|
8509
|
+
};
|
|
8510
|
+
const response = await fetch(`${endpoint}/${indexPattern}/_search`, {
|
|
8511
|
+
method: "POST",
|
|
8512
|
+
headers: {
|
|
8513
|
+
"Content-Type": "application/json",
|
|
8514
|
+
"Authorization": `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
|
8515
|
+
},
|
|
8516
|
+
body: JSON.stringify(query)
|
|
8517
|
+
});
|
|
8518
|
+
if (!response.ok) {
|
|
8519
|
+
const errorText = await response.text();
|
|
8520
|
+
throw new Error(`OpenSearch query failed: ${response.status} - ${errorText}`);
|
|
8521
|
+
}
|
|
8522
|
+
const data = await response.json();
|
|
8523
|
+
const spans = data.hits?.hits?.map((h) => h._source) || [];
|
|
8524
|
+
return computeMetricsFromSpans(runId, spans);
|
|
8525
|
+
}
|
|
8526
|
+
async function computeBatchMetrics(runIds, osConfig) {
|
|
8527
|
+
if (runIds.length === 0) return [];
|
|
8528
|
+
const { endpoint, username, password, indexPattern = "otel-v1-apm-span-*" } = osConfig;
|
|
8529
|
+
const CHUNK_SIZE = 50;
|
|
8530
|
+
const allResults = [];
|
|
8531
|
+
const chunks = [];
|
|
8532
|
+
for (let i = 0; i < runIds.length; i += CHUNK_SIZE) {
|
|
8533
|
+
chunks.push(runIds.slice(i, i + CHUNK_SIZE));
|
|
8534
|
+
}
|
|
8535
|
+
const chunkResults = await Promise.all(chunks.map(async (chunk) => {
|
|
8536
|
+
const query = {
|
|
8537
|
+
size: 1e4,
|
|
8538
|
+
sort: [{ startTime: { order: "asc" } }],
|
|
8539
|
+
_source: METRICS_SOURCE_FIELDS,
|
|
8540
|
+
query: {
|
|
8541
|
+
bool: {
|
|
8542
|
+
must: [
|
|
8543
|
+
{ terms: { "span.attributes.gen_ai@request@id": chunk } }
|
|
8544
|
+
]
|
|
8545
|
+
}
|
|
8546
|
+
}
|
|
8547
|
+
};
|
|
8548
|
+
const response = await fetch(`${endpoint}/${indexPattern}/_search`, {
|
|
8549
|
+
method: "POST",
|
|
8550
|
+
headers: {
|
|
8551
|
+
"Content-Type": "application/json",
|
|
8552
|
+
"Authorization": `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
|
8553
|
+
},
|
|
8554
|
+
body: JSON.stringify(query)
|
|
8555
|
+
});
|
|
8556
|
+
if (!response.ok) {
|
|
8557
|
+
const responseBody = await response.text();
|
|
8558
|
+
console.warn(
|
|
8559
|
+
`OpenSearch metrics query failed for chunk (${chunk.length} run IDs): ${response.status} ${response.statusText}. Response body: ${responseBody}`
|
|
8560
|
+
);
|
|
8561
|
+
return chunk.map((runId) => computeMetricsFromSpans(runId, []));
|
|
8562
|
+
}
|
|
8563
|
+
const data = await response.json();
|
|
8564
|
+
const allSpans = data.hits?.hits?.map((h) => h._source) || [];
|
|
8565
|
+
const totalHits = data.hits?.total?.value ?? allSpans.length;
|
|
8566
|
+
if (totalHits > 1e4) {
|
|
8567
|
+
console.warn(
|
|
8568
|
+
`OpenSearch batch metrics query returned ${allSpans.length} of ${totalHits} spans for chunk of ${chunk.length} run IDs. Metrics may be incomplete.`
|
|
8569
|
+
);
|
|
8570
|
+
}
|
|
8571
|
+
const spansByRunId = /* @__PURE__ */ new Map();
|
|
8572
|
+
for (const rid of chunk) spansByRunId.set(rid, []);
|
|
8573
|
+
for (const span of allSpans) {
|
|
8574
|
+
const rid = span["span.attributes.gen_ai@request@id"];
|
|
8575
|
+
if (rid && spansByRunId.has(rid)) {
|
|
8576
|
+
spansByRunId.get(rid).push(span);
|
|
8577
|
+
}
|
|
8578
|
+
}
|
|
8579
|
+
return chunk.map((runId) => computeMetricsFromSpans(runId, spansByRunId.get(runId) || []));
|
|
8580
|
+
}));
|
|
8581
|
+
for (const results of chunkResults) {
|
|
8582
|
+
allResults.push(...results);
|
|
8583
|
+
}
|
|
8584
|
+
return allResults;
|
|
8585
|
+
}
|
|
8381
8586
|
function computeAggregateMetrics(metricsArray) {
|
|
8382
8587
|
if (!metricsArray || metricsArray.length === 0) {
|
|
8383
8588
|
return {
|
|
@@ -8456,15 +8661,6 @@ router5.get("/api/metrics/:runId", async (req, res) => {
|
|
|
8456
8661
|
res.status(500).json({ error: error.message });
|
|
8457
8662
|
}
|
|
8458
8663
|
});
|
|
8459
|
-
async function processBatches(items, batchSize, processor) {
|
|
8460
|
-
const results = [];
|
|
8461
|
-
for (let i = 0; i < items.length; i += batchSize) {
|
|
8462
|
-
const batch = items.slice(i, i + batchSize);
|
|
8463
|
-
const batchResults = await Promise.all(batch.map(processor));
|
|
8464
|
-
results.push(...batchResults);
|
|
8465
|
-
}
|
|
8466
|
-
return results;
|
|
8467
|
-
}
|
|
8468
8664
|
router5.post("/api/metrics/batch", async (req, res) => {
|
|
8469
8665
|
try {
|
|
8470
8666
|
const { runIds } = req.body;
|
|
@@ -8495,16 +8691,15 @@ router5.post("/api/metrics/batch", async (req, res) => {
|
|
|
8495
8691
|
password: config2.password,
|
|
8496
8692
|
indexPattern
|
|
8497
8693
|
};
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
(runId) => computeMetrics(runId, osConfig).catch((e) => ({
|
|
8694
|
+
try {
|
|
8695
|
+
realResults = await computeBatchMetrics(realRunIds, osConfig);
|
|
8696
|
+
} catch (e) {
|
|
8697
|
+
realResults = realRunIds.map((runId) => ({
|
|
8503
8698
|
runId,
|
|
8504
8699
|
error: e.message,
|
|
8505
8700
|
status: "error"
|
|
8506
|
-
}))
|
|
8507
|
-
|
|
8701
|
+
}));
|
|
8702
|
+
}
|
|
8508
8703
|
}
|
|
8509
8704
|
}
|
|
8510
8705
|
const resultsMap = /* @__PURE__ */ new Map();
|
|
@@ -12174,11 +12369,150 @@ var OpenAICompatibleConnector = class extends BaseConnector {
|
|
|
12174
12369
|
};
|
|
12175
12370
|
var openaiCompatibleConnector = new OpenAICompatibleConnector();
|
|
12176
12371
|
|
|
12372
|
+
// services/connectors/langgraph/LangGraphConnector.ts
|
|
12373
|
+
var LangGraphConnector = class extends BaseConnector {
|
|
12374
|
+
constructor() {
|
|
12375
|
+
super(...arguments);
|
|
12376
|
+
this.type = "langgraph";
|
|
12377
|
+
this.name = "LangGraph (REST)";
|
|
12378
|
+
this.supportsStreaming = false;
|
|
12379
|
+
}
|
|
12380
|
+
buildPayload(request2) {
|
|
12381
|
+
const config2 = request2.connectorConfig || {};
|
|
12382
|
+
return {
|
|
12383
|
+
input: {
|
|
12384
|
+
messages: [
|
|
12385
|
+
{
|
|
12386
|
+
role: "user",
|
|
12387
|
+
content: request2.testCase.initialPrompt
|
|
12388
|
+
}
|
|
12389
|
+
]
|
|
12390
|
+
},
|
|
12391
|
+
config: {
|
|
12392
|
+
configurable: {
|
|
12393
|
+
...request2.modelId && { model: request2.modelId },
|
|
12394
|
+
...config2.configurable
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
};
|
|
12398
|
+
}
|
|
12399
|
+
async execute(endpoint, request2, auth, onProgress, onRawEvent) {
|
|
12400
|
+
const payload = request2.payload || this.buildPayload(request2);
|
|
12401
|
+
const headers = this.buildAuthHeaders(auth);
|
|
12402
|
+
const config2 = request2.connectorConfig || {};
|
|
12403
|
+
const graphId = config2.graphId || "agent";
|
|
12404
|
+
const baseUrl = endpoint.replace(/\/+$/, "");
|
|
12405
|
+
const threadId = config2.threadId || request2.threadId;
|
|
12406
|
+
const invokeUrl = threadId ? `${baseUrl}/threads/${threadId}/runs/wait` : `${baseUrl}/assistants/${graphId}/invoke`;
|
|
12407
|
+
this.debug("Executing LangGraph request");
|
|
12408
|
+
this.debug("URL:", invokeUrl);
|
|
12409
|
+
const response = await fetch(invokeUrl, {
|
|
12410
|
+
method: "POST",
|
|
12411
|
+
headers: {
|
|
12412
|
+
"Content-Type": "application/json",
|
|
12413
|
+
...headers
|
|
12414
|
+
},
|
|
12415
|
+
body: JSON.stringify(payload)
|
|
12416
|
+
});
|
|
12417
|
+
if (!response.ok) {
|
|
12418
|
+
const errorText = await response.text();
|
|
12419
|
+
throw new Error(`LangGraph request failed: ${response.status} - ${errorText}`);
|
|
12420
|
+
}
|
|
12421
|
+
const data = await response.json();
|
|
12422
|
+
onRawEvent?.(data);
|
|
12423
|
+
const trajectory = this.parseResponse(data);
|
|
12424
|
+
trajectory.forEach((step) => onProgress?.(step));
|
|
12425
|
+
return {
|
|
12426
|
+
trajectory,
|
|
12427
|
+
runId: data.run_id || data.thread_id || threadId || null,
|
|
12428
|
+
rawEvents: [data],
|
|
12429
|
+
metadata: {
|
|
12430
|
+
graphId,
|
|
12431
|
+
threadId: data.thread_id || threadId
|
|
12432
|
+
}
|
|
12433
|
+
};
|
|
12434
|
+
}
|
|
12435
|
+
parseResponse(data) {
|
|
12436
|
+
const steps = [];
|
|
12437
|
+
const messages = data.output?.messages || data.values?.messages || data.messages || [];
|
|
12438
|
+
for (const msg of messages) {
|
|
12439
|
+
const role = msg.type || msg.role;
|
|
12440
|
+
const content = typeof msg.content === "string" ? msg.content : Array.isArray(msg.content) ? msg.content.map((c) => c.text || JSON.stringify(c)).join("\n") : JSON.stringify(msg.content);
|
|
12441
|
+
if (role === "ai" || role === "assistant") {
|
|
12442
|
+
if (msg.tool_calls && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
12443
|
+
for (const toolCall of msg.tool_calls) {
|
|
12444
|
+
steps.push(this.createStep("action", `Calling ${toolCall.name}...`, {
|
|
12445
|
+
toolName: toolCall.name,
|
|
12446
|
+
toolArgs: toolCall.args
|
|
12447
|
+
}));
|
|
12448
|
+
}
|
|
12449
|
+
}
|
|
12450
|
+
if (content && !msg.tool_calls?.length) {
|
|
12451
|
+
steps.push(this.createStep("assistant", content));
|
|
12452
|
+
}
|
|
12453
|
+
} else if (role === "tool") {
|
|
12454
|
+
steps.push(this.createStep("tool_result", content, {
|
|
12455
|
+
toolName: msg.name,
|
|
12456
|
+
status: "SUCCESS"
|
|
12457
|
+
}));
|
|
12458
|
+
}
|
|
12459
|
+
}
|
|
12460
|
+
const lastAssistant = [...steps].reverse().find((s) => s.type === "assistant");
|
|
12461
|
+
if (lastAssistant) {
|
|
12462
|
+
lastAssistant.type = "response";
|
|
12463
|
+
}
|
|
12464
|
+
if (data.intermediate_steps && Array.isArray(data.intermediate_steps)) {
|
|
12465
|
+
for (const [action, observation] of data.intermediate_steps) {
|
|
12466
|
+
if (action?.tool) {
|
|
12467
|
+
steps.push(this.createStep("action", `Calling ${action.tool}...`, {
|
|
12468
|
+
toolName: action.tool,
|
|
12469
|
+
toolArgs: action.tool_input
|
|
12470
|
+
}));
|
|
12471
|
+
}
|
|
12472
|
+
if (observation !== void 0) {
|
|
12473
|
+
const obsContent = typeof observation === "string" ? observation : JSON.stringify(observation);
|
|
12474
|
+
steps.push(this.createStep("tool_result", obsContent, {
|
|
12475
|
+
status: "SUCCESS"
|
|
12476
|
+
}));
|
|
12477
|
+
}
|
|
12478
|
+
}
|
|
12479
|
+
}
|
|
12480
|
+
if (data.output && typeof data.output === "string" && steps.length === 0) {
|
|
12481
|
+
steps.push(this.createStep("response", data.output));
|
|
12482
|
+
}
|
|
12483
|
+
if (steps.length === 0 && data) {
|
|
12484
|
+
steps.push(this.createStep("response", JSON.stringify(data, null, 2)));
|
|
12485
|
+
}
|
|
12486
|
+
return steps;
|
|
12487
|
+
}
|
|
12488
|
+
async healthCheck(endpoint, auth) {
|
|
12489
|
+
try {
|
|
12490
|
+
const headers = this.buildAuthHeaders(auth);
|
|
12491
|
+
const baseUrl = endpoint.replace(/\/+$/, "");
|
|
12492
|
+
const response = await fetch(`${baseUrl}/ok`, {
|
|
12493
|
+
method: "GET",
|
|
12494
|
+
headers
|
|
12495
|
+
});
|
|
12496
|
+
return response.ok;
|
|
12497
|
+
} catch {
|
|
12498
|
+
try {
|
|
12499
|
+
const headers = this.buildAuthHeaders(auth);
|
|
12500
|
+
const response = await fetch(endpoint, { method: "GET", headers });
|
|
12501
|
+
return response.ok;
|
|
12502
|
+
} catch {
|
|
12503
|
+
return false;
|
|
12504
|
+
}
|
|
12505
|
+
}
|
|
12506
|
+
}
|
|
12507
|
+
};
|
|
12508
|
+
var langgraphConnector = new LangGraphConnector();
|
|
12509
|
+
|
|
12177
12510
|
// services/connectors/index.ts
|
|
12178
12511
|
connectorRegistry.register(aguiStreamingConnector);
|
|
12179
12512
|
connectorRegistry.register(mockConnector);
|
|
12180
12513
|
connectorRegistry.register(restConnector);
|
|
12181
12514
|
connectorRegistry.register(openaiCompatibleConnector);
|
|
12515
|
+
connectorRegistry.register(langgraphConnector);
|
|
12182
12516
|
console.log("[Connectors] Browser-safe connectors registered:", connectorRegistry.getRegisteredTypes().join(", "));
|
|
12183
12517
|
|
|
12184
12518
|
// services/connectors/subprocess/SubprocessConnector.ts
|
|
@@ -12222,6 +12556,7 @@ Question: ${prompt}`;
|
|
|
12222
12556
|
const command = endpoint || this.config.command;
|
|
12223
12557
|
const args = this.config.args || [];
|
|
12224
12558
|
const input = request2.payload || this.buildPayload(request2);
|
|
12559
|
+
const runId = `subprocess-${Date.now()}`;
|
|
12225
12560
|
this.debug("Command:", command);
|
|
12226
12561
|
this.debug("Args:", args);
|
|
12227
12562
|
this.debug("Input mode:", this.config.inputMode);
|
|
@@ -12229,10 +12564,12 @@ Question: ${prompt}`;
|
|
|
12229
12564
|
this.debug("Timeout:", this.config.timeout);
|
|
12230
12565
|
this.debug("Input (first 500 chars):", input.substring(0, 500));
|
|
12231
12566
|
this.debug("Working dir:", this.config.workingDir || process.cwd());
|
|
12567
|
+
this.debug("Run ID:", runId);
|
|
12232
12568
|
const env = {
|
|
12233
12569
|
...process.env,
|
|
12234
12570
|
...this.buildAuthEnv(auth),
|
|
12235
|
-
...this.config.env
|
|
12571
|
+
...this.config.env,
|
|
12572
|
+
AGENT_EVAL_RUN_ID: runId
|
|
12236
12573
|
};
|
|
12237
12574
|
return new Promise((resolve2, reject) => {
|
|
12238
12575
|
const trajectory = [];
|
|
@@ -12291,6 +12628,17 @@ Question: ${prompt}`;
|
|
|
12291
12628
|
this.error(`Process exited with code ${code}`);
|
|
12292
12629
|
this.error("stderr:", stderr);
|
|
12293
12630
|
}
|
|
12631
|
+
if (this.config.outputParser === "streaming") {
|
|
12632
|
+
this.onBeforeStreamEnd(trajectory, onProgress);
|
|
12633
|
+
if (code !== 0 && trajectory.length === 0) {
|
|
12634
|
+
const errorContent = stderr.trim() ? `Error: Process exited with code ${code}. ${stderr.trim()}` : `Error: Process exited with code ${code}`;
|
|
12635
|
+
const errorStep = this.createStep("tool_result", errorContent, {
|
|
12636
|
+
status: "FAILURE" /* FAILURE */
|
|
12637
|
+
});
|
|
12638
|
+
trajectory.push(errorStep);
|
|
12639
|
+
onProgress?.(errorStep);
|
|
12640
|
+
}
|
|
12641
|
+
}
|
|
12294
12642
|
const finalTrajectory = this.config.outputParser === "streaming" ? trajectory : this.parseResponse({ stdout, stderr, exitCode: code });
|
|
12295
12643
|
if (this.config.outputParser !== "streaming") {
|
|
12296
12644
|
finalTrajectory.forEach((step) => onProgress?.(step));
|
|
@@ -12298,7 +12646,7 @@ Question: ${prompt}`;
|
|
|
12298
12646
|
this.debug("Resolving with trajectory of", finalTrajectory.length, "steps");
|
|
12299
12647
|
resolve2({
|
|
12300
12648
|
trajectory: finalTrajectory,
|
|
12301
|
-
runId
|
|
12649
|
+
runId,
|
|
12302
12650
|
rawEvents: rawOutput,
|
|
12303
12651
|
metadata: {
|
|
12304
12652
|
command,
|
|
@@ -12329,6 +12677,12 @@ Question: ${prompt}`;
|
|
|
12329
12677
|
});
|
|
12330
12678
|
this.debug("========== execute() COMPLETED ==========");
|
|
12331
12679
|
}
|
|
12680
|
+
/**
|
|
12681
|
+
* Hook called before returning the streaming trajectory on process close.
|
|
12682
|
+
* Subclasses can override to flush internal buffers.
|
|
12683
|
+
*/
|
|
12684
|
+
onBeforeStreamEnd(_trajectory, _onProgress) {
|
|
12685
|
+
}
|
|
12332
12686
|
/**
|
|
12333
12687
|
* Parse streaming output and emit steps in real-time
|
|
12334
12688
|
*/
|
|
@@ -12423,6 +12777,7 @@ var ClaudeCodeConnector = class extends SubprocessConnector {
|
|
|
12423
12777
|
this.name = "Claude Code CLI";
|
|
12424
12778
|
this.outputBuffer = "";
|
|
12425
12779
|
this.thinkingBuffer = "";
|
|
12780
|
+
this.textBuffer = "";
|
|
12426
12781
|
this.isInThinking = false;
|
|
12427
12782
|
}
|
|
12428
12783
|
/**
|
|
@@ -12492,11 +12847,17 @@ var ClaudeCodeConnector = class extends SubprocessConnector {
|
|
|
12492
12847
|
if (event.delta?.type === "thinking_delta" && event.delta.thinking) {
|
|
12493
12848
|
this.thinkingBuffer += event.delta.thinking;
|
|
12494
12849
|
} else if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
12495
|
-
|
|
12850
|
+
this.textBuffer += event.delta.text;
|
|
12851
|
+
}
|
|
12852
|
+
} else if (event.type === "content_block_stop") {
|
|
12853
|
+
if (this.thinkingBuffer) {
|
|
12854
|
+
steps.push(this.createStep("thinking", this.thinkingBuffer));
|
|
12855
|
+
this.thinkingBuffer = "";
|
|
12856
|
+
}
|
|
12857
|
+
if (this.textBuffer) {
|
|
12858
|
+
steps.push(this.createStep("assistant", this.textBuffer));
|
|
12859
|
+
this.textBuffer = "";
|
|
12496
12860
|
}
|
|
12497
|
-
} else if (event.type === "content_block_stop" && this.thinkingBuffer) {
|
|
12498
|
-
steps.push(this.createStep("thinking", this.thinkingBuffer));
|
|
12499
|
-
this.thinkingBuffer = "";
|
|
12500
12861
|
} else if (event.type === "result" && event.result) {
|
|
12501
12862
|
steps.push(this.createStep(
|
|
12502
12863
|
"response",
|
|
@@ -12542,8 +12903,41 @@ var ClaudeCodeConnector = class extends SubprocessConnector {
|
|
|
12542
12903
|
resetState() {
|
|
12543
12904
|
this.outputBuffer = "";
|
|
12544
12905
|
this.thinkingBuffer = "";
|
|
12906
|
+
this.textBuffer = "";
|
|
12545
12907
|
this.isInThinking = false;
|
|
12546
12908
|
}
|
|
12909
|
+
/**
|
|
12910
|
+
* Flush remaining buffers when the subprocess stream ends.
|
|
12911
|
+
*/
|
|
12912
|
+
onBeforeStreamEnd(trajectory, onProgress) {
|
|
12913
|
+
if (this.outputBuffer.trim()) {
|
|
12914
|
+
try {
|
|
12915
|
+
const event = JSON.parse(this.outputBuffer.trim());
|
|
12916
|
+
const steps = this.parseJsonEvent(event);
|
|
12917
|
+
for (const step of steps) {
|
|
12918
|
+
trajectory.push(step);
|
|
12919
|
+
onProgress?.(step);
|
|
12920
|
+
}
|
|
12921
|
+
} catch {
|
|
12922
|
+
const step = this.createStep("assistant", this.outputBuffer.trim());
|
|
12923
|
+
trajectory.push(step);
|
|
12924
|
+
onProgress?.(step);
|
|
12925
|
+
}
|
|
12926
|
+
this.outputBuffer = "";
|
|
12927
|
+
}
|
|
12928
|
+
if (this.thinkingBuffer) {
|
|
12929
|
+
const step = this.createStep("thinking", this.thinkingBuffer);
|
|
12930
|
+
trajectory.push(step);
|
|
12931
|
+
onProgress?.(step);
|
|
12932
|
+
this.thinkingBuffer = "";
|
|
12933
|
+
}
|
|
12934
|
+
if (this.textBuffer) {
|
|
12935
|
+
const step = this.createStep("response", this.textBuffer);
|
|
12936
|
+
trajectory.push(step);
|
|
12937
|
+
onProgress?.(step);
|
|
12938
|
+
this.textBuffer = "";
|
|
12939
|
+
}
|
|
12940
|
+
}
|
|
12547
12941
|
/**
|
|
12548
12942
|
* Build CLI args from ClaudeCodeConnectorConfig
|
|
12549
12943
|
*/
|
|
@@ -12644,9 +13038,214 @@ var ClaudeCodeConnector = class extends SubprocessConnector {
|
|
|
12644
13038
|
};
|
|
12645
13039
|
var claudeCodeConnector = new ClaudeCodeConnector();
|
|
12646
13040
|
|
|
13041
|
+
// services/connectors/strands/StrandsConnector.ts
|
|
13042
|
+
var StrandsConnector = class extends BaseConnector {
|
|
13043
|
+
constructor() {
|
|
13044
|
+
super(...arguments);
|
|
13045
|
+
this.type = "strands";
|
|
13046
|
+
this.name = "Amazon Strands";
|
|
13047
|
+
this.supportsStreaming = true;
|
|
13048
|
+
}
|
|
13049
|
+
buildPayload(request2) {
|
|
13050
|
+
const config2 = request2.connectorConfig || {};
|
|
13051
|
+
return {
|
|
13052
|
+
agentId: "",
|
|
13053
|
+
// Set from endpoint in execute()
|
|
13054
|
+
agentAliasId: config2.agentAliasId || "TSTALIASID",
|
|
13055
|
+
sessionId: config2.sessionId || `eval-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
13056
|
+
inputText: request2.testCase.initialPrompt,
|
|
13057
|
+
enableTrace: config2.enableTrace !== false
|
|
13058
|
+
};
|
|
13059
|
+
}
|
|
13060
|
+
async execute(endpoint, request2, auth, onProgress, onRawEvent) {
|
|
13061
|
+
const payload = request2.payload || this.buildPayload(request2);
|
|
13062
|
+
payload.agentId = endpoint;
|
|
13063
|
+
const config2 = request2.connectorConfig || {};
|
|
13064
|
+
const region = config2.region || auth.awsRegion || process.env.AWS_REGION || "us-east-1";
|
|
13065
|
+
this.debug("Executing Strands agent");
|
|
13066
|
+
this.debug("Agent ID:", endpoint);
|
|
13067
|
+
this.debug("Alias:", payload.agentAliasId);
|
|
13068
|
+
this.debug("Region:", region);
|
|
13069
|
+
const { BedrockAgentRuntimeClient, InvokeAgentCommand } = await import("@aws-sdk/client-bedrock-agent-runtime");
|
|
13070
|
+
const clientConfig = { region };
|
|
13071
|
+
if (auth.type === "aws-sigv4" && auth.awsAccessKeyId && auth.awsSecretAccessKey) {
|
|
13072
|
+
clientConfig.credentials = {
|
|
13073
|
+
accessKeyId: auth.awsAccessKeyId,
|
|
13074
|
+
secretAccessKey: auth.awsSecretAccessKey,
|
|
13075
|
+
...auth.awsSessionToken && { sessionToken: auth.awsSessionToken }
|
|
13076
|
+
};
|
|
13077
|
+
}
|
|
13078
|
+
const client = new BedrockAgentRuntimeClient(clientConfig);
|
|
13079
|
+
const command = new InvokeAgentCommand({
|
|
13080
|
+
agentId: payload.agentId,
|
|
13081
|
+
agentAliasId: payload.agentAliasId,
|
|
13082
|
+
sessionId: payload.sessionId,
|
|
13083
|
+
inputText: payload.inputText,
|
|
13084
|
+
enableTrace: payload.enableTrace
|
|
13085
|
+
});
|
|
13086
|
+
const response = await client.send(command);
|
|
13087
|
+
const trajectory = [];
|
|
13088
|
+
let finalOutput = "";
|
|
13089
|
+
if (response.completion) {
|
|
13090
|
+
for await (const event of response.completion) {
|
|
13091
|
+
onRawEvent?.(event);
|
|
13092
|
+
if (event.chunk?.bytes) {
|
|
13093
|
+
const text = new TextDecoder().decode(event.chunk.bytes);
|
|
13094
|
+
finalOutput += text;
|
|
13095
|
+
}
|
|
13096
|
+
if (event.trace?.trace) {
|
|
13097
|
+
const traceSteps = this.parseTraceEvent(event.trace.trace);
|
|
13098
|
+
for (const step of traceSteps) {
|
|
13099
|
+
trajectory.push(step);
|
|
13100
|
+
onProgress?.(step);
|
|
13101
|
+
}
|
|
13102
|
+
}
|
|
13103
|
+
}
|
|
13104
|
+
}
|
|
13105
|
+
const hasResponseFromTrace = trajectory.some((s) => s.type === "response");
|
|
13106
|
+
if (finalOutput && !hasResponseFromTrace) {
|
|
13107
|
+
const responseStep = this.createStep("response", finalOutput);
|
|
13108
|
+
trajectory.push(responseStep);
|
|
13109
|
+
onProgress?.(responseStep);
|
|
13110
|
+
}
|
|
13111
|
+
return {
|
|
13112
|
+
trajectory,
|
|
13113
|
+
runId: payload.sessionId,
|
|
13114
|
+
metadata: {
|
|
13115
|
+
agentId: endpoint,
|
|
13116
|
+
agentAliasId: payload.agentAliasId,
|
|
13117
|
+
sessionId: payload.sessionId,
|
|
13118
|
+
region
|
|
13119
|
+
}
|
|
13120
|
+
};
|
|
13121
|
+
}
|
|
13122
|
+
parseResponse(rawResponse) {
|
|
13123
|
+
if (rawResponse?.trace?.trace) {
|
|
13124
|
+
return this.parseTraceEvent(rawResponse.trace.trace);
|
|
13125
|
+
}
|
|
13126
|
+
if (typeof rawResponse === "string") {
|
|
13127
|
+
return [this.createStep("response", rawResponse)];
|
|
13128
|
+
}
|
|
13129
|
+
return [];
|
|
13130
|
+
}
|
|
13131
|
+
/**
|
|
13132
|
+
* Parse a Bedrock Agent trace event into TrajectorySteps
|
|
13133
|
+
*/
|
|
13134
|
+
parseTraceEvent(trace3) {
|
|
13135
|
+
const steps = [];
|
|
13136
|
+
if (trace3.preProcessingTrace) {
|
|
13137
|
+
const pre = trace3.preProcessingTrace;
|
|
13138
|
+
if (pre.modelInvocationOutput?.parsedResponse?.isValid !== void 0) {
|
|
13139
|
+
steps.push(this.createStep(
|
|
13140
|
+
"thinking",
|
|
13141
|
+
`Pre-processing: Input ${pre.modelInvocationOutput.parsedResponse.isValid ? "valid" : "invalid"}` + (pre.modelInvocationOutput.parsedResponse.rationale ? ` \u2014 ${pre.modelInvocationOutput.parsedResponse.rationale}` : "")
|
|
13142
|
+
));
|
|
13143
|
+
}
|
|
13144
|
+
}
|
|
13145
|
+
if (trace3.orchestrationTrace) {
|
|
13146
|
+
const orch = trace3.orchestrationTrace;
|
|
13147
|
+
if (orch.rationale?.text) {
|
|
13148
|
+
steps.push(this.createStep("thinking", orch.rationale.text));
|
|
13149
|
+
}
|
|
13150
|
+
if (orch.modelInvocationInput?.text) {
|
|
13151
|
+
steps.push(this.createStep("thinking", `Model input: ${this.truncate(orch.modelInvocationInput.text, 500)}`));
|
|
13152
|
+
}
|
|
13153
|
+
if (orch.invocationInput) {
|
|
13154
|
+
const inv = orch.invocationInput;
|
|
13155
|
+
if (inv.actionGroupInvocationInput) {
|
|
13156
|
+
const action = inv.actionGroupInvocationInput;
|
|
13157
|
+
steps.push(this.createStep("action", `Calling ${action.actionGroupName || "action"}::${action.apiPath || action.function || "invoke"}`, {
|
|
13158
|
+
toolName: `${action.actionGroupName || "action"}::${action.apiPath || action.function || "invoke"}`,
|
|
13159
|
+
toolArgs: action.parameters ? Object.fromEntries(
|
|
13160
|
+
action.parameters.map((p) => [p.name, p.value])
|
|
13161
|
+
) : void 0
|
|
13162
|
+
}));
|
|
13163
|
+
}
|
|
13164
|
+
if (inv.knowledgeBaseLookupInput) {
|
|
13165
|
+
steps.push(this.createStep("action", `Knowledge base lookup: ${inv.knowledgeBaseLookupInput.text}`, {
|
|
13166
|
+
toolName: "knowledge_base_lookup",
|
|
13167
|
+
toolArgs: { query: inv.knowledgeBaseLookupInput.text }
|
|
13168
|
+
}));
|
|
13169
|
+
}
|
|
13170
|
+
}
|
|
13171
|
+
if (orch.observation) {
|
|
13172
|
+
const obs = orch.observation;
|
|
13173
|
+
if (obs.actionGroupInvocationOutput?.text) {
|
|
13174
|
+
steps.push(this.createStep("tool_result", obs.actionGroupInvocationOutput.text, {
|
|
13175
|
+
status: "SUCCESS"
|
|
13176
|
+
}));
|
|
13177
|
+
}
|
|
13178
|
+
if (obs.knowledgeBaseLookupOutput?.retrievedReferences) {
|
|
13179
|
+
const refs = obs.knowledgeBaseLookupOutput.retrievedReferences;
|
|
13180
|
+
steps.push(this.createStep(
|
|
13181
|
+
"tool_result",
|
|
13182
|
+
`Retrieved ${refs.length} reference(s) from knowledge base`,
|
|
13183
|
+
{
|
|
13184
|
+
status: "SUCCESS",
|
|
13185
|
+
toolOutput: refs.map((r) => ({
|
|
13186
|
+
content: this.truncate(r.content?.text, 200),
|
|
13187
|
+
location: r.location
|
|
13188
|
+
}))
|
|
13189
|
+
}
|
|
13190
|
+
));
|
|
13191
|
+
}
|
|
13192
|
+
if (obs.finalResponse?.text) {
|
|
13193
|
+
steps.push(this.createStep("response", obs.finalResponse.text));
|
|
13194
|
+
}
|
|
13195
|
+
}
|
|
13196
|
+
}
|
|
13197
|
+
if (trace3.postProcessingTrace) {
|
|
13198
|
+
const post = trace3.postProcessingTrace;
|
|
13199
|
+
if (post.modelInvocationOutput?.parsedResponse?.text) {
|
|
13200
|
+
steps.push(this.createStep(
|
|
13201
|
+
"thinking",
|
|
13202
|
+
`Post-processing: ${this.truncate(post.modelInvocationOutput.parsedResponse.text, 300)}`
|
|
13203
|
+
));
|
|
13204
|
+
}
|
|
13205
|
+
}
|
|
13206
|
+
if (trace3.failureTrace) {
|
|
13207
|
+
steps.push(this.createStep(
|
|
13208
|
+
"response",
|
|
13209
|
+
`Agent error: ${trace3.failureTrace.failureReason || "Unknown failure"}`
|
|
13210
|
+
));
|
|
13211
|
+
}
|
|
13212
|
+
return steps;
|
|
13213
|
+
}
|
|
13214
|
+
/**
|
|
13215
|
+
* Health check: verify the agent exists via GetAgent
|
|
13216
|
+
*/
|
|
13217
|
+
async healthCheck(endpoint, auth) {
|
|
13218
|
+
try {
|
|
13219
|
+
const config2 = auth;
|
|
13220
|
+
const region = config2.awsRegion || process.env.AWS_REGION || "us-east-1";
|
|
13221
|
+
const { BedrockAgentClient, GetAgentCommand } = await import("@aws-sdk/client-bedrock-agent");
|
|
13222
|
+
const clientConfig = { region };
|
|
13223
|
+
if (auth.type === "aws-sigv4" && auth.awsAccessKeyId && auth.awsSecretAccessKey) {
|
|
13224
|
+
clientConfig.credentials = {
|
|
13225
|
+
accessKeyId: auth.awsAccessKeyId,
|
|
13226
|
+
secretAccessKey: auth.awsSecretAccessKey,
|
|
13227
|
+
...auth.awsSessionToken && { sessionToken: auth.awsSessionToken }
|
|
13228
|
+
};
|
|
13229
|
+
}
|
|
13230
|
+
const client = new BedrockAgentClient(clientConfig);
|
|
13231
|
+
const result = await client.send(new GetAgentCommand({ agentId: endpoint }));
|
|
13232
|
+
return result.agent?.agentStatus === "PREPARED";
|
|
13233
|
+
} catch (error) {
|
|
13234
|
+
this.error("Health check failed:", error);
|
|
13235
|
+
return false;
|
|
13236
|
+
}
|
|
13237
|
+
}
|
|
13238
|
+
truncate(text, maxLen) {
|
|
13239
|
+
if (!text) return "";
|
|
13240
|
+
return text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
|
|
13241
|
+
}
|
|
13242
|
+
};
|
|
13243
|
+
var strandsConnector = new StrandsConnector();
|
|
13244
|
+
|
|
12647
13245
|
// services/connectors/server.ts
|
|
12648
13246
|
connectorRegistry.register(subprocessConnector);
|
|
12649
13247
|
connectorRegistry.register(claudeCodeConnector);
|
|
13248
|
+
connectorRegistry.register(strandsConnector);
|
|
12650
13249
|
console.log("[Connectors] Server connectors registered:", connectorRegistry.getRegisteredTypes().join(", "));
|
|
12651
13250
|
|
|
12652
13251
|
// services/benchmarkRunner.ts
|
|
@@ -12752,6 +13351,9 @@ var runStorage = {
|
|
|
12752
13351
|
const params = new URLSearchParams();
|
|
12753
13352
|
if (options.size) params.append("size", options.size.toString());
|
|
12754
13353
|
if (options.from) params.append("from", options.from.toString());
|
|
13354
|
+
if (options._source && options._source.length > 0) {
|
|
13355
|
+
params.append("fields", options._source.join(","));
|
|
13356
|
+
}
|
|
12755
13357
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
12756
13358
|
return request("GET", `/runs${query}`);
|
|
12757
13359
|
},
|
|
@@ -13398,7 +14000,489 @@ var TracePollingManager = class {
|
|
|
13398
14000
|
};
|
|
13399
14001
|
var tracePollingManager = new TracePollingManager();
|
|
13400
14002
|
|
|
14003
|
+
// lib/telemetry/provider.ts
|
|
14004
|
+
import { trace } from "@opentelemetry/api";
|
|
14005
|
+
import { NodeTracerProvider, BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
14006
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
14007
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
14008
|
+
|
|
14009
|
+
// lib/telemetry/constants.ts
|
|
14010
|
+
import {
|
|
14011
|
+
ATTR_TEST_SUITE_NAME,
|
|
14012
|
+
ATTR_TEST_SUITE_RUN_STATUS,
|
|
14013
|
+
ATTR_TEST_CASE_NAME,
|
|
14014
|
+
ATTR_TEST_CASE_RESULT_STATUS,
|
|
14015
|
+
TEST_SUITE_RUN_STATUS_VALUE_SUCCESS,
|
|
14016
|
+
TEST_SUITE_RUN_STATUS_VALUE_FAILURE,
|
|
14017
|
+
TEST_SUITE_RUN_STATUS_VALUE_IN_PROGRESS,
|
|
14018
|
+
TEST_CASE_RESULT_STATUS_VALUE_PASS,
|
|
14019
|
+
TEST_CASE_RESULT_STATUS_VALUE_FAIL,
|
|
14020
|
+
ATTR_GEN_AI_OPERATION_NAME as ATTR_GEN_AI_OPERATION_NAME4,
|
|
14021
|
+
ATTR_GEN_AI_EVALUATION_NAME,
|
|
14022
|
+
ATTR_GEN_AI_EVALUATION_SCORE_VALUE,
|
|
14023
|
+
ATTR_GEN_AI_EVALUATION_SCORE_LABEL,
|
|
14024
|
+
ATTR_GEN_AI_EVALUATION_EXPLANATION,
|
|
14025
|
+
EVENT_GEN_AI_EVALUATION_RESULT
|
|
14026
|
+
} from "@opentelemetry/semantic-conventions/incubating";
|
|
14027
|
+
var ATTR_TEST_SUITE_RUN_ID = "test.suite.run.id";
|
|
14028
|
+
var ATTR_TEST_CASE_ID = "test.case.id";
|
|
14029
|
+
var ATTR_TEST_CASE_INPUT = "test.case.input";
|
|
14030
|
+
var ATTR_TEST_CASE_OUTPUT = "test.case.output";
|
|
14031
|
+
var ATTR_TEST_CASE_EXPECTED = "test.case.expected";
|
|
14032
|
+
var ATTR_AGENT_HEALTH_JUDGE_MODEL_ID = "agent_health.judge.model_id";
|
|
14033
|
+
var ATTR_AGENT_HEALTH_JUDGE_DURATION_MS = "agent_health.judge.duration_ms";
|
|
14034
|
+
var ATTR_AGENT_HEALTH_JUDGE_ATTEMPTS = "agent_health.judge.attempts";
|
|
14035
|
+
var ATTR_AGENT_HEALTH_AGENT_DURATION_MS = "agent_health.agent.duration_ms";
|
|
14036
|
+
var ATTR_AGENT_HEALTH_CONNECTOR_PROTOCOL = "agent_health.connector.protocol";
|
|
14037
|
+
var ATTR_AGENT_HEALTH_AGENT_RUN_ID = "gen_ai.request.id";
|
|
14038
|
+
var GEN_AI_OPERATION_NAME_VALUE_EVALUATION = "evaluation";
|
|
14039
|
+
var MAX_ATTRIBUTE_LENGTH = 1e4;
|
|
14040
|
+
var MAX_EXPLANATION_LENGTH = 4e3;
|
|
14041
|
+
var EVAL_TRACER_NAME = "agent-health-eval";
|
|
14042
|
+
|
|
14043
|
+
// lib/telemetry/opensearchExporter.ts
|
|
14044
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
14045
|
+
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
14046
|
+
import { Client as Client2 } from "@opensearch-project/opensearch";
|
|
14047
|
+
function hrTimeToDate(hrTime) {
|
|
14048
|
+
const ms = hrTime[0] * 1e3 + hrTime[1] / 1e6;
|
|
14049
|
+
return new Date(ms).toISOString();
|
|
14050
|
+
}
|
|
14051
|
+
function hrTimeToNanos(hrTime) {
|
|
14052
|
+
return hrTime[0] * 1e9 + hrTime[1];
|
|
14053
|
+
}
|
|
14054
|
+
function durationNanos(start, end) {
|
|
14055
|
+
return hrTimeToNanos(end) - hrTimeToNanos(start);
|
|
14056
|
+
}
|
|
14057
|
+
function flattenKey(key) {
|
|
14058
|
+
return key.replace(/\./g, "@");
|
|
14059
|
+
}
|
|
14060
|
+
function spanKindToString(kind) {
|
|
14061
|
+
switch (kind) {
|
|
14062
|
+
case SpanKind.INTERNAL:
|
|
14063
|
+
return "SPAN_KIND_INTERNAL";
|
|
14064
|
+
case SpanKind.SERVER:
|
|
14065
|
+
return "SPAN_KIND_SERVER";
|
|
14066
|
+
case SpanKind.CLIENT:
|
|
14067
|
+
return "SPAN_KIND_CLIENT";
|
|
14068
|
+
case SpanKind.PRODUCER:
|
|
14069
|
+
return "SPAN_KIND_PRODUCER";
|
|
14070
|
+
case SpanKind.CONSUMER:
|
|
14071
|
+
return "SPAN_KIND_CONSUMER";
|
|
14072
|
+
default:
|
|
14073
|
+
return "SPAN_KIND_INTERNAL";
|
|
14074
|
+
}
|
|
14075
|
+
}
|
|
14076
|
+
function statusCodeToInt(code) {
|
|
14077
|
+
switch (code) {
|
|
14078
|
+
case SpanStatusCode.UNSET:
|
|
14079
|
+
return 0;
|
|
14080
|
+
case SpanStatusCode.OK:
|
|
14081
|
+
return 1;
|
|
14082
|
+
case SpanStatusCode.ERROR:
|
|
14083
|
+
return 2;
|
|
14084
|
+
default:
|
|
14085
|
+
return 0;
|
|
14086
|
+
}
|
|
14087
|
+
}
|
|
14088
|
+
function spanToDocument(span) {
|
|
14089
|
+
const doc = {
|
|
14090
|
+
traceId: span.spanContext().traceId,
|
|
14091
|
+
spanId: span.spanContext().spanId,
|
|
14092
|
+
parentSpanId: span.parentSpanContext?.spanId && span.parentSpanContext.spanId !== "0000000000000000" ? span.parentSpanContext.spanId : "",
|
|
14093
|
+
name: span.name,
|
|
14094
|
+
kind: spanKindToString(span.kind),
|
|
14095
|
+
startTime: hrTimeToDate(span.startTime),
|
|
14096
|
+
endTime: hrTimeToDate(span.endTime),
|
|
14097
|
+
durationInNanos: durationNanos(span.startTime, span.endTime),
|
|
14098
|
+
"status.code": statusCodeToInt(span.status.code),
|
|
14099
|
+
"status.message": span.status.message || "",
|
|
14100
|
+
traceState: "",
|
|
14101
|
+
droppedAttributesCount: span.droppedAttributesCount,
|
|
14102
|
+
droppedEventsCount: span.droppedEventsCount,
|
|
14103
|
+
droppedLinksCount: span.droppedLinksCount
|
|
14104
|
+
};
|
|
14105
|
+
for (const [key, value] of Object.entries(span.attributes)) {
|
|
14106
|
+
if (value !== void 0) {
|
|
14107
|
+
doc[`span.attributes.${flattenKey(key)}`] = value;
|
|
14108
|
+
}
|
|
14109
|
+
}
|
|
14110
|
+
const resource = span.resource;
|
|
14111
|
+
if (resource?.attributes) {
|
|
14112
|
+
for (const [key, value] of Object.entries(resource.attributes)) {
|
|
14113
|
+
if (value !== void 0) {
|
|
14114
|
+
doc[`resource.attributes.${flattenKey(key)}`] = value;
|
|
14115
|
+
}
|
|
14116
|
+
}
|
|
14117
|
+
}
|
|
14118
|
+
const serviceName = resource?.attributes?.["service.name"] || "unknown";
|
|
14119
|
+
doc.serviceName = serviceName;
|
|
14120
|
+
const scope = span.instrumentationScope ?? span.instrumentationLibrary ?? {};
|
|
14121
|
+
doc["instrumentationScope.name"] = scope.name || "";
|
|
14122
|
+
if (scope.version) {
|
|
14123
|
+
doc["instrumentationScope.version"] = scope.version;
|
|
14124
|
+
}
|
|
14125
|
+
if (span.events.length > 0) {
|
|
14126
|
+
doc.events = span.events.map((event) => {
|
|
14127
|
+
const eventDoc = {
|
|
14128
|
+
name: event.name,
|
|
14129
|
+
time: hrTimeToDate(event.time),
|
|
14130
|
+
droppedAttributesCount: event.droppedAttributesCount || 0
|
|
14131
|
+
};
|
|
14132
|
+
if (event.attributes) {
|
|
14133
|
+
const attrs = {};
|
|
14134
|
+
for (const [key, value] of Object.entries(event.attributes)) {
|
|
14135
|
+
if (value !== void 0) {
|
|
14136
|
+
attrs[flattenKey(key)] = value;
|
|
14137
|
+
}
|
|
14138
|
+
}
|
|
14139
|
+
eventDoc.attributes = attrs;
|
|
14140
|
+
}
|
|
14141
|
+
return eventDoc;
|
|
14142
|
+
});
|
|
14143
|
+
} else {
|
|
14144
|
+
doc.events = [];
|
|
14145
|
+
}
|
|
14146
|
+
if (span.links.length > 0) {
|
|
14147
|
+
doc.links = span.links.map((link) => ({
|
|
14148
|
+
traceId: link.context.traceId,
|
|
14149
|
+
spanId: link.context.spanId,
|
|
14150
|
+
traceState: link.context.traceState?.serialize() || "",
|
|
14151
|
+
attributes: link.attributes || {},
|
|
14152
|
+
droppedAttributesCount: link.droppedAttributesCount || 0
|
|
14153
|
+
}));
|
|
14154
|
+
} else {
|
|
14155
|
+
doc.links = [];
|
|
14156
|
+
}
|
|
14157
|
+
const hasRealParent = span.parentSpanContext?.spanId && span.parentSpanContext.spanId !== "0000000000000000";
|
|
14158
|
+
if (!hasRealParent) {
|
|
14159
|
+
doc.traceGroup = span.name;
|
|
14160
|
+
doc.traceGroupFields = {
|
|
14161
|
+
endTime: hrTimeToDate(span.endTime),
|
|
14162
|
+
durationInNanos: durationNanos(span.startTime, span.endTime),
|
|
14163
|
+
statusCode: statusCodeToInt(span.status.code)
|
|
14164
|
+
};
|
|
14165
|
+
}
|
|
14166
|
+
return doc;
|
|
14167
|
+
}
|
|
14168
|
+
var OpenSearchSpanExporter = class {
|
|
14169
|
+
constructor(config2) {
|
|
14170
|
+
this.shutdownRequested = false;
|
|
14171
|
+
const auth = config2.username && config2.password ? `${config2.username}:${config2.password}` : void 0;
|
|
14172
|
+
this.client = new Client2({
|
|
14173
|
+
node: config2.endpoint,
|
|
14174
|
+
auth: auth ? { username: config2.username, password: config2.password } : void 0,
|
|
14175
|
+
ssl: {
|
|
14176
|
+
rejectUnauthorized: config2.tlsSkipVerify !== true
|
|
14177
|
+
}
|
|
14178
|
+
});
|
|
14179
|
+
this.indexName = config2.indexName || "otel-v1-apm-span-000001";
|
|
14180
|
+
console.log(`[Telemetry] OpenSearch exporter initialized \u2192 ${config2.endpoint} (index: ${this.indexName})`);
|
|
14181
|
+
}
|
|
14182
|
+
export(spans, resultCallback) {
|
|
14183
|
+
if (this.shutdownRequested) {
|
|
14184
|
+
resultCallback({ code: ExportResultCode.FAILED });
|
|
14185
|
+
return;
|
|
14186
|
+
}
|
|
14187
|
+
this._export(spans).then(() => resultCallback({ code: ExportResultCode.SUCCESS })).catch((err) => {
|
|
14188
|
+
console.error("[Telemetry] OpenSearch export failed:", err instanceof Error ? err.message : err);
|
|
14189
|
+
resultCallback({ code: ExportResultCode.FAILED });
|
|
14190
|
+
});
|
|
14191
|
+
}
|
|
14192
|
+
async _export(spans) {
|
|
14193
|
+
if (spans.length === 0) return;
|
|
14194
|
+
const body = [];
|
|
14195
|
+
for (const span of spans) {
|
|
14196
|
+
const doc = spanToDocument(span);
|
|
14197
|
+
const docId = `${doc.traceId}/${doc.spanId}`;
|
|
14198
|
+
body.push({ index: { _index: this.indexName, _id: docId } });
|
|
14199
|
+
body.push(doc);
|
|
14200
|
+
}
|
|
14201
|
+
const response = await this.client.bulk({ body });
|
|
14202
|
+
if (response.body.errors) {
|
|
14203
|
+
const errors = response.body.items.filter((item) => item.index?.error).map((item) => `${item.index.error.type}: ${item.index.error.reason}`);
|
|
14204
|
+
if (errors.length > 0) {
|
|
14205
|
+
console.warn(`[Telemetry] OpenSearch bulk write had ${errors.length} error(s):`, errors[0]);
|
|
14206
|
+
}
|
|
14207
|
+
} else {
|
|
14208
|
+
console.log(`[Telemetry] Exported ${spans.length} span(s) to OpenSearch`);
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
async shutdown() {
|
|
14212
|
+
this.shutdownRequested = true;
|
|
14213
|
+
await this.client.close();
|
|
14214
|
+
}
|
|
14215
|
+
async forceFlush() {
|
|
14216
|
+
}
|
|
14217
|
+
};
|
|
14218
|
+
|
|
14219
|
+
// lib/telemetry/provider.ts
|
|
14220
|
+
var provider = null;
|
|
14221
|
+
var telemetryEnabled = false;
|
|
14222
|
+
function resolveEvalTelemetryConfig(userConfig) {
|
|
14223
|
+
const enabled = userConfig?.enabled ?? process.env.OTEL_EVAL_ENABLED === "true";
|
|
14224
|
+
const exporterEndpoint = userConfig?.exporterEndpoint ?? process.env.OTEL_EVAL_EXPORTER_ENDPOINT ?? "http://localhost:4318/v1/traces";
|
|
14225
|
+
let exporterHeaders = userConfig?.exporterHeaders;
|
|
14226
|
+
if (!exporterHeaders && process.env.OTEL_EVAL_EXPORTER_HEADERS) {
|
|
14227
|
+
try {
|
|
14228
|
+
exporterHeaders = JSON.parse(process.env.OTEL_EVAL_EXPORTER_HEADERS);
|
|
14229
|
+
} catch {
|
|
14230
|
+
console.warn("[Telemetry] Failed to parse OTEL_EVAL_EXPORTER_HEADERS as JSON");
|
|
14231
|
+
}
|
|
14232
|
+
}
|
|
14233
|
+
const serviceName = userConfig?.serviceName ?? process.env.OTEL_EVAL_SERVICE_NAME ?? "agent-health";
|
|
14234
|
+
return { enabled, exporterEndpoint, exporterHeaders, serviceName, opensearch: userConfig?.opensearch };
|
|
14235
|
+
}
|
|
14236
|
+
function initEvalTracerProvider(config2) {
|
|
14237
|
+
if (provider) {
|
|
14238
|
+
return;
|
|
14239
|
+
}
|
|
14240
|
+
if (!config2.enabled) {
|
|
14241
|
+
telemetryEnabled = false;
|
|
14242
|
+
console.log("[Telemetry] Evaluation telemetry disabled (OTEL_EVAL_ENABLED != true)");
|
|
14243
|
+
return;
|
|
14244
|
+
}
|
|
14245
|
+
const resource = resourceFromAttributes({
|
|
14246
|
+
"service.name": config2.serviceName ?? "agent-health",
|
|
14247
|
+
"telemetry.sdk.name": "agent-health",
|
|
14248
|
+
"telemetry.sdk.language": "nodejs"
|
|
14249
|
+
});
|
|
14250
|
+
const spanProcessors = [];
|
|
14251
|
+
const hasExplicitOtlpEndpoint = config2.exporterEndpoint !== "http://localhost:4318/v1/traces";
|
|
14252
|
+
if (hasExplicitOtlpEndpoint) {
|
|
14253
|
+
const otlpExporter = new OTLPTraceExporter({
|
|
14254
|
+
url: config2.exporterEndpoint,
|
|
14255
|
+
headers: config2.exporterHeaders
|
|
14256
|
+
});
|
|
14257
|
+
spanProcessors.push(new BatchSpanProcessor(otlpExporter));
|
|
14258
|
+
console.log(`[Telemetry] Evaluation telemetry enabled \u2192 OTLP (${config2.exporterEndpoint})`);
|
|
14259
|
+
} else if (config2.opensearch) {
|
|
14260
|
+
const osExporter = new OpenSearchSpanExporter(config2.opensearch);
|
|
14261
|
+
spanProcessors.push(new SimpleSpanProcessor(osExporter));
|
|
14262
|
+
console.log(`[Telemetry] Evaluation telemetry enabled \u2192 OpenSearch direct (${config2.opensearch.endpoint})`);
|
|
14263
|
+
} else {
|
|
14264
|
+
const otlpExporter = new OTLPTraceExporter({
|
|
14265
|
+
url: config2.exporterEndpoint,
|
|
14266
|
+
headers: config2.exporterHeaders
|
|
14267
|
+
});
|
|
14268
|
+
spanProcessors.push(new BatchSpanProcessor(otlpExporter));
|
|
14269
|
+
console.log(`[Telemetry] Evaluation telemetry enabled \u2192 OTLP (${config2.exporterEndpoint})`);
|
|
14270
|
+
}
|
|
14271
|
+
provider = new NodeTracerProvider({
|
|
14272
|
+
resource,
|
|
14273
|
+
spanProcessors
|
|
14274
|
+
});
|
|
14275
|
+
provider.register();
|
|
14276
|
+
telemetryEnabled = true;
|
|
14277
|
+
}
|
|
14278
|
+
function getEvalTracer() {
|
|
14279
|
+
return trace.getTracer(EVAL_TRACER_NAME);
|
|
14280
|
+
}
|
|
14281
|
+
function isEvalTelemetryEnabled() {
|
|
14282
|
+
return telemetryEnabled;
|
|
14283
|
+
}
|
|
14284
|
+
async function flushEvalTracer() {
|
|
14285
|
+
if (provider) {
|
|
14286
|
+
try {
|
|
14287
|
+
await provider.forceFlush();
|
|
14288
|
+
} catch (err) {
|
|
14289
|
+
console.warn("[Telemetry] forceFlush failed:", err instanceof Error ? err.message : err);
|
|
14290
|
+
}
|
|
14291
|
+
}
|
|
14292
|
+
}
|
|
14293
|
+
async function shutdownEvalTracer() {
|
|
14294
|
+
if (provider) {
|
|
14295
|
+
await provider.shutdown();
|
|
14296
|
+
provider = null;
|
|
14297
|
+
telemetryEnabled = false;
|
|
14298
|
+
console.log("[Telemetry] Evaluation telemetry shut down");
|
|
14299
|
+
}
|
|
14300
|
+
}
|
|
14301
|
+
|
|
14302
|
+
// lib/telemetry/evalSpans.ts
|
|
14303
|
+
import {
|
|
14304
|
+
context,
|
|
14305
|
+
trace as trace2,
|
|
14306
|
+
TraceFlags,
|
|
14307
|
+
SpanKind as SpanKind2,
|
|
14308
|
+
SpanStatusCode as SpanStatusCode2
|
|
14309
|
+
} from "@opentelemetry/api";
|
|
14310
|
+
function truncate(value, maxLength) {
|
|
14311
|
+
if (!value) return void 0;
|
|
14312
|
+
return value.length > maxLength ? value.slice(0, maxLength) + "..." : value;
|
|
14313
|
+
}
|
|
14314
|
+
function extractAgentResponse(report) {
|
|
14315
|
+
const responseStep = [...report.trajectory].reverse().find(
|
|
14316
|
+
(s) => s.type === "response" || s.type === "assistant"
|
|
14317
|
+
);
|
|
14318
|
+
return responseStep?.content;
|
|
14319
|
+
}
|
|
14320
|
+
function startTestSuiteRunSpan(benchmark, run) {
|
|
14321
|
+
if (!isEvalTelemetryEnabled()) return null;
|
|
14322
|
+
const tracer = getEvalTracer();
|
|
14323
|
+
const span = tracer.startSpan(`test_suite_run ${benchmark.name}`, {
|
|
14324
|
+
kind: SpanKind2.INTERNAL,
|
|
14325
|
+
attributes: {
|
|
14326
|
+
[ATTR_TEST_SUITE_NAME]: benchmark.name,
|
|
14327
|
+
[ATTR_TEST_SUITE_RUN_ID]: run.id,
|
|
14328
|
+
[ATTR_GEN_AI_OPERATION_NAME4]: GEN_AI_OPERATION_NAME_VALUE_EVALUATION
|
|
14329
|
+
}
|
|
14330
|
+
});
|
|
14331
|
+
const ctx = context.active();
|
|
14332
|
+
const spanContext = trace2.setSpan(ctx, span);
|
|
14333
|
+
console.log(`[Telemetry] Started test_suite_run span for "${benchmark.name}" (runId=${run.id})`);
|
|
14334
|
+
return { span, context: spanContext };
|
|
14335
|
+
}
|
|
14336
|
+
function startTestCaseSpan(parentCtx, testCase, benchmark, run, agentRunId, links) {
|
|
14337
|
+
if (!isEvalTelemetryEnabled()) return null;
|
|
14338
|
+
const tracer = getEvalTracer();
|
|
14339
|
+
const attributes = {
|
|
14340
|
+
[ATTR_TEST_SUITE_NAME]: benchmark.name,
|
|
14341
|
+
[ATTR_TEST_SUITE_RUN_ID]: run.id,
|
|
14342
|
+
[ATTR_TEST_CASE_ID]: testCase.id,
|
|
14343
|
+
[ATTR_TEST_CASE_NAME]: testCase.name,
|
|
14344
|
+
[ATTR_GEN_AI_OPERATION_NAME4]: GEN_AI_OPERATION_NAME_VALUE_EVALUATION
|
|
14345
|
+
};
|
|
14346
|
+
if (agentRunId) {
|
|
14347
|
+
attributes[ATTR_AGENT_HEALTH_AGENT_RUN_ID] = agentRunId;
|
|
14348
|
+
}
|
|
14349
|
+
attributes[ATTR_TEST_CASE_INPUT] = truncate(testCase.initialPrompt || "", MAX_ATTRIBUTE_LENGTH);
|
|
14350
|
+
const expected = truncate(
|
|
14351
|
+
testCase.expectedOutcomes ? JSON.stringify(testCase.expectedOutcomes) : void 0,
|
|
14352
|
+
MAX_ATTRIBUTE_LENGTH
|
|
14353
|
+
);
|
|
14354
|
+
if (expected) attributes[ATTR_TEST_CASE_EXPECTED] = expected;
|
|
14355
|
+
const span = tracer.startSpan(
|
|
14356
|
+
"test_case",
|
|
14357
|
+
{
|
|
14358
|
+
kind: SpanKind2.INTERNAL,
|
|
14359
|
+
attributes,
|
|
14360
|
+
links
|
|
14361
|
+
},
|
|
14362
|
+
parentCtx
|
|
14363
|
+
);
|
|
14364
|
+
const ctx = trace2.setSpan(parentCtx, span);
|
|
14365
|
+
console.log(`[Telemetry] Started test_case span for "${testCase.name}" (agentRunId=${agentRunId || "none"})`);
|
|
14366
|
+
return { span, context: ctx };
|
|
14367
|
+
}
|
|
14368
|
+
function addEvaluationResultEvents(span, report) {
|
|
14369
|
+
if (!isEvalTelemetryEnabled()) return;
|
|
14370
|
+
if (report.metrics?.accuracy !== void 0) {
|
|
14371
|
+
span.addEvent(EVENT_GEN_AI_EVALUATION_RESULT, {
|
|
14372
|
+
[ATTR_GEN_AI_EVALUATION_NAME]: "accuracy",
|
|
14373
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_VALUE]: report.metrics.accuracy,
|
|
14374
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_LABEL]: report.passFailStatus === "passed" ? "pass" : "fail",
|
|
14375
|
+
[ATTR_GEN_AI_EVALUATION_EXPLANATION]: truncate(report.llmJudgeReasoning, MAX_EXPLANATION_LENGTH)
|
|
14376
|
+
});
|
|
14377
|
+
}
|
|
14378
|
+
if (report.metrics?.faithfulness !== void 0) {
|
|
14379
|
+
span.addEvent(EVENT_GEN_AI_EVALUATION_RESULT, {
|
|
14380
|
+
[ATTR_GEN_AI_EVALUATION_NAME]: "faithfulness",
|
|
14381
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_VALUE]: report.metrics.faithfulness
|
|
14382
|
+
});
|
|
14383
|
+
}
|
|
14384
|
+
if (report.metrics?.latency_score !== void 0) {
|
|
14385
|
+
span.addEvent(EVENT_GEN_AI_EVALUATION_RESULT, {
|
|
14386
|
+
[ATTR_GEN_AI_EVALUATION_NAME]: "latency_score",
|
|
14387
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_VALUE]: report.metrics.latency_score
|
|
14388
|
+
});
|
|
14389
|
+
}
|
|
14390
|
+
if (report.metrics?.trajectory_alignment_score !== void 0) {
|
|
14391
|
+
span.addEvent(EVENT_GEN_AI_EVALUATION_RESULT, {
|
|
14392
|
+
[ATTR_GEN_AI_EVALUATION_NAME]: "trajectory_alignment_score",
|
|
14393
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_VALUE]: report.metrics.trajectory_alignment_score
|
|
14394
|
+
});
|
|
14395
|
+
}
|
|
14396
|
+
}
|
|
14397
|
+
function finalizeTestCaseSpan(span, report, endTime) {
|
|
14398
|
+
if (!isEvalTelemetryEnabled()) return;
|
|
14399
|
+
const status = report.passFailStatus === "passed" ? TEST_CASE_RESULT_STATUS_VALUE_PASS : TEST_CASE_RESULT_STATUS_VALUE_FAIL;
|
|
14400
|
+
span.setAttribute(ATTR_TEST_CASE_RESULT_STATUS, status);
|
|
14401
|
+
const output = extractAgentResponse(report) || "";
|
|
14402
|
+
span.setAttribute(ATTR_TEST_CASE_OUTPUT, truncate(output, MAX_ATTRIBUTE_LENGTH));
|
|
14403
|
+
if (report.connectorProtocol) {
|
|
14404
|
+
span.setAttribute(ATTR_AGENT_HEALTH_CONNECTOR_PROTOCOL, report.connectorProtocol);
|
|
14405
|
+
}
|
|
14406
|
+
if (report.performanceMetrics?.agentDurationMs !== void 0) {
|
|
14407
|
+
span.setAttribute(ATTR_AGENT_HEALTH_AGENT_DURATION_MS, report.performanceMetrics.agentDurationMs);
|
|
14408
|
+
}
|
|
14409
|
+
if (report.performanceMetrics?.judgeDurationMs !== void 0) {
|
|
14410
|
+
span.setAttribute(ATTR_AGENT_HEALTH_JUDGE_DURATION_MS, report.performanceMetrics.judgeDurationMs);
|
|
14411
|
+
}
|
|
14412
|
+
if (report.performanceMetrics?.judgeAttempts !== void 0) {
|
|
14413
|
+
span.setAttribute(ATTR_AGENT_HEALTH_JUDGE_ATTEMPTS, report.performanceMetrics.judgeAttempts);
|
|
14414
|
+
}
|
|
14415
|
+
if (report.llmJudgeResponse?.modelId) {
|
|
14416
|
+
span.setAttribute(ATTR_AGENT_HEALTH_JUDGE_MODEL_ID, report.llmJudgeResponse.modelId);
|
|
14417
|
+
}
|
|
14418
|
+
if (report.status === "failed") {
|
|
14419
|
+
span.setStatus({ code: SpanStatusCode2.ERROR, message: "Evaluation failed" });
|
|
14420
|
+
}
|
|
14421
|
+
span.end(endTime);
|
|
14422
|
+
console.log(`[Telemetry] Finalized test_case span (status=${status}, passFailStatus=${report.passFailStatus})`);
|
|
14423
|
+
}
|
|
14424
|
+
function finalizeTestSuiteRunSpan(span, run) {
|
|
14425
|
+
if (!isEvalTelemetryEnabled()) return;
|
|
14426
|
+
const results = Object.values(run.results);
|
|
14427
|
+
const hasFailures = results.some((r) => r.status === "failed");
|
|
14428
|
+
const status = hasFailures ? TEST_SUITE_RUN_STATUS_VALUE_FAILURE : TEST_SUITE_RUN_STATUS_VALUE_SUCCESS;
|
|
14429
|
+
span.setAttribute(ATTR_TEST_SUITE_RUN_STATUS, status);
|
|
14430
|
+
if (hasFailures) {
|
|
14431
|
+
const failCount = results.filter((r) => r.status === "failed").length;
|
|
14432
|
+
span.setStatus({ code: SpanStatusCode2.ERROR, message: `${failCount} case(s) failed` });
|
|
14433
|
+
}
|
|
14434
|
+
span.end();
|
|
14435
|
+
console.log(`[Telemetry] Finalized test_suite_run span (status=${status}), flushing...`);
|
|
14436
|
+
flushEvalTracer().catch(() => {
|
|
14437
|
+
});
|
|
14438
|
+
}
|
|
14439
|
+
function emitDeferredTestCaseSpan(testCase, report, benchmark, runId, agentRunId, startTime, endTime, agentTraceId) {
|
|
14440
|
+
if (!isEvalTelemetryEnabled()) return;
|
|
14441
|
+
const tracer = getEvalTracer();
|
|
14442
|
+
const attributes = {
|
|
14443
|
+
[ATTR_TEST_SUITE_NAME]: benchmark.name,
|
|
14444
|
+
[ATTR_TEST_SUITE_RUN_ID]: runId,
|
|
14445
|
+
[ATTR_TEST_CASE_ID]: testCase.id,
|
|
14446
|
+
[ATTR_TEST_CASE_NAME]: testCase.name,
|
|
14447
|
+
[ATTR_GEN_AI_OPERATION_NAME4]: GEN_AI_OPERATION_NAME_VALUE_EVALUATION
|
|
14448
|
+
};
|
|
14449
|
+
if (agentRunId) {
|
|
14450
|
+
attributes[ATTR_AGENT_HEALTH_AGENT_RUN_ID] = agentRunId;
|
|
14451
|
+
}
|
|
14452
|
+
attributes[ATTR_TEST_CASE_INPUT] = truncate(testCase.initialPrompt || "", MAX_ATTRIBUTE_LENGTH);
|
|
14453
|
+
const expected = truncate(
|
|
14454
|
+
testCase.expectedOutcomes ? JSON.stringify(testCase.expectedOutcomes) : void 0,
|
|
14455
|
+
MAX_ATTRIBUTE_LENGTH
|
|
14456
|
+
);
|
|
14457
|
+
if (expected) attributes[ATTR_TEST_CASE_EXPECTED] = expected;
|
|
14458
|
+
let parentCtx;
|
|
14459
|
+
if (agentTraceId) {
|
|
14460
|
+
const remoteSpanContext = {
|
|
14461
|
+
traceId: agentTraceId,
|
|
14462
|
+
// Use a valid synthetic spanId — an all-zero spanId is considered invalid
|
|
14463
|
+
// by the OTel SDK and would cause it to ignore this context entirely.
|
|
14464
|
+
// This "remote" context carries the traceId forward so the eval span
|
|
14465
|
+
// shares the same trace as the agent spans.
|
|
14466
|
+
spanId: "eeee000000000001",
|
|
14467
|
+
traceFlags: TraceFlags.SAMPLED,
|
|
14468
|
+
isRemote: true
|
|
14469
|
+
};
|
|
14470
|
+
parentCtx = trace2.setSpanContext(context.active(), remoteSpanContext);
|
|
14471
|
+
}
|
|
14472
|
+
const span = tracer.startSpan("test_case", {
|
|
14473
|
+
kind: SpanKind2.INTERNAL,
|
|
14474
|
+
startTime,
|
|
14475
|
+
attributes
|
|
14476
|
+
}, parentCtx);
|
|
14477
|
+
addEvaluationResultEvents(span, report);
|
|
14478
|
+
finalizeTestCaseSpan(span, report, endTime);
|
|
14479
|
+
console.log(`[Telemetry] Emitted deferred test_case span for "${testCase.name}" (agentRunId=${agentRunId || "none"}, traceId=${agentTraceId || "new"}), flushing...`);
|
|
14480
|
+
flushEvalTracer().catch(() => {
|
|
14481
|
+
});
|
|
14482
|
+
}
|
|
14483
|
+
|
|
13401
14484
|
// services/benchmarkRunner.ts
|
|
14485
|
+
import { SpanStatusCode as SpanStatusCode3 } from "@opentelemetry/api";
|
|
13402
14486
|
function getConfig() {
|
|
13403
14487
|
try {
|
|
13404
14488
|
return loadConfigSync();
|
|
@@ -13456,6 +14540,9 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13456
14540
|
const concurrency = run.concurrency ?? 1;
|
|
13457
14541
|
const runStartTime = Date.now();
|
|
13458
14542
|
console.log(`[BenchmarkRunner] Starting run ${run.id} with concurrency=${concurrency} for ${totalTestCases} test cases`);
|
|
14543
|
+
const suiteSpanResult = startTestSuiteRunSpan(benchmark, run);
|
|
14544
|
+
const suiteSpan = suiteSpanResult?.span;
|
|
14545
|
+
const suiteContext = suiteSpanResult?.context;
|
|
13459
14546
|
if (!run.results) {
|
|
13460
14547
|
run.results = {};
|
|
13461
14548
|
}
|
|
@@ -13506,6 +14593,7 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13506
14593
|
});
|
|
13507
14594
|
debug("BenchmarkRunner", `[${testCaseId}] Starting evaluation (${completedCount}/${totalTestCases} completed)`);
|
|
13508
14595
|
const testCaseStartTime = Date.now();
|
|
14596
|
+
let caseSpan;
|
|
13509
14597
|
run.results[testCaseId] = { reportId: "", status: "running" };
|
|
13510
14598
|
try {
|
|
13511
14599
|
const agentConfig = buildAgentConfigForRun(run);
|
|
@@ -13525,7 +14613,23 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13525
14613
|
});
|
|
13526
14614
|
updateTestCaseLastRunAt(client, testCaseId, (/* @__PURE__ */ new Date()).toISOString()).catch((err) => console.warn(`[BenchmarkRunner] Failed to update lastRunAt for ${testCaseId}:`, err.message));
|
|
13527
14615
|
if (savedReport.metricsStatus === "pending" && savedReport.runId) {
|
|
13528
|
-
startTracePollingForReport(savedReport, testCase, client);
|
|
14616
|
+
startTracePollingForReport(savedReport, testCase, client, benchmark, run);
|
|
14617
|
+
}
|
|
14618
|
+
if (suiteContext) {
|
|
14619
|
+
const caseSpanResult = startTestCaseSpan(
|
|
14620
|
+
suiteContext,
|
|
14621
|
+
testCase,
|
|
14622
|
+
benchmark,
|
|
14623
|
+
run,
|
|
14624
|
+
savedReport.runId
|
|
14625
|
+
);
|
|
14626
|
+
caseSpan = caseSpanResult?.span;
|
|
14627
|
+
if (caseSpan && savedReport.metricsStatus !== "pending") {
|
|
14628
|
+
addEvaluationResultEvents(caseSpan, savedReport);
|
|
14629
|
+
finalizeTestCaseSpan(caseSpan, savedReport);
|
|
14630
|
+
} else if (caseSpan) {
|
|
14631
|
+
caseSpan.end();
|
|
14632
|
+
}
|
|
13529
14633
|
}
|
|
13530
14634
|
run.results[testCaseId] = {
|
|
13531
14635
|
reportId: savedReport.id,
|
|
@@ -13544,6 +14648,10 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13544
14648
|
const testCaseDuration = Date.now() - testCaseStartTime;
|
|
13545
14649
|
debug("BenchmarkRunner", `[${testCaseId}] Failed in ${testCaseDuration}ms: ${errorMsg}`);
|
|
13546
14650
|
run.results[testCaseId] = { reportId: "", status: "failed", error: errorMsg };
|
|
14651
|
+
if (caseSpan) {
|
|
14652
|
+
caseSpan.setStatus({ code: SpanStatusCode3.ERROR, message: errorMsg });
|
|
14653
|
+
caseSpan.end();
|
|
14654
|
+
}
|
|
13547
14655
|
completedCount++;
|
|
13548
14656
|
if (errorMsg.includes("ThrottlingException") || errorMsg.includes("rate limit") || errorMsg.includes("429")) {
|
|
13549
14657
|
consecutiveThrottles++;
|
|
@@ -13587,6 +14695,9 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13587
14695
|
maxTestCaseDurationMs: testCaseDurations.length > 0 ? Math.max(...testCaseDurations) : 0,
|
|
13588
14696
|
minTestCaseDurationMs: testCaseDurations.length > 0 ? Math.min(...testCaseDurations) : 0
|
|
13589
14697
|
};
|
|
14698
|
+
if (suiteSpan) {
|
|
14699
|
+
finalizeTestSuiteRunSpan(suiteSpan, run);
|
|
14700
|
+
}
|
|
13590
14701
|
return run;
|
|
13591
14702
|
} catch (error) {
|
|
13592
14703
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -13595,6 +14706,10 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13595
14706
|
run.results[testCaseId] = { reportId: "", status: "failed", error: `Benchmark execution failed: ${errorMsg}` };
|
|
13596
14707
|
}
|
|
13597
14708
|
});
|
|
14709
|
+
if (suiteSpan) {
|
|
14710
|
+
suiteSpan.setStatus({ code: SpanStatusCode3.ERROR, message: errorMsg });
|
|
14711
|
+
suiteSpan.end();
|
|
14712
|
+
}
|
|
13598
14713
|
throw error;
|
|
13599
14714
|
}
|
|
13600
14715
|
}
|
|
@@ -13626,6 +14741,7 @@ async function saveReportWithModule(storage, report) {
|
|
|
13626
14741
|
async function runSingleUseCase(run, testCase, storage, onStep) {
|
|
13627
14742
|
const agentConfig = buildAgentConfigForRun(run);
|
|
13628
14743
|
const bedrockModelId = getBedrockModelId(run.modelId);
|
|
14744
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
13629
14745
|
const report = await runEvaluationWithConnector(
|
|
13630
14746
|
agentConfig,
|
|
13631
14747
|
bedrockModelId,
|
|
@@ -13643,6 +14759,17 @@ async function runSingleUseCase(run, testCase, storage, onStep) {
|
|
|
13643
14759
|
if (savedReport.metricsStatus === "pending" && savedReport.runId) {
|
|
13644
14760
|
startTracePollingForReportWithModule(savedReport, testCase, storage);
|
|
13645
14761
|
}
|
|
14762
|
+
if (savedReport.metricsStatus !== "pending") {
|
|
14763
|
+
emitDeferredTestCaseSpan(
|
|
14764
|
+
testCase,
|
|
14765
|
+
savedReport,
|
|
14766
|
+
{ name: `standalone:${agentConfig.name || agentConfig.key}` },
|
|
14767
|
+
run.id,
|
|
14768
|
+
savedReport.runId,
|
|
14769
|
+
startTime,
|
|
14770
|
+
/* @__PURE__ */ new Date()
|
|
14771
|
+
);
|
|
14772
|
+
}
|
|
13646
14773
|
return savedReport.id;
|
|
13647
14774
|
}
|
|
13648
14775
|
function startTracePollingForReportWithModule(report, testCase, storage) {
|
|
@@ -13682,6 +14809,24 @@ function startTracePollingForReportWithModule(report, testCase, storage) {
|
|
|
13682
14809
|
llmJudgeReasoning: judgment.llmJudgeReasoning,
|
|
13683
14810
|
improvementStrategies: judgment.improvementStrategies
|
|
13684
14811
|
});
|
|
14812
|
+
const completedReport = {
|
|
14813
|
+
...report,
|
|
14814
|
+
passFailStatus: judgment.passFailStatus,
|
|
14815
|
+
metrics: judgment.metrics,
|
|
14816
|
+
llmJudgeReasoning: judgment.llmJudgeReasoning
|
|
14817
|
+
};
|
|
14818
|
+
emitDeferredTestCaseSpan(
|
|
14819
|
+
testCase,
|
|
14820
|
+
completedReport,
|
|
14821
|
+
{ name: report.experimentId ? `benchmark:${report.experimentId}` : `standalone:${report.agentKey}` },
|
|
14822
|
+
report.experimentRunId || report.id,
|
|
14823
|
+
report.runId,
|
|
14824
|
+
void 0,
|
|
14825
|
+
// startTime
|
|
14826
|
+
void 0,
|
|
14827
|
+
// endTime
|
|
14828
|
+
spans[0]?.traceId
|
|
14829
|
+
);
|
|
13685
14830
|
if (report.experimentId) {
|
|
13686
14831
|
await refreshBenchmarkRunStats(storage, report.experimentId, report.id);
|
|
13687
14832
|
}
|
|
@@ -13709,7 +14854,7 @@ function startTracePollingForReportWithModule(report, testCase, storage) {
|
|
|
13709
14854
|
}
|
|
13710
14855
|
);
|
|
13711
14856
|
}
|
|
13712
|
-
function startTracePollingForReport(report, testCase, client) {
|
|
14857
|
+
function startTracePollingForReport(report, testCase, client, benchmark, run) {
|
|
13713
14858
|
if (!report.runId) {
|
|
13714
14859
|
console.warn(`[BenchmarkRunner] No runId for report ${report.id}, cannot start trace polling`);
|
|
13715
14860
|
return;
|
|
@@ -13744,6 +14889,16 @@ function startTracePollingForReport(report, testCase, client) {
|
|
|
13744
14889
|
if (report.experimentId) {
|
|
13745
14890
|
await updateBenchmarkRunStatsForReport(client, report.experimentId, report.id);
|
|
13746
14891
|
}
|
|
14892
|
+
if (benchmark && run) {
|
|
14893
|
+
const completedReport = {
|
|
14894
|
+
...report,
|
|
14895
|
+
passFailStatus: judgment.passFailStatus,
|
|
14896
|
+
metrics: judgment.metrics,
|
|
14897
|
+
llmJudgeReasoning: judgment.llmJudgeReasoning
|
|
14898
|
+
};
|
|
14899
|
+
const agentTraceId = spans[0]?.traceId;
|
|
14900
|
+
emitDeferredTestCaseSpan(testCase, completedReport, benchmark, run.id, report.runId, void 0, void 0, agentTraceId);
|
|
14901
|
+
}
|
|
13747
14902
|
} catch (error) {
|
|
13748
14903
|
console.error(`[BenchmarkRunner] Failed to judge report ${report.id}:`, error instanceof Error ? error.message : error);
|
|
13749
14904
|
await updateRunWithClient(client, report.id, {
|
|
@@ -15179,11 +16334,13 @@ router10.get("/api/storage/runs", async (req, res) => {
|
|
|
15179
16334
|
try {
|
|
15180
16335
|
const { size = "100", from = "0", fields } = req.query;
|
|
15181
16336
|
let realData = [];
|
|
16337
|
+
const fieldList = typeof fields === "string" ? fields.split(",").map((f) => f.trim()).filter(Boolean) : void 0;
|
|
15182
16338
|
const storage = getStorageModule();
|
|
15183
16339
|
try {
|
|
15184
16340
|
const result = await storage.runs.getAll({
|
|
15185
16341
|
size: parseInt(size),
|
|
15186
|
-
from: parseInt(from)
|
|
16342
|
+
from: parseInt(from),
|
|
16343
|
+
_source: fieldList
|
|
15187
16344
|
});
|
|
15188
16345
|
realData = result.items;
|
|
15189
16346
|
} catch (e) {
|
|
@@ -19349,7 +20506,25 @@ async function initializeStorageBackend() {
|
|
|
19349
20506
|
}
|
|
19350
20507
|
async function createApp() {
|
|
19351
20508
|
await migrateYamlToJsonIfNeeded();
|
|
19352
|
-
await loadConfig();
|
|
20509
|
+
const config2 = await loadConfig();
|
|
20510
|
+
const obsConfig = getObservabilityConfigFromFile() ?? getObservabilityConfigFromEnv();
|
|
20511
|
+
let opensearchExporterConfig;
|
|
20512
|
+
if (obsConfig?.endpoint) {
|
|
20513
|
+
opensearchExporterConfig = {
|
|
20514
|
+
endpoint: obsConfig.endpoint,
|
|
20515
|
+
username: obsConfig.username,
|
|
20516
|
+
password: obsConfig.password,
|
|
20517
|
+
tlsSkipVerify: obsConfig.tlsSkipVerify,
|
|
20518
|
+
indexName: "otel-v1-apm-span"
|
|
20519
|
+
};
|
|
20520
|
+
}
|
|
20521
|
+
initEvalTracerProvider(resolveEvalTelemetryConfig({
|
|
20522
|
+
...config2.telemetry,
|
|
20523
|
+
opensearch: opensearchExporterConfig
|
|
20524
|
+
}));
|
|
20525
|
+
process.once("SIGTERM", async () => {
|
|
20526
|
+
await shutdownEvalTracer();
|
|
20527
|
+
});
|
|
19353
20528
|
await initializeStorageBackend();
|
|
19354
20529
|
const app = express2();
|
|
19355
20530
|
setupMiddleware(app);
|
|
@@ -19358,8 +20533,110 @@ async function createApp() {
|
|
|
19358
20533
|
return app;
|
|
19359
20534
|
}
|
|
19360
20535
|
|
|
20536
|
+
// server/services/observioAgent.ts
|
|
20537
|
+
import { spawn as spawn2 } from "child_process";
|
|
20538
|
+
import { existsSync as existsSync5 } from "fs";
|
|
20539
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
20540
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
20541
|
+
import net from "net";
|
|
20542
|
+
var __filename2 = fileURLToPath3(import.meta.url);
|
|
20543
|
+
var __dirname2 = dirname2(__filename2);
|
|
20544
|
+
var OBSERVIO_PORT = 3001;
|
|
20545
|
+
var observioChild = null;
|
|
20546
|
+
function findObservioRoot() {
|
|
20547
|
+
let dir = __dirname2;
|
|
20548
|
+
for (let i = 0; i < 5; i++) {
|
|
20549
|
+
const candidate = join5(dir, "observio-sample-agent");
|
|
20550
|
+
if (existsSync5(join5(candidate, "package.json"))) {
|
|
20551
|
+
return candidate;
|
|
20552
|
+
}
|
|
20553
|
+
dir = dirname2(dir);
|
|
20554
|
+
}
|
|
20555
|
+
return null;
|
|
20556
|
+
}
|
|
20557
|
+
function isPortFree(port) {
|
|
20558
|
+
return new Promise((resolve2) => {
|
|
20559
|
+
let resolved = false;
|
|
20560
|
+
const done = (value) => {
|
|
20561
|
+
if (!resolved) {
|
|
20562
|
+
resolved = true;
|
|
20563
|
+
socket.destroy();
|
|
20564
|
+
resolve2(value);
|
|
20565
|
+
}
|
|
20566
|
+
};
|
|
20567
|
+
const socket = new net.Socket();
|
|
20568
|
+
socket.setTimeout(1e3);
|
|
20569
|
+
socket.once("connect", () => done(false));
|
|
20570
|
+
socket.once("timeout", () => done(true));
|
|
20571
|
+
socket.once("error", () => done(true));
|
|
20572
|
+
socket.connect(port, "localhost");
|
|
20573
|
+
});
|
|
20574
|
+
}
|
|
20575
|
+
function spawnObservioAgent(cwd) {
|
|
20576
|
+
if (!existsSync5(join5(cwd, "node_modules", "@langchain", "langgraph"))) {
|
|
20577
|
+
console.log(" [observio] Dependencies not installed. Run `cd observio-sample-agent && npm install --legacy-peer-deps` first.");
|
|
20578
|
+
return null;
|
|
20579
|
+
}
|
|
20580
|
+
const child = spawn2("npm", ["run", "start:ag-ui"], {
|
|
20581
|
+
cwd,
|
|
20582
|
+
detached: false,
|
|
20583
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
20584
|
+
shell: true
|
|
20585
|
+
});
|
|
20586
|
+
observioChild = child;
|
|
20587
|
+
child.once("exit", () => {
|
|
20588
|
+
if (observioChild === child) observioChild = null;
|
|
20589
|
+
});
|
|
20590
|
+
child.stdout?.on("data", (data) => {
|
|
20591
|
+
const lines = data.toString().trim().split("\n");
|
|
20592
|
+
for (const line of lines) {
|
|
20593
|
+
if (line.trim()) {
|
|
20594
|
+
console.log(` [observio] ${line}`);
|
|
20595
|
+
}
|
|
20596
|
+
}
|
|
20597
|
+
});
|
|
20598
|
+
child.stderr?.on("data", (data) => {
|
|
20599
|
+
const lines = data.toString().trim().split("\n");
|
|
20600
|
+
for (const line of lines) {
|
|
20601
|
+
if (line.trim()) {
|
|
20602
|
+
console.error(` [observio] ${line}`);
|
|
20603
|
+
}
|
|
20604
|
+
}
|
|
20605
|
+
});
|
|
20606
|
+
return child;
|
|
20607
|
+
}
|
|
20608
|
+
|
|
19361
20609
|
// server/index.ts
|
|
19362
20610
|
var PORT2 = config_default.PORT;
|
|
20611
|
+
var MAX_PORT_ATTEMPTS = 10;
|
|
20612
|
+
var observioChild2 = null;
|
|
20613
|
+
async function tryStartObservioAgent() {
|
|
20614
|
+
try {
|
|
20615
|
+
const root = findObservioRoot();
|
|
20616
|
+
if (!root) {
|
|
20617
|
+
console.log(" Observio sample agent: not found (skipped)");
|
|
20618
|
+
return;
|
|
20619
|
+
}
|
|
20620
|
+
const free = await isPortFree(OBSERVIO_PORT);
|
|
20621
|
+
if (!free) {
|
|
20622
|
+
console.log(` Observio sample agent: port ${OBSERVIO_PORT} already in use (skipped)`);
|
|
20623
|
+
return;
|
|
20624
|
+
}
|
|
20625
|
+
observioChild2 = spawnObservioAgent(root);
|
|
20626
|
+
if (!observioChild2) {
|
|
20627
|
+
return;
|
|
20628
|
+
}
|
|
20629
|
+
console.log(` Observio sample agent: starting on port ${OBSERVIO_PORT}...`);
|
|
20630
|
+
observioChild2.on("exit", (code) => {
|
|
20631
|
+
if (code !== null && code !== 0) {
|
|
20632
|
+
console.log(` [observio] Exited with code ${code}`);
|
|
20633
|
+
}
|
|
20634
|
+
observioChild2 = null;
|
|
20635
|
+
});
|
|
20636
|
+
} catch (err) {
|
|
20637
|
+
console.log(` Observio sample agent: failed to start (${err instanceof Error ? err.message : err})`);
|
|
20638
|
+
}
|
|
20639
|
+
}
|
|
19363
20640
|
async function startServer() {
|
|
19364
20641
|
const app = await createApp();
|
|
19365
20642
|
try {
|
|
@@ -19369,38 +20646,60 @@ async function startServer() {
|
|
|
19369
20646
|
}
|
|
19370
20647
|
} catch {
|
|
19371
20648
|
}
|
|
19372
|
-
const
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
20649
|
+
const tryListen = (port) => {
|
|
20650
|
+
return new Promise((resolve2, reject) => {
|
|
20651
|
+
const server = app.listen(port, "0.0.0.0");
|
|
20652
|
+
server.on("listening", () => {
|
|
20653
|
+
console.log(`
|
|
20654
|
+
Backend Server running on http://0.0.0.0:${port}`);
|
|
20655
|
+
console.log(` Health check: http://localhost:${port}/health`);
|
|
20656
|
+
console.log(` AWS Region: ${process.env.AWS_REGION || "us-west-2"}`);
|
|
20657
|
+
console.log(` Bedrock Model: ${process.env.BEDROCK_MODEL_ID || "us.anthropic.claude-sonnet-4-5-20250929-v1:0"}`);
|
|
20658
|
+
const storageEndpoint = process.env.OPENSEARCH_STORAGE_ENDPOINT || getStorageConfigFromFile()?.endpoint;
|
|
20659
|
+
if (storageEndpoint) {
|
|
20660
|
+
console.log(` OpenSearch Storage: ${storageEndpoint}`);
|
|
20661
|
+
} else {
|
|
20662
|
+
console.log(` OpenSearch Storage: NOT CONFIGURED`);
|
|
20663
|
+
}
|
|
20664
|
+
console.log("");
|
|
20665
|
+
const shutdown = (signal) => {
|
|
20666
|
+
console.log(`
|
|
19388
20667
|
Received ${signal}, shutting down gracefully...`);
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
|
|
19393
|
-
|
|
19394
|
-
|
|
19395
|
-
|
|
19396
|
-
|
|
19397
|
-
|
|
19398
|
-
|
|
20668
|
+
if (observioChild2 && !observioChild2.killed) {
|
|
20669
|
+
console.log(" Stopping observio sample agent...");
|
|
20670
|
+
observioChild2.kill("SIGTERM");
|
|
20671
|
+
observioChild2 = null;
|
|
20672
|
+
}
|
|
20673
|
+
try {
|
|
20674
|
+
const { codingAgentRegistry: codingAgentRegistry3 } = (init_codingAgents(), __toCommonJS(codingAgents_exports));
|
|
20675
|
+
if (codingAgentRegistry3) {
|
|
20676
|
+
codingAgentRegistry3.stopBackgroundRefresh();
|
|
20677
|
+
}
|
|
20678
|
+
} catch {
|
|
20679
|
+
}
|
|
20680
|
+
server.close(() => {
|
|
20681
|
+
console.log(" Server closed.");
|
|
20682
|
+
process.exit(0);
|
|
20683
|
+
});
|
|
20684
|
+
setTimeout(() => process.exit(0), 5e3).unref();
|
|
20685
|
+
};
|
|
20686
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
20687
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
20688
|
+
tryStartObservioAgent();
|
|
20689
|
+
resolve2();
|
|
20690
|
+
});
|
|
20691
|
+
server.on("error", (err) => {
|
|
20692
|
+
server.close();
|
|
20693
|
+
if (err.code === "EADDRINUSE" && port <= PORT2 + MAX_PORT_ATTEMPTS) {
|
|
20694
|
+
console.log(` Port ${port} is in use, trying ${port + 1}...`);
|
|
20695
|
+
resolve2(tryListen(port + 1));
|
|
20696
|
+
} else {
|
|
20697
|
+
reject(err);
|
|
20698
|
+
}
|
|
20699
|
+
});
|
|
19399
20700
|
});
|
|
19400
|
-
setTimeout(() => process.exit(0), 5e3).unref();
|
|
19401
20701
|
};
|
|
19402
|
-
|
|
19403
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
20702
|
+
await tryListen(PORT2);
|
|
19404
20703
|
}
|
|
19405
20704
|
startServer().catch((error) => {
|
|
19406
20705
|
console.error("Failed to start server:", error);
|