@opensearch-project/agent-health 0.0.1 → 0.1.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.
@@ -0,0 +1,1527 @@
1
+ // lib/config/defineConfig.ts
2
+ function defineConfig(config) {
3
+ return config;
4
+ }
5
+
6
+ // lib/debug.ts
7
+ var isBrowser = typeof window !== "undefined";
8
+ var serverDebugEnabled = !isBrowser && typeof process !== "undefined" && process.env?.DEBUG === "true";
9
+ function isDebugEnabled() {
10
+ if (isBrowser) {
11
+ try {
12
+ return localStorage.getItem("agenteval_debug") === "true";
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+ return serverDebugEnabled;
18
+ }
19
+ function debug(module, ...args) {
20
+ if (isDebugEnabled()) {
21
+ console.debug(`[${module}]`, ...args);
22
+ }
23
+ }
24
+
25
+ // services/connectors/base/BaseConnector.ts
26
+ var BaseConnector = class {
27
+ /**
28
+ * Build HTTP headers from auth configuration
29
+ * @param auth Authentication configuration
30
+ * @returns Headers object ready for fetch/axios
31
+ */
32
+ buildAuthHeaders(auth) {
33
+ const headers = {};
34
+ switch (auth.type) {
35
+ case "basic":
36
+ if (auth.username && auth.password) {
37
+ const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
38
+ headers["Authorization"] = `Basic ${credentials}`;
39
+ }
40
+ break;
41
+ case "bearer":
42
+ if (auth.token) {
43
+ headers["Authorization"] = `Bearer ${auth.token}`;
44
+ }
45
+ break;
46
+ case "api-key":
47
+ if (auth.token) {
48
+ headers["X-API-Key"] = auth.token;
49
+ headers["x-api-key"] = auth.token;
50
+ }
51
+ break;
52
+ case "aws-sigv4":
53
+ console.warn("[BaseConnector] AWS SigV4 auth requires runtime signing");
54
+ break;
55
+ case "none":
56
+ default:
57
+ break;
58
+ }
59
+ if (auth.headers) {
60
+ Object.assign(headers, auth.headers);
61
+ }
62
+ return headers;
63
+ }
64
+ /**
65
+ * Build environment variables from auth configuration
66
+ * Used by subprocess connectors
67
+ */
68
+ buildAuthEnv(auth) {
69
+ const env = {};
70
+ if (auth.type === "aws-sigv4") {
71
+ if (auth.awsRegion) env["AWS_REGION"] = auth.awsRegion;
72
+ if (auth.awsAccessKeyId) env["AWS_ACCESS_KEY_ID"] = auth.awsAccessKeyId;
73
+ if (auth.awsSecretAccessKey) env["AWS_SECRET_ACCESS_KEY"] = auth.awsSecretAccessKey;
74
+ if (auth.awsSessionToken) env["AWS_SESSION_TOKEN"] = auth.awsSessionToken;
75
+ }
76
+ return env;
77
+ }
78
+ /**
79
+ * Generate a unique ID for trajectory steps
80
+ */
81
+ generateId() {
82
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
83
+ }
84
+ /**
85
+ * Create a trajectory step with common fields
86
+ */
87
+ createStep(type, content, extra) {
88
+ return {
89
+ id: this.generateId(),
90
+ timestamp: Date.now(),
91
+ type,
92
+ content,
93
+ ...extra
94
+ };
95
+ }
96
+ /**
97
+ * Default health check implementation
98
+ * Subclasses can override for protocol-specific checks
99
+ */
100
+ async healthCheck(endpoint, auth) {
101
+ try {
102
+ const headers = this.buildAuthHeaders(auth);
103
+ const response = await fetch(endpoint, {
104
+ method: "HEAD",
105
+ headers
106
+ });
107
+ return response.ok;
108
+ } catch (error) {
109
+ console.error(`[${this.type}] Health check failed:`, error);
110
+ return false;
111
+ }
112
+ }
113
+ /**
114
+ * Log debug message with connector type prefix
115
+ */
116
+ debug(message, ...args) {
117
+ debug(this.type, message, ...args);
118
+ }
119
+ /**
120
+ * Log error message with connector type prefix
121
+ */
122
+ error(message, ...args) {
123
+ console.error(`[${this.type}] ${message}`, ...args);
124
+ }
125
+ };
126
+
127
+ // types/agui.ts
128
+ import { EventType } from "@ag-ui/core";
129
+ var AGUIEventType = EventType;
130
+
131
+ // services/agent/sseStream.ts
132
+ var SSEClient = class {
133
+ constructor() {
134
+ this.abortController = null;
135
+ }
136
+ /**
137
+ * Start consuming SSE stream from the agent endpoint
138
+ */
139
+ async consume(options) {
140
+ const {
141
+ url,
142
+ method = "POST",
143
+ headers = {},
144
+ body,
145
+ onEvent,
146
+ onError,
147
+ onComplete,
148
+ completeOnRunEnd = false,
149
+ idleTimeoutMs = 12e4
150
+ // 2 minute idle timeout by default (LLM agents can be slow)
151
+ } = options;
152
+ this.abortController = new AbortController();
153
+ debug("SSE", "Connecting to", url);
154
+ debug("SSE", "Method:", method);
155
+ debug("SSE", "Payload:", JSON.stringify(body, null, 2).substring(0, 500));
156
+ try {
157
+ const response = await fetch(url, {
158
+ method,
159
+ headers: {
160
+ "Content-Type": "application/json",
161
+ Accept: "text/event-stream",
162
+ ...headers
163
+ },
164
+ body: body ? JSON.stringify(body) : void 0,
165
+ signal: this.abortController.signal
166
+ });
167
+ if (!response.ok) {
168
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
169
+ }
170
+ if (!response.body) {
171
+ throw new Error("Response body is null");
172
+ }
173
+ debug("SSE", "Connected, streaming events...");
174
+ debug("SSE", "Response status:", response.status);
175
+ debug("SSE", "Content-Type:", response.headers.get("content-type"));
176
+ const completionReason = await this.processStream(response.body, onEvent, completeOnRunEnd, idleTimeoutMs);
177
+ debug("SSE", `Stream completed: ${completionReason}`);
178
+ onComplete?.();
179
+ } catch (error) {
180
+ if (error instanceof Error) {
181
+ if (error.name === "AbortError") {
182
+ debug("SSE", "Stream aborted (expected after run completion)");
183
+ onComplete?.();
184
+ } else {
185
+ console.error("[SSE] Stream error:", error.message);
186
+ onError?.(error);
187
+ }
188
+ } else {
189
+ onError?.(new Error("Unknown error occurred"));
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Process the ReadableStream and parse SSE events
195
+ * @returns Reason for stream completion
196
+ */
197
+ async processStream(stream, onEvent, completeOnRunEnd = false, idleTimeoutMs = 12e4) {
198
+ const reader = stream.getReader();
199
+ const decoder = new TextDecoder();
200
+ let buffer = "";
201
+ let lastEventTime = Date.now();
202
+ let eventCount = 0;
203
+ let idleCheckInterval = null;
204
+ const idleTimeoutPromise = new Promise((resolve) => {
205
+ idleCheckInterval = setInterval(() => {
206
+ const idleTime = Date.now() - lastEventTime;
207
+ if (eventCount > 0 && idleTime > idleTimeoutMs) {
208
+ debug("SSE", `Idle timeout: no events for ${idleTime}ms (threshold: ${idleTimeoutMs}ms)`);
209
+ this.abort();
210
+ resolve("idle_timeout");
211
+ }
212
+ }, 1e3);
213
+ });
214
+ try {
215
+ const streamPromise = (async () => {
216
+ while (true) {
217
+ const { done, value } = await reader.read();
218
+ if (done) {
219
+ return "connection_closed";
220
+ }
221
+ lastEventTime = Date.now();
222
+ buffer += decoder.decode(value, { stream: true });
223
+ const lines = buffer.split("\n");
224
+ buffer = lines.pop() || "";
225
+ for (const line of lines) {
226
+ if (line.startsWith("data: ")) {
227
+ const data = line.slice(6);
228
+ if (data.trim()) {
229
+ debug("SSE", "Raw event:", data.substring(0, 200) + (data.length > 200 ? "..." : ""));
230
+ try {
231
+ const event = JSON.parse(data);
232
+ debug("SSE", "Parsed:", event.type);
233
+ eventCount++;
234
+ onEvent(event);
235
+ if (completeOnRunEnd && (event.type === AGUIEventType.RUN_FINISHED || event.type === AGUIEventType.RUN_ERROR)) {
236
+ debug("SSE", `Received ${event.type}, completing stream`);
237
+ this.abort();
238
+ return `event:${event.type}`;
239
+ }
240
+ } catch (parseError) {
241
+ console.error("[SSE] Parse error:", parseError);
242
+ debug("SSE", "Failed data:", data);
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+ })();
249
+ const reason = await Promise.race([streamPromise, idleTimeoutPromise]);
250
+ return reason;
251
+ } finally {
252
+ if (idleCheckInterval) {
253
+ clearInterval(idleCheckInterval);
254
+ }
255
+ reader.releaseLock();
256
+ }
257
+ }
258
+ /**
259
+ * Abort the current stream connection
260
+ */
261
+ abort() {
262
+ this.abortController?.abort();
263
+ }
264
+ };
265
+ async function consumeSSEStream(url, payload, onEvent, headers, options) {
266
+ const client = new SSEClient();
267
+ return new Promise((resolve, reject) => {
268
+ client.consume({
269
+ url,
270
+ method: "POST",
271
+ headers,
272
+ body: payload,
273
+ onEvent,
274
+ onError: (error) => reject(error),
275
+ onComplete: () => resolve(),
276
+ // Enable auto-completion on RUN_FINISHED/RUN_ERROR events
277
+ // This prevents hanging when the agent doesn't close the connection
278
+ completeOnRunEnd: true,
279
+ idleTimeoutMs: options?.idleTimeoutMs
280
+ });
281
+ });
282
+ }
283
+
284
+ // services/agent/payloadBuilder.ts
285
+ var DEFAULT_PPL_TOOL = {
286
+ name: "execute_ppl_query",
287
+ description: "Update the query bar with a PPL query and optionally execute it",
288
+ parameters: {
289
+ type: "object",
290
+ properties: {
291
+ query: {
292
+ type: "string",
293
+ description: "The PPL query to set in the query bar"
294
+ },
295
+ autoExecute: {
296
+ type: "boolean",
297
+ description: "Whether to automatically execute the query (default: true)"
298
+ },
299
+ description: {
300
+ type: "string",
301
+ description: "Optional description of what the query does"
302
+ }
303
+ },
304
+ required: ["query"]
305
+ }
306
+ };
307
+ function generateId(prefix) {
308
+ const timestamp = Date.now();
309
+ const random = Math.random().toString(36).substring(2, 11);
310
+ return `${prefix}-${timestamp}-${random}`;
311
+ }
312
+ function buildAgentPayload(testCase, modelId, threadId, runId) {
313
+ const tools = testCase.tools || [DEFAULT_PPL_TOOL];
314
+ return {
315
+ threadId: threadId || generateId("thread"),
316
+ runId: runId || generateId("run"),
317
+ messages: [
318
+ {
319
+ id: generateId("msg"),
320
+ role: "user",
321
+ content: testCase.initialPrompt
322
+ }
323
+ ],
324
+ tools,
325
+ context: testCase.context || [],
326
+ state: {},
327
+ forwardedProps: {}
328
+ };
329
+ }
330
+
331
+ // services/agent/aguiConverter.ts
332
+ import { v4 as uuidv4 } from "uuid";
333
+ var AGUIToTrajectoryConverter = class {
334
+ constructor() {
335
+ this.currentTextMessage = null;
336
+ this.activeTools = /* @__PURE__ */ new Map();
337
+ this.hasEmittedAction = false;
338
+ this.runFinished = false;
339
+ this.pendingTextIsResponse = false;
340
+ this.runId = null;
341
+ this.threadId = null;
342
+ // Thinking state tracking
343
+ this.isThinking = false;
344
+ this.currentThinkingMessage = null;
345
+ }
346
+ /**
347
+ * Process a single AG UI event and convert it to TrajectoryStep(s)
348
+ * Returns an array of steps (usually 0 or 1, sometimes more)
349
+ */
350
+ processEvent(event) {
351
+ debug("Converter", `Event: ${event.type}`, JSON.stringify(event).substring(0, 300));
352
+ let steps = [];
353
+ switch (event.type) {
354
+ case AGUIEventType.RUN_STARTED:
355
+ steps = this.handleRunStarted(event);
356
+ break;
357
+ case AGUIEventType.RUN_FINISHED:
358
+ steps = this.handleRunFinished(event);
359
+ break;
360
+ case AGUIEventType.RUN_ERROR:
361
+ steps = this.handleRunError(event);
362
+ break;
363
+ case AGUIEventType.TEXT_MESSAGE_START:
364
+ steps = this.handleTextMessageStart(event);
365
+ break;
366
+ case AGUIEventType.TEXT_MESSAGE_CONTENT:
367
+ steps = this.handleTextMessageContent(event);
368
+ break;
369
+ case AGUIEventType.TEXT_MESSAGE_END:
370
+ steps = this.handleTextMessageEnd(event);
371
+ break;
372
+ case AGUIEventType.ACTIVITY_SNAPSHOT:
373
+ steps = this.handleActivitySnapshot(event);
374
+ break;
375
+ case AGUIEventType.ACTIVITY_DELTA:
376
+ steps = this.handleActivityDelta(event);
377
+ break;
378
+ case AGUIEventType.TOOL_CALL_START:
379
+ steps = this.handleToolCallStart(event);
380
+ break;
381
+ case AGUIEventType.TOOL_CALL_ARGS:
382
+ steps = this.handleToolCallArgs(event);
383
+ break;
384
+ case AGUIEventType.TOOL_CALL_END:
385
+ steps = this.handleToolCallEnd(event);
386
+ break;
387
+ case AGUIEventType.TOOL_CALL_RESULT:
388
+ steps = this.handleToolCallResult(event);
389
+ break;
390
+ // Thinking events - extended reasoning from the model
391
+ case AGUIEventType.THINKING_START:
392
+ steps = this.handleThinkingStart(event);
393
+ break;
394
+ case AGUIEventType.THINKING_END:
395
+ steps = this.handleThinkingEnd(event);
396
+ break;
397
+ case AGUIEventType.THINKING_TEXT_MESSAGE_START:
398
+ steps = this.handleThinkingTextMessageStart(event);
399
+ break;
400
+ case AGUIEventType.THINKING_TEXT_MESSAGE_CONTENT:
401
+ steps = this.handleThinkingTextMessageContent(event);
402
+ break;
403
+ case AGUIEventType.THINKING_TEXT_MESSAGE_END:
404
+ steps = this.handleThinkingTextMessageEnd(event);
405
+ break;
406
+ default:
407
+ debug("Converter", `Skipped unhandled event: ${event.type}`);
408
+ break;
409
+ }
410
+ if (steps.length > 0) {
411
+ debug("Converter", `Generated ${steps.length} step(s):`, steps.map((s) => `${s.type}${s.toolName ? `:${s.toolName}` : ""}`).join(", "));
412
+ }
413
+ return steps;
414
+ }
415
+ handleRunStarted(event) {
416
+ this.runId = event.runId;
417
+ this.threadId = event.threadId;
418
+ debug("Converter", `Run started - runId: ${this.runId}, threadId: ${this.threadId}`);
419
+ this.currentTextMessage = null;
420
+ this.activeTools.clear();
421
+ this.hasEmittedAction = false;
422
+ this.runFinished = false;
423
+ this.pendingTextIsResponse = false;
424
+ this.isThinking = false;
425
+ this.currentThinkingMessage = null;
426
+ return [];
427
+ }
428
+ getRunId() {
429
+ return this.runId;
430
+ }
431
+ getThreadId() {
432
+ return this.threadId;
433
+ }
434
+ handleRunFinished(event) {
435
+ this.runFinished = true;
436
+ if (this.currentTextMessage) {
437
+ this.pendingTextIsResponse = true;
438
+ }
439
+ return [];
440
+ }
441
+ handleRunError(event) {
442
+ console.error("[Converter] Run error:", event.message);
443
+ return [{
444
+ id: uuidv4(),
445
+ timestamp: event.timestamp,
446
+ type: "tool_result",
447
+ content: `Error: ${event.message}`,
448
+ status: "FAILURE" /* FAILURE */
449
+ }];
450
+ }
451
+ handleTextMessageStart(event) {
452
+ debug("Converter", `Text message start: ${event.messageId}`);
453
+ this.currentTextMessage = {
454
+ messageId: event.messageId,
455
+ startTime: event.timestamp,
456
+ content: ""
457
+ };
458
+ return [];
459
+ }
460
+ handleTextMessageContent(event) {
461
+ if (this.currentTextMessage && this.currentTextMessage.messageId === event.messageId) {
462
+ this.currentTextMessage.content += event.delta;
463
+ }
464
+ return [];
465
+ }
466
+ handleTextMessageEnd(event) {
467
+ if (!this.currentTextMessage || this.currentTextMessage.messageId !== event.messageId) {
468
+ debug("Converter", `Text end for unknown message: ${event.messageId}`);
469
+ return [];
470
+ }
471
+ const latencyMs = event.timestamp - this.currentTextMessage.startTime;
472
+ const content = this.currentTextMessage.content.trim();
473
+ let stepType;
474
+ if (this.pendingTextIsResponse || this.runFinished) {
475
+ stepType = "response";
476
+ } else {
477
+ stepType = "assistant";
478
+ }
479
+ debug("Converter", `Classification: ${stepType} (runFinished=${this.runFinished}, pendingResponse=${this.pendingTextIsResponse}, hasAction=${this.hasEmittedAction})`);
480
+ if (stepType === "assistant" && content.length === 0) {
481
+ debug("Converter", "Skipping empty assistant message");
482
+ this.currentTextMessage = null;
483
+ return [];
484
+ }
485
+ const step = {
486
+ id: uuidv4(),
487
+ timestamp: event.timestamp,
488
+ type: stepType,
489
+ content,
490
+ latencyMs
491
+ };
492
+ this.currentTextMessage = null;
493
+ return [step];
494
+ }
495
+ handleActivitySnapshot(event) {
496
+ const toolName = this.extractToolName(event.content.title);
497
+ const toolArgs = this.parseToolArgs(event.content.description);
498
+ const actionStepId = uuidv4();
499
+ this.activeTools.set(event.messageId, {
500
+ messageId: event.messageId,
501
+ toolName,
502
+ toolArgs,
503
+ argsAccumulator: "",
504
+ startTime: event.timestamp,
505
+ actionStepId
506
+ });
507
+ this.hasEmittedAction = true;
508
+ debug("Converter", `Tool action: ${toolName}`, toolArgs);
509
+ return [{
510
+ id: actionStepId,
511
+ timestamp: event.timestamp,
512
+ type: "action",
513
+ content: `Calling ${toolName}...`,
514
+ toolName,
515
+ toolArgs
516
+ }];
517
+ }
518
+ handleActivityDelta(event) {
519
+ const toolState = this.activeTools.get(event.messageId);
520
+ if (!toolState) return [];
521
+ const isCompletion = event.patch.some(
522
+ (op) => op.path === "/icon" && (op.value === "CheckCircle" || op.value === "Check")
523
+ );
524
+ if (!isCompletion) return [];
525
+ const descriptionPatch = event.patch.find((op) => op.path === "/description");
526
+ const resultContent = descriptionPatch?.value || "Tool execution completed";
527
+ const latencyMs = event.timestamp - toolState.startTime;
528
+ this.activeTools.delete(event.messageId);
529
+ return [{
530
+ id: uuidv4(),
531
+ timestamp: event.timestamp,
532
+ type: "tool_result",
533
+ content: resultContent,
534
+ status: "SUCCESS" /* SUCCESS */,
535
+ latencyMs
536
+ }];
537
+ }
538
+ handleToolCallStart(event) {
539
+ const actionStepId = uuidv4();
540
+ this.activeTools.set(event.toolCallId, {
541
+ messageId: event.toolCallId,
542
+ toolName: event.toolCallName,
543
+ toolArgs: {},
544
+ argsAccumulator: "",
545
+ // Will accumulate delta strings
546
+ startTime: event.timestamp,
547
+ actionStepId
548
+ });
549
+ this.hasEmittedAction = true;
550
+ debug("Converter", `Tool call started: ${event.toolCallName} (${event.toolCallId})`);
551
+ return [];
552
+ }
553
+ handleToolCallArgs(event) {
554
+ const toolState = this.activeTools.get(event.toolCallId);
555
+ if (toolState) {
556
+ toolState.argsAccumulator += event.delta;
557
+ debug("Converter", `Tool args delta accumulated (${toolState.argsAccumulator.length} chars total)`);
558
+ }
559
+ return [];
560
+ }
561
+ /**
562
+ * Handle TOOL_CALL_END - emits the action step with complete args
563
+ * This is called when the agent is done sending tool call arguments
564
+ * and expects the client to execute the tool
565
+ */
566
+ handleToolCallEnd(event) {
567
+ const toolState = this.activeTools.get(event.toolCallId);
568
+ if (!toolState) {
569
+ debug("Converter", `Tool call end for unknown tool: ${event.toolCallId}`);
570
+ return [];
571
+ }
572
+ let parsedArgs = {};
573
+ if (toolState.argsAccumulator) {
574
+ try {
575
+ parsedArgs = JSON.parse(toolState.argsAccumulator);
576
+ debug("Converter", `Tool args parsed: ${JSON.stringify(parsedArgs).substring(0, 200)}`);
577
+ } catch {
578
+ parsedArgs = { _raw: toolState.argsAccumulator };
579
+ }
580
+ }
581
+ toolState.toolArgs = parsedArgs;
582
+ const latencyMs = event.timestamp - toolState.startTime;
583
+ const actionStep = {
584
+ id: toolState.actionStepId,
585
+ timestamp: toolState.startTime,
586
+ type: "action",
587
+ content: `Calling ${toolState.toolName}...`,
588
+ toolName: toolState.toolName,
589
+ toolArgs: parsedArgs,
590
+ latencyMs
591
+ };
592
+ debug("Converter", `Tool call end: ${toolState.toolName} - action step emitted with args`);
593
+ toolState.actionStepId = "";
594
+ return [actionStep];
595
+ }
596
+ handleToolCallResult(event) {
597
+ const toolState = this.activeTools.get(event.toolCallId);
598
+ if (!toolState) return [];
599
+ const latencyMs = event.timestamp - toolState.startTime;
600
+ const steps = [];
601
+ const actionAlreadyEmitted = !toolState.actionStepId;
602
+ if (!actionAlreadyEmitted) {
603
+ let parsedArgs = {};
604
+ if (toolState.argsAccumulator) {
605
+ try {
606
+ parsedArgs = JSON.parse(toolState.argsAccumulator);
607
+ debug("Converter", `Tool args parsed: ${JSON.stringify(parsedArgs).substring(0, 200)}`);
608
+ } catch {
609
+ parsedArgs = { _raw: toolState.argsAccumulator };
610
+ }
611
+ }
612
+ toolState.toolArgs = parsedArgs;
613
+ steps.push({
614
+ id: toolState.actionStepId,
615
+ timestamp: toolState.startTime,
616
+ type: "action",
617
+ content: `Calling ${toolState.toolName}...`,
618
+ toolName: toolState.toolName,
619
+ toolArgs: parsedArgs
620
+ });
621
+ }
622
+ let resultContent;
623
+ try {
624
+ const parsed = JSON.parse(event.content);
625
+ resultContent = typeof parsed === "string" ? parsed : JSON.stringify(parsed, null, 2);
626
+ } catch (e) {
627
+ resultContent = event.content;
628
+ }
629
+ steps.push({
630
+ id: uuidv4(),
631
+ timestamp: event.timestamp,
632
+ type: "tool_result",
633
+ content: resultContent,
634
+ status: "SUCCESS" /* SUCCESS */,
635
+ latencyMs
636
+ });
637
+ this.activeTools.delete(event.toolCallId);
638
+ debug("Converter", `Tool call result: ${toolState.toolName} -> ${steps.length} steps emitted (action already emitted: ${actionAlreadyEmitted})`);
639
+ return steps;
640
+ }
641
+ // ============ THINKING Event Handlers ============
642
+ handleThinkingStart(event) {
643
+ this.isThinking = true;
644
+ debug("Converter", "Thinking started");
645
+ return [];
646
+ }
647
+ handleThinkingEnd(event) {
648
+ this.isThinking = false;
649
+ debug("Converter", "Thinking ended");
650
+ return [];
651
+ }
652
+ handleThinkingTextMessageStart(event) {
653
+ this.currentThinkingMessage = {
654
+ startTime: event.timestamp || Date.now(),
655
+ content: ""
656
+ };
657
+ debug("Converter", "Thinking text message started");
658
+ return [];
659
+ }
660
+ handleThinkingTextMessageContent(event) {
661
+ if (this.currentThinkingMessage) {
662
+ this.currentThinkingMessage.content += event.delta;
663
+ }
664
+ return [];
665
+ }
666
+ handleThinkingTextMessageEnd(event) {
667
+ if (!this.currentThinkingMessage) {
668
+ debug("Converter", "Thinking text end with no active thinking message");
669
+ return [];
670
+ }
671
+ const content = this.currentThinkingMessage.content.trim();
672
+ if (content.length === 0) {
673
+ debug("Converter", "Skipping empty thinking message");
674
+ this.currentThinkingMessage = null;
675
+ return [];
676
+ }
677
+ const latencyMs = (event.timestamp || Date.now()) - this.currentThinkingMessage.startTime;
678
+ const step = {
679
+ id: uuidv4(),
680
+ timestamp: this.currentThinkingMessage.startTime,
681
+ type: "thinking",
682
+ content,
683
+ latencyMs
684
+ };
685
+ debug("Converter", `Thinking message completed: ${content.length} chars`);
686
+ this.currentThinkingMessage = null;
687
+ return [step];
688
+ }
689
+ extractToolName(title) {
690
+ const runningMatch = title.match(/^Running\s+(.+)$/);
691
+ if (runningMatch) return runningMatch[1];
692
+ const completedMatch = title.match(/^(.+)\s+completed$/i);
693
+ if (completedMatch) return completedMatch[1];
694
+ return title;
695
+ }
696
+ parseToolArgs(description) {
697
+ try {
698
+ const args = {};
699
+ const pairs = description.match(/(\w+):\s*("(?:[^"]|\\")*"|\w+)/g);
700
+ if (pairs) {
701
+ pairs.forEach((pair) => {
702
+ const [key, rawValue] = pair.split(":").map((s) => s.trim());
703
+ let value = rawValue;
704
+ if (rawValue.startsWith('"') && rawValue.endsWith('"')) {
705
+ value = rawValue.slice(1, -1);
706
+ } else if (rawValue === "true") {
707
+ value = true;
708
+ } else if (rawValue === "false") {
709
+ value = false;
710
+ } else if (!isNaN(Number(rawValue))) {
711
+ value = Number(rawValue);
712
+ }
713
+ args[key] = value;
714
+ });
715
+ return args;
716
+ }
717
+ return { description };
718
+ } catch (e) {
719
+ return { description };
720
+ }
721
+ }
722
+ };
723
+ function computeTrajectoryFromRawEvents(rawEvents) {
724
+ const converter = new AGUIToTrajectoryConverter();
725
+ const trajectory = [];
726
+ for (const event of rawEvents) {
727
+ const steps = converter.processEvent(event);
728
+ trajectory.push(...steps);
729
+ }
730
+ trajectory.sort((a, b) => a.timestamp - b.timestamp);
731
+ return trajectory;
732
+ }
733
+
734
+ // services/connectors/agui/AGUIStreamingConnector.ts
735
+ var AGUIStreamingConnector = class extends BaseConnector {
736
+ constructor() {
737
+ super(...arguments);
738
+ this.type = "agui-streaming";
739
+ this.name = "AG-UI Streaming";
740
+ this.supportsStreaming = true;
741
+ }
742
+ /**
743
+ * Build AG-UI payload from standard request
744
+ */
745
+ buildPayload(request) {
746
+ return buildAgentPayload(
747
+ request.testCase,
748
+ request.modelId,
749
+ request.threadId,
750
+ request.runId
751
+ );
752
+ }
753
+ /**
754
+ * Execute the request using SSE streaming
755
+ */
756
+ async execute(endpoint, request, auth, onProgress, onRawEvent) {
757
+ const hasPrebuiltPayload = !!request.payload;
758
+ const payload = request.payload || this.buildPayload(request);
759
+ const headers = this.buildAuthHeaders(auth);
760
+ const trajectory = [];
761
+ const rawEvents = [];
762
+ const converter = new AGUIToTrajectoryConverter();
763
+ this.debug("Executing AG-UI streaming request");
764
+ await consumeSSEStream(
765
+ endpoint,
766
+ payload,
767
+ (event) => {
768
+ rawEvents.push(event);
769
+ onRawEvent?.(event);
770
+ const steps = converter.processEvent(event);
771
+ steps.forEach((step) => {
772
+ trajectory.push(step);
773
+ onProgress?.(step);
774
+ });
775
+ },
776
+ headers
777
+ );
778
+ const runId = converter.getRunId();
779
+ this.debug("Stream completed. RunId:", runId, "Steps:", trajectory.length);
780
+ return {
781
+ trajectory,
782
+ runId,
783
+ rawEvents,
784
+ metadata: {
785
+ threadId: converter.getThreadId()
786
+ }
787
+ };
788
+ }
789
+ /**
790
+ * Parse raw AG-UI events into trajectory steps
791
+ * Used for re-processing stored raw events
792
+ */
793
+ parseResponse(rawEvents) {
794
+ return computeTrajectoryFromRawEvents(rawEvents);
795
+ }
796
+ /**
797
+ * Health check for AG-UI endpoint
798
+ * Tries to connect without sending a full request
799
+ */
800
+ async healthCheck(endpoint, auth) {
801
+ try {
802
+ const headers = this.buildAuthHeaders(auth);
803
+ const response = await fetch(endpoint, {
804
+ method: "OPTIONS",
805
+ headers
806
+ });
807
+ return true;
808
+ } catch (error) {
809
+ this.error("Health check failed:", error);
810
+ return false;
811
+ }
812
+ }
813
+ };
814
+ var aguiStreamingConnector = new AGUIStreamingConnector();
815
+
816
+ // services/connectors/rest/RESTConnector.ts
817
+ var RESTConnector = class extends BaseConnector {
818
+ constructor() {
819
+ super(...arguments);
820
+ this.type = "rest";
821
+ this.name = "REST API";
822
+ this.supportsStreaming = false;
823
+ }
824
+ /**
825
+ * Build generic REST payload
826
+ * Can be customized via connectorConfig
827
+ */
828
+ buildPayload(request) {
829
+ return {
830
+ prompt: request.testCase.initialPrompt,
831
+ context: request.testCase.context,
832
+ model: request.modelId,
833
+ tools: request.testCase.tools
834
+ };
835
+ }
836
+ /**
837
+ * Execute REST request
838
+ */
839
+ async execute(endpoint, request, auth, onProgress, onRawEvent) {
840
+ const payload = request.payload || this.buildPayload(request);
841
+ const headers = this.buildAuthHeaders(auth);
842
+ this.debug("Executing REST request");
843
+ this.debug("Endpoint:", endpoint);
844
+ this.debug("Payload:", JSON.stringify(payload).substring(0, 500));
845
+ const response = await fetch(endpoint, {
846
+ method: "POST",
847
+ headers: {
848
+ "Content-Type": "application/json",
849
+ ...headers
850
+ },
851
+ body: JSON.stringify(payload)
852
+ });
853
+ if (!response.ok) {
854
+ const errorText = await response.text();
855
+ throw new Error(`REST request failed: ${response.status} - ${errorText}`);
856
+ }
857
+ const data = await response.json();
858
+ onRawEvent?.(data);
859
+ const trajectory = this.parseResponse(data);
860
+ trajectory.forEach((step) => onProgress?.(step));
861
+ return {
862
+ trajectory,
863
+ runId: data.runId || data.id || null,
864
+ rawEvents: [data],
865
+ metadata: {
866
+ status: response.status,
867
+ responseHeaders: Object.fromEntries(response.headers.entries())
868
+ }
869
+ };
870
+ }
871
+ /**
872
+ * Parse REST response into trajectory steps
873
+ * This is a generic implementation - subclass for specific APIs
874
+ */
875
+ parseResponse(data) {
876
+ const steps = [];
877
+ if (data.thinking) {
878
+ steps.push(this.createStep("thinking", data.thinking));
879
+ }
880
+ if (data.toolCalls && Array.isArray(data.toolCalls)) {
881
+ for (const call of data.toolCalls) {
882
+ steps.push(this.createStep("action", `Calling ${call.name}...`, {
883
+ toolName: call.name,
884
+ toolArgs: call.args || call.input
885
+ }));
886
+ if (call.result !== void 0) {
887
+ steps.push(this.createStep(
888
+ "tool_result",
889
+ typeof call.result === "string" ? call.result : JSON.stringify(call.result),
890
+ { status: "SUCCESS" }
891
+ ));
892
+ }
893
+ }
894
+ }
895
+ const responseContent = data.response || data.content || data.answer || data.text || data.message;
896
+ if (responseContent) {
897
+ steps.push(this.createStep(
898
+ "response",
899
+ typeof responseContent === "string" ? responseContent : JSON.stringify(responseContent)
900
+ ));
901
+ }
902
+ if (data.inference_results) {
903
+ const outputs = data.inference_results[0]?.output || [];
904
+ for (const output of outputs) {
905
+ if (output.name === "response") {
906
+ const content = output.dataAsMap?.response || output.result;
907
+ if (content) {
908
+ steps.push(this.createStep("response", content));
909
+ }
910
+ }
911
+ }
912
+ }
913
+ if (steps.length === 0 && data) {
914
+ steps.push(this.createStep("response", JSON.stringify(data, null, 2)));
915
+ }
916
+ return steps;
917
+ }
918
+ };
919
+ var restConnector = new RESTConnector();
920
+
921
+ // services/connectors/subprocess/SubprocessConnector.ts
922
+ import { spawn } from "child_process";
923
+ var DEFAULT_SUBPROCESS_CONFIG = {
924
+ command: "",
925
+ args: [],
926
+ env: {},
927
+ inputMode: "stdin",
928
+ outputParser: "text",
929
+ timeout: 3e5
930
+ // 5 minutes
931
+ };
932
+ var SubprocessConnector = class extends BaseConnector {
933
+ constructor(config) {
934
+ super();
935
+ this.type = "subprocess";
936
+ this.name = "Subprocess (CLI)";
937
+ this.supportsStreaming = true;
938
+ this.config = { ...DEFAULT_SUBPROCESS_CONFIG, ...config };
939
+ }
940
+ /**
941
+ * Build input for the subprocess
942
+ */
943
+ buildPayload(request) {
944
+ let prompt = request.testCase.initialPrompt;
945
+ if (request.testCase.context && request.testCase.context.length > 0) {
946
+ const contextStr = request.testCase.context.map((c) => `${c.description}: ${c.value}`).join("\n");
947
+ prompt = `Context:
948
+ ${contextStr}
949
+
950
+ Question: ${prompt}`;
951
+ }
952
+ return prompt;
953
+ }
954
+ /**
955
+ * Execute subprocess and capture output
956
+ */
957
+ async execute(endpoint, request, auth, onProgress, onRawEvent) {
958
+ this.debug("========== execute() STARTED ==========");
959
+ const command = endpoint || this.config.command;
960
+ const args = this.config.args || [];
961
+ const input = request.payload || this.buildPayload(request);
962
+ this.debug("Command:", command);
963
+ this.debug("Args:", args);
964
+ this.debug("Input mode:", this.config.inputMode);
965
+ this.debug("Output parser:", this.config.outputParser);
966
+ this.debug("Timeout:", this.config.timeout);
967
+ this.debug("Input (first 500 chars):", input.substring(0, 500));
968
+ this.debug("Working dir:", this.config.workingDir || process.cwd());
969
+ const env = {
970
+ ...process.env,
971
+ ...this.buildAuthEnv(auth),
972
+ ...this.config.env
973
+ };
974
+ return new Promise((resolve, reject) => {
975
+ const trajectory = [];
976
+ const rawOutput = [];
977
+ let stdout = "";
978
+ let stderr = "";
979
+ let settled = false;
980
+ const finalArgs = this.config.inputMode === "arg" ? [...args, input] : args;
981
+ this.debug("Spawning process...");
982
+ this.debug("Full command:", command, finalArgs.join(" "));
983
+ const proc = spawn(command, finalArgs, {
984
+ env,
985
+ cwd: this.config.workingDir,
986
+ shell: true
987
+ });
988
+ this.debug("Process spawned, PID:", proc.pid);
989
+ const timeoutId = setTimeout(() => {
990
+ if (settled) return;
991
+ settled = true;
992
+ this.debug("TIMEOUT reached, killing process");
993
+ proc.kill("SIGTERM");
994
+ reject(new Error(`Subprocess timed out after ${this.config.timeout}ms`));
995
+ }, this.config.timeout);
996
+ if (this.config.inputMode === "stdin") {
997
+ this.debug("Writing input to stdin...");
998
+ proc.stdin.write(input);
999
+ proc.stdin.end();
1000
+ this.debug("stdin closed");
1001
+ }
1002
+ proc.stdout.on("data", (data) => {
1003
+ const chunk = data.toString();
1004
+ this.debug("stdout received:", chunk.length, "bytes");
1005
+ this.debug("stdout preview:", chunk.substring(0, 200));
1006
+ stdout += chunk;
1007
+ rawOutput.push({ type: "stdout", data: chunk, timestamp: Date.now() });
1008
+ onRawEvent?.({ type: "stdout", data: chunk });
1009
+ if (this.config.outputParser === "streaming") {
1010
+ this.parseStreamingOutput(chunk, trajectory, onProgress);
1011
+ }
1012
+ });
1013
+ proc.stderr.on("data", (data) => {
1014
+ const chunk = data.toString();
1015
+ this.debug("stderr received:", chunk.length, "bytes");
1016
+ this.debug("stderr:", chunk);
1017
+ stderr += chunk;
1018
+ onRawEvent?.({ type: "stderr", data: chunk });
1019
+ this.debug("stderr:", chunk);
1020
+ });
1021
+ proc.on("close", (code, signal) => {
1022
+ this.debug("Process closed with code:", code, "signal:", signal);
1023
+ clearTimeout(timeoutId);
1024
+ if (settled) return;
1025
+ settled = true;
1026
+ if (code !== 0) {
1027
+ this.debug("Non-zero exit code:", code);
1028
+ this.error(`Process exited with code ${code}`);
1029
+ this.error("stderr:", stderr);
1030
+ }
1031
+ const finalTrajectory = this.config.outputParser === "streaming" ? trajectory : this.parseResponse({ stdout, stderr, exitCode: code });
1032
+ if (this.config.outputParser !== "streaming") {
1033
+ finalTrajectory.forEach((step) => onProgress?.(step));
1034
+ }
1035
+ this.debug("Resolving with trajectory of", finalTrajectory.length, "steps");
1036
+ resolve({
1037
+ trajectory: finalTrajectory,
1038
+ runId: `subprocess-${Date.now()}`,
1039
+ rawEvents: rawOutput,
1040
+ metadata: {
1041
+ command,
1042
+ args: finalArgs,
1043
+ exitCode: code,
1044
+ stderr: stderr || void 0
1045
+ }
1046
+ });
1047
+ });
1048
+ proc.on("error", (error) => {
1049
+ this.debug("ERROR event:", error.message);
1050
+ clearTimeout(timeoutId);
1051
+ if (settled) return;
1052
+ settled = true;
1053
+ let errorMsg = `Failed to spawn subprocess: ${error.message}`;
1054
+ if (error.message.includes("ENOENT")) {
1055
+ errorMsg = `Command '${command}' not found. Is it installed and in PATH?`;
1056
+ console.error(`[Subprocess] ENOENT error - command '${command}' not found in PATH`);
1057
+ } else if (error.message.includes("EACCES")) {
1058
+ errorMsg = `Permission denied executing '${command}'. Check file permissions.`;
1059
+ console.error(`[Subprocess] EACCES error - permission denied for '${command}'`);
1060
+ } else if (error.message.includes("EPERM")) {
1061
+ errorMsg = `Operation not permitted for '${command}'. May require elevated privileges.`;
1062
+ console.error(`[Subprocess] EPERM error - operation not permitted`);
1063
+ }
1064
+ reject(new Error(errorMsg));
1065
+ });
1066
+ });
1067
+ this.debug("========== execute() COMPLETED ==========");
1068
+ }
1069
+ /**
1070
+ * Parse streaming output and emit steps in real-time
1071
+ */
1072
+ parseStreamingOutput(chunk, trajectory, onProgress) {
1073
+ const lines = chunk.split("\n").filter((line) => line.trim());
1074
+ for (const line of lines) {
1075
+ const step = this.createStep("assistant", line);
1076
+ trajectory.push(step);
1077
+ onProgress?.(step);
1078
+ }
1079
+ }
1080
+ /**
1081
+ * Parse final subprocess output
1082
+ */
1083
+ parseResponse(data) {
1084
+ const steps = [];
1085
+ if (this.config.outputParser === "json") {
1086
+ try {
1087
+ const parsed = JSON.parse(data.stdout);
1088
+ return this.parseJsonOutput(parsed);
1089
+ } catch {
1090
+ this.debug("Failed to parse JSON output, falling back to text");
1091
+ }
1092
+ }
1093
+ if (data.stdout.trim()) {
1094
+ steps.push(this.createStep("response", data.stdout.trim()));
1095
+ }
1096
+ if (data.exitCode !== 0 && data.stderr.trim()) {
1097
+ steps.push(this.createStep("tool_result", `Error: ${data.stderr.trim()}`, {
1098
+ status: "FAILURE"
1099
+ }));
1100
+ }
1101
+ return steps;
1102
+ }
1103
+ /**
1104
+ * Parse JSON output into trajectory steps
1105
+ */
1106
+ parseJsonOutput(data) {
1107
+ const steps = [];
1108
+ if (data.thinking) {
1109
+ steps.push(this.createStep("thinking", data.thinking));
1110
+ }
1111
+ if (data.steps && Array.isArray(data.steps)) {
1112
+ for (const step of data.steps) {
1113
+ steps.push(this.createStep(step.type || "assistant", step.content, {
1114
+ toolName: step.toolName,
1115
+ toolArgs: step.toolArgs
1116
+ }));
1117
+ }
1118
+ }
1119
+ if (data.response || data.answer || data.content) {
1120
+ steps.push(this.createStep("response", data.response || data.answer || data.content));
1121
+ }
1122
+ return steps;
1123
+ }
1124
+ /**
1125
+ * Health check - verify command exists
1126
+ */
1127
+ async healthCheck(endpoint, auth) {
1128
+ const command = endpoint || this.config.command;
1129
+ if (!command) return false;
1130
+ return new Promise((resolve) => {
1131
+ const proc = spawn("which", [command], { shell: true });
1132
+ proc.on("close", (code) => resolve(code === 0));
1133
+ proc.on("error", () => resolve(false));
1134
+ });
1135
+ }
1136
+ };
1137
+ var subprocessConnector = new SubprocessConnector();
1138
+
1139
+ // services/connectors/claude-code/ClaudeCodeConnector.ts
1140
+ var CLAUDE_CODE_DEFAULT_CONFIG = {
1141
+ command: "claude",
1142
+ args: ["--print", "--verbose", "--output-format", "stream-json"],
1143
+ // Structured JSON output (--verbose required with stream-json)
1144
+ env: {
1145
+ // These can be overridden by agent config or environment
1146
+ DISABLE_PROMPT_CACHING: "1",
1147
+ DISABLE_ERROR_REPORTING: "1"
1148
+ // Note: DISABLE_TELEMETRY removed - telemetry enabled by default
1149
+ // Configure OTEL_EXPORTER_OTLP_ENDPOINT in .env to send traces
1150
+ },
1151
+ inputMode: "stdin",
1152
+ outputParser: "streaming",
1153
+ timeout: 6e5
1154
+ // 10 minutes for Claude Code
1155
+ };
1156
+ var ClaudeCodeConnector = class extends SubprocessConnector {
1157
+ constructor(config) {
1158
+ super({ ...CLAUDE_CODE_DEFAULT_CONFIG, ...config });
1159
+ this.type = "claude-code";
1160
+ this.name = "Claude Code CLI";
1161
+ this.outputBuffer = "";
1162
+ this.thinkingBuffer = "";
1163
+ this.isInThinking = false;
1164
+ }
1165
+ /**
1166
+ * Build prompt for Claude Code
1167
+ * Structures the input to get the best RCA results
1168
+ */
1169
+ buildPayload(request) {
1170
+ const parts = [];
1171
+ if (request.testCase.context && request.testCase.context.length > 0) {
1172
+ parts.push("## Context");
1173
+ for (const ctx of request.testCase.context) {
1174
+ parts.push(`**${ctx.description}:**`);
1175
+ parts.push(ctx.value);
1176
+ parts.push("");
1177
+ }
1178
+ }
1179
+ parts.push("## Task");
1180
+ parts.push(request.testCase.initialPrompt);
1181
+ return parts.join("\n");
1182
+ }
1183
+ /**
1184
+ * Parse Claude Code streaming output (stream-json format)
1185
+ * Each line is a JSON object with type and content
1186
+ */
1187
+ parseStreamingOutput(chunk, trajectory, onProgress) {
1188
+ this.outputBuffer += chunk;
1189
+ const lines = this.outputBuffer.split("\n");
1190
+ this.outputBuffer = lines.pop() || "";
1191
+ for (const line of lines) {
1192
+ const trimmed = line.trim();
1193
+ if (!trimmed) continue;
1194
+ try {
1195
+ const event = JSON.parse(trimmed);
1196
+ const steps = this.parseJsonEvent(event);
1197
+ for (const step of steps) {
1198
+ trajectory.push(step);
1199
+ onProgress?.(step);
1200
+ }
1201
+ } catch {
1202
+ if (trimmed) {
1203
+ const step = this.createStep("assistant", trimmed);
1204
+ trajectory.push(step);
1205
+ onProgress?.(step);
1206
+ }
1207
+ }
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Parse a single JSON event from stream-json output
1212
+ */
1213
+ parseJsonEvent(event) {
1214
+ const steps = [];
1215
+ if (event.type === "assistant" && event.message?.content) {
1216
+ for (const block of event.message.content) {
1217
+ if (block.type === "thinking" && block.thinking) {
1218
+ steps.push(this.createStep("thinking", block.thinking));
1219
+ } else if (block.type === "text" && block.text) {
1220
+ steps.push(this.createStep("assistant", block.text));
1221
+ } else if (block.type === "tool_use") {
1222
+ steps.push(this.createStep("action", JSON.stringify(block.input || {}), {
1223
+ toolName: block.name,
1224
+ toolArgs: block.input
1225
+ }));
1226
+ }
1227
+ }
1228
+ } else if (event.type === "content_block_delta") {
1229
+ if (event.delta?.type === "thinking_delta" && event.delta.thinking) {
1230
+ this.thinkingBuffer += event.delta.thinking;
1231
+ } else if (event.delta?.type === "text_delta" && event.delta.text) {
1232
+ steps.push(this.createStep("assistant", event.delta.text));
1233
+ }
1234
+ } else if (event.type === "content_block_stop" && this.thinkingBuffer) {
1235
+ steps.push(this.createStep("thinking", this.thinkingBuffer));
1236
+ this.thinkingBuffer = "";
1237
+ } else if (event.type === "result" && event.result) {
1238
+ steps.push(this.createStep(
1239
+ "response",
1240
+ typeof event.result === "string" ? event.result : JSON.stringify(event.result)
1241
+ ));
1242
+ } else if (event.type === "tool_result") {
1243
+ steps.push(this.createStep(
1244
+ "tool_result",
1245
+ typeof event.content === "string" ? event.content : JSON.stringify(event.content),
1246
+ { status: event.is_error ? "FAILURE" /* FAILURE */ : "SUCCESS" /* SUCCESS */ }
1247
+ ));
1248
+ }
1249
+ return steps;
1250
+ }
1251
+ /**
1252
+ * Parse final output for Claude Code
1253
+ */
1254
+ parseResponse(data) {
1255
+ const steps = [];
1256
+ let content = data.stdout;
1257
+ const thinkingMatches = content.matchAll(/<thinking>([\s\S]*?)<\/thinking>/g);
1258
+ for (const match of thinkingMatches) {
1259
+ const thinking = match[1].trim();
1260
+ if (thinking) {
1261
+ steps.push(this.createStep("thinking", thinking));
1262
+ }
1263
+ content = content.replace(match[0], "");
1264
+ }
1265
+ const response = content.trim();
1266
+ if (response) {
1267
+ steps.push(this.createStep("response", response));
1268
+ }
1269
+ if (data.exitCode !== 0 && data.stderr.trim()) {
1270
+ steps.push(this.createStep("tool_result", `Error: ${data.stderr.trim()}`, {
1271
+ status: "FAILURE" /* FAILURE */
1272
+ }));
1273
+ }
1274
+ return steps;
1275
+ }
1276
+ /**
1277
+ * Reset state for new execution
1278
+ */
1279
+ resetState() {
1280
+ this.outputBuffer = "";
1281
+ this.thinkingBuffer = "";
1282
+ this.isInThinking = false;
1283
+ }
1284
+ /**
1285
+ * Override execute to reset state
1286
+ */
1287
+ async execute(endpoint, request, auth, onProgress, onRawEvent) {
1288
+ this.debug("========== execute() STARTED ==========");
1289
+ this.debug("Endpoint:", endpoint);
1290
+ this.debug("Test case:", request.testCase.name);
1291
+ this.debug("Config:", this["config"]);
1292
+ this.resetState();
1293
+ this.debug("State reset, calling super.execute()...");
1294
+ const result = await super.execute(endpoint, request, auth, onProgress, onRawEvent);
1295
+ this.debug("super.execute() returned with", result.trajectory.length, "steps");
1296
+ this.debug("========== execute() COMPLETED ==========");
1297
+ return result;
1298
+ }
1299
+ /**
1300
+ * Health check - verify claude command exists
1301
+ */
1302
+ async healthCheck(endpoint, auth) {
1303
+ return super.healthCheck(endpoint || "claude", auth);
1304
+ }
1305
+ };
1306
+ var claudeCodeConnector = new ClaudeCodeConnector();
1307
+
1308
+ // services/connectors/mock/MockConnector.ts
1309
+ var MockConnector = class extends BaseConnector {
1310
+ constructor() {
1311
+ super(...arguments);
1312
+ this.type = "mock";
1313
+ this.name = "Demo Agent (Mock)";
1314
+ this.supportsStreaming = true;
1315
+ }
1316
+ /**
1317
+ * Build payload - not used for mock but required by interface
1318
+ */
1319
+ buildPayload(request) {
1320
+ return {
1321
+ question: request.testCase.initialPrompt,
1322
+ context: request.testCase.context
1323
+ };
1324
+ }
1325
+ /**
1326
+ * Execute mock request - generates realistic trajectory with delays
1327
+ */
1328
+ async execute(endpoint, request, auth, onProgress, onRawEvent) {
1329
+ const trajectory = [];
1330
+ const rawEvents = [];
1331
+ const runId = `mock-run-${Date.now()}`;
1332
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1333
+ this.debug("Generating mock trajectory for:", request.testCase.name);
1334
+ const emitStep = (step) => {
1335
+ trajectory.push(step);
1336
+ onProgress?.(step);
1337
+ rawEvents.push({ type: "MOCK_STEP", step });
1338
+ onRawEvent?.({ type: "MOCK_STEP", step });
1339
+ };
1340
+ await sleep(100);
1341
+ emitStep(this.createStep(
1342
+ "assistant",
1343
+ "I need to investigate this issue. Let me start by checking the cluster health and then drill down into specific metrics."
1344
+ ));
1345
+ await sleep(300);
1346
+ emitStep(this.createStep(
1347
+ "action",
1348
+ "Calling opensearch_cluster_health...",
1349
+ {
1350
+ toolName: "opensearch_cluster_health",
1351
+ toolArgs: { local: true }
1352
+ }
1353
+ ));
1354
+ await sleep(500);
1355
+ emitStep(this.createStep(
1356
+ "tool_result",
1357
+ JSON.stringify({ status: "yellow", number_of_nodes: 3, unassigned_shards: 0 }, null, 2),
1358
+ {
1359
+ status: "SUCCESS",
1360
+ latencyMs: 500
1361
+ }
1362
+ ));
1363
+ await sleep(300);
1364
+ emitStep(this.createStep(
1365
+ "assistant",
1366
+ "The cluster is in yellow state. Let me check the node stats to identify which node might be causing issues."
1367
+ ));
1368
+ await sleep(300);
1369
+ emitStep(this.createStep(
1370
+ "action",
1371
+ "Calling opensearch_nodes_stats...",
1372
+ {
1373
+ toolName: "opensearch_nodes_stats",
1374
+ toolArgs: { metric: "jvm,os" }
1375
+ }
1376
+ ));
1377
+ await sleep(600);
1378
+ emitStep(this.createStep(
1379
+ "tool_result",
1380
+ "Node-1: CPU 12%, JVM Heap 45%\nNode-2: CPU 15%, JVM Heap 52%\nNode-3: CPU 98%, JVM Heap 89% (Data Node)",
1381
+ {
1382
+ status: "SUCCESS",
1383
+ latencyMs: 600
1384
+ }
1385
+ ));
1386
+ await sleep(400);
1387
+ emitStep(this.createStep(
1388
+ "response",
1389
+ `## Root Cause Analysis Complete
1390
+
1391
+ **Finding:** High CPU utilization detected on Node-3 (98% CPU, 89% JVM Heap)
1392
+
1393
+ **Root Cause:** Node-3 is experiencing resource exhaustion, likely due to:
1394
+ 1. Heavy indexing or search operations
1395
+ 2. Garbage collection pressure from high heap usage
1396
+ 3. Possible hot spot in shard distribution
1397
+
1398
+ **Recommendations:**
1399
+ 1. Check hot threads on Node-3 using \`_nodes/Node-3/hot_threads\`
1400
+ 2. Review shard distribution and consider rebalancing
1401
+ 3. Monitor GC logs for long pauses
1402
+ 4. Consider scaling horizontally if load persists`
1403
+ ));
1404
+ this.debug("Mock trajectory completed. Steps:", trajectory.length);
1405
+ return {
1406
+ trajectory,
1407
+ runId,
1408
+ rawEvents,
1409
+ metadata: {
1410
+ mock: true,
1411
+ testCaseId: request.testCase.id,
1412
+ testCaseName: request.testCase.name
1413
+ }
1414
+ };
1415
+ }
1416
+ /**
1417
+ * Parse raw events - for mock, just extract steps from our custom format
1418
+ */
1419
+ parseResponse(rawEvents) {
1420
+ return rawEvents.filter((e) => e.type === "MOCK_STEP" && e.step).map((e) => e.step);
1421
+ }
1422
+ /**
1423
+ * Health check - mock is always "healthy"
1424
+ */
1425
+ async healthCheck(endpoint, auth) {
1426
+ return true;
1427
+ }
1428
+ };
1429
+ var mockConnector = new MockConnector();
1430
+
1431
+ // services/connectors/registry.ts
1432
+ var DEFAULT_CONNECTOR_TYPE = "agui-streaming";
1433
+ var ConnectorRegistryImpl = class {
1434
+ constructor() {
1435
+ this.connectors = /* @__PURE__ */ new Map();
1436
+ }
1437
+ /**
1438
+ * Register a connector implementation
1439
+ * @throws Error if connector with same type is already registered
1440
+ */
1441
+ register(connector) {
1442
+ if (this.connectors.has(connector.type)) {
1443
+ console.warn(
1444
+ `[ConnectorRegistry] Overwriting existing connector for type: ${connector.type}`
1445
+ );
1446
+ }
1447
+ this.connectors.set(connector.type, connector);
1448
+ }
1449
+ /**
1450
+ * Get a connector by protocol type
1451
+ */
1452
+ get(type) {
1453
+ return this.connectors.get(type);
1454
+ }
1455
+ /**
1456
+ * Get all registered connectors
1457
+ */
1458
+ getAll() {
1459
+ return Array.from(this.connectors.values());
1460
+ }
1461
+ /**
1462
+ * Check if a connector is registered
1463
+ */
1464
+ has(type) {
1465
+ return this.connectors.has(type);
1466
+ }
1467
+ /**
1468
+ * Get connector for an agent config
1469
+ * Handles backwards compatibility with legacy configs
1470
+ *
1471
+ * Resolution order:
1472
+ * 1. If endpoint starts with 'mock://', use mock connector
1473
+ * 2. If connectorType is specified, use that
1474
+ * 3. Default to 'agui-streaming'
1475
+ */
1476
+ getForAgent(agent) {
1477
+ if (agent.endpoint.startsWith("mock://")) {
1478
+ const mockConnector2 = this.get("mock");
1479
+ if (mockConnector2) {
1480
+ return mockConnector2;
1481
+ }
1482
+ console.warn("[ConnectorRegistry] Mock connector not registered, falling back to default");
1483
+ }
1484
+ const connectorType = agent.connectorType ?? DEFAULT_CONNECTOR_TYPE;
1485
+ const connector = this.get(connectorType);
1486
+ if (!connector) {
1487
+ console.error(
1488
+ `[ConnectorRegistry] Connector not found for type: ${connectorType}, falling back to ${DEFAULT_CONNECTOR_TYPE}`
1489
+ );
1490
+ const defaultConnector = this.get(DEFAULT_CONNECTOR_TYPE);
1491
+ if (!defaultConnector) {
1492
+ throw new Error(
1493
+ `No connector registered for type '${connectorType}' and no default connector available`
1494
+ );
1495
+ }
1496
+ return defaultConnector;
1497
+ }
1498
+ return connector;
1499
+ }
1500
+ /**
1501
+ * Clear all registered connectors (useful for testing)
1502
+ */
1503
+ clear() {
1504
+ this.connectors.clear();
1505
+ }
1506
+ /**
1507
+ * Get list of registered connector types
1508
+ */
1509
+ getRegisteredTypes() {
1510
+ return Array.from(this.connectors.keys());
1511
+ }
1512
+ };
1513
+ var connectorRegistry = new ConnectorRegistryImpl();
1514
+ function registerConnector(connector) {
1515
+ connectorRegistry.register(connector);
1516
+ }
1517
+ export {
1518
+ AGUIStreamingConnector,
1519
+ BaseConnector,
1520
+ ClaudeCodeConnector,
1521
+ MockConnector,
1522
+ RESTConnector,
1523
+ SubprocessConnector,
1524
+ connectorRegistry,
1525
+ defineConfig,
1526
+ registerConnector
1527
+ };