@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/build/agent/ProbeAgent.js +43 -3
- package/build/agent/index.js +186 -46
- package/build/agent/schemaUtils.js +140 -10
- package/build/agent/tools.js +3 -2
- package/build/tools/common.js +62 -40
- package/cjs/agent/ProbeAgent.cjs +189 -49
- package/cjs/index.cjs +189 -49
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +43 -3
- package/src/agent/schemaUtils.js +140 -10
- package/src/agent/tools.js +3 -2
- package/src/tools/common.js +62 -40
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
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
2669
|
-
const
|
|
2670
|
-
const
|
|
2671
|
-
const
|
|
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 =
|
|
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
|
+
"<": "<",
|
|
2825
|
+
">": ">",
|
|
2826
|
+
"&": "&",
|
|
2827
|
+
""": '"',
|
|
2828
|
+
"'": "'",
|
|
2829
|
+
" ": " "
|
|
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
|
-
${
|
|
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
|
|
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
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -580,8 +580,15 @@ When troubleshooting:
|
|
|
580
580
|
throw new Error(finalResult);
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
-
// Parse tool call from response
|
|
584
|
-
const
|
|
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
|
-
|
|
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;
|
package/src/agent/schemaUtils.js
CHANGED
|
@@ -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
|
+
'<': '<',
|
|
11
|
+
'>': '>',
|
|
12
|
+
'&': '&',
|
|
13
|
+
'"': '"',
|
|
14
|
+
''': "'",
|
|
15
|
+
' ': ' '
|
|
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
|
-
${
|
|
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
|
|
810
|
+
// Some diagrams are invalid, first try HTML entity decoding auto-fix
|
|
755
811
|
if (debug) {
|
|
756
|
-
console.error('[DEBUG] Invalid Mermaid diagrams detected,
|
|
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
|
-
//
|
|
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 =
|
|
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
|
|
package/src/agent/tools.js
CHANGED
|
@@ -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) {
|