@probelabs/probe 0.6.0-rc67 → 0.6.0-rc69

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.
@@ -173,52 +173,74 @@ export const searchDescription = 'Search code in the repository using Elasticsea
173
173
  export const queryDescription = 'Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.';
174
174
  export const extractDescription = 'Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files.';
175
175
 
176
- // Simple XML parser helper
177
- export function parseXmlToolCall(xmlString) {
178
- const toolMatch = xmlString.match(/<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/);
179
- if (!toolMatch) {
180
- return null;
181
- }
176
+ // Valid tool names that should be parsed as tool calls
177
+ const DEFAULT_VALID_TOOLS = [
178
+ 'search',
179
+ 'query',
180
+ 'extract',
181
+ 'listFiles',
182
+ 'searchFiles',
183
+ 'implement',
184
+ 'attempt_completion'
185
+ ];
182
186
 
183
- const toolName = toolMatch[1];
184
- const innerContent = toolMatch[2];
185
- const params = {};
186
-
187
- const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
188
- let paramMatch;
189
- while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
190
- const paramName = paramMatch[1];
191
- let paramValue = paramMatch[2].trim();
192
-
193
- // Basic type inference (can be improved)
194
- if (paramValue.toLowerCase() === 'true') {
195
- paramValue = true;
196
- } else if (paramValue.toLowerCase() === 'false') {
197
- paramValue = false;
198
- } else if (!isNaN(paramValue) && paramValue.trim() !== '') {
199
- // Check if it's potentially a number (handle integers and floats)
200
- const num = Number(paramValue);
201
- if (Number.isFinite(num)) { // Use Number.isFinite to avoid Infinity/NaN
202
- paramValue = num;
203
- }
204
- // Keep as string if not a valid finite number
187
+ // Simple XML parser helper
188
+ export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
189
+ // Find all potential XML tag matches
190
+ const globalRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
191
+ let match;
192
+
193
+ // Look through all matches to find the first valid tool
194
+ while ((match = globalRegex.exec(xmlString)) !== null) {
195
+ const toolName = match[1];
196
+
197
+ // Only parse XML tags that correspond to valid tools
198
+ if (!validTools.includes(toolName)) {
199
+ continue; // Skip non-tool tags and look for the next match
205
200
  }
206
201
 
207
- params[paramName] = paramValue;
208
- }
202
+ const innerContent = match[2];
203
+ const params = {};
204
+
205
+ const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
206
+ let paramMatch;
207
+ while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
208
+ const paramName = paramMatch[1];
209
+ let paramValue = paramMatch[2].trim();
210
+
211
+ // Basic type inference (can be improved)
212
+ if (paramValue.toLowerCase() === 'true') {
213
+ paramValue = true;
214
+ } else if (paramValue.toLowerCase() === 'false') {
215
+ paramValue = false;
216
+ } else if (!isNaN(paramValue) && paramValue.trim() !== '') {
217
+ // Check if it's potentially a number (handle integers and floats)
218
+ const num = Number(paramValue);
219
+ if (Number.isFinite(num)) { // Use Number.isFinite to avoid Infinity/NaN
220
+ paramValue = num;
221
+ }
222
+ // Keep as string if not a valid finite number
223
+ }
209
224
 
210
- // Special handling for attempt_completion where result might contain nested XML/code
211
- if (toolName === 'attempt_completion') {
212
- const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
213
- if (resultMatch) {
214
- params['result'] = resultMatch[1].trim(); // Keep result content as is
225
+ params[paramName] = paramValue;
215
226
  }
216
- const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
217
- if (commandMatch) {
218
- params['command'] = commandMatch[1].trim();
227
+
228
+ // Special handling for attempt_completion where result might contain nested XML/code
229
+ if (toolName === 'attempt_completion') {
230
+ const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
231
+ if (resultMatch) {
232
+ params['result'] = resultMatch[1].trim(); // Keep result content as is
233
+ }
234
+ const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
235
+ if (commandMatch) {
236
+ params['command'] = commandMatch[1].trim();
237
+ }
219
238
  }
220
- }
221
239
 
240
+ // Return the first valid tool found
241
+ return { toolName, params };
242
+ }
222
243
 
223
- return { toolName, params };
244
+ // No valid tool found
245
+ return null;
224
246
  }
@@ -1429,44 +1429,48 @@ var init_extract = __esm({
1429
1429
  });
1430
1430
 
1431
1431
  // src/tools/common.js
1432
- function parseXmlToolCall(xmlString) {
1433
- const toolMatch = xmlString.match(/<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/);
1434
- if (!toolMatch) {
1435
- return null;
1432
+ function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
1433
+ const globalRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1434
+ let match;
1435
+ while ((match = globalRegex.exec(xmlString)) !== null) {
1436
+ const toolName = match[1];
1437
+ if (!validTools.includes(toolName)) {
1438
+ continue;
1439
+ }
1440
+ const innerContent = match[2];
1441
+ const params = {};
1442
+ const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1443
+ let paramMatch;
1444
+ while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
1445
+ const paramName = paramMatch[1];
1446
+ let paramValue = paramMatch[2].trim();
1447
+ if (paramValue.toLowerCase() === "true") {
1448
+ paramValue = true;
1449
+ } else if (paramValue.toLowerCase() === "false") {
1450
+ paramValue = false;
1451
+ } else if (!isNaN(paramValue) && paramValue.trim() !== "") {
1452
+ const num = Number(paramValue);
1453
+ if (Number.isFinite(num)) {
1454
+ paramValue = num;
1455
+ }
1456
+ }
1457
+ params[paramName] = paramValue;
1458
+ }
1459
+ if (toolName === "attempt_completion") {
1460
+ const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
1461
+ if (resultMatch) {
1462
+ params["result"] = resultMatch[1].trim();
1463
+ }
1464
+ const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
1465
+ if (commandMatch) {
1466
+ params["command"] = commandMatch[1].trim();
1467
+ }
1468
+ }
1469
+ return { toolName, params };
1436
1470
  }
1437
- const toolName = toolMatch[1];
1438
- const innerContent = toolMatch[2];
1439
- const params = {};
1440
- const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1441
- let paramMatch;
1442
- while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
1443
- const paramName = paramMatch[1];
1444
- let paramValue = paramMatch[2].trim();
1445
- if (paramValue.toLowerCase() === "true") {
1446
- paramValue = true;
1447
- } else if (paramValue.toLowerCase() === "false") {
1448
- paramValue = false;
1449
- } else if (!isNaN(paramValue) && paramValue.trim() !== "") {
1450
- const num = Number(paramValue);
1451
- if (Number.isFinite(num)) {
1452
- paramValue = num;
1453
- }
1454
- }
1455
- params[paramName] = paramValue;
1456
- }
1457
- if (toolName === "attempt_completion") {
1458
- const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
1459
- if (resultMatch) {
1460
- params["result"] = resultMatch[1].trim();
1461
- }
1462
- const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
1463
- if (commandMatch) {
1464
- params["command"] = commandMatch[1].trim();
1465
- }
1466
- }
1467
- return { toolName, params };
1471
+ return null;
1468
1472
  }
1469
- var import_zod, searchSchema, querySchema, extractSchema, attemptCompletionSchema, searchToolDefinition, queryToolDefinition, extractToolDefinition, attemptCompletionToolDefinition, searchDescription, queryDescription, extractDescription;
1473
+ var import_zod, searchSchema, querySchema, extractSchema, attemptCompletionSchema, searchToolDefinition, queryToolDefinition, extractToolDefinition, attemptCompletionToolDefinition, searchDescription, queryDescription, extractDescription, DEFAULT_VALID_TOOLS;
1470
1474
  var init_common = __esm({
1471
1475
  "src/tools/common.js"() {
1472
1476
  "use strict";
@@ -1625,6 +1629,15 @@ Usage Example:
1625
1629
  searchDescription = "Search code in the repository using Elasticsearch-like query syntax. Use this tool first for any code-related questions.";
1626
1630
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
1627
1631
  extractDescription = "Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files.";
1632
+ DEFAULT_VALID_TOOLS = [
1633
+ "search",
1634
+ "query",
1635
+ "extract",
1636
+ "listFiles",
1637
+ "searchFiles",
1638
+ "implement",
1639
+ "attempt_completion"
1640
+ ];
1628
1641
  }
1629
1642
  });
1630
1643
 
@@ -2026,11 +2039,11 @@ function createTools(configOptions) {
2026
2039
  extractTool: extractTool(configOptions)
2027
2040
  };
2028
2041
  }
2029
- function parseXmlToolCallWithThinking(xmlString) {
2042
+ function parseXmlToolCallWithThinking(xmlString, validTools) {
2030
2043
  const thinkingMatch = xmlString.match(/<thinking>([\s\S]*?)<\/thinking>/);
2031
2044
  const thinkingContent = thinkingMatch ? thinkingMatch[1].trim() : null;
2032
2045
  const cleanedXmlString = xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, "").trim();
2033
- const parsedTool = parseXmlToolCall(cleanedXmlString);
2046
+ const parsedTool = parseXmlToolCall(cleanedXmlString, validTools);
2034
2047
  if (process.env.DEBUG === "1" && thinkingContent) {
2035
2048
  console.log(`[DEBUG] AI Thinking Process:
2036
2049
  ${thinkingContent}`);
@@ -2305,6 +2318,16 @@ var init_probeTool = __esm({
2305
2318
  });
2306
2319
 
2307
2320
  // src/agent/schemaUtils.js
2321
+ function decodeHtmlEntities(text) {
2322
+ if (!text || typeof text !== "string") {
2323
+ return text;
2324
+ }
2325
+ let decoded = text;
2326
+ for (const [entity, character] of Object.entries(HTML_ENTITY_MAP)) {
2327
+ decoded = decoded.replace(new RegExp(entity, "g"), character);
2328
+ }
2329
+ return decoded;
2330
+ }
2308
2331
  function cleanSchemaResponse(response) {
2309
2332
  if (!response || typeof response !== "string") {
2310
2333
  return response;
@@ -2581,9 +2604,65 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2581
2604
  };
2582
2605
  }
2583
2606
  if (debug) {
2584
- console.error("[DEBUG] Invalid Mermaid diagrams detected, starting specialized fixing agent...");
2607
+ console.error("[DEBUG] Invalid Mermaid diagrams detected, trying HTML entity auto-fix first...");
2585
2608
  }
2586
2609
  try {
2610
+ let fixedResponse = response;
2611
+ const fixingResults = [];
2612
+ let htmlEntityFixesApplied = false;
2613
+ const { diagrams } = extractMermaidFromMarkdown(response);
2614
+ const invalidDiagrams = validation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2615
+ for (const invalidDiagram of invalidDiagrams) {
2616
+ const originalContent = invalidDiagram.content;
2617
+ const decodedContent = decodeHtmlEntities(originalContent);
2618
+ if (decodedContent !== originalContent) {
2619
+ try {
2620
+ const quickValidation = await validateMermaidDiagram(decodedContent);
2621
+ if (quickValidation.isValid) {
2622
+ const originalDiagram = diagrams[invalidDiagram.originalIndex];
2623
+ const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : "";
2624
+ const newCodeBlock = `\`\`\`mermaid${attributesStr}
2625
+ ${decodedContent}
2626
+ \`\`\``;
2627
+ fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) + newCodeBlock + fixedResponse.slice(originalDiagram.endIndex);
2628
+ fixingResults.push({
2629
+ diagramIndex: invalidDiagram.originalIndex,
2630
+ wasFixed: true,
2631
+ originalContent,
2632
+ fixedContent: decodedContent,
2633
+ originalError: invalidDiagram.error,
2634
+ fixedWithHtmlDecoding: true
2635
+ });
2636
+ htmlEntityFixesApplied = true;
2637
+ if (debug) {
2638
+ console.error(`[DEBUG] Fixed diagram ${invalidDiagram.originalIndex + 1} with HTML entity decoding: ${invalidDiagram.error}`);
2639
+ }
2640
+ }
2641
+ } catch (error) {
2642
+ if (debug) {
2643
+ console.error(`[DEBUG] HTML entity decoding didn't fix diagram ${invalidDiagram.originalIndex + 1}: ${error.message}`);
2644
+ }
2645
+ }
2646
+ }
2647
+ }
2648
+ if (htmlEntityFixesApplied) {
2649
+ const revalidation = await validateMermaidResponse(fixedResponse);
2650
+ if (revalidation.isValid) {
2651
+ if (debug) {
2652
+ console.error("[DEBUG] All diagrams fixed with HTML entity decoding, no AI needed");
2653
+ }
2654
+ return {
2655
+ ...revalidation,
2656
+ wasFixed: true,
2657
+ originalResponse: response,
2658
+ fixedResponse,
2659
+ fixingResults
2660
+ };
2661
+ }
2662
+ }
2663
+ if (debug) {
2664
+ console.error("[DEBUG] Some diagrams still invalid after HTML entity decoding, starting AI fixing...");
2665
+ }
2587
2666
  const mermaidFixer = new MermaidFixingAgent({
2588
2667
  path: path6,
2589
2668
  provider,
@@ -2591,11 +2670,10 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2591
2670
  debug,
2592
2671
  tracer
2593
2672
  });
2594
- let fixedResponse = response;
2595
- const fixingResults = [];
2596
- const { diagrams } = extractMermaidFromMarkdown(response);
2597
- const invalidDiagrams = validation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2598
- for (const invalidDiagram of invalidDiagrams) {
2673
+ const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
2674
+ const updatedValidation = await validateMermaidResponse(fixedResponse);
2675
+ const stillInvalidDiagrams = updatedValidation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2676
+ for (const invalidDiagram of stillInvalidDiagrams) {
2599
2677
  try {
2600
2678
  const fixedContent = await mermaidFixer.fixMermaidDiagram(
2601
2679
  invalidDiagram.content,
@@ -2603,7 +2681,7 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2603
2681
  { diagramType: invalidDiagram.diagramType }
2604
2682
  );
2605
2683
  if (fixedContent && fixedContent !== invalidDiagram.content) {
2606
- const originalDiagram = diagrams[invalidDiagram.originalIndex];
2684
+ const originalDiagram = updatedDiagrams[invalidDiagram.originalIndex];
2607
2685
  const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : "";
2608
2686
  const newCodeBlock = `\`\`\`mermaid${attributesStr}
2609
2687
  ${fixedContent}
@@ -2664,10 +2742,18 @@ ${fixedContent}
2664
2742
  };
2665
2743
  }
2666
2744
  }
2667
- var MermaidFixingAgent;
2745
+ var HTML_ENTITY_MAP, MermaidFixingAgent;
2668
2746
  var init_schemaUtils = __esm({
2669
2747
  "src/agent/schemaUtils.js"() {
2670
2748
  "use strict";
2749
+ HTML_ENTITY_MAP = {
2750
+ "&lt;": "<",
2751
+ "&gt;": ">",
2752
+ "&amp;": "&",
2753
+ "&quot;": '"',
2754
+ "&#39;": "'",
2755
+ "&nbsp;": " "
2756
+ };
2671
2757
  MermaidFixingAgent = class {
2672
2758
  constructor(options = {}) {
2673
2759
  this.ProbeAgent = null;
@@ -2753,6 +2839,22 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
2753
2839
  * @returns {Promise<string>} - The corrected Mermaid diagram
2754
2840
  */
2755
2841
  async fixMermaidDiagram(diagramContent, originalErrors = [], diagramInfo = {}) {
2842
+ const decodedContent = decodeHtmlEntities(diagramContent);
2843
+ if (decodedContent !== diagramContent) {
2844
+ try {
2845
+ const quickValidation = await validateMermaidDiagram(decodedContent);
2846
+ if (quickValidation.isValid) {
2847
+ if (this.options.debug) {
2848
+ console.error("[DEBUG] Fixed Mermaid diagram with HTML entity decoding only");
2849
+ }
2850
+ return decodedContent;
2851
+ }
2852
+ } catch (error) {
2853
+ if (this.options.debug) {
2854
+ console.error("[DEBUG] HTML entity decoding didn't fully fix diagram, continuing with AI fixing");
2855
+ }
2856
+ }
2857
+ }
2756
2858
  await this.initializeAgent();
2757
2859
  const errorContext = originalErrors.length > 0 ? `
2758
2860
 
@@ -2760,11 +2862,12 @@ Detected errors: ${originalErrors.join(", ")}` : "";
2760
2862
  const diagramTypeHint = diagramInfo.diagramType ? `
2761
2863
 
2762
2864
  Expected diagram type: ${diagramInfo.diagramType}` : "";
2865
+ const contentToFix = decodedContent !== diagramContent ? decodedContent : diagramContent;
2763
2866
  const prompt = `Analyze and fix the following Mermaid diagram.${errorContext}${diagramTypeHint}
2764
2867
 
2765
2868
  Broken Mermaid diagram:
2766
2869
  \`\`\`mermaid
2767
- ${diagramContent}
2870
+ ${contentToFix}
2768
2871
  \`\`\`
2769
2872
 
2770
2873
  Provide only the corrected Mermaid diagram within a mermaid code block. Do not add any explanations or additional text.`;
@@ -3291,7 +3394,18 @@ You are working with a repository located at: ${searchDirectory}
3291
3394
  finalResult = `Error: Failed to get response from AI model during iteration ${currentIteration}. ${error.message}`;
3292
3395
  throw new Error(finalResult);
3293
3396
  }
3294
- const parsedTool = parseXmlToolCallWithThinking(assistantResponseContent);
3397
+ const validTools = [
3398
+ "search",
3399
+ "query",
3400
+ "extract",
3401
+ "listFiles",
3402
+ "searchFiles",
3403
+ "attempt_completion"
3404
+ ];
3405
+ if (this.allowEdit) {
3406
+ validTools.push("implement");
3407
+ }
3408
+ const parsedTool = parseXmlToolCallWithThinking(assistantResponseContent, validTools);
3295
3409
  if (parsedTool) {
3296
3410
  const { toolName, params } = parsedTool;
3297
3411
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
@@ -3410,7 +3524,7 @@ Error: Unknown tool '${toolName}'. Available tools: ${Object.keys(this.toolImple
3410
3524
  }
3411
3525
  }
3412
3526
  this.tokenCounter.updateHistory(this.history);
3413
- if (options.schema && !options._schemaFormatted) {
3527
+ if (options.schema && !options._schemaFormatted && !completionAttempted) {
3414
3528
  if (this.debug) {
3415
3529
  console.log("[DEBUG] Schema provided, applying automatic formatting...");
3416
3530
  }
@@ -3490,6 +3604,32 @@ Convert your previous response content into this JSON format now. Return nothing
3490
3604
  } catch (error) {
3491
3605
  console.error("[ERROR] Schema formatting failed:", error);
3492
3606
  }
3607
+ } else if (completionAttempted && options.schema) {
3608
+ try {
3609
+ finalResult = cleanSchemaResponse(finalResult);
3610
+ const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
3611
+ debug: this.debug,
3612
+ path: this.allowedFolders[0],
3613
+ provider: this.clientApiProvider,
3614
+ model: this.model
3615
+ });
3616
+ if (mermaidValidation.wasFixed) {
3617
+ finalResult = mermaidValidation.fixedResponse;
3618
+ if (this.debug) {
3619
+ console.log(`[DEBUG] Mermaid diagrams fixed in attempt_completion result`);
3620
+ }
3621
+ }
3622
+ if (isJsonSchema(options.schema)) {
3623
+ const validation = validateJsonResponse(finalResult);
3624
+ if (!validation.isValid && this.debug) {
3625
+ console.log(`[DEBUG] attempt_completion result JSON validation failed: ${validation.error}`);
3626
+ }
3627
+ }
3628
+ } catch (error) {
3629
+ if (this.debug) {
3630
+ console.log(`[DEBUG] attempt_completion result cleanup failed: ${error.message}`);
3631
+ }
3632
+ }
3493
3633
  }
3494
3634
  return finalResult;
3495
3635
  } catch (error) {