@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.
@@ -406,6 +406,209 @@ export class ProbeAgent {
406
406
  return mcpToolNames.filter(toolName => this._isMcpToolAllowed(toolName));
407
407
  }
408
408
 
409
+ /**
410
+ * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
411
+ * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
412
+ * @private
413
+ */
414
+ _isAppTracerStyle() {
415
+ // AppTracer has recordThinkingContent(sessionId, iteration, content) signature
416
+ // SimpleAppTracer has recordThinkingContent(content, metadata) signature
417
+ // We detect by checking if there's a sessionSpans map (AppTracer-specific)
418
+ return this.tracer && typeof this.tracer.sessionSpans !== 'undefined';
419
+ }
420
+
421
+ /**
422
+ * Record an error classification event for telemetry
423
+ * Provides unified error recording across all error types
424
+ * @param {string} errorType - Error type (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker)
425
+ * @param {string} message - Error message
426
+ * @param {Object} context - Additional context data
427
+ * @param {number} iteration - Current iteration number
428
+ * @private
429
+ */
430
+ _recordErrorTelemetry(errorType, message, context, iteration) {
431
+ if (!this.tracer) return;
432
+
433
+ if (this._isAppTracerStyle() && typeof this.tracer.recordErrorClassification === 'function') {
434
+ // AppTracer style: (sessionId, iteration, errorType, details)
435
+ this.tracer.recordErrorClassification(this.sessionId, iteration, errorType, {
436
+ message,
437
+ context
438
+ });
439
+ } else if (typeof this.tracer.recordErrorEvent === 'function') {
440
+ // SimpleAppTracer style: (errorType, details)
441
+ this.tracer.recordErrorEvent(errorType, {
442
+ message,
443
+ context: { ...context, iteration }
444
+ });
445
+ } else {
446
+ this.tracer.addEvent(`error.${errorType}`, {
447
+ 'error.type': errorType,
448
+ 'error.message': message,
449
+ 'error.recoverable': errorType !== 'circuit_breaker',
450
+ 'error.context': JSON.stringify(context).substring(0, 1000),
451
+ 'iteration': iteration
452
+ });
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Record AI thinking content for telemetry
458
+ * @param {string} thinkingContent - The thinking content
459
+ * @param {number} iteration - Current iteration number
460
+ * @private
461
+ */
462
+ _recordThinkingTelemetry(thinkingContent, iteration) {
463
+ if (!this.tracer || !thinkingContent) return;
464
+
465
+ if (this._isAppTracerStyle() && typeof this.tracer.recordThinkingContent === 'function') {
466
+ // AppTracer style: (sessionId, iteration, content)
467
+ this.tracer.recordThinkingContent(this.sessionId, iteration, thinkingContent);
468
+ } else if (typeof this.tracer.recordThinkingContent === 'function') {
469
+ // SimpleAppTracer style: (content, metadata)
470
+ this.tracer.recordThinkingContent(thinkingContent, { iteration });
471
+ } else {
472
+ this.tracer.addEvent('ai.thinking', {
473
+ 'ai.thinking.content': thinkingContent.substring(0, 50000),
474
+ 'ai.thinking.length': thinkingContent.length,
475
+ 'iteration': iteration
476
+ });
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Record AI tool decision for telemetry
482
+ * @param {string} toolName - The tool name
483
+ * @param {Object} params - Tool parameters
484
+ * @param {number} responseLength - Length of AI response
485
+ * @param {number} iteration - Current iteration number
486
+ * @private
487
+ */
488
+ _recordToolDecisionTelemetry(toolName, params, responseLength, iteration) {
489
+ if (!this.tracer) return;
490
+
491
+ if (this._isAppTracerStyle() && typeof this.tracer.recordAIToolDecision === 'function') {
492
+ // AppTracer style: (sessionId, iteration, toolName, params)
493
+ this.tracer.recordAIToolDecision(this.sessionId, iteration, toolName, params);
494
+ } else if (typeof this.tracer.recordToolDecision === 'function') {
495
+ // SimpleAppTracer style: (toolName, params, metadata)
496
+ this.tracer.recordToolDecision(toolName, params, {
497
+ iteration,
498
+ 'ai.tool_decision.raw_response_length': responseLength
499
+ });
500
+ } else {
501
+ this.tracer.addEvent('ai.tool_decision', {
502
+ 'ai.tool_decision.name': toolName,
503
+ 'ai.tool_decision.params': JSON.stringify(params || {}).substring(0, 2000),
504
+ 'ai.tool_decision.raw_response_length': responseLength,
505
+ 'iteration': iteration
506
+ });
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Record tool result for telemetry
512
+ * @param {string} toolName - The tool name
513
+ * @param {string|Object} result - Tool result
514
+ * @param {boolean} success - Whether tool succeeded
515
+ * @param {number} durationMs - Execution duration in milliseconds
516
+ * @param {number} iteration - Current iteration number
517
+ * @private
518
+ */
519
+ _recordToolResultTelemetry(toolName, result, success, durationMs, iteration) {
520
+ if (!this.tracer) return;
521
+
522
+ if (this._isAppTracerStyle() && typeof this.tracer.recordToolResult === 'function') {
523
+ // AppTracer style: (sessionId, iteration, toolName, result, success, durationMs)
524
+ this.tracer.recordToolResult(this.sessionId, iteration, toolName, result, success, durationMs);
525
+ } else if (typeof this.tracer.recordToolResult === 'function') {
526
+ // SimpleAppTracer style: (toolName, result, success, durationMs, metadata)
527
+ this.tracer.recordToolResult(toolName, result, success, durationMs, { iteration });
528
+ } else {
529
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result || '');
530
+ this.tracer.addEvent('tool.result', {
531
+ 'tool.name': toolName,
532
+ 'tool.result': resultStr.substring(0, 10000),
533
+ 'tool.result.length': resultStr.length,
534
+ 'tool.duration_ms': durationMs,
535
+ 'tool.success': success,
536
+ 'iteration': iteration
537
+ });
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Record MCP tool lifecycle event for telemetry
543
+ * @param {string} phase - 'start' or 'end'
544
+ * @param {string} toolName - MCP tool name
545
+ * @param {Object} params - Tool parameters (for start) or null (for end)
546
+ * @param {number} iteration - Current iteration number
547
+ * @param {Object} [endData] - Additional data for end phase (result, success, durationMs, error)
548
+ * @private
549
+ */
550
+ _recordMcpToolTelemetry(phase, toolName, params, iteration, endData = null) {
551
+ if (!this.tracer) return;
552
+
553
+ if (phase === 'start') {
554
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolStart === 'function') {
555
+ // AppTracer style: (sessionId, iteration, toolName, serverName, params)
556
+ this.tracer.recordMcpToolStart(this.sessionId, iteration, toolName, 'mcp', params);
557
+ } else if (typeof this.tracer.recordMcpToolStart === 'function') {
558
+ // SimpleAppTracer style: (toolName, serverName, params, metadata)
559
+ this.tracer.recordMcpToolStart(toolName, 'mcp', params, { iteration });
560
+ } else {
561
+ this.tracer.addEvent('mcp.tool.start', {
562
+ 'mcp.tool.name': toolName,
563
+ 'mcp.tool.server': 'mcp',
564
+ 'mcp.tool.params': JSON.stringify(params || {}).substring(0, 2000),
565
+ 'iteration': iteration
566
+ });
567
+ }
568
+ } else if (phase === 'end' && endData) {
569
+ const { result, success, durationMs, error } = endData;
570
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolEnd === 'function') {
571
+ // AppTracer style: (sessionId, iteration, toolName, serverName, result, success, durationMs, error)
572
+ this.tracer.recordMcpToolEnd(this.sessionId, iteration, toolName, 'mcp', result, success, durationMs, error);
573
+ } else if (typeof this.tracer.recordMcpToolEnd === 'function') {
574
+ // SimpleAppTracer style: (toolName, serverName, result, success, durationMs, error, metadata)
575
+ this.tracer.recordMcpToolEnd(toolName, 'mcp', result, success, durationMs, error, { iteration });
576
+ } else {
577
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result || '');
578
+ this.tracer.addEvent('mcp.tool.end', {
579
+ 'mcp.tool.name': toolName,
580
+ 'mcp.tool.server': 'mcp',
581
+ 'mcp.tool.result': resultStr.substring(0, 10000),
582
+ 'mcp.tool.result.length': resultStr.length,
583
+ 'mcp.tool.duration_ms': durationMs,
584
+ 'mcp.tool.success': success,
585
+ 'mcp.tool.error': error,
586
+ 'iteration': iteration
587
+ });
588
+ }
589
+ }
590
+ }
591
+
592
+ /**
593
+ * Record iteration lifecycle event for telemetry
594
+ * @param {string} phase - 'end' (start is already handled elsewhere)
595
+ * @param {number} iteration - Current iteration number
596
+ * @param {Object} data - Additional iteration data
597
+ * @private
598
+ */
599
+ _recordIterationTelemetry(phase, iteration, data = {}) {
600
+ if (!this.tracer) return;
601
+
602
+ if (typeof this.tracer.recordIterationEvent === 'function') {
603
+ this.tracer.recordIterationEvent(phase, iteration, data);
604
+ } else {
605
+ this.tracer.addEvent(`iteration.${phase}`, {
606
+ 'iteration': iteration,
607
+ ...data
608
+ });
609
+ }
610
+ }
611
+
409
612
  /**
410
613
  * Initialize the agent asynchronously (must be called after constructor)
411
614
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
@@ -2854,8 +3057,18 @@ Follow these instructions carefully:
2854
3057
  const parsedTool = (this.mcpBridge && !options._disableTools)
2855
3058
  ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge)
2856
3059
  : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
3060
+
3061
+ // Capture AI thinking content if present (for debugging and telemetry)
3062
+ if (parsedTool?.thinkingContent) {
3063
+ this._recordThinkingTelemetry(parsedTool.thinkingContent, currentIteration);
3064
+ }
3065
+
2857
3066
  if (parsedTool) {
2858
3067
  const { toolName, params } = parsedTool;
3068
+
3069
+ // Record AI tool decision for telemetry
3070
+ this._recordToolDecisionTelemetry(toolName, params, assistantResponseContent.length, currentIteration);
3071
+
2859
3072
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
2860
3073
 
2861
3074
  if (toolName === 'attempt_completion') {
@@ -2962,6 +3175,9 @@ Follow these instructions carefully:
2962
3175
 
2963
3176
  if (type === 'mcp' && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
2964
3177
  // Execute MCP tool
3178
+ const mcpStartTime = Date.now();
3179
+ this._recordMcpToolTelemetry('start', toolName, params, currentIteration);
3180
+
2965
3181
  try {
2966
3182
  // Log MCP tool execution in debug mode
2967
3183
  if (this.debug) {
@@ -2999,6 +3215,15 @@ Follow these instructions carefully:
2999
3215
  console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
3000
3216
  }
3001
3217
 
3218
+ // Record MCP tool end event (success)
3219
+ const mcpDurationMs = Date.now() - mcpStartTime;
3220
+ this._recordMcpToolTelemetry('end', toolName, null, currentIteration, {
3221
+ result: toolResultContent,
3222
+ success: true,
3223
+ durationMs: mcpDurationMs,
3224
+ error: null
3225
+ });
3226
+
3002
3227
  // Log MCP tool result in debug mode
3003
3228
  if (this.debug) {
3004
3229
  const preview = toolResultContent.length > 500 ? toolResultContent.substring(0, 500) + '...' : toolResultContent;
@@ -3011,6 +3236,15 @@ Follow these instructions carefully:
3011
3236
 
3012
3237
  currentMessages.push({ role: 'user', content: `<tool_result>\n${toolResultContent}\n</tool_result>` });
3013
3238
  } catch (error) {
3239
+ // Record MCP tool end event (failure)
3240
+ const mcpDurationMs = Date.now() - mcpStartTime;
3241
+ this._recordMcpToolTelemetry('end', toolName, null, currentIteration, {
3242
+ result: null,
3243
+ success: false,
3244
+ durationMs: mcpDurationMs,
3245
+ error: error.message
3246
+ });
3247
+
3014
3248
  console.error(`Error executing MCP tool ${toolName}:`, error);
3015
3249
 
3016
3250
  // Log MCP tool error in debug mode
@@ -3118,6 +3352,7 @@ Follow these instructions carefully:
3118
3352
  };
3119
3353
 
3120
3354
  let toolResult;
3355
+ const toolStartTime = Date.now();
3121
3356
  try {
3122
3357
  if (this.tracer) {
3123
3358
  toolResult = await this.tracer.withSpan('tool.call', executeToolCall, {
@@ -3128,7 +3363,11 @@ Follow these instructions carefully:
3128
3363
  } else {
3129
3364
  toolResult = await executeToolCall();
3130
3365
  }
3131
-
3366
+
3367
+ // Record tool result in telemetry
3368
+ const toolDurationMs = Date.now() - toolStartTime;
3369
+ this._recordToolResultTelemetry(toolName, toolResult, true, toolDurationMs, currentIteration);
3370
+
3132
3371
  // Log tool result in debug mode
3133
3372
  if (this.debug) {
3134
3373
  const resultPreview = typeof toolResult === 'string'
@@ -3201,6 +3440,22 @@ Follow these instructions carefully:
3201
3440
  content: toolResultMessage
3202
3441
  });
3203
3442
 
3443
+ // Record conversation turns in telemetry
3444
+ if (this.tracer) {
3445
+ if (typeof this.tracer.recordConversationTurn === 'function') {
3446
+ this.tracer.recordConversationTurn('assistant', assistantResponseContent, {
3447
+ iteration: currentIteration,
3448
+ has_tool_call: true,
3449
+ tool_name: toolName
3450
+ });
3451
+ this.tracer.recordConversationTurn('tool_result', toolResultContent, {
3452
+ iteration: currentIteration,
3453
+ tool_name: toolName,
3454
+ tool_success: true
3455
+ });
3456
+ }
3457
+ }
3458
+
3204
3459
  // NOTE: Automatic image processing removed (GitHub issue #305)
3205
3460
  // Images are now only loaded when the AI explicitly calls the readImage tool
3206
3461
  // This prevents: 1) implicit behavior that users don't expect
@@ -3294,6 +3549,10 @@ Follow these instructions carefully:
3294
3549
  if (this.debug) {
3295
3550
  console.log(`[DEBUG] Detected wrapped tool '${wrappedToolName}' in assistant response - wrong XML format.`);
3296
3551
  }
3552
+
3553
+ // Record wrapped tool error in telemetry
3554
+ this._recordErrorTelemetry('wrapped_tool', 'Tool call wrapped in markdown', { toolName: wrappedToolName }, currentIteration);
3555
+
3297
3556
  const toolError = new ParameterError(
3298
3557
  `Tool '${wrappedToolName}' found but in WRONG FORMAT - do not wrap tools in other XML tags.`,
3299
3558
  {
@@ -3318,12 +3577,19 @@ Remove ALL wrapper tags and use <${wrappedToolName}> directly as the outermost t
3318
3577
  if (this.debug) {
3319
3578
  console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
3320
3579
  }
3580
+
3581
+ // Record unrecognized tool error in telemetry
3582
+ this._recordErrorTelemetry('unrecognized_tool', `Unknown tool: ${unrecognizedTool}`, { toolName: unrecognizedTool, validTools }, currentIteration);
3583
+
3321
3584
  const toolError = new ParameterError(`Tool '${unrecognizedTool}' is not available in this context.`, {
3322
3585
  suggestion: `Available tools: ${validTools.join(', ')}. Please use one of these tools instead.`
3323
3586
  });
3324
3587
  reminderContent = `<tool_result>\n${formatErrorForAI(toolError)}\n</tool_result>`;
3325
3588
  } else {
3326
- // No tool call detected at all - check if this is the last iteration
3589
+ // No tool call detected at all - record in telemetry
3590
+ this._recordErrorTelemetry('no_tool_call', 'AI response did not contain tool call', { responsePreview: assistantResponseContent.substring(0, 500) }, currentIteration);
3591
+
3592
+ // Check if this is the last iteration
3327
3593
  // On the last iteration, if the AI gave a substantive response without using
3328
3594
  // attempt_completion, accept it as the final answer rather than losing the content
3329
3595
  if (currentIteration >= maxIterations) {
@@ -3439,6 +3705,10 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
3439
3705
  sameFormatErrorCount++;
3440
3706
  if (sameFormatErrorCount >= MAX_REPEATED_FORMAT_ERRORS) {
3441
3707
  const errorDesc = isWrapped ? 'wrapped tool format' : unrecognizedTool;
3708
+
3709
+ // Record circuit breaker error in telemetry
3710
+ this._recordErrorTelemetry('circuit_breaker', 'Format error limit exceeded', { formatErrorCount: sameFormatErrorCount, errorCategory }, currentIteration);
3711
+
3442
3712
  console.error(`[ERROR] Format error category '${errorCategory}' repeated ${sameFormatErrorCount} times. Breaking loop early to prevent infinite iteration.`);
3443
3713
  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.`;
3444
3714
  break;
@@ -3454,13 +3724,19 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
3454
3724
  }
3455
3725
  }
3456
3726
 
3727
+ // Record iteration end event
3728
+ this._recordIterationTelemetry('end', currentIteration, {
3729
+ 'iteration.completed': completionAttempted,
3730
+ 'iteration.message_count': currentMessages.length
3731
+ });
3732
+
3457
3733
  // Keep message history manageable
3458
3734
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
3459
3735
  const messagesBefore = currentMessages.length;
3460
3736
  const systemMsg = currentMessages[0]; // Keep system message
3461
3737
  const recentMessages = currentMessages.slice(-MAX_HISTORY_MESSAGES + 1);
3462
3738
  currentMessages = [systemMsg, ...recentMessages];
3463
-
3739
+
3464
3740
  if (this.debug) {
3465
3741
  console.log(`[DEBUG] Trimmed message history from ${messagesBefore} to ${currentMessages.length} messages`);
3466
3742
  }