@probelabs/probe 0.6.0-rc223 → 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.
@@ -3776,6 +3776,19 @@ async function delegate({
3776
3776
  if (!task || typeof task !== "string") {
3777
3777
  throw new Error("Task parameter is required and must be a string");
3778
3778
  }
3779
+ const hasExplicitTimeout = Object.prototype.hasOwnProperty.call(arguments?.[0] ?? {}, "timeout");
3780
+ if (!hasExplicitTimeout) {
3781
+ const envTimeoutMs = parseInt(process.env.DELEGATION_TIMEOUT_MS || "", 10);
3782
+ const envTimeoutSeconds = parseInt(
3783
+ process.env.DELEGATION_TIMEOUT_SECONDS || process.env.DELEGATION_TIMEOUT || "",
3784
+ 10
3785
+ );
3786
+ if (!Number.isNaN(envTimeoutMs) && envTimeoutMs > 0) {
3787
+ timeout = Math.max(1, Math.ceil(envTimeoutMs / 1e3));
3788
+ } else if (!Number.isNaN(envTimeoutSeconds) && envTimeoutSeconds > 0) {
3789
+ timeout = Math.max(1, envTimeoutSeconds);
3790
+ }
3791
+ }
3779
3792
  const manager = delegationManager || defaultDelegationManager;
3780
3793
  const sessionId = randomUUID();
3781
3794
  const startTime = Date.now();
@@ -12700,6 +12713,183 @@ var init_simpleTelemetry = __esm({
12700
12713
  console.log("[Attributes]", attributes);
12701
12714
  }
12702
12715
  }
12716
+ /**
12717
+ * Hash content for deduplication/comparison purposes
12718
+ * @param {string} content - The content to hash
12719
+ * @returns {string} - Hex string hash
12720
+ */
12721
+ hashContent(content) {
12722
+ let hash = 0;
12723
+ const len = Math.min(content.length, 1e3);
12724
+ for (let i = 0; i < len; i++) {
12725
+ hash = (hash << 5) - hash + content.charCodeAt(i);
12726
+ hash |= 0;
12727
+ }
12728
+ return hash.toString(16);
12729
+ }
12730
+ /**
12731
+ * Record a conversation turn (assistant response or tool result)
12732
+ * @param {string} role - The role (assistant, tool_result)
12733
+ * @param {string} content - The turn content
12734
+ * @param {Object} metadata - Additional metadata
12735
+ */
12736
+ recordConversationTurn(role, content, metadata = {}) {
12737
+ if (!this.isEnabled()) return;
12738
+ this.addEvent(`conversation.turn.${role}`, {
12739
+ "session.id": this.sessionId,
12740
+ "conversation.role": role,
12741
+ "conversation.content": content.substring(0, 1e4),
12742
+ "conversation.content.length": content.length,
12743
+ "conversation.content.hash": this.hashContent(content),
12744
+ ...metadata
12745
+ });
12746
+ }
12747
+ /**
12748
+ * Record error events with classification
12749
+ * @param {string} errorType - The type of error (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker, etc.)
12750
+ * @param {Object} errorDetails - Error details including message, stack, context
12751
+ */
12752
+ recordErrorEvent(errorType, errorDetails = {}) {
12753
+ if (!this.isEnabled()) return;
12754
+ this.addEvent(`error.${errorType}`, {
12755
+ "session.id": this.sessionId,
12756
+ "error.type": errorType,
12757
+ "error.message": errorDetails.message?.substring(0, 1e3) || null,
12758
+ "error.stack": errorDetails.stack?.substring(0, 2e3) || null,
12759
+ "error.recoverable": errorDetails.recoverable ?? true,
12760
+ "error.context": JSON.stringify(errorDetails.context || {}).substring(0, 1e3),
12761
+ ...Object.fromEntries(
12762
+ Object.entries(errorDetails).filter(([k]) => !["message", "stack", "context", "recoverable"].includes(k)).map(([k, v]) => [`error.${k}`, v])
12763
+ )
12764
+ });
12765
+ }
12766
+ /**
12767
+ * Record AI thinking/reasoning content
12768
+ * @param {string} thinkingContent - The thinking content from AI response
12769
+ * @param {Object} metadata - Additional metadata
12770
+ */
12771
+ recordThinkingContent(thinkingContent, metadata = {}) {
12772
+ if (!this.isEnabled() || !thinkingContent) return;
12773
+ this.addEvent("ai.thinking", {
12774
+ "session.id": this.sessionId,
12775
+ "ai.thinking.content": thinkingContent.substring(0, 5e4),
12776
+ "ai.thinking.length": thinkingContent.length,
12777
+ "ai.thinking.hash": this.hashContent(thinkingContent),
12778
+ ...metadata
12779
+ });
12780
+ }
12781
+ /**
12782
+ * Record AI tool call decision
12783
+ * @param {string} toolName - The tool name AI decided to call
12784
+ * @param {Object} params - The parameters AI provided
12785
+ * @param {Object} metadata - Additional metadata
12786
+ */
12787
+ recordToolDecision(toolName, params, metadata = {}) {
12788
+ if (!this.isEnabled()) return;
12789
+ this.addEvent("ai.tool_decision", {
12790
+ "session.id": this.sessionId,
12791
+ "ai.tool_decision.name": toolName,
12792
+ "ai.tool_decision.params": JSON.stringify(params || {}).substring(0, 2e3),
12793
+ ...metadata
12794
+ });
12795
+ }
12796
+ /**
12797
+ * Record tool result after execution
12798
+ * @param {string} toolName - The tool that was executed
12799
+ * @param {string|Object} result - The tool result
12800
+ * @param {boolean} success - Whether the tool succeeded
12801
+ * @param {number} durationMs - Execution duration in milliseconds
12802
+ * @param {Object} metadata - Additional metadata
12803
+ */
12804
+ recordToolResult(toolName, result, success, durationMs, metadata = {}) {
12805
+ if (!this.isEnabled()) return;
12806
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result);
12807
+ this.addEvent("tool.result", {
12808
+ "session.id": this.sessionId,
12809
+ "tool.name": toolName,
12810
+ "tool.result": resultStr.substring(0, 1e4),
12811
+ "tool.result.length": resultStr.length,
12812
+ "tool.result.hash": this.hashContent(resultStr),
12813
+ "tool.duration_ms": durationMs,
12814
+ "tool.success": success,
12815
+ ...metadata
12816
+ });
12817
+ }
12818
+ /**
12819
+ * Record MCP tool execution start
12820
+ * @param {string} toolName - MCP tool name
12821
+ * @param {string} serverName - MCP server name
12822
+ * @param {Object} params - Tool parameters
12823
+ * @param {Object} metadata - Additional metadata
12824
+ */
12825
+ recordMcpToolStart(toolName, serverName, params, metadata = {}) {
12826
+ if (!this.isEnabled()) return;
12827
+ this.addEvent("mcp.tool.start", {
12828
+ "session.id": this.sessionId,
12829
+ "mcp.tool.name": toolName,
12830
+ "mcp.tool.server": serverName || "unknown",
12831
+ "mcp.tool.params": JSON.stringify(params || {}).substring(0, 2e3),
12832
+ ...metadata
12833
+ });
12834
+ }
12835
+ /**
12836
+ * Record MCP tool execution end
12837
+ * @param {string} toolName - MCP tool name
12838
+ * @param {string} serverName - MCP server name
12839
+ * @param {string|Object} result - Tool result
12840
+ * @param {boolean} success - Whether succeeded
12841
+ * @param {number} durationMs - Execution duration
12842
+ * @param {string} errorMessage - Error message if failed
12843
+ * @param {Object} metadata - Additional metadata
12844
+ */
12845
+ recordMcpToolEnd(toolName, serverName, result, success, durationMs, errorMessage = null, metadata = {}) {
12846
+ if (!this.isEnabled()) return;
12847
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
12848
+ this.addEvent("mcp.tool.end", {
12849
+ "session.id": this.sessionId,
12850
+ "mcp.tool.name": toolName,
12851
+ "mcp.tool.server": serverName || "unknown",
12852
+ "mcp.tool.result": resultStr.substring(0, 1e4),
12853
+ "mcp.tool.result.length": resultStr.length,
12854
+ "mcp.tool.duration_ms": durationMs,
12855
+ "mcp.tool.success": success,
12856
+ "mcp.tool.error": errorMessage,
12857
+ ...metadata
12858
+ });
12859
+ }
12860
+ /**
12861
+ * Record iteration lifecycle event
12862
+ * @param {string} eventType - start or end
12863
+ * @param {number} iteration - Iteration number
12864
+ * @param {Object} data - Additional data
12865
+ */
12866
+ recordIterationEvent(eventType, iteration, data = {}) {
12867
+ if (!this.isEnabled()) return;
12868
+ this.addEvent(`iteration.${eventType}`, {
12869
+ "session.id": this.sessionId,
12870
+ "iteration": iteration,
12871
+ ...data
12872
+ });
12873
+ }
12874
+ /**
12875
+ * Record per-turn token breakdown
12876
+ * @param {number} iteration - Iteration number
12877
+ * @param {Object} tokenData - Token metrics
12878
+ */
12879
+ recordTokenTurn(iteration, tokenData = {}) {
12880
+ if (!this.isEnabled()) return;
12881
+ this.addEvent("tokens.turn", {
12882
+ "session.id": this.sessionId,
12883
+ "iteration": iteration,
12884
+ "tokens.input": tokenData.inputTokens || 0,
12885
+ "tokens.output": tokenData.outputTokens || 0,
12886
+ "tokens.total": (tokenData.inputTokens || 0) + (tokenData.outputTokens || 0),
12887
+ "tokens.cache_read": tokenData.cacheReadTokens || 0,
12888
+ "tokens.cache_write": tokenData.cacheWriteTokens || 0,
12889
+ "tokens.context_used": tokenData.contextTokens || 0,
12890
+ "tokens.context_remaining": tokenData.maxContextTokens ? tokenData.maxContextTokens - (tokenData.contextTokens || 0) : null
12891
+ });
12892
+ }
12703
12893
  async withSpan(spanName, fn, attributes = {}) {
12704
12894
  if (!this.isEnabled()) {
12705
12895
  return fn();
@@ -20406,11 +20596,12 @@ function createTools(configOptions) {
20406
20596
  return tools2;
20407
20597
  }
20408
20598
  function parseXmlToolCallWithThinking(xmlString, validTools) {
20409
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
20599
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
20410
20600
  if (recoveryResult) {
20411
- return recoveryResult;
20601
+ return { ...recoveryResult, thinkingContent };
20412
20602
  }
20413
- return parseXmlToolCall(cleanedXmlString, validTools);
20603
+ const toolCall = parseXmlToolCall(cleanedXmlString, validTools);
20604
+ return toolCall ? { ...toolCall, thinkingContent } : null;
20414
20605
  }
20415
20606
  var implementToolDefinition, listFilesToolDefinition, searchFilesToolDefinition, listSkillsToolDefinition, useSkillToolDefinition, readImageToolDefinition;
20416
20607
  var init_tools2 = __esm({
@@ -59704,25 +59895,27 @@ function parseXmlMcpToolCall(xmlString, mcpToolNames = []) {
59704
59895
  function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge = null) {
59705
59896
  const nativeResult = parseNativeXmlToolWithThinking(xmlString, nativeTools);
59706
59897
  if (nativeResult) {
59707
- return { ...nativeResult, type: "native" };
59898
+ const { thinkingContent, ...rest } = nativeResult;
59899
+ return { ...rest, type: "native", thinkingContent };
59708
59900
  }
59709
59901
  if (mcpBridge) {
59710
59902
  const mcpResult = parseXmlMcpToolCall(xmlString, mcpBridge.getToolNames());
59711
59903
  if (mcpResult) {
59712
- return { ...mcpResult, type: "mcp" };
59904
+ const { thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, []);
59905
+ return { ...mcpResult, type: "mcp", thinkingContent };
59713
59906
  }
59714
59907
  }
59715
59908
  return null;
59716
59909
  }
59717
59910
  function parseNativeXmlToolWithThinking(xmlString, validTools) {
59718
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
59911
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
59719
59912
  if (recoveryResult) {
59720
- return recoveryResult;
59913
+ return { ...recoveryResult, thinkingContent };
59721
59914
  }
59722
59915
  for (const toolName of validTools) {
59723
59916
  const result = parseNativeXmlTool(cleanedXmlString, toolName);
59724
59917
  if (result) {
59725
- return result;
59918
+ return { ...result, thinkingContent };
59726
59919
  }
59727
59920
  }
59728
59921
  return null;
@@ -70184,6 +70377,181 @@ var init_ProbeAgent = __esm({
70184
70377
  _filterMcpTools(mcpToolNames) {
70185
70378
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
70186
70379
  }
70380
+ /**
70381
+ * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
70382
+ * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
70383
+ * @private
70384
+ */
70385
+ _isAppTracerStyle() {
70386
+ return this.tracer && typeof this.tracer.sessionSpans !== "undefined";
70387
+ }
70388
+ /**
70389
+ * Record an error classification event for telemetry
70390
+ * Provides unified error recording across all error types
70391
+ * @param {string} errorType - Error type (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker)
70392
+ * @param {string} message - Error message
70393
+ * @param {Object} context - Additional context data
70394
+ * @param {number} iteration - Current iteration number
70395
+ * @private
70396
+ */
70397
+ _recordErrorTelemetry(errorType, message, context, iteration) {
70398
+ if (!this.tracer) return;
70399
+ if (this._isAppTracerStyle() && typeof this.tracer.recordErrorClassification === "function") {
70400
+ this.tracer.recordErrorClassification(this.sessionId, iteration, errorType, {
70401
+ message,
70402
+ context
70403
+ });
70404
+ } else if (typeof this.tracer.recordErrorEvent === "function") {
70405
+ this.tracer.recordErrorEvent(errorType, {
70406
+ message,
70407
+ context: { ...context, iteration }
70408
+ });
70409
+ } else {
70410
+ this.tracer.addEvent(`error.${errorType}`, {
70411
+ "error.type": errorType,
70412
+ "error.message": message,
70413
+ "error.recoverable": errorType !== "circuit_breaker",
70414
+ "error.context": JSON.stringify(context).substring(0, 1e3),
70415
+ "iteration": iteration
70416
+ });
70417
+ }
70418
+ }
70419
+ /**
70420
+ * Record AI thinking content for telemetry
70421
+ * @param {string} thinkingContent - The thinking content
70422
+ * @param {number} iteration - Current iteration number
70423
+ * @private
70424
+ */
70425
+ _recordThinkingTelemetry(thinkingContent, iteration) {
70426
+ if (!this.tracer || !thinkingContent) return;
70427
+ if (this._isAppTracerStyle() && typeof this.tracer.recordThinkingContent === "function") {
70428
+ this.tracer.recordThinkingContent(this.sessionId, iteration, thinkingContent);
70429
+ } else if (typeof this.tracer.recordThinkingContent === "function") {
70430
+ this.tracer.recordThinkingContent(thinkingContent, { iteration });
70431
+ } else {
70432
+ this.tracer.addEvent("ai.thinking", {
70433
+ "ai.thinking.content": thinkingContent.substring(0, 5e4),
70434
+ "ai.thinking.length": thinkingContent.length,
70435
+ "iteration": iteration
70436
+ });
70437
+ }
70438
+ }
70439
+ /**
70440
+ * Record AI tool decision for telemetry
70441
+ * @param {string} toolName - The tool name
70442
+ * @param {Object} params - Tool parameters
70443
+ * @param {number} responseLength - Length of AI response
70444
+ * @param {number} iteration - Current iteration number
70445
+ * @private
70446
+ */
70447
+ _recordToolDecisionTelemetry(toolName, params, responseLength, iteration) {
70448
+ if (!this.tracer) return;
70449
+ if (this._isAppTracerStyle() && typeof this.tracer.recordAIToolDecision === "function") {
70450
+ this.tracer.recordAIToolDecision(this.sessionId, iteration, toolName, params);
70451
+ } else if (typeof this.tracer.recordToolDecision === "function") {
70452
+ this.tracer.recordToolDecision(toolName, params, {
70453
+ iteration,
70454
+ "ai.tool_decision.raw_response_length": responseLength
70455
+ });
70456
+ } else {
70457
+ this.tracer.addEvent("ai.tool_decision", {
70458
+ "ai.tool_decision.name": toolName,
70459
+ "ai.tool_decision.params": JSON.stringify(params || {}).substring(0, 2e3),
70460
+ "ai.tool_decision.raw_response_length": responseLength,
70461
+ "iteration": iteration
70462
+ });
70463
+ }
70464
+ }
70465
+ /**
70466
+ * Record tool result for telemetry
70467
+ * @param {string} toolName - The tool name
70468
+ * @param {string|Object} result - Tool result
70469
+ * @param {boolean} success - Whether tool succeeded
70470
+ * @param {number} durationMs - Execution duration in milliseconds
70471
+ * @param {number} iteration - Current iteration number
70472
+ * @private
70473
+ */
70474
+ _recordToolResultTelemetry(toolName, result, success, durationMs, iteration) {
70475
+ if (!this.tracer) return;
70476
+ if (this._isAppTracerStyle() && typeof this.tracer.recordToolResult === "function") {
70477
+ this.tracer.recordToolResult(this.sessionId, iteration, toolName, result, success, durationMs);
70478
+ } else if (typeof this.tracer.recordToolResult === "function") {
70479
+ this.tracer.recordToolResult(toolName, result, success, durationMs, { iteration });
70480
+ } else {
70481
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
70482
+ this.tracer.addEvent("tool.result", {
70483
+ "tool.name": toolName,
70484
+ "tool.result": resultStr.substring(0, 1e4),
70485
+ "tool.result.length": resultStr.length,
70486
+ "tool.duration_ms": durationMs,
70487
+ "tool.success": success,
70488
+ "iteration": iteration
70489
+ });
70490
+ }
70491
+ }
70492
+ /**
70493
+ * Record MCP tool lifecycle event for telemetry
70494
+ * @param {string} phase - 'start' or 'end'
70495
+ * @param {string} toolName - MCP tool name
70496
+ * @param {Object} params - Tool parameters (for start) or null (for end)
70497
+ * @param {number} iteration - Current iteration number
70498
+ * @param {Object} [endData] - Additional data for end phase (result, success, durationMs, error)
70499
+ * @private
70500
+ */
70501
+ _recordMcpToolTelemetry(phase, toolName, params, iteration, endData = null) {
70502
+ if (!this.tracer) return;
70503
+ if (phase === "start") {
70504
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolStart === "function") {
70505
+ this.tracer.recordMcpToolStart(this.sessionId, iteration, toolName, "mcp", params);
70506
+ } else if (typeof this.tracer.recordMcpToolStart === "function") {
70507
+ this.tracer.recordMcpToolStart(toolName, "mcp", params, { iteration });
70508
+ } else {
70509
+ this.tracer.addEvent("mcp.tool.start", {
70510
+ "mcp.tool.name": toolName,
70511
+ "mcp.tool.server": "mcp",
70512
+ "mcp.tool.params": JSON.stringify(params || {}).substring(0, 2e3),
70513
+ "iteration": iteration
70514
+ });
70515
+ }
70516
+ } else if (phase === "end" && endData) {
70517
+ const { result, success, durationMs, error } = endData;
70518
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolEnd === "function") {
70519
+ this.tracer.recordMcpToolEnd(this.sessionId, iteration, toolName, "mcp", result, success, durationMs, error);
70520
+ } else if (typeof this.tracer.recordMcpToolEnd === "function") {
70521
+ this.tracer.recordMcpToolEnd(toolName, "mcp", result, success, durationMs, error, { iteration });
70522
+ } else {
70523
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
70524
+ this.tracer.addEvent("mcp.tool.end", {
70525
+ "mcp.tool.name": toolName,
70526
+ "mcp.tool.server": "mcp",
70527
+ "mcp.tool.result": resultStr.substring(0, 1e4),
70528
+ "mcp.tool.result.length": resultStr.length,
70529
+ "mcp.tool.duration_ms": durationMs,
70530
+ "mcp.tool.success": success,
70531
+ "mcp.tool.error": error,
70532
+ "iteration": iteration
70533
+ });
70534
+ }
70535
+ }
70536
+ }
70537
+ /**
70538
+ * Record iteration lifecycle event for telemetry
70539
+ * @param {string} phase - 'end' (start is already handled elsewhere)
70540
+ * @param {number} iteration - Current iteration number
70541
+ * @param {Object} data - Additional iteration data
70542
+ * @private
70543
+ */
70544
+ _recordIterationTelemetry(phase, iteration, data = {}) {
70545
+ if (!this.tracer) return;
70546
+ if (typeof this.tracer.recordIterationEvent === "function") {
70547
+ this.tracer.recordIterationEvent(phase, iteration, data);
70548
+ } else {
70549
+ this.tracer.addEvent(`iteration.${phase}`, {
70550
+ "iteration": iteration,
70551
+ ...data
70552
+ });
70553
+ }
70554
+ }
70187
70555
  /**
70188
70556
  * Initialize the agent asynchronously (must be called after constructor)
70189
70557
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
@@ -72138,8 +72506,12 @@ You are working with a repository located at: ${searchDirectory}
72138
72506
  }
72139
72507
  const nativeTools = validTools;
72140
72508
  const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
72509
+ if (parsedTool?.thinkingContent) {
72510
+ this._recordThinkingTelemetry(parsedTool.thinkingContent, currentIteration);
72511
+ }
72141
72512
  if (parsedTool) {
72142
72513
  const { toolName, params } = parsedTool;
72514
+ this._recordToolDecisionTelemetry(toolName, params, assistantResponseContent.length, currentIteration);
72143
72515
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
72144
72516
  if (toolName === "attempt_completion") {
72145
72517
  completionAttempted = true;
@@ -72216,6 +72588,8 @@ You are working with a repository located at: ${searchDirectory}
72216
72588
  } else {
72217
72589
  const { type } = parsedTool;
72218
72590
  if (type === "mcp" && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
72591
+ const mcpStartTime = Date.now();
72592
+ this._recordMcpToolTelemetry("start", toolName, params, currentIteration);
72219
72593
  try {
72220
72594
  if (this.debug) {
72221
72595
  console.error(`
@@ -72245,6 +72619,13 @@ You are working with a repository located at: ${searchDirectory}
72245
72619
  } catch (truncateError) {
72246
72620
  console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
72247
72621
  }
72622
+ const mcpDurationMs = Date.now() - mcpStartTime;
72623
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
72624
+ result: toolResultContent,
72625
+ success: true,
72626
+ durationMs: mcpDurationMs,
72627
+ error: null
72628
+ });
72248
72629
  if (this.debug) {
72249
72630
  const preview = toolResultContent.length > 500 ? toolResultContent.substring(0, 500) + "..." : toolResultContent;
72250
72631
  console.error(`[DEBUG] ========================================`);
@@ -72258,6 +72639,13 @@ You are working with a repository located at: ${searchDirectory}
72258
72639
  ${toolResultContent}
72259
72640
  </tool_result>` });
72260
72641
  } catch (error) {
72642
+ const mcpDurationMs = Date.now() - mcpStartTime;
72643
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
72644
+ result: null,
72645
+ success: false,
72646
+ durationMs: mcpDurationMs,
72647
+ error: error.message
72648
+ });
72261
72649
  console.error(`Error executing MCP tool ${toolName}:`, error);
72262
72650
  if (this.debug) {
72263
72651
  console.error(`[DEBUG] ========================================`);
@@ -72349,6 +72737,7 @@ ${errorXml}
72349
72737
  return await this.toolImplementations[toolName].execute(toolParams);
72350
72738
  };
72351
72739
  let toolResult;
72740
+ const toolStartTime = Date.now();
72352
72741
  try {
72353
72742
  if (this.tracer) {
72354
72743
  toolResult = await this.tracer.withSpan("tool.call", executeToolCall, {
@@ -72359,6 +72748,8 @@ ${errorXml}
72359
72748
  } else {
72360
72749
  toolResult = await executeToolCall();
72361
72750
  }
72751
+ const toolDurationMs = Date.now() - toolStartTime;
72752
+ this._recordToolResultTelemetry(toolName, toolResult, true, toolDurationMs, currentIteration);
72362
72753
  if (this.debug) {
72363
72754
  const resultPreview = typeof toolResult === "string" ? toolResult.length > 500 ? toolResult.substring(0, 500) + "..." : toolResult : toolResult ? JSON.stringify(toolResult, null, 2).substring(0, 500) + "..." : "No Result";
72364
72755
  console.error(`[DEBUG] ========================================`);
@@ -72415,6 +72806,20 @@ ${toolResultContent}
72415
72806
  role: "user",
72416
72807
  content: toolResultMessage
72417
72808
  });
72809
+ if (this.tracer) {
72810
+ if (typeof this.tracer.recordConversationTurn === "function") {
72811
+ this.tracer.recordConversationTurn("assistant", assistantResponseContent, {
72812
+ iteration: currentIteration,
72813
+ has_tool_call: true,
72814
+ tool_name: toolName
72815
+ });
72816
+ this.tracer.recordConversationTurn("tool_result", toolResultContent, {
72817
+ iteration: currentIteration,
72818
+ tool_name: toolName,
72819
+ tool_success: true
72820
+ });
72821
+ }
72822
+ }
72418
72823
  if (this.debug) {
72419
72824
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
72420
72825
  }
@@ -72485,6 +72890,7 @@ ${errorXml}
72485
72890
  if (this.debug) {
72486
72891
  console.log(`[DEBUG] Detected wrapped tool '${wrappedToolName}' in assistant response - wrong XML format.`);
72487
72892
  }
72893
+ this._recordErrorTelemetry("wrapped_tool", "Tool call wrapped in markdown", { toolName: wrappedToolName }, currentIteration);
72488
72894
  const toolError = new ParameterError(
72489
72895
  `Tool '${wrappedToolName}' found but in WRONG FORMAT - do not wrap tools in other XML tags.`,
72490
72896
  {
@@ -72510,6 +72916,7 @@ ${formatErrorForAI(toolError)}
72510
72916
  if (this.debug) {
72511
72917
  console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
72512
72918
  }
72919
+ this._recordErrorTelemetry("unrecognized_tool", `Unknown tool: ${unrecognizedTool}`, { toolName: unrecognizedTool, validTools }, currentIteration);
72513
72920
  const toolError = new ParameterError(`Tool '${unrecognizedTool}' is not available in this context.`, {
72514
72921
  suggestion: `Available tools: ${validTools.join(", ")}. Please use one of these tools instead.`
72515
72922
  });
@@ -72517,6 +72924,7 @@ ${formatErrorForAI(toolError)}
72517
72924
  ${formatErrorForAI(toolError)}
72518
72925
  </tool_result>`;
72519
72926
  } else {
72927
+ this._recordErrorTelemetry("no_tool_call", "AI response did not contain tool call", { responsePreview: assistantResponseContent.substring(0, 500) }, currentIteration);
72520
72928
  if (currentIteration >= maxIterations) {
72521
72929
  let cleanedResponse = assistantResponseContent;
72522
72930
  cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
@@ -72592,6 +73000,7 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
72592
73000
  sameFormatErrorCount++;
72593
73001
  if (sameFormatErrorCount >= MAX_REPEATED_FORMAT_ERRORS) {
72594
73002
  const errorDesc = isWrapped ? "wrapped tool format" : unrecognizedTool;
73003
+ this._recordErrorTelemetry("circuit_breaker", "Format error limit exceeded", { formatErrorCount: sameFormatErrorCount, errorCategory }, currentIteration);
72595
73004
  console.error(`[ERROR] Format error category '${errorCategory}' repeated ${sameFormatErrorCount} times. Breaking loop early to prevent infinite iteration.`);
72596
73005
  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.`;
72597
73006
  break;
@@ -72605,6 +73014,10 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
72605
73014
  sameFormatErrorCount = 0;
72606
73015
  }
72607
73016
  }
73017
+ this._recordIterationTelemetry("end", currentIteration, {
73018
+ "iteration.completed": completionAttempted,
73019
+ "iteration.message_count": currentMessages.length
73020
+ });
72608
73021
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
72609
73022
  const messagesBefore = currentMessages.length;
72610
73023
  const systemMsg = currentMessages[0];
@@ -321,14 +321,17 @@ export function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge =
321
321
  // This includes thinking tag removal and attempt_complete recovery logic
322
322
  const nativeResult = parseNativeXmlToolWithThinking(xmlString, nativeTools);
323
323
  if (nativeResult) {
324
- return { ...nativeResult, type: 'native' };
324
+ const { thinkingContent, ...rest } = nativeResult;
325
+ return { ...rest, type: 'native', thinkingContent };
325
326
  }
326
327
 
327
328
  // Then try MCP tools if bridge is available
328
329
  if (mcpBridge) {
329
330
  const mcpResult = parseXmlMcpToolCall(xmlString, mcpBridge.getToolNames());
330
331
  if (mcpResult) {
331
- return { ...mcpResult, type: 'mcp' };
332
+ // Extract thinking content for MCP tools as well
333
+ const { thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, []);
334
+ return { ...mcpResult, type: 'mcp', thinkingContent };
332
335
  }
333
336
  }
334
337
 
@@ -344,18 +347,18 @@ export function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge =
344
347
  */
345
348
  function parseNativeXmlToolWithThinking(xmlString, validTools) {
346
349
  // Use the shared processing logic
347
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
348
-
349
- // If recovery found an attempt_complete pattern, return it
350
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
351
+
352
+ // If recovery found an attempt_complete pattern, return it with thinking content
350
353
  if (recoveryResult) {
351
- return recoveryResult;
354
+ return { ...recoveryResult, thinkingContent };
352
355
  }
353
356
 
354
357
  // Use the original parseNativeXmlTool function to parse the cleaned XML string
355
358
  for (const toolName of validTools) {
356
359
  const result = parseNativeXmlTool(cleanedXmlString, toolName);
357
360
  if (result) {
358
- return result;
361
+ return { ...result, thinkingContent };
359
362
  }
360
363
  }
361
364