@probelabs/probe 0.6.0-rc224 → 0.6.0-rc225

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.
@@ -264,6 +264,204 @@ export class SimpleAppTracer {
264
264
  }
265
265
  }
266
266
 
267
+ /**
268
+ * Hash content for deduplication/comparison purposes
269
+ * @param {string} content - The content to hash
270
+ * @returns {string} - Hex string hash
271
+ */
272
+ hashContent(content) {
273
+ let hash = 0;
274
+ const len = Math.min(content.length, 1000);
275
+ for (let i = 0; i < len; i++) {
276
+ hash = ((hash << 5) - hash) + content.charCodeAt(i);
277
+ hash |= 0; // Convert to 32-bit integer
278
+ }
279
+ return hash.toString(16);
280
+ }
281
+
282
+ /**
283
+ * Record a conversation turn (assistant response or tool result)
284
+ * @param {string} role - The role (assistant, tool_result)
285
+ * @param {string} content - The turn content
286
+ * @param {Object} metadata - Additional metadata
287
+ */
288
+ recordConversationTurn(role, content, metadata = {}) {
289
+ if (!this.isEnabled()) return;
290
+
291
+ this.addEvent(`conversation.turn.${role}`, {
292
+ 'session.id': this.sessionId,
293
+ 'conversation.role': role,
294
+ 'conversation.content': content.substring(0, 10000),
295
+ 'conversation.content.length': content.length,
296
+ 'conversation.content.hash': this.hashContent(content),
297
+ ...metadata
298
+ });
299
+ }
300
+
301
+ /**
302
+ * Record error events with classification
303
+ * @param {string} errorType - The type of error (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker, etc.)
304
+ * @param {Object} errorDetails - Error details including message, stack, context
305
+ */
306
+ recordErrorEvent(errorType, errorDetails = {}) {
307
+ if (!this.isEnabled()) return;
308
+
309
+ this.addEvent(`error.${errorType}`, {
310
+ 'session.id': this.sessionId,
311
+ 'error.type': errorType,
312
+ 'error.message': errorDetails.message?.substring(0, 1000) || null,
313
+ 'error.stack': errorDetails.stack?.substring(0, 2000) || null,
314
+ 'error.recoverable': errorDetails.recoverable ?? true,
315
+ 'error.context': JSON.stringify(errorDetails.context || {}).substring(0, 1000),
316
+ ...Object.fromEntries(
317
+ Object.entries(errorDetails)
318
+ .filter(([k]) => !['message', 'stack', 'context', 'recoverable'].includes(k))
319
+ .map(([k, v]) => [`error.${k}`, v])
320
+ )
321
+ });
322
+ }
323
+
324
+ /**
325
+ * Record AI thinking/reasoning content
326
+ * @param {string} thinkingContent - The thinking content from AI response
327
+ * @param {Object} metadata - Additional metadata
328
+ */
329
+ recordThinkingContent(thinkingContent, metadata = {}) {
330
+ if (!this.isEnabled() || !thinkingContent) return;
331
+
332
+ this.addEvent('ai.thinking', {
333
+ 'session.id': this.sessionId,
334
+ 'ai.thinking.content': thinkingContent.substring(0, 50000),
335
+ 'ai.thinking.length': thinkingContent.length,
336
+ 'ai.thinking.hash': this.hashContent(thinkingContent),
337
+ ...metadata
338
+ });
339
+ }
340
+
341
+ /**
342
+ * Record AI tool call decision
343
+ * @param {string} toolName - The tool name AI decided to call
344
+ * @param {Object} params - The parameters AI provided
345
+ * @param {Object} metadata - Additional metadata
346
+ */
347
+ recordToolDecision(toolName, params, metadata = {}) {
348
+ if (!this.isEnabled()) return;
349
+
350
+ this.addEvent('ai.tool_decision', {
351
+ 'session.id': this.sessionId,
352
+ 'ai.tool_decision.name': toolName,
353
+ 'ai.tool_decision.params': JSON.stringify(params || {}).substring(0, 2000),
354
+ ...metadata
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Record tool result after execution
360
+ * @param {string} toolName - The tool that was executed
361
+ * @param {string|Object} result - The tool result
362
+ * @param {boolean} success - Whether the tool succeeded
363
+ * @param {number} durationMs - Execution duration in milliseconds
364
+ * @param {Object} metadata - Additional metadata
365
+ */
366
+ recordToolResult(toolName, result, success, durationMs, metadata = {}) {
367
+ if (!this.isEnabled()) return;
368
+
369
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
370
+ this.addEvent('tool.result', {
371
+ 'session.id': this.sessionId,
372
+ 'tool.name': toolName,
373
+ 'tool.result': resultStr.substring(0, 10000),
374
+ 'tool.result.length': resultStr.length,
375
+ 'tool.result.hash': this.hashContent(resultStr),
376
+ 'tool.duration_ms': durationMs,
377
+ 'tool.success': success,
378
+ ...metadata
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Record MCP tool execution start
384
+ * @param {string} toolName - MCP tool name
385
+ * @param {string} serverName - MCP server name
386
+ * @param {Object} params - Tool parameters
387
+ * @param {Object} metadata - Additional metadata
388
+ */
389
+ recordMcpToolStart(toolName, serverName, params, metadata = {}) {
390
+ if (!this.isEnabled()) return;
391
+
392
+ this.addEvent('mcp.tool.start', {
393
+ 'session.id': this.sessionId,
394
+ 'mcp.tool.name': toolName,
395
+ 'mcp.tool.server': serverName || 'unknown',
396
+ 'mcp.tool.params': JSON.stringify(params || {}).substring(0, 2000),
397
+ ...metadata
398
+ });
399
+ }
400
+
401
+ /**
402
+ * Record MCP tool execution end
403
+ * @param {string} toolName - MCP tool name
404
+ * @param {string} serverName - MCP server name
405
+ * @param {string|Object} result - Tool result
406
+ * @param {boolean} success - Whether succeeded
407
+ * @param {number} durationMs - Execution duration
408
+ * @param {string} errorMessage - Error message if failed
409
+ * @param {Object} metadata - Additional metadata
410
+ */
411
+ recordMcpToolEnd(toolName, serverName, result, success, durationMs, errorMessage = null, metadata = {}) {
412
+ if (!this.isEnabled()) return;
413
+
414
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result || '');
415
+ this.addEvent('mcp.tool.end', {
416
+ 'session.id': this.sessionId,
417
+ 'mcp.tool.name': toolName,
418
+ 'mcp.tool.server': serverName || 'unknown',
419
+ 'mcp.tool.result': resultStr.substring(0, 10000),
420
+ 'mcp.tool.result.length': resultStr.length,
421
+ 'mcp.tool.duration_ms': durationMs,
422
+ 'mcp.tool.success': success,
423
+ 'mcp.tool.error': errorMessage,
424
+ ...metadata
425
+ });
426
+ }
427
+
428
+ /**
429
+ * Record iteration lifecycle event
430
+ * @param {string} eventType - start or end
431
+ * @param {number} iteration - Iteration number
432
+ * @param {Object} data - Additional data
433
+ */
434
+ recordIterationEvent(eventType, iteration, data = {}) {
435
+ if (!this.isEnabled()) return;
436
+
437
+ this.addEvent(`iteration.${eventType}`, {
438
+ 'session.id': this.sessionId,
439
+ 'iteration': iteration,
440
+ ...data
441
+ });
442
+ }
443
+
444
+ /**
445
+ * Record per-turn token breakdown
446
+ * @param {number} iteration - Iteration number
447
+ * @param {Object} tokenData - Token metrics
448
+ */
449
+ recordTokenTurn(iteration, tokenData = {}) {
450
+ if (!this.isEnabled()) return;
451
+
452
+ this.addEvent('tokens.turn', {
453
+ 'session.id': this.sessionId,
454
+ 'iteration': iteration,
455
+ 'tokens.input': tokenData.inputTokens || 0,
456
+ 'tokens.output': tokenData.outputTokens || 0,
457
+ 'tokens.total': (tokenData.inputTokens || 0) + (tokenData.outputTokens || 0),
458
+ 'tokens.cache_read': tokenData.cacheReadTokens || 0,
459
+ 'tokens.cache_write': tokenData.cacheWriteTokens || 0,
460
+ 'tokens.context_used': tokenData.contextTokens || 0,
461
+ 'tokens.context_remaining': tokenData.maxContextTokens ? (tokenData.maxContextTokens - (tokenData.contextTokens || 0)) : null
462
+ });
463
+ }
464
+
267
465
  async withSpan(spanName, fn, attributes = {}) {
268
466
  if (!this.isEnabled()) {
269
467
  return fn();
@@ -270,13 +270,16 @@ User: Analyze the diagram in docs/architecture.svg
270
270
  */
271
271
  export function parseXmlToolCallWithThinking(xmlString, validTools) {
272
272
  // Use the shared processing logic
273
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
274
-
275
- // If recovery found an attempt_complete pattern, return it
273
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
274
+
275
+ // If recovery found an attempt_complete pattern, return it with thinking content
276
276
  if (recoveryResult) {
277
- return recoveryResult;
277
+ return { ...recoveryResult, thinkingContent };
278
278
  }
279
279
 
280
280
  // Otherwise, use the original parseXmlToolCall function to parse the cleaned XML string
281
- return parseXmlToolCall(cleanedXmlString, validTools);
281
+ const toolCall = parseXmlToolCall(cleanedXmlString, validTools);
282
+
283
+ // Return tool call with thinking content attached
284
+ return toolCall ? { ...toolCall, thinkingContent } : null;
282
285
  }
@@ -49551,11 +49551,12 @@ function createTools(configOptions) {
49551
49551
  return tools2;
49552
49552
  }
49553
49553
  function parseXmlToolCallWithThinking(xmlString, validTools) {
49554
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
49554
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
49555
49555
  if (recoveryResult) {
49556
- return recoveryResult;
49556
+ return { ...recoveryResult, thinkingContent };
49557
49557
  }
49558
- return parseXmlToolCall(cleanedXmlString, validTools);
49558
+ const toolCall = parseXmlToolCall(cleanedXmlString, validTools);
49559
+ return toolCall ? { ...toolCall, thinkingContent } : null;
49559
49560
  }
49560
49561
  var import_crypto4, implementToolDefinition, listFilesToolDefinition, searchFilesToolDefinition, listSkillsToolDefinition, useSkillToolDefinition, readImageToolDefinition;
49561
49562
  var init_tools2 = __esm({
@@ -88850,25 +88851,27 @@ function parseXmlMcpToolCall(xmlString, mcpToolNames = []) {
88850
88851
  function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge = null) {
88851
88852
  const nativeResult = parseNativeXmlToolWithThinking(xmlString, nativeTools);
88852
88853
  if (nativeResult) {
88853
- return { ...nativeResult, type: "native" };
88854
+ const { thinkingContent, ...rest } = nativeResult;
88855
+ return { ...rest, type: "native", thinkingContent };
88854
88856
  }
88855
88857
  if (mcpBridge) {
88856
88858
  const mcpResult = parseXmlMcpToolCall(xmlString, mcpBridge.getToolNames());
88857
88859
  if (mcpResult) {
88858
- return { ...mcpResult, type: "mcp" };
88860
+ const { thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, []);
88861
+ return { ...mcpResult, type: "mcp", thinkingContent };
88859
88862
  }
88860
88863
  }
88861
88864
  return null;
88862
88865
  }
88863
88866
  function parseNativeXmlToolWithThinking(xmlString, validTools) {
88864
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
88867
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
88865
88868
  if (recoveryResult) {
88866
- return recoveryResult;
88869
+ return { ...recoveryResult, thinkingContent };
88867
88870
  }
88868
88871
  for (const toolName of validTools) {
88869
88872
  const result = parseNativeXmlTool(cleanedXmlString, toolName);
88870
88873
  if (result) {
88871
- return result;
88874
+ return { ...result, thinkingContent };
88872
88875
  }
88873
88876
  }
88874
88877
  return null;
@@ -99329,6 +99332,181 @@ var init_ProbeAgent = __esm({
99329
99332
  _filterMcpTools(mcpToolNames) {
99330
99333
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
99331
99334
  }
99335
+ /**
99336
+ * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
99337
+ * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
99338
+ * @private
99339
+ */
99340
+ _isAppTracerStyle() {
99341
+ return this.tracer && typeof this.tracer.sessionSpans !== "undefined";
99342
+ }
99343
+ /**
99344
+ * Record an error classification event for telemetry
99345
+ * Provides unified error recording across all error types
99346
+ * @param {string} errorType - Error type (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker)
99347
+ * @param {string} message - Error message
99348
+ * @param {Object} context - Additional context data
99349
+ * @param {number} iteration - Current iteration number
99350
+ * @private
99351
+ */
99352
+ _recordErrorTelemetry(errorType, message, context, iteration) {
99353
+ if (!this.tracer) return;
99354
+ if (this._isAppTracerStyle() && typeof this.tracer.recordErrorClassification === "function") {
99355
+ this.tracer.recordErrorClassification(this.sessionId, iteration, errorType, {
99356
+ message,
99357
+ context
99358
+ });
99359
+ } else if (typeof this.tracer.recordErrorEvent === "function") {
99360
+ this.tracer.recordErrorEvent(errorType, {
99361
+ message,
99362
+ context: { ...context, iteration }
99363
+ });
99364
+ } else {
99365
+ this.tracer.addEvent(`error.${errorType}`, {
99366
+ "error.type": errorType,
99367
+ "error.message": message,
99368
+ "error.recoverable": errorType !== "circuit_breaker",
99369
+ "error.context": JSON.stringify(context).substring(0, 1e3),
99370
+ "iteration": iteration
99371
+ });
99372
+ }
99373
+ }
99374
+ /**
99375
+ * Record AI thinking content for telemetry
99376
+ * @param {string} thinkingContent - The thinking content
99377
+ * @param {number} iteration - Current iteration number
99378
+ * @private
99379
+ */
99380
+ _recordThinkingTelemetry(thinkingContent, iteration) {
99381
+ if (!this.tracer || !thinkingContent) return;
99382
+ if (this._isAppTracerStyle() && typeof this.tracer.recordThinkingContent === "function") {
99383
+ this.tracer.recordThinkingContent(this.sessionId, iteration, thinkingContent);
99384
+ } else if (typeof this.tracer.recordThinkingContent === "function") {
99385
+ this.tracer.recordThinkingContent(thinkingContent, { iteration });
99386
+ } else {
99387
+ this.tracer.addEvent("ai.thinking", {
99388
+ "ai.thinking.content": thinkingContent.substring(0, 5e4),
99389
+ "ai.thinking.length": thinkingContent.length,
99390
+ "iteration": iteration
99391
+ });
99392
+ }
99393
+ }
99394
+ /**
99395
+ * Record AI tool decision for telemetry
99396
+ * @param {string} toolName - The tool name
99397
+ * @param {Object} params - Tool parameters
99398
+ * @param {number} responseLength - Length of AI response
99399
+ * @param {number} iteration - Current iteration number
99400
+ * @private
99401
+ */
99402
+ _recordToolDecisionTelemetry(toolName, params, responseLength, iteration) {
99403
+ if (!this.tracer) return;
99404
+ if (this._isAppTracerStyle() && typeof this.tracer.recordAIToolDecision === "function") {
99405
+ this.tracer.recordAIToolDecision(this.sessionId, iteration, toolName, params);
99406
+ } else if (typeof this.tracer.recordToolDecision === "function") {
99407
+ this.tracer.recordToolDecision(toolName, params, {
99408
+ iteration,
99409
+ "ai.tool_decision.raw_response_length": responseLength
99410
+ });
99411
+ } else {
99412
+ this.tracer.addEvent("ai.tool_decision", {
99413
+ "ai.tool_decision.name": toolName,
99414
+ "ai.tool_decision.params": JSON.stringify(params || {}).substring(0, 2e3),
99415
+ "ai.tool_decision.raw_response_length": responseLength,
99416
+ "iteration": iteration
99417
+ });
99418
+ }
99419
+ }
99420
+ /**
99421
+ * Record tool result for telemetry
99422
+ * @param {string} toolName - The tool name
99423
+ * @param {string|Object} result - Tool result
99424
+ * @param {boolean} success - Whether tool succeeded
99425
+ * @param {number} durationMs - Execution duration in milliseconds
99426
+ * @param {number} iteration - Current iteration number
99427
+ * @private
99428
+ */
99429
+ _recordToolResultTelemetry(toolName, result, success, durationMs, iteration) {
99430
+ if (!this.tracer) return;
99431
+ if (this._isAppTracerStyle() && typeof this.tracer.recordToolResult === "function") {
99432
+ this.tracer.recordToolResult(this.sessionId, iteration, toolName, result, success, durationMs);
99433
+ } else if (typeof this.tracer.recordToolResult === "function") {
99434
+ this.tracer.recordToolResult(toolName, result, success, durationMs, { iteration });
99435
+ } else {
99436
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
99437
+ this.tracer.addEvent("tool.result", {
99438
+ "tool.name": toolName,
99439
+ "tool.result": resultStr.substring(0, 1e4),
99440
+ "tool.result.length": resultStr.length,
99441
+ "tool.duration_ms": durationMs,
99442
+ "tool.success": success,
99443
+ "iteration": iteration
99444
+ });
99445
+ }
99446
+ }
99447
+ /**
99448
+ * Record MCP tool lifecycle event for telemetry
99449
+ * @param {string} phase - 'start' or 'end'
99450
+ * @param {string} toolName - MCP tool name
99451
+ * @param {Object} params - Tool parameters (for start) or null (for end)
99452
+ * @param {number} iteration - Current iteration number
99453
+ * @param {Object} [endData] - Additional data for end phase (result, success, durationMs, error)
99454
+ * @private
99455
+ */
99456
+ _recordMcpToolTelemetry(phase, toolName, params, iteration, endData = null) {
99457
+ if (!this.tracer) return;
99458
+ if (phase === "start") {
99459
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolStart === "function") {
99460
+ this.tracer.recordMcpToolStart(this.sessionId, iteration, toolName, "mcp", params);
99461
+ } else if (typeof this.tracer.recordMcpToolStart === "function") {
99462
+ this.tracer.recordMcpToolStart(toolName, "mcp", params, { iteration });
99463
+ } else {
99464
+ this.tracer.addEvent("mcp.tool.start", {
99465
+ "mcp.tool.name": toolName,
99466
+ "mcp.tool.server": "mcp",
99467
+ "mcp.tool.params": JSON.stringify(params || {}).substring(0, 2e3),
99468
+ "iteration": iteration
99469
+ });
99470
+ }
99471
+ } else if (phase === "end" && endData) {
99472
+ const { result, success, durationMs, error: error2 } = endData;
99473
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolEnd === "function") {
99474
+ this.tracer.recordMcpToolEnd(this.sessionId, iteration, toolName, "mcp", result, success, durationMs, error2);
99475
+ } else if (typeof this.tracer.recordMcpToolEnd === "function") {
99476
+ this.tracer.recordMcpToolEnd(toolName, "mcp", result, success, durationMs, error2, { iteration });
99477
+ } else {
99478
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
99479
+ this.tracer.addEvent("mcp.tool.end", {
99480
+ "mcp.tool.name": toolName,
99481
+ "mcp.tool.server": "mcp",
99482
+ "mcp.tool.result": resultStr.substring(0, 1e4),
99483
+ "mcp.tool.result.length": resultStr.length,
99484
+ "mcp.tool.duration_ms": durationMs,
99485
+ "mcp.tool.success": success,
99486
+ "mcp.tool.error": error2,
99487
+ "iteration": iteration
99488
+ });
99489
+ }
99490
+ }
99491
+ }
99492
+ /**
99493
+ * Record iteration lifecycle event for telemetry
99494
+ * @param {string} phase - 'end' (start is already handled elsewhere)
99495
+ * @param {number} iteration - Current iteration number
99496
+ * @param {Object} data - Additional iteration data
99497
+ * @private
99498
+ */
99499
+ _recordIterationTelemetry(phase, iteration, data2 = {}) {
99500
+ if (!this.tracer) return;
99501
+ if (typeof this.tracer.recordIterationEvent === "function") {
99502
+ this.tracer.recordIterationEvent(phase, iteration, data2);
99503
+ } else {
99504
+ this.tracer.addEvent(`iteration.${phase}`, {
99505
+ "iteration": iteration,
99506
+ ...data2
99507
+ });
99508
+ }
99509
+ }
99332
99510
  /**
99333
99511
  * Initialize the agent asynchronously (must be called after constructor)
99334
99512
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
@@ -101283,8 +101461,12 @@ You are working with a repository located at: ${searchDirectory}
101283
101461
  }
101284
101462
  const nativeTools = validTools;
101285
101463
  const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
101464
+ if (parsedTool?.thinkingContent) {
101465
+ this._recordThinkingTelemetry(parsedTool.thinkingContent, currentIteration);
101466
+ }
101286
101467
  if (parsedTool) {
101287
101468
  const { toolName, params } = parsedTool;
101469
+ this._recordToolDecisionTelemetry(toolName, params, assistantResponseContent.length, currentIteration);
101288
101470
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
101289
101471
  if (toolName === "attempt_completion") {
101290
101472
  completionAttempted = true;
@@ -101361,6 +101543,8 @@ You are working with a repository located at: ${searchDirectory}
101361
101543
  } else {
101362
101544
  const { type } = parsedTool;
101363
101545
  if (type === "mcp" && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
101546
+ const mcpStartTime = Date.now();
101547
+ this._recordMcpToolTelemetry("start", toolName, params, currentIteration);
101364
101548
  try {
101365
101549
  if (this.debug) {
101366
101550
  console.error(`
@@ -101390,6 +101574,13 @@ You are working with a repository located at: ${searchDirectory}
101390
101574
  } catch (truncateError) {
101391
101575
  console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
101392
101576
  }
101577
+ const mcpDurationMs = Date.now() - mcpStartTime;
101578
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
101579
+ result: toolResultContent,
101580
+ success: true,
101581
+ durationMs: mcpDurationMs,
101582
+ error: null
101583
+ });
101393
101584
  if (this.debug) {
101394
101585
  const preview = toolResultContent.length > 500 ? toolResultContent.substring(0, 500) + "..." : toolResultContent;
101395
101586
  console.error(`[DEBUG] ========================================`);
@@ -101403,6 +101594,13 @@ You are working with a repository located at: ${searchDirectory}
101403
101594
  ${toolResultContent}
101404
101595
  </tool_result>` });
101405
101596
  } catch (error2) {
101597
+ const mcpDurationMs = Date.now() - mcpStartTime;
101598
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
101599
+ result: null,
101600
+ success: false,
101601
+ durationMs: mcpDurationMs,
101602
+ error: error2.message
101603
+ });
101406
101604
  console.error(`Error executing MCP tool ${toolName}:`, error2);
101407
101605
  if (this.debug) {
101408
101606
  console.error(`[DEBUG] ========================================`);
@@ -101494,6 +101692,7 @@ ${errorXml}
101494
101692
  return await this.toolImplementations[toolName].execute(toolParams);
101495
101693
  };
101496
101694
  let toolResult;
101695
+ const toolStartTime = Date.now();
101497
101696
  try {
101498
101697
  if (this.tracer) {
101499
101698
  toolResult = await this.tracer.withSpan("tool.call", executeToolCall, {
@@ -101504,6 +101703,8 @@ ${errorXml}
101504
101703
  } else {
101505
101704
  toolResult = await executeToolCall();
101506
101705
  }
101706
+ const toolDurationMs = Date.now() - toolStartTime;
101707
+ this._recordToolResultTelemetry(toolName, toolResult, true, toolDurationMs, currentIteration);
101507
101708
  if (this.debug) {
101508
101709
  const resultPreview = typeof toolResult === "string" ? toolResult.length > 500 ? toolResult.substring(0, 500) + "..." : toolResult : toolResult ? JSON.stringify(toolResult, null, 2).substring(0, 500) + "..." : "No Result";
101509
101710
  console.error(`[DEBUG] ========================================`);
@@ -101560,6 +101761,20 @@ ${toolResultContent}
101560
101761
  role: "user",
101561
101762
  content: toolResultMessage
101562
101763
  });
101764
+ if (this.tracer) {
101765
+ if (typeof this.tracer.recordConversationTurn === "function") {
101766
+ this.tracer.recordConversationTurn("assistant", assistantResponseContent, {
101767
+ iteration: currentIteration,
101768
+ has_tool_call: true,
101769
+ tool_name: toolName
101770
+ });
101771
+ this.tracer.recordConversationTurn("tool_result", toolResultContent, {
101772
+ iteration: currentIteration,
101773
+ tool_name: toolName,
101774
+ tool_success: true
101775
+ });
101776
+ }
101777
+ }
101563
101778
  if (this.debug) {
101564
101779
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
101565
101780
  }
@@ -101630,6 +101845,7 @@ ${errorXml}
101630
101845
  if (this.debug) {
101631
101846
  console.log(`[DEBUG] Detected wrapped tool '${wrappedToolName}' in assistant response - wrong XML format.`);
101632
101847
  }
101848
+ this._recordErrorTelemetry("wrapped_tool", "Tool call wrapped in markdown", { toolName: wrappedToolName }, currentIteration);
101633
101849
  const toolError = new ParameterError(
101634
101850
  `Tool '${wrappedToolName}' found but in WRONG FORMAT - do not wrap tools in other XML tags.`,
101635
101851
  {
@@ -101655,6 +101871,7 @@ ${formatErrorForAI(toolError)}
101655
101871
  if (this.debug) {
101656
101872
  console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
101657
101873
  }
101874
+ this._recordErrorTelemetry("unrecognized_tool", `Unknown tool: ${unrecognizedTool}`, { toolName: unrecognizedTool, validTools }, currentIteration);
101658
101875
  const toolError = new ParameterError(`Tool '${unrecognizedTool}' is not available in this context.`, {
101659
101876
  suggestion: `Available tools: ${validTools.join(", ")}. Please use one of these tools instead.`
101660
101877
  });
@@ -101662,6 +101879,7 @@ ${formatErrorForAI(toolError)}
101662
101879
  ${formatErrorForAI(toolError)}
101663
101880
  </tool_result>`;
101664
101881
  } else {
101882
+ this._recordErrorTelemetry("no_tool_call", "AI response did not contain tool call", { responsePreview: assistantResponseContent.substring(0, 500) }, currentIteration);
101665
101883
  if (currentIteration >= maxIterations) {
101666
101884
  let cleanedResponse = assistantResponseContent;
101667
101885
  cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
@@ -101737,6 +101955,7 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
101737
101955
  sameFormatErrorCount++;
101738
101956
  if (sameFormatErrorCount >= MAX_REPEATED_FORMAT_ERRORS) {
101739
101957
  const errorDesc = isWrapped ? "wrapped tool format" : unrecognizedTool;
101958
+ this._recordErrorTelemetry("circuit_breaker", "Format error limit exceeded", { formatErrorCount: sameFormatErrorCount, errorCategory }, currentIteration);
101740
101959
  console.error(`[ERROR] Format error category '${errorCategory}' repeated ${sameFormatErrorCount} times. Breaking loop early to prevent infinite iteration.`);
101741
101960
  finalResult = `Error: Unable to complete request. The AI model repeatedly used incorrect tool call format (${errorDesc}). Please try rephrasing your question or using a different model.`;
101742
101961
  break;
@@ -101750,6 +101969,10 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
101750
101969
  sameFormatErrorCount = 0;
101751
101970
  }
101752
101971
  }
101972
+ this._recordIterationTelemetry("end", currentIteration, {
101973
+ "iteration.completed": completionAttempted,
101974
+ "iteration.message_count": currentMessages.length
101975
+ });
101753
101976
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
101754
101977
  const messagesBefore = currentMessages.length;
101755
101978
  const systemMsg = currentMessages[0];