@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.
package/cjs/index.cjs CHANGED
@@ -1129,44 +1129,48 @@ var init_extract = __esm({
1129
1129
  });
1130
1130
 
1131
1131
  // src/tools/common.js
1132
- function parseXmlToolCall(xmlString) {
1133
- const toolMatch = xmlString.match(/<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/);
1134
- if (!toolMatch) {
1135
- return null;
1132
+ function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
1133
+ const globalRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1134
+ let match;
1135
+ while ((match = globalRegex.exec(xmlString)) !== null) {
1136
+ const toolName = match[1];
1137
+ if (!validTools.includes(toolName)) {
1138
+ continue;
1139
+ }
1140
+ const innerContent = match[2];
1141
+ const params = {};
1142
+ const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1143
+ let paramMatch;
1144
+ while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
1145
+ const paramName = paramMatch[1];
1146
+ let paramValue = paramMatch[2].trim();
1147
+ if (paramValue.toLowerCase() === "true") {
1148
+ paramValue = true;
1149
+ } else if (paramValue.toLowerCase() === "false") {
1150
+ paramValue = false;
1151
+ } else if (!isNaN(paramValue) && paramValue.trim() !== "") {
1152
+ const num = Number(paramValue);
1153
+ if (Number.isFinite(num)) {
1154
+ paramValue = num;
1155
+ }
1156
+ }
1157
+ params[paramName] = paramValue;
1158
+ }
1159
+ if (toolName === "attempt_completion") {
1160
+ const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
1161
+ if (resultMatch) {
1162
+ params["result"] = resultMatch[1].trim();
1163
+ }
1164
+ const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
1165
+ if (commandMatch) {
1166
+ params["command"] = commandMatch[1].trim();
1167
+ }
1168
+ }
1169
+ return { toolName, params };
1136
1170
  }
1137
- const toolName = toolMatch[1];
1138
- const innerContent = toolMatch[2];
1139
- const params = {};
1140
- const paramRegex = /<([a-zA-Z0-9_]+)>([\s\S]*?)<\/\1>/g;
1141
- let paramMatch;
1142
- while ((paramMatch = paramRegex.exec(innerContent)) !== null) {
1143
- const paramName = paramMatch[1];
1144
- let paramValue = paramMatch[2].trim();
1145
- if (paramValue.toLowerCase() === "true") {
1146
- paramValue = true;
1147
- } else if (paramValue.toLowerCase() === "false") {
1148
- paramValue = false;
1149
- } else if (!isNaN(paramValue) && paramValue.trim() !== "") {
1150
- const num = Number(paramValue);
1151
- if (Number.isFinite(num)) {
1152
- paramValue = num;
1153
- }
1154
- }
1155
- params[paramName] = paramValue;
1156
- }
1157
- if (toolName === "attempt_completion") {
1158
- const resultMatch = innerContent.match(/<result>([\s\S]*?)<\/result>/);
1159
- if (resultMatch) {
1160
- params["result"] = resultMatch[1].trim();
1161
- }
1162
- const commandMatch = innerContent.match(/<command>([\s\S]*?)<\/command>/);
1163
- if (commandMatch) {
1164
- params["command"] = commandMatch[1].trim();
1165
- }
1166
- }
1167
- return { toolName, params };
1171
+ return null;
1168
1172
  }
1169
- var import_zod, searchSchema, querySchema, extractSchema, attemptCompletionSchema, searchToolDefinition, queryToolDefinition, extractToolDefinition, attemptCompletionToolDefinition, searchDescription, queryDescription, extractDescription;
1173
+ var import_zod, searchSchema, querySchema, extractSchema, attemptCompletionSchema, searchToolDefinition, queryToolDefinition, extractToolDefinition, attemptCompletionToolDefinition, searchDescription, queryDescription, extractDescription, DEFAULT_VALID_TOOLS;
1170
1174
  var init_common = __esm({
1171
1175
  "src/tools/common.js"() {
1172
1176
  "use strict";
@@ -1325,6 +1329,15 @@ Usage Example:
1325
1329
  searchDescription = "Search code in the repository using Elasticsearch-like query syntax. Use this tool first for any code-related questions.";
1326
1330
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
1327
1331
  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.";
1332
+ DEFAULT_VALID_TOOLS = [
1333
+ "search",
1334
+ "query",
1335
+ "extract",
1336
+ "listFiles",
1337
+ "searchFiles",
1338
+ "implement",
1339
+ "attempt_completion"
1340
+ ];
1328
1341
  }
1329
1342
  });
1330
1343
 
@@ -2100,11 +2113,11 @@ function createTools(configOptions) {
2100
2113
  extractTool: extractTool(configOptions)
2101
2114
  };
2102
2115
  }
2103
- function parseXmlToolCallWithThinking(xmlString) {
2116
+ function parseXmlToolCallWithThinking(xmlString, validTools) {
2104
2117
  const thinkingMatch = xmlString.match(/<thinking>([\s\S]*?)<\/thinking>/);
2105
2118
  const thinkingContent = thinkingMatch ? thinkingMatch[1].trim() : null;
2106
2119
  const cleanedXmlString = xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, "").trim();
2107
- const parsedTool = parseXmlToolCall(cleanedXmlString);
2120
+ const parsedTool = parseXmlToolCall(cleanedXmlString, validTools);
2108
2121
  if (process.env.DEBUG === "1" && thinkingContent) {
2109
2122
  console.log(`[DEBUG] AI Thinking Process:
2110
2123
  ${thinkingContent}`);
@@ -2379,6 +2392,16 @@ var init_probeTool = __esm({
2379
2392
  });
2380
2393
 
2381
2394
  // src/agent/schemaUtils.js
2395
+ function decodeHtmlEntities(text) {
2396
+ if (!text || typeof text !== "string") {
2397
+ return text;
2398
+ }
2399
+ let decoded = text;
2400
+ for (const [entity, character] of Object.entries(HTML_ENTITY_MAP)) {
2401
+ decoded = decoded.replace(new RegExp(entity, "g"), character);
2402
+ }
2403
+ return decoded;
2404
+ }
2382
2405
  function cleanSchemaResponse(response) {
2383
2406
  if (!response || typeof response !== "string") {
2384
2407
  return response;
@@ -2655,9 +2678,65 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2655
2678
  };
2656
2679
  }
2657
2680
  if (debug) {
2658
- console.error("[DEBUG] Invalid Mermaid diagrams detected, starting specialized fixing agent...");
2681
+ console.error("[DEBUG] Invalid Mermaid diagrams detected, trying HTML entity auto-fix first...");
2659
2682
  }
2660
2683
  try {
2684
+ let fixedResponse = response;
2685
+ const fixingResults = [];
2686
+ let htmlEntityFixesApplied = false;
2687
+ const { diagrams } = extractMermaidFromMarkdown(response);
2688
+ const invalidDiagrams = validation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2689
+ for (const invalidDiagram of invalidDiagrams) {
2690
+ const originalContent = invalidDiagram.content;
2691
+ const decodedContent = decodeHtmlEntities(originalContent);
2692
+ if (decodedContent !== originalContent) {
2693
+ try {
2694
+ const quickValidation = await validateMermaidDiagram(decodedContent);
2695
+ if (quickValidation.isValid) {
2696
+ const originalDiagram = diagrams[invalidDiagram.originalIndex];
2697
+ const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : "";
2698
+ const newCodeBlock = `\`\`\`mermaid${attributesStr}
2699
+ ${decodedContent}
2700
+ \`\`\``;
2701
+ fixedResponse = fixedResponse.slice(0, originalDiagram.startIndex) + newCodeBlock + fixedResponse.slice(originalDiagram.endIndex);
2702
+ fixingResults.push({
2703
+ diagramIndex: invalidDiagram.originalIndex,
2704
+ wasFixed: true,
2705
+ originalContent,
2706
+ fixedContent: decodedContent,
2707
+ originalError: invalidDiagram.error,
2708
+ fixedWithHtmlDecoding: true
2709
+ });
2710
+ htmlEntityFixesApplied = true;
2711
+ if (debug) {
2712
+ console.error(`[DEBUG] Fixed diagram ${invalidDiagram.originalIndex + 1} with HTML entity decoding: ${invalidDiagram.error}`);
2713
+ }
2714
+ }
2715
+ } catch (error) {
2716
+ if (debug) {
2717
+ console.error(`[DEBUG] HTML entity decoding didn't fix diagram ${invalidDiagram.originalIndex + 1}: ${error.message}`);
2718
+ }
2719
+ }
2720
+ }
2721
+ }
2722
+ if (htmlEntityFixesApplied) {
2723
+ const revalidation = await validateMermaidResponse(fixedResponse);
2724
+ if (revalidation.isValid) {
2725
+ if (debug) {
2726
+ console.error("[DEBUG] All diagrams fixed with HTML entity decoding, no AI needed");
2727
+ }
2728
+ return {
2729
+ ...revalidation,
2730
+ wasFixed: true,
2731
+ originalResponse: response,
2732
+ fixedResponse,
2733
+ fixingResults
2734
+ };
2735
+ }
2736
+ }
2737
+ if (debug) {
2738
+ console.error("[DEBUG] Some diagrams still invalid after HTML entity decoding, starting AI fixing...");
2739
+ }
2661
2740
  const mermaidFixer = new MermaidFixingAgent({
2662
2741
  path: path6,
2663
2742
  provider,
@@ -2665,11 +2744,10 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2665
2744
  debug,
2666
2745
  tracer
2667
2746
  });
2668
- let fixedResponse = response;
2669
- const fixingResults = [];
2670
- const { diagrams } = extractMermaidFromMarkdown(response);
2671
- const invalidDiagrams = validation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2672
- for (const invalidDiagram of invalidDiagrams) {
2747
+ const { diagrams: updatedDiagrams } = extractMermaidFromMarkdown(fixedResponse);
2748
+ const updatedValidation = await validateMermaidResponse(fixedResponse);
2749
+ const stillInvalidDiagrams = updatedValidation.diagrams.map((result, index) => ({ ...result, originalIndex: index })).filter((result) => !result.isValid).reverse();
2750
+ for (const invalidDiagram of stillInvalidDiagrams) {
2673
2751
  try {
2674
2752
  const fixedContent = await mermaidFixer.fixMermaidDiagram(
2675
2753
  invalidDiagram.content,
@@ -2677,7 +2755,7 @@ async function validateAndFixMermaidResponse(response, options = {}) {
2677
2755
  { diagramType: invalidDiagram.diagramType }
2678
2756
  );
2679
2757
  if (fixedContent && fixedContent !== invalidDiagram.content) {
2680
- const originalDiagram = diagrams[invalidDiagram.originalIndex];
2758
+ const originalDiagram = updatedDiagrams[invalidDiagram.originalIndex];
2681
2759
  const attributesStr = originalDiagram.attributes ? ` ${originalDiagram.attributes}` : "";
2682
2760
  const newCodeBlock = `\`\`\`mermaid${attributesStr}
2683
2761
  ${fixedContent}
@@ -2738,10 +2816,18 @@ ${fixedContent}
2738
2816
  };
2739
2817
  }
2740
2818
  }
2741
- var MermaidFixingAgent;
2819
+ var HTML_ENTITY_MAP, MermaidFixingAgent;
2742
2820
  var init_schemaUtils = __esm({
2743
2821
  "src/agent/schemaUtils.js"() {
2744
2822
  "use strict";
2823
+ HTML_ENTITY_MAP = {
2824
+ "&lt;": "<",
2825
+ "&gt;": ">",
2826
+ "&amp;": "&",
2827
+ "&quot;": '"',
2828
+ "&#39;": "'",
2829
+ "&nbsp;": " "
2830
+ };
2745
2831
  MermaidFixingAgent = class {
2746
2832
  constructor(options = {}) {
2747
2833
  this.ProbeAgent = null;
@@ -2827,6 +2913,22 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
2827
2913
  * @returns {Promise<string>} - The corrected Mermaid diagram
2828
2914
  */
2829
2915
  async fixMermaidDiagram(diagramContent, originalErrors = [], diagramInfo = {}) {
2916
+ const decodedContent = decodeHtmlEntities(diagramContent);
2917
+ if (decodedContent !== diagramContent) {
2918
+ try {
2919
+ const quickValidation = await validateMermaidDiagram(decodedContent);
2920
+ if (quickValidation.isValid) {
2921
+ if (this.options.debug) {
2922
+ console.error("[DEBUG] Fixed Mermaid diagram with HTML entity decoding only");
2923
+ }
2924
+ return decodedContent;
2925
+ }
2926
+ } catch (error) {
2927
+ if (this.options.debug) {
2928
+ console.error("[DEBUG] HTML entity decoding didn't fully fix diagram, continuing with AI fixing");
2929
+ }
2930
+ }
2931
+ }
2830
2932
  await this.initializeAgent();
2831
2933
  const errorContext = originalErrors.length > 0 ? `
2832
2934
 
@@ -2834,11 +2936,12 @@ Detected errors: ${originalErrors.join(", ")}` : "";
2834
2936
  const diagramTypeHint = diagramInfo.diagramType ? `
2835
2937
 
2836
2938
  Expected diagram type: ${diagramInfo.diagramType}` : "";
2939
+ const contentToFix = decodedContent !== diagramContent ? decodedContent : diagramContent;
2837
2940
  const prompt = `Analyze and fix the following Mermaid diagram.${errorContext}${diagramTypeHint}
2838
2941
 
2839
2942
  Broken Mermaid diagram:
2840
2943
  \`\`\`mermaid
2841
- ${diagramContent}
2944
+ ${contentToFix}
2842
2945
  \`\`\`
2843
2946
 
2844
2947
  Provide only the corrected Mermaid diagram within a mermaid code block. Do not add any explanations or additional text.`;
@@ -3365,7 +3468,18 @@ You are working with a repository located at: ${searchDirectory}
3365
3468
  finalResult = `Error: Failed to get response from AI model during iteration ${currentIteration}. ${error.message}`;
3366
3469
  throw new Error(finalResult);
3367
3470
  }
3368
- const parsedTool = parseXmlToolCallWithThinking(assistantResponseContent);
3471
+ const validTools = [
3472
+ "search",
3473
+ "query",
3474
+ "extract",
3475
+ "listFiles",
3476
+ "searchFiles",
3477
+ "attempt_completion"
3478
+ ];
3479
+ if (this.allowEdit) {
3480
+ validTools.push("implement");
3481
+ }
3482
+ const parsedTool = parseXmlToolCallWithThinking(assistantResponseContent, validTools);
3369
3483
  if (parsedTool) {
3370
3484
  const { toolName, params } = parsedTool;
3371
3485
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
@@ -3484,7 +3598,7 @@ Error: Unknown tool '${toolName}'. Available tools: ${Object.keys(this.toolImple
3484
3598
  }
3485
3599
  }
3486
3600
  this.tokenCounter.updateHistory(this.history);
3487
- if (options.schema && !options._schemaFormatted) {
3601
+ if (options.schema && !options._schemaFormatted && !completionAttempted) {
3488
3602
  if (this.debug) {
3489
3603
  console.log("[DEBUG] Schema provided, applying automatic formatting...");
3490
3604
  }
@@ -3564,6 +3678,32 @@ Convert your previous response content into this JSON format now. Return nothing
3564
3678
  } catch (error) {
3565
3679
  console.error("[ERROR] Schema formatting failed:", error);
3566
3680
  }
3681
+ } else if (completionAttempted && options.schema) {
3682
+ try {
3683
+ finalResult = cleanSchemaResponse(finalResult);
3684
+ const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
3685
+ debug: this.debug,
3686
+ path: this.allowedFolders[0],
3687
+ provider: this.clientApiProvider,
3688
+ model: this.model
3689
+ });
3690
+ if (mermaidValidation.wasFixed) {
3691
+ finalResult = mermaidValidation.fixedResponse;
3692
+ if (this.debug) {
3693
+ console.log(`[DEBUG] Mermaid diagrams fixed in attempt_completion result`);
3694
+ }
3695
+ }
3696
+ if (isJsonSchema(options.schema)) {
3697
+ const validation = validateJsonResponse(finalResult);
3698
+ if (!validation.isValid && this.debug) {
3699
+ console.log(`[DEBUG] attempt_completion result JSON validation failed: ${validation.error}`);
3700
+ }
3701
+ }
3702
+ } catch (error) {
3703
+ if (this.debug) {
3704
+ console.log(`[DEBUG] attempt_completion result cleanup failed: ${error.message}`);
3705
+ }
3706
+ }
3567
3707
  }
3568
3708
  return finalResult;
3569
3709
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc67",
3
+ "version": "0.6.0-rc69",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -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;
@@ -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) {