@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.
@@ -580,8 +580,15 @@ When troubleshooting:
580
580
  throw new Error(finalResult);
581
581
  }
582
582
 
583
- // Parse tool call from response
584
- const parsedTool = parseXmlToolCallWithThinking(assistantResponseContent);
583
+ // Parse tool call from response with valid tools list
584
+ const validTools = [
585
+ 'search', 'query', 'extract', 'listFiles', 'searchFiles', 'attempt_completion'
586
+ ];
587
+ if (this.allowEdit) {
588
+ validTools.push('implement');
589
+ }
590
+
591
+ const parsedTool = parseXmlToolCallWithThinking(assistantResponseContent, validTools);
585
592
  if (parsedTool) {
586
593
  const { toolName, params } = parsedTool;
587
594
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
@@ -722,7 +729,8 @@ When troubleshooting:
722
729
  this.tokenCounter.updateHistory(this.history);
723
730
 
724
731
  // Schema handling - format response according to provided schema
725
- if (options.schema && !options._schemaFormatted) {
732
+ // Skip schema processing if result came from attempt_completion tool
733
+ if (options.schema && !options._schemaFormatted && !completionAttempted) {
726
734
  if (this.debug) {
727
735
  console.log('[DEBUG] Schema provided, applying automatic formatting...');
728
736
  }
@@ -822,6 +830,38 @@ Convert your previous response content into this JSON format now. Return nothing
822
830
  console.error('[ERROR] Schema formatting failed:', error);
823
831
  // Return the original result if schema formatting fails
824
832
  }
833
+ } else if (completionAttempted && options.schema) {
834
+ // For attempt_completion results with schema, still clean markdown if needed
835
+ try {
836
+ finalResult = cleanSchemaResponse(finalResult);
837
+
838
+ // Validate and fix Mermaid diagrams if present
839
+ const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
840
+ debug: this.debug,
841
+ path: this.allowedFolders[0],
842
+ provider: this.clientApiProvider,
843
+ model: this.model
844
+ });
845
+
846
+ if (mermaidValidation.wasFixed) {
847
+ finalResult = mermaidValidation.fixedResponse;
848
+ if (this.debug) {
849
+ console.log(`[DEBUG] Mermaid diagrams fixed in attempt_completion result`);
850
+ }
851
+ }
852
+
853
+ // Validate JSON if schema expects JSON
854
+ if (isJsonSchema(options.schema)) {
855
+ const validation = validateJsonResponse(finalResult);
856
+ if (!validation.isValid && this.debug) {
857
+ console.log(`[DEBUG] attempt_completion result JSON validation failed: ${validation.error}`);
858
+ }
859
+ }
860
+ } catch (error) {
861
+ if (this.debug) {
862
+ console.log(`[DEBUG] attempt_completion result cleanup failed: ${error.message}`);
863
+ }
864
+ }
825
865
  }
826
866
 
827
867
  return finalResult;
@@ -1409,44 +1409,48 @@ var init_extract = __esm({
1409
1409
 
1410
1410
  // src/tools/common.js
1411
1411
  import { z } from "zod";
1412
- function parseXmlToolCall(xmlString) {
1413
- const toolMatch = xmlString.match(/<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/);
1414
- if (!toolMatch) {
1415
- return null;
1416
- }
1417
- const toolName = toolMatch[1];
1418
- const innerContent = toolMatch[2];
1419
- const params = {};
1420
- const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1421
- let paramMatch;
1422
- while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
1423
- const paramName = paramMatch[1];
1424
- let paramValue = paramMatch[2].trim();
1425
- if (paramValue.toLowerCase() === "true") {
1426
- paramValue = true;
1427
- } else if (paramValue.toLowerCase() === "false") {
1428
- paramValue = false;
1429
- } else if (!isNaN(paramValue) && paramValue.trim() !== "") {
1430
- const num = Number(paramValue);
1431
- if (Number.isFinite(num)) {
1432
- paramValue = num;
1433
- }
1412
+ function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
1413
+ const globalRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1414
+ let match;
1415
+ while ((match = globalRegex.exec(xmlString)) !== null) {
1416
+ const toolName = match[1];
1417
+ if (!validTools.includes(toolName)) {
1418
+ continue;
1434
1419
  }
1435
- params[paramName] = paramValue;
1436
- }
1437
- if (toolName === "attempt_completion") {
1438
- const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
1439
- if (resultMatch) {
1440
- params["result"] = resultMatch[1].trim();
1420
+ const innerContent = match[2];
1421
+ const params = {};
1422
+ const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1423
+ let paramMatch;
1424
+ while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
1425
+ const paramName = paramMatch[1];
1426
+ let paramValue = paramMatch[2].trim();
1427
+ if (paramValue.toLowerCase() === "true") {
1428
+ paramValue = true;
1429
+ } else if (paramValue.toLowerCase() === "false") {
1430
+ paramValue = false;
1431
+ } else if (!isNaN(paramValue) && paramValue.trim() !== "") {
1432
+ const num = Number(paramValue);
1433
+ if (Number.isFinite(num)) {
1434
+ paramValue = num;
1435
+ }
1436
+ }
1437
+ params[paramName] = paramValue;
1441
1438
  }
1442
- const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
1443
- if (commandMatch) {
1444
- params["command"] = commandMatch[1].trim();
1439
+ if (toolName === "attempt_completion") {
1440
+ const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
1441
+ if (resultMatch) {
1442
+ params["result"] = resultMatch[1].trim();
1443
+ }
1444
+ const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
1445
+ if (commandMatch) {
1446
+ params["command"] = commandMatch[1].trim();
1447
+ }
1445
1448
  }
1449
+ return { toolName, params };
1446
1450
  }
1447
- return { toolName, params };
1451
+ return null;
1448
1452
  }
1449
- var searchSchema, querySchema, extractSchema, attemptCompletionSchema, searchToolDefinition, queryToolDefinition, extractToolDefinition, attemptCompletionToolDefinition, searchDescription, queryDescription, extractDescription;
1453
+ var searchSchema, querySchema, extractSchema, attemptCompletionSchema, searchToolDefinition, queryToolDefinition, extractToolDefinition, attemptCompletionToolDefinition, searchDescription, queryDescription, extractDescription, DEFAULT_VALID_TOOLS;
1450
1454
  var init_common = __esm({
1451
1455
  "src/tools/common.js"() {
1452
1456
  "use strict";
@@ -1604,6 +1608,15 @@ Usage Example:
1604
1608
  searchDescription = "Search code in the repository using Elasticsearch-like query syntax. Use this tool first for any code-related questions.";
1605
1609
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
1606
1610
  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.";
1611
+ DEFAULT_VALID_TOOLS = [
1612
+ "search",
1613
+ "query",
1614
+ "extract",
1615
+ "listFiles",
1616
+ "searchFiles",
1617
+ "implement",
1618
+ "attempt_completion"
1619
+ ];
1607
1620
  }
1608
1621
  });
1609
1622
 
@@ -2006,11 +2019,11 @@ function createTools(configOptions) {
2006
2019
  extractTool: extractTool(configOptions)
2007
2020
  };
2008
2021
  }
2009
- function parseXmlToolCallWithThinking(xmlString) {
2022
+ function parseXmlToolCallWithThinking(xmlString, validTools) {
2010
2023
  const thinkingMatch = xmlString.match(/<thinking>([\s\S]*?)<\/thinking>/);
2011
2024
  const thinkingContent = thinkingMatch ? thinkingMatch[1].trim() : null;
2012
2025
  const cleanedXmlString = xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, "").trim();
2013
- const parsedTool = parseXmlToolCall(cleanedXmlString);
2026
+ const parsedTool = parseXmlToolCall(cleanedXmlString, validTools);
2014
2027
  if (process.env.DEBUG === "1" && thinkingContent) {
2015
2028
  console.log(`[DEBUG] AI Thinking Process:
2016
2029
  ${thinkingContent}`);
@@ -2284,6 +2297,16 @@ var init_probeTool = __esm({
2284
2297
  });
2285
2298
 
2286
2299
  // src/agent/schemaUtils.js
2300
+ function decodeHtmlEntities(text) {
2301
+ if (!text || typeof text !== "string") {
2302
+ return text;
2303
+ }
2304
+ let decoded = text;
2305
+ for (const [entity, character] of Object.entries(HTML_ENTITY_MAP)) {
2306
+ decoded = decoded.replace(new RegExp(entity, "g"), character);
2307
+ }
2308
+ return decoded;
2309
+ }
2287
2310
  function cleanSchemaResponse(response) {
2288
2311
  if (!response || typeof response !== "string") {
2289
2312
  return response;
@@ -2594,9 +2617,65 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2594
2617
  };
2595
2618
  }
2596
2619
  if (debug) {
2597
- console.error("[DEBUG] Invalid Mermaid diagrams detected, starting specialized fixing agent...");
2620
+ console.error("[DEBUG] Invalid Mermaid diagrams detected, trying HTML entity auto-fix first...");
2598
2621
  }
2599
2622
  try {
2623
+ let fixedResponse = response;
2624
+ const fixingResults = [];
2625
+ let htmlEntityFixesApplied = false;
2626
+ const { diagrams } = extractMermaidFromMarkdown(response);
2627
+ const invalidDiagrams = validation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2628
+ for (const invalidDiagram of invalidDiagrams) {
2629
+ const originalContent = invalidDiagram.content;
2630
+ const decodedContent = decodeHtmlEntities(originalContent);
2631
+ if (decodedContent !== originalContent) {
2632
+ try {
2633
+ const quickValidation = await validateMermaidDiagram(decodedContent);
2634
+ if (quickValidation.isValid) {
2635
+ const originalDiagram = diagrams[invalidDiagram.originalIndex];
2636
+ const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : "";
2637
+ const newCodeBlock = `\`\`\`mermaid${attributesStr}
2638
+ ${decodedContent}
2639
+ \`\`\``;
2640
+ fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) + newCodeBlock + fixedResponse.slice(originalDiagram.endIndex);
2641
+ fixingResults.push({
2642
+ diagramIndex: invalidDiagram.originalIndex,
2643
+ wasFixed: true,
2644
+ originalContent,
2645
+ fixedContent: decodedContent,
2646
+ originalError: invalidDiagram.error,
2647
+ fixedWithHtmlDecoding: true
2648
+ });
2649
+ htmlEntityFixesApplied = true;
2650
+ if (debug) {
2651
+ console.error(`[DEBUG] Fixed diagram ${invalidDiagram.originalIndex + 1} with HTML entity decoding: ${invalidDiagram.error}`);
2652
+ }
2653
+ }
2654
+ } catch (error) {
2655
+ if (debug) {
2656
+ console.error(`[DEBUG] HTML entity decoding didn't fix diagram ${invalidDiagram.originalIndex + 1}: ${error.message}`);
2657
+ }
2658
+ }
2659
+ }
2660
+ }
2661
+ if (htmlEntityFixesApplied) {
2662
+ const revalidation = await validateMermaidResponse(fixedResponse);
2663
+ if (revalidation.isValid) {
2664
+ if (debug) {
2665
+ console.error("[DEBUG] All diagrams fixed with HTML entity decoding, no AI needed");
2666
+ }
2667
+ return {
2668
+ ...revalidation,
2669
+ wasFixed: true,
2670
+ originalResponse: response,
2671
+ fixedResponse,
2672
+ fixingResults
2673
+ };
2674
+ }
2675
+ }
2676
+ if (debug) {
2677
+ console.error("[DEBUG] Some diagrams still invalid after HTML entity decoding, starting AI fixing...");
2678
+ }
2600
2679
  const mermaidFixer = new MermaidFixingAgent({
2601
2680
  path: path6,
2602
2681
  provider,
@@ -2604,11 +2683,10 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2604
2683
  debug,
2605
2684
  tracer
2606
2685
  });
2607
- let fixedResponse = response;
2608
- const fixingResults = [];
2609
- const { diagrams } = extractMermaidFromMarkdown(response);
2610
- const invalidDiagrams = validation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2611
- for (const invalidDiagram of invalidDiagrams) {
2686
+ const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
2687
+ const updatedValidation = await validateMermaidResponse(fixedResponse);
2688
+ const stillInvalidDiagrams = updatedValidation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2689
+ for (const invalidDiagram of stillInvalidDiagrams) {
2612
2690
  try {
2613
2691
  const fixedContent = await mermaidFixer.fixMermaidDiagram(
2614
2692
  invalidDiagram.content,
@@ -2616,7 +2694,7 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2616
2694
  { diagramType: invalidDiagram.diagramType }
2617
2695
  );
2618
2696
  if (fixedContent && fixedContent !== invalidDiagram.content) {
2619
- const originalDiagram = diagrams[invalidDiagram.originalIndex];
2697
+ const originalDiagram = updatedDiagrams[invalidDiagram.originalIndex];
2620
2698
  const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : "";
2621
2699
  const newCodeBlock = `\`\`\`mermaid${attributesStr}
2622
2700
  ${fixedContent}
@@ -2677,10 +2755,18 @@ ${fixedContent}
2677
2755
  };
2678
2756
  }
2679
2757
  }
2680
- var MermaidFixingAgent;
2758
+ var HTML_ENTITY_MAP, MermaidFixingAgent;
2681
2759
  var init_schemaUtils = __esm({
2682
2760
  "src/agent/schemaUtils.js"() {
2683
2761
  "use strict";
2762
+ HTML_ENTITY_MAP = {
2763
+ "&lt;": "<",
2764
+ "&gt;": ">",
2765
+ "&amp;": "&",
2766
+ "&quot;": '"',
2767
+ "&#39;": "'",
2768
+ "&nbsp;": " "
2769
+ };
2684
2770
  MermaidFixingAgent = class {
2685
2771
  constructor(options = {}) {
2686
2772
  this.ProbeAgent = null;
@@ -2766,6 +2852,22 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
2766
2852
  * @returns {Promise<string>} - The corrected Mermaid diagram
2767
2853
  */
2768
2854
  async fixMermaidDiagram(diagramContent, originalErrors = [], diagramInfo = {}) {
2855
+ const decodedContent = decodeHtmlEntities(diagramContent);
2856
+ if (decodedContent !== diagramContent) {
2857
+ try {
2858
+ const quickValidation = await validateMermaidDiagram(decodedContent);
2859
+ if (quickValidation.isValid) {
2860
+ if (this.options.debug) {
2861
+ console.error("[DEBUG] Fixed Mermaid diagram with HTML entity decoding only");
2862
+ }
2863
+ return decodedContent;
2864
+ }
2865
+ } catch (error) {
2866
+ if (this.options.debug) {
2867
+ console.error("[DEBUG] HTML entity decoding didn't fully fix diagram, continuing with AI fixing");
2868
+ }
2869
+ }
2870
+ }
2769
2871
  await this.initializeAgent();
2770
2872
  const errorContext = originalErrors.length > 0 ? `
2771
2873
 
@@ -2773,11 +2875,12 @@ Detected errors: ${originalErrors.join(", ")}` : "";
2773
2875
  const diagramTypeHint = diagramInfo.diagramType ? `
2774
2876
 
2775
2877
  Expected diagram type: ${diagramInfo.diagramType}` : "";
2878
+ const contentToFix = decodedContent !== diagramContent ? decodedContent : diagramContent;
2776
2879
  const prompt = `Analyze and fix the following Mermaid diagram.${errorContext}${diagramTypeHint}
2777
2880
 
2778
2881
  Broken Mermaid diagram:
2779
2882
  \`\`\`mermaid
2780
- ${diagramContent}
2883
+ ${contentToFix}
2781
2884
  \`\`\`
2782
2885
 
2783
2886
  Provide only the corrected Mermaid diagram within a mermaid code block. Do not add any explanations or additional text.`;
@@ -3304,7 +3407,18 @@ You are working with a repository located at: ${searchDirectory}
3304
3407
  finalResult = `Error: Failed to get response from AI model during iteration ${currentIteration}. ${error.message}`;
3305
3408
  throw new Error(finalResult);
3306
3409
  }
3307
- const parsedTool = parseXmlToolCallWithThinking(assistantResponseContent);
3410
+ const validTools = [
3411
+ "search",
3412
+ "query",
3413
+ "extract",
3414
+ "listFiles",
3415
+ "searchFiles",
3416
+ "attempt_completion"
3417
+ ];
3418
+ if (this.allowEdit) {
3419
+ validTools.push("implement");
3420
+ }
3421
+ const parsedTool = parseXmlToolCallWithThinking(assistantResponseContent, validTools);
3308
3422
  if (parsedTool) {
3309
3423
  const { toolName, params } = parsedTool;
3310
3424
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
@@ -3423,7 +3537,7 @@ Error: Unknown tool '${toolName}'. Available tools: ${Object.keys(this.toolImple
3423
3537
  }
3424
3538
  }
3425
3539
  this.tokenCounter.updateHistory(this.history);
3426
- if (options.schema && !options._schemaFormatted) {
3540
+ if (options.schema && !options._schemaFormatted && !completionAttempted) {
3427
3541
  if (this.debug) {
3428
3542
  console.log("[DEBUG] Schema provided, applying automatic formatting...");
3429
3543
  }
@@ -3503,6 +3617,32 @@ Convert your previous response content into this JSON format now. Return nothing
3503
3617
  } catch (error) {
3504
3618
  console.error("[ERROR] Schema formatting failed:", error);
3505
3619
  }
3620
+ } else if (completionAttempted && options.schema) {
3621
+ try {
3622
+ finalResult = cleanSchemaResponse(finalResult);
3623
+ const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
3624
+ debug: this.debug,
3625
+ path: this.allowedFolders[0],
3626
+ provider: this.clientApiProvider,
3627
+ model: this.model
3628
+ });
3629
+ if (mermaidValidation.wasFixed) {
3630
+ finalResult = mermaidValidation.fixedResponse;
3631
+ if (this.debug) {
3632
+ console.log(`[DEBUG] Mermaid diagrams fixed in attempt_completion result`);
3633
+ }
3634
+ }
3635
+ if (isJsonSchema(options.schema)) {
3636
+ const validation = validateJsonResponse(finalResult);
3637
+ if (!validation.isValid && this.debug) {
3638
+ console.log(`[DEBUG] attempt_completion result JSON validation failed: ${validation.error}`);
3639
+ }
3640
+ }
3641
+ } catch (error) {
3642
+ if (this.debug) {
3643
+ console.log(`[DEBUG] attempt_completion result cleanup failed: ${error.message}`);
3644
+ }
3645
+ }
3506
3646
  }
3507
3647
  return finalResult;
3508
3648
  } catch (error) {
@@ -3,6 +3,37 @@
3
3
  * Supports JSON and Mermaid diagram validation
4
4
  */
5
5
 
6
+ /**
7
+ * HTML entity decoder map for common entities that might appear in mermaid diagrams
8
+ */
9
+ const HTML_ENTITY_MAP = {
10
+ '&lt;': '<',
11
+ '&gt;': '>',
12
+ '&amp;': '&',
13
+ '&quot;': '"',
14
+ '&#39;': "'",
15
+ '&nbsp;': ' '
16
+ };
17
+
18
+ /**
19
+ * Decode HTML entities in text without requiring external dependencies
20
+ * @param {string} text - Text that may contain HTML entities
21
+ * @returns {string} - Text with HTML entities decoded
22
+ */
23
+ export function decodeHtmlEntities(text) {
24
+ if (!text || typeof text !== 'string') {
25
+ return text;
26
+ }
27
+
28
+ let decoded = text;
29
+ for (const [entity, character] of Object.entries(HTML_ENTITY_MAP)) {
30
+ // Use global replacement to catch all instances
31
+ decoded = decoded.replace(new RegExp(entity, 'g'), character);
32
+ }
33
+
34
+ return decoded;
35
+ }
36
+
6
37
  /**
7
38
  * Clean AI response by extracting JSON content when response contains JSON
8
39
  * Only processes responses that contain JSON structures { or [
@@ -644,6 +675,28 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
644
675
  * @returns {Promise<string>} - The corrected Mermaid diagram
645
676
  */
646
677
  async fixMermaidDiagram(diagramContent, originalErrors = [], diagramInfo = {}) {
678
+ // First, try auto-fixing HTML entities without AI
679
+ const decodedContent = decodeHtmlEntities(diagramContent);
680
+
681
+ // If HTML entity decoding changed the content, validate it first
682
+ if (decodedContent !== diagramContent) {
683
+ try {
684
+ const quickValidation = await validateMermaidDiagram(decodedContent);
685
+ if (quickValidation.isValid) {
686
+ // HTML entity decoding fixed the issue, no need for AI
687
+ if (this.options.debug) {
688
+ console.error('[DEBUG] Fixed Mermaid diagram with HTML entity decoding only');
689
+ }
690
+ return decodedContent;
691
+ }
692
+ } catch (error) {
693
+ // If validation fails, continue with AI fixing using decoded content
694
+ if (this.options.debug) {
695
+ console.error('[DEBUG] HTML entity decoding didn\'t fully fix diagram, continuing with AI fixing');
696
+ }
697
+ }
698
+ }
699
+
647
700
  await this.initializeAgent();
648
701
 
649
702
  const errorContext = originalErrors.length > 0
@@ -654,11 +707,14 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
654
707
  ? `\n\nExpected diagram type: ${diagramInfo.diagramType}`
655
708
  : '';
656
709
 
710
+ // Use decoded content for AI fixing to ensure HTML entities are handled
711
+ const contentToFix = decodedContent !== diagramContent ? decodedContent : diagramContent;
712
+
657
713
  const prompt = `Analyze and fix the following Mermaid diagram.${errorContext}${diagramTypeHint}
658
714
 
659
715
  Broken Mermaid diagram:
660
716
  \`\`\`mermaid
661
- ${diagramContent}
717
+ ${contentToFix}
662
718
  \`\`\`
663
719
 
664
720
  Provide only the corrected Mermaid diagram within a mermaid code block. Do not add any explanations or additional text.`;
@@ -751,30 +807,104 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
751
807
  };
752
808
  }
753
809
 
754
- // Some diagrams are invalid, try to fix them with specialized agent
810
+ // Some diagrams are invalid, first try HTML entity decoding auto-fix
755
811
  if (debug) {
756
- console.error('[DEBUG] Invalid Mermaid diagrams detected, starting specialized fixing agent...');
812
+ console.error('[DEBUG] Invalid Mermaid diagrams detected, trying HTML entity auto-fix first...');
757
813
  }
758
814
 
759
815
  try {
760
- // Create specialized fixing agent
761
- const mermaidFixer = new MermaidFixingAgent({
762
- path, provider, model, debug, tracer
763
- });
764
-
765
816
  let fixedResponse = response;
766
817
  const fixingResults = [];
818
+ let htmlEntityFixesApplied = false;
767
819
 
768
820
  // Extract diagrams with position information for replacement
769
821
  const { diagrams } = extractMermaidFromMarkdown(response);
770
822
 
771
- // Fix invalid diagrams in reverse order to preserve indices
823
+ // First pass: Try HTML entity decoding on invalid diagrams
772
824
  const invalidDiagrams = validation.diagrams
773
825
  .map((result, index) => ({ ...result, originalIndex: index }))
774
826
  .filter(result => !result.isValid)
775
827
  .reverse();
776
828
 
777
829
  for (const invalidDiagram of invalidDiagrams) {
830
+ const originalContent = invalidDiagram.content;
831
+ const decodedContent = decodeHtmlEntities(originalContent);
832
+
833
+ if (decodedContent !== originalContent) {
834
+ // HTML entities were found and decoded, validate the result
835
+ try {
836
+ const quickValidation = await validateMermaidDiagram(decodedContent);
837
+ if (quickValidation.isValid) {
838
+ // HTML entity decoding fixed this diagram!
839
+ const originalDiagram = diagrams[invalidDiagram.originalIndex];
840
+ const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
841
+ const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${decodedContent}\n\`\`\``;
842
+
843
+ fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) +
844
+ newCodeBlock +
845
+ fixedResponse.slice(originalDiagram.endIndex);
846
+
847
+ fixingResults.push({
848
+ diagramIndex: invalidDiagram.originalIndex,
849
+ wasFixed: true,
850
+ originalContent: originalContent,
851
+ fixedContent: decodedContent,
852
+ originalError: invalidDiagram.error,
853
+ fixedWithHtmlDecoding: true
854
+ });
855
+
856
+ htmlEntityFixesApplied = true;
857
+
858
+ if (debug) {
859
+ console.error(`[DEBUG] Fixed diagram ${invalidDiagram.originalIndex + 1} with HTML entity decoding: ${invalidDiagram.error}`);
860
+ }
861
+ }
862
+ } catch (error) {
863
+ if (debug) {
864
+ console.error(`[DEBUG] HTML entity decoding didn't fix diagram ${invalidDiagram.originalIndex + 1}: ${error.message}`);
865
+ }
866
+ }
867
+ }
868
+ }
869
+
870
+ // If HTML entity fixes were applied, re-validate the entire response
871
+ if (htmlEntityFixesApplied) {
872
+ const revalidation = await validateMermaidResponse(fixedResponse);
873
+ if (revalidation.isValid) {
874
+ // All diagrams are now valid, return without AI fixing
875
+ if (debug) {
876
+ console.error('[DEBUG] All diagrams fixed with HTML entity decoding, no AI needed');
877
+ }
878
+ return {
879
+ ...revalidation,
880
+ wasFixed: true,
881
+ originalResponse: response,
882
+ fixedResponse: fixedResponse,
883
+ fixingResults: fixingResults
884
+ };
885
+ }
886
+ }
887
+
888
+ // Still have invalid diagrams after HTML entity decoding, proceed with AI fixing
889
+ if (debug) {
890
+ console.error('[DEBUG] Some diagrams still invalid after HTML entity decoding, starting AI fixing...');
891
+ }
892
+
893
+ // Create specialized fixing agent for remaining invalid diagrams
894
+ const mermaidFixer = new MermaidFixingAgent({
895
+ path, provider, model, debug, tracer
896
+ });
897
+
898
+ // Re-extract diagrams and re-validate after HTML entity fixes
899
+ const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
900
+ const updatedValidation = await validateMermaidResponse(fixedResponse);
901
+
902
+ const stillInvalidDiagrams = updatedValidation.diagrams
903
+ .map((result, index) => ({ ...result, originalIndex: index }))
904
+ .filter(result => !result.isValid)
905
+ .reverse();
906
+
907
+ for (const invalidDiagram of stillInvalidDiagrams) {
778
908
  try {
779
909
  const fixedContent = await mermaidFixer.fixMermaidDiagram(
780
910
  invalidDiagram.content,
@@ -784,7 +914,7 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
784
914
 
785
915
  if (fixedContent && fixedContent !== invalidDiagram.content) {
786
916
  // Replace the diagram in the response
787
- const originalDiagram = diagrams[invalidDiagram.originalIndex];
917
+ const originalDiagram = updatedDiagrams[invalidDiagram.originalIndex];
788
918
  const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : '';
789
919
  const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${fixedContent}\n\`\`\``;
790
920
 
@@ -124,9 +124,10 @@ User: Find all markdown files in the docs directory, but only at the top level.
124
124
  * This function removes any <thinking></thinking> tags from the input string
125
125
  * before passing it to the original parseXmlToolCall function
126
126
  * @param {string} xmlString - The XML string to parse
127
+ * @param {string[]} [validTools] - List of valid tool names to parse (optional)
127
128
  * @returns {Object|null} - The parsed tool call or null if no valid tool call found
128
129
  */
129
- export function parseXmlToolCallWithThinking(xmlString) {
130
+ export function parseXmlToolCallWithThinking(xmlString, validTools) {
130
131
  // Extract thinking content if present (for potential logging or analysis)
131
132
  const thinkingMatch = xmlString.match(/<thinking>([\s\S]*?)<\/thinking>/);
132
133
  const thinkingContent = thinkingMatch ? thinkingMatch[1].trim() : null;
@@ -135,7 +136,7 @@ export function parseXmlToolCallWithThinking(xmlString) {
135
136
  const cleanedXmlString = xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
136
137
 
137
138
  // Use the original parseXmlToolCall function to parse the cleaned XML string
138
- const parsedTool = parseXmlToolCall(cleanedXmlString);
139
+ const parsedTool = parseXmlToolCall(cleanedXmlString, validTools);
139
140
 
140
141
  // If debugging is enabled, log the thinking content
141
142
  if (process.env.DEBUG === '1' && thinkingContent) {