@probelabs/probe 0.6.0-rc108 → 0.6.0-rc112

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.
@@ -5,11 +5,39 @@
5
5
 
6
6
  /**
7
7
  * Remove thinking tags and their content from XML string
8
+ * Handles both closed and unclosed thinking tags
8
9
  * @param {string} xmlString - The XML string to clean
9
10
  * @returns {string} - Cleaned XML string without thinking tags
10
11
  */
11
12
  export function removeThinkingTags(xmlString) {
12
- return xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
13
+ let result = xmlString;
14
+
15
+ // Remove all properly closed thinking tags first
16
+ result = result.replace(/<thinking>[\s\S]*?<\/thinking>/g, '');
17
+
18
+ // Handle unclosed thinking tags
19
+ // Find any remaining <thinking> tag (which means it's unclosed)
20
+ const thinkingIndex = result.indexOf('<thinking>');
21
+ if (thinkingIndex !== -1) {
22
+ // Check if there's a tool tag after the thinking tag
23
+ // We want to preserve tool tags even if they're after unclosed thinking
24
+ const afterThinking = result.substring(thinkingIndex + '<thinking>'.length);
25
+
26
+ // Look for any tool tags in the remaining content
27
+ const toolPattern = /<(search|query|extract|listFiles|searchFiles|implement|attempt_completion|attempt_complete)>/;
28
+ const toolMatch = afterThinking.match(toolPattern);
29
+
30
+ if (toolMatch) {
31
+ // Found a tool tag - remove thinking tag and its content up to the tool tag
32
+ const toolStart = thinkingIndex + '<thinking>'.length + toolMatch.index;
33
+ result = result.substring(0, thinkingIndex) + result.substring(toolStart);
34
+ } else {
35
+ // No tool tag found - remove everything from <thinking> onwards
36
+ result = result.substring(0, thinkingIndex);
37
+ }
38
+ }
39
+
40
+ return result.trim();
13
41
  }
14
42
 
15
43
  /**
@@ -29,6 +57,30 @@ export function extractThinkingContent(xmlString) {
29
57
  * @returns {Object|null} - Standardized attempt_completion result or null
30
58
  */
31
59
  export function checkAttemptCompleteRecovery(cleanedXmlString, validTools = []) {
60
+ // Check for <attempt_completion> with content (with or without closing tag)
61
+ // This handles: "<attempt_completion>content" or "<attempt_completion>content</attempt_completion>"
62
+ const attemptCompletionMatch = cleanedXmlString.match(/<attempt_completion>([\s\S]*?)(?:<\/attempt_completion>|$)/);
63
+ if (attemptCompletionMatch) {
64
+ const content = attemptCompletionMatch[1].trim();
65
+ const hasClosingTag = cleanedXmlString.includes('</attempt_completion>');
66
+
67
+ if (content) {
68
+ // If there's content after the tag, use it as the result
69
+ return {
70
+ toolName: 'attempt_completion',
71
+ params: { result: content }
72
+ };
73
+ }
74
+
75
+ // If the tag exists but is empty:
76
+ // - With closing tag (e.g., "<attempt_completion></attempt_completion>"): use empty string
77
+ // - Without closing tag (e.g., "<attempt_completion>"): use previous response
78
+ return {
79
+ toolName: 'attempt_completion',
80
+ params: { result: hasClosingTag ? '' : '__PREVIOUS_RESPONSE__' }
81
+ };
82
+ }
83
+
32
84
  // Enhanced recovery logic for attempt_complete shorthand
33
85
  const attemptCompletePatterns = [
34
86
  // Standard shorthand with optional whitespace
@@ -61,7 +113,7 @@ export function checkAttemptCompleteRecovery(cleanedXmlString, validTools = [])
61
113
  if (cleanedXmlString.includes('<attempt_complete') && !hasOtherToolTags(cleanedXmlString, validTools)) {
62
114
  // This handles malformed cases where attempt_complete appears but is broken
63
115
  return {
64
- toolName: 'attempt_completion',
116
+ toolName: 'attempt_completion',
65
117
  params: { result: '__PREVIOUS_RESPONSE__' }
66
118
  };
67
119
  }
@@ -364,47 +364,62 @@ export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
364
364
  for (const toolName of validTools) {
365
365
  const openTag = `<${toolName}>`;
366
366
  const closeTag = `</${toolName}>`;
367
-
367
+
368
368
  const openIndex = xmlString.indexOf(openTag);
369
369
  if (openIndex === -1) {
370
370
  continue; // Tool not found, try next tool
371
371
  }
372
-
373
- const closeIndex = xmlString.indexOf(closeTag, openIndex + openTag.length);
372
+
373
+ let closeIndex = xmlString.indexOf(closeTag, openIndex + openTag.length);
374
+ let hasClosingTag = closeIndex !== -1;
375
+
376
+ // If no closing tag found, use content until end of string
377
+ // This makes the parser more resilient to AI formatting errors
374
378
  if (closeIndex === -1) {
375
- continue; // No closing tag found, try next tool
379
+ closeIndex = xmlString.length;
376
380
  }
377
-
378
- // Extract the content between tags
381
+
382
+ // Extract the content between tags (or until end if no closing tag)
379
383
  const innerContent = xmlString.substring(
380
- openIndex + openTag.length,
384
+ openIndex + openTag.length,
381
385
  closeIndex
382
386
  );
383
-
387
+
384
388
  const params = {};
385
389
 
386
390
  // Parse parameters using string-based approach for better safety
387
391
  // Common parameter names to look for (can be extended as needed)
388
392
  // Note: includes both camelCase and underscore_case variants to handle inconsistencies
389
- const commonParams = ['query', 'file_path', 'line', 'end_line', 'path', 'recursive', 'includeHidden',
393
+ const commonParams = ['query', 'file_path', 'line', 'end_line', 'path', 'recursive', 'includeHidden',
390
394
  'max_results', 'maxResults', 'result', 'command', 'description', 'task', 'param', 'pattern',
391
395
  'allow_tests', 'exact', 'maxTokens', 'language', 'input_content',
392
396
  'context_lines', 'format', 'directory', 'autoCommits', 'files', 'targets'];
393
-
397
+
394
398
  for (const paramName of commonParams) {
395
399
  const paramOpenTag = `<${paramName}>`;
396
400
  const paramCloseTag = `</${paramName}>`;
397
-
401
+
398
402
  const paramOpenIndex = innerContent.indexOf(paramOpenTag);
399
403
  if (paramOpenIndex === -1) {
400
404
  continue; // Parameter not found
401
405
  }
402
-
403
- const paramCloseIndex = innerContent.indexOf(paramCloseTag, paramOpenIndex + paramOpenTag.length);
406
+
407
+ let paramCloseIndex = innerContent.indexOf(paramCloseTag, paramOpenIndex + paramOpenTag.length);
408
+
409
+ // Handle unclosed parameter tags - use content until next tag or end of content
404
410
  if (paramCloseIndex === -1) {
405
- continue; // No closing tag found
411
+ // Find the next opening tag after this parameter
412
+ let nextTagIndex = innerContent.length;
413
+ for (const nextParam of commonParams) {
414
+ const nextOpenTag = `<${nextParam}>`;
415
+ const nextIndex = innerContent.indexOf(nextOpenTag, paramOpenIndex + paramOpenTag.length);
416
+ if (nextIndex !== -1 && nextIndex < nextTagIndex) {
417
+ nextTagIndex = nextIndex;
418
+ }
419
+ }
420
+ paramCloseIndex = nextTagIndex;
406
421
  }
407
-
422
+
408
423
  let paramValue = innerContent.substring(
409
424
  paramOpenIndex + paramOpenTag.length,
410
425
  paramCloseIndex