@probelabs/probe 0.6.0-rc164 → 0.6.0-rc165
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 +15 -6
- package/build/agent/index.js +182 -19
- package/build/agent/schemaUtils.js +253 -15
- package/cjs/agent/ProbeAgent.cjs +3754 -4610
- package/cjs/index.cjs +3754 -4610
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +15 -6
- package/src/agent/schemaUtils.js +253 -15
|
@@ -1706,16 +1706,25 @@ When troubleshooting:
|
|
|
1706
1706
|
}
|
|
1707
1707
|
|
|
1708
1708
|
// Parse tool call from response with valid tools list
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
if (this.
|
|
1709
|
+
// Build validTools based on allowedTools configuration (same pattern as getSystemMessage)
|
|
1710
|
+
const validTools = [];
|
|
1711
|
+
if (this.allowedTools.isEnabled('search')) validTools.push('search');
|
|
1712
|
+
if (this.allowedTools.isEnabled('query')) validTools.push('query');
|
|
1713
|
+
if (this.allowedTools.isEnabled('extract')) validTools.push('extract');
|
|
1714
|
+
if (this.allowedTools.isEnabled('listFiles')) validTools.push('listFiles');
|
|
1715
|
+
if (this.allowedTools.isEnabled('searchFiles')) validTools.push('searchFiles');
|
|
1716
|
+
if (this.allowedTools.isEnabled('attempt_completion')) validTools.push('attempt_completion');
|
|
1717
|
+
|
|
1718
|
+
// Edit tools (require both allowEdit flag AND allowedTools permission)
|
|
1719
|
+
if (this.allowEdit && this.allowedTools.isEnabled('implement')) {
|
|
1713
1720
|
validTools.push('implement', 'edit', 'create');
|
|
1714
1721
|
}
|
|
1715
|
-
|
|
1722
|
+
// Bash tool (require both enableBash flag AND allowedTools permission)
|
|
1723
|
+
if (this.enableBash && this.allowedTools.isEnabled('bash')) {
|
|
1716
1724
|
validTools.push('bash');
|
|
1717
1725
|
}
|
|
1718
|
-
|
|
1726
|
+
// Delegate tool (require both enableDelegate flag AND allowedTools permission)
|
|
1727
|
+
if (this.enableDelegate && this.allowedTools.isEnabled('delegate')) {
|
|
1719
1728
|
validTools.push('delegate');
|
|
1720
1729
|
}
|
|
1721
1730
|
|
package/build/agent/index.js
CHANGED
|
@@ -54263,6 +54263,7 @@ __export(schemaUtils_exports, {
|
|
|
54263
54263
|
createMermaidCorrectionPrompt: () => createMermaidCorrectionPrompt,
|
|
54264
54264
|
createSchemaDefinitionCorrectionPrompt: () => createSchemaDefinitionCorrectionPrompt,
|
|
54265
54265
|
decodeHtmlEntities: () => decodeHtmlEntities,
|
|
54266
|
+
extractMermaidFromJson: () => extractMermaidFromJson,
|
|
54266
54267
|
extractMermaidFromMarkdown: () => extractMermaidFromMarkdown,
|
|
54267
54268
|
generateExampleFromSchema: () => generateExampleFromSchema,
|
|
54268
54269
|
generateSchemaInstructions: () => generateSchemaInstructions,
|
|
@@ -54270,6 +54271,7 @@ __export(schemaUtils_exports, {
|
|
|
54270
54271
|
isJsonSchemaDefinition: () => isJsonSchemaDefinition,
|
|
54271
54272
|
isMermaidSchema: () => isMermaidSchema,
|
|
54272
54273
|
processSchemaResponse: () => processSchemaResponse,
|
|
54274
|
+
replaceMermaidDiagramsInJson: () => replaceMermaidDiagramsInJson,
|
|
54273
54275
|
replaceMermaidDiagramsInMarkdown: () => replaceMermaidDiagramsInMarkdown,
|
|
54274
54276
|
tryMaidAutoFix: () => tryMaidAutoFix,
|
|
54275
54277
|
validateAndFixMermaidResponse: () => validateAndFixMermaidResponse,
|
|
@@ -54373,6 +54375,49 @@ function decodeHtmlEntities(text) {
|
|
|
54373
54375
|
}
|
|
54374
54376
|
return decoded;
|
|
54375
54377
|
}
|
|
54378
|
+
function normalizeJsonQuotes(str) {
|
|
54379
|
+
if (!str || typeof str !== "string") {
|
|
54380
|
+
return str;
|
|
54381
|
+
}
|
|
54382
|
+
if (!str.includes("'")) {
|
|
54383
|
+
return str;
|
|
54384
|
+
}
|
|
54385
|
+
let result = "";
|
|
54386
|
+
let inDoubleQuote = false;
|
|
54387
|
+
let inSingleQuote = false;
|
|
54388
|
+
let escaped = false;
|
|
54389
|
+
for (let i = 0; i < str.length; i++) {
|
|
54390
|
+
const char = str[i];
|
|
54391
|
+
const prevChar = i > 0 ? str[i - 1] : "";
|
|
54392
|
+
if (escaped) {
|
|
54393
|
+
result += char;
|
|
54394
|
+
escaped = false;
|
|
54395
|
+
continue;
|
|
54396
|
+
}
|
|
54397
|
+
if (char === "\\") {
|
|
54398
|
+
escaped = true;
|
|
54399
|
+
result += char;
|
|
54400
|
+
continue;
|
|
54401
|
+
}
|
|
54402
|
+
if (char === '"' && !inSingleQuote) {
|
|
54403
|
+
inDoubleQuote = !inDoubleQuote;
|
|
54404
|
+
result += char;
|
|
54405
|
+
continue;
|
|
54406
|
+
}
|
|
54407
|
+
if (char === "'" && !inDoubleQuote) {
|
|
54408
|
+
if (inSingleQuote) {
|
|
54409
|
+
result += '"';
|
|
54410
|
+
inSingleQuote = false;
|
|
54411
|
+
} else {
|
|
54412
|
+
result += '"';
|
|
54413
|
+
inSingleQuote = true;
|
|
54414
|
+
}
|
|
54415
|
+
continue;
|
|
54416
|
+
}
|
|
54417
|
+
result += char;
|
|
54418
|
+
}
|
|
54419
|
+
return result;
|
|
54420
|
+
}
|
|
54376
54421
|
function cleanSchemaResponse(response) {
|
|
54377
54422
|
if (!response || typeof response !== "string") {
|
|
54378
54423
|
return response;
|
|
@@ -54380,23 +54425,27 @@ function cleanSchemaResponse(response) {
|
|
|
54380
54425
|
const trimmed = response.trim();
|
|
54381
54426
|
const jsonBlockMatch = trimmed.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
54382
54427
|
if (jsonBlockMatch) {
|
|
54383
|
-
return jsonBlockMatch[1].trim();
|
|
54428
|
+
return normalizeJsonQuotes(jsonBlockMatch[1].trim());
|
|
54384
54429
|
}
|
|
54385
54430
|
const anyBlockMatch = trimmed.match(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/);
|
|
54386
54431
|
if (anyBlockMatch) {
|
|
54387
|
-
return anyBlockMatch[1].trim();
|
|
54432
|
+
return normalizeJsonQuotes(anyBlockMatch[1].trim());
|
|
54388
54433
|
}
|
|
54389
54434
|
const codeBlockPatterns = [
|
|
54390
54435
|
/```json\s*\n?([{\[][\s\S]*?[}\]])\s*\n?```/,
|
|
54391
|
-
/```\s*\n?([{\[][\s\S]*?[}\]])\s*\n
|
|
54392
|
-
/`([{\[][\s\S]*?[}\]])`/
|
|
54436
|
+
/```\s*\n?([{\[][\s\S]*?[}\]])\s*\n?```/
|
|
54393
54437
|
];
|
|
54394
54438
|
for (const pattern of codeBlockPatterns) {
|
|
54395
54439
|
const match2 = trimmed.match(pattern);
|
|
54396
54440
|
if (match2) {
|
|
54397
|
-
return match2[1].trim();
|
|
54441
|
+
return normalizeJsonQuotes(match2[1].trim());
|
|
54398
54442
|
}
|
|
54399
54443
|
}
|
|
54444
|
+
const singleBacktickPattern = /^`([{\[][\s\S]*?[}\]])`$/;
|
|
54445
|
+
const singleBacktickMatch = trimmed.match(singleBacktickPattern);
|
|
54446
|
+
if (singleBacktickMatch) {
|
|
54447
|
+
return normalizeJsonQuotes(singleBacktickMatch[1].trim());
|
|
54448
|
+
}
|
|
54400
54449
|
const codeBlockStartPattern = /```(?:json)?\s*\n?\s*([{\[])/;
|
|
54401
54450
|
const codeBlockMatch = trimmed.match(codeBlockStartPattern);
|
|
54402
54451
|
if (codeBlockMatch) {
|
|
@@ -54415,7 +54464,7 @@ function cleanSchemaResponse(response) {
|
|
|
54415
54464
|
endIndex++;
|
|
54416
54465
|
}
|
|
54417
54466
|
if (bracketCount === 0) {
|
|
54418
|
-
return trimmed.substring(startIndex, endIndex);
|
|
54467
|
+
return normalizeJsonQuotes(trimmed.substring(startIndex, endIndex));
|
|
54419
54468
|
}
|
|
54420
54469
|
}
|
|
54421
54470
|
let cleaned = trimmed;
|
|
@@ -54427,7 +54476,7 @@ function cleanSchemaResponse(response) {
|
|
|
54427
54476
|
const isJsonObject = firstChar === "{" && lastChar === "}";
|
|
54428
54477
|
const isJsonArray = firstChar === "[" && lastChar === "]";
|
|
54429
54478
|
if (isJsonObject || isJsonArray) {
|
|
54430
|
-
return cleaned;
|
|
54479
|
+
return normalizeJsonQuotes(cleaned);
|
|
54431
54480
|
}
|
|
54432
54481
|
return response;
|
|
54433
54482
|
}
|
|
@@ -54837,10 +54886,66 @@ function isMermaidSchema(schema) {
|
|
|
54837
54886
|
];
|
|
54838
54887
|
return mermaidIndicators.some((indicator) => indicator);
|
|
54839
54888
|
}
|
|
54889
|
+
function extractMermaidFromJson(response) {
|
|
54890
|
+
if (!response || typeof response !== "string") {
|
|
54891
|
+
return { diagrams: [], jsonPaths: [], parsedJson: null };
|
|
54892
|
+
}
|
|
54893
|
+
let jsonContent = response.trim();
|
|
54894
|
+
const jsonBlockMatch = jsonContent.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
54895
|
+
if (jsonBlockMatch) {
|
|
54896
|
+
jsonContent = jsonBlockMatch[1].trim();
|
|
54897
|
+
} else {
|
|
54898
|
+
const anyBlockMatch = jsonContent.match(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/);
|
|
54899
|
+
if (anyBlockMatch) {
|
|
54900
|
+
jsonContent = anyBlockMatch[1].trim();
|
|
54901
|
+
}
|
|
54902
|
+
}
|
|
54903
|
+
let parsedJson;
|
|
54904
|
+
try {
|
|
54905
|
+
parsedJson = JSON.parse(jsonContent);
|
|
54906
|
+
} catch (e) {
|
|
54907
|
+
return { diagrams: [], jsonPaths: [], parsedJson: null };
|
|
54908
|
+
}
|
|
54909
|
+
const diagrams = [];
|
|
54910
|
+
const jsonPaths = [];
|
|
54911
|
+
function searchObject(obj, path7 = []) {
|
|
54912
|
+
if (typeof obj === "string") {
|
|
54913
|
+
const mermaidPattern = /```mermaid([^\n`]*?)(?:\n|\\n)([\s\S]*?)```/gi;
|
|
54914
|
+
let match2;
|
|
54915
|
+
while ((match2 = mermaidPattern.exec(obj)) !== null) {
|
|
54916
|
+
const attributes = match2[1] ? match2[1].trim() : "";
|
|
54917
|
+
const content = match2[2].replace(/\\n/g, "\n");
|
|
54918
|
+
diagrams.push({
|
|
54919
|
+
content,
|
|
54920
|
+
fullMatch: match2[0],
|
|
54921
|
+
startIndex: match2.index,
|
|
54922
|
+
endIndex: match2.index + match2[0].length,
|
|
54923
|
+
attributes,
|
|
54924
|
+
isInJson: true,
|
|
54925
|
+
jsonPath: path7.join(".")
|
|
54926
|
+
});
|
|
54927
|
+
jsonPaths.push(path7.join("."));
|
|
54928
|
+
}
|
|
54929
|
+
} else if (Array.isArray(obj)) {
|
|
54930
|
+
obj.forEach((item, index) => searchObject(item, [...path7, `[${index}]`]));
|
|
54931
|
+
} else if (obj && typeof obj === "object") {
|
|
54932
|
+
Object.entries(obj).forEach(([key, value]) => searchObject(value, [...path7, key]));
|
|
54933
|
+
}
|
|
54934
|
+
}
|
|
54935
|
+
searchObject(parsedJson);
|
|
54936
|
+
return { diagrams, jsonPaths, parsedJson };
|
|
54937
|
+
}
|
|
54840
54938
|
function extractMermaidFromMarkdown(response) {
|
|
54841
54939
|
if (!response || typeof response !== "string") {
|
|
54842
54940
|
return { diagrams: [], cleanedResponse: response };
|
|
54843
54941
|
}
|
|
54942
|
+
const trimmed = response.trim();
|
|
54943
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.includes("```json")) {
|
|
54944
|
+
const jsonResult = extractMermaidFromJson(response);
|
|
54945
|
+
if (jsonResult.diagrams.length > 0) {
|
|
54946
|
+
return { diagrams: jsonResult.diagrams, cleanedResponse: response };
|
|
54947
|
+
}
|
|
54948
|
+
}
|
|
54844
54949
|
const mermaidBlockRegex = /```mermaid([^\n]*)\n([\s\S]*?)```/gi;
|
|
54845
54950
|
const diagrams = [];
|
|
54846
54951
|
let match2;
|
|
@@ -54852,11 +54957,66 @@ function extractMermaidFromMarkdown(response) {
|
|
|
54852
54957
|
fullMatch: match2[0],
|
|
54853
54958
|
startIndex: match2.index,
|
|
54854
54959
|
endIndex: match2.index + match2[0].length,
|
|
54855
|
-
attributes
|
|
54960
|
+
attributes,
|
|
54961
|
+
isInJson: false
|
|
54856
54962
|
});
|
|
54857
54963
|
}
|
|
54858
54964
|
return { diagrams, cleanedResponse: response };
|
|
54859
54965
|
}
|
|
54966
|
+
function replaceMermaidDiagramsInJson(originalResponse, correctedDiagrams) {
|
|
54967
|
+
if (!originalResponse || typeof originalResponse !== "string") {
|
|
54968
|
+
return originalResponse;
|
|
54969
|
+
}
|
|
54970
|
+
if (!correctedDiagrams || correctedDiagrams.length === 0) {
|
|
54971
|
+
return originalResponse;
|
|
54972
|
+
}
|
|
54973
|
+
const jsonResult = extractMermaidFromJson(originalResponse);
|
|
54974
|
+
if (!jsonResult.parsedJson) {
|
|
54975
|
+
return originalResponse;
|
|
54976
|
+
}
|
|
54977
|
+
let modifiedJson = jsonResult.parsedJson;
|
|
54978
|
+
for (const diagram of correctedDiagrams) {
|
|
54979
|
+
if (!diagram.jsonPath || !diagram.isInJson) {
|
|
54980
|
+
continue;
|
|
54981
|
+
}
|
|
54982
|
+
const pathParts = diagram.jsonPath.split(".").filter((p) => p);
|
|
54983
|
+
let current = modifiedJson;
|
|
54984
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
54985
|
+
const part = pathParts[i];
|
|
54986
|
+
if (part.startsWith("[") && part.endsWith("]")) {
|
|
54987
|
+
const index = parseInt(part.slice(1, -1), 10);
|
|
54988
|
+
current = current[index];
|
|
54989
|
+
} else {
|
|
54990
|
+
current = current[part];
|
|
54991
|
+
}
|
|
54992
|
+
}
|
|
54993
|
+
const lastPart = pathParts[pathParts.length - 1];
|
|
54994
|
+
const attributesStr = diagram.attributes ? ` ${diagram.attributes}` : "";
|
|
54995
|
+
const newCodeBlock = `\`\`\`mermaid${attributesStr}
|
|
54996
|
+
${diagram.content}
|
|
54997
|
+
\`\`\``;
|
|
54998
|
+
if (lastPart.startsWith("[") && lastPart.endsWith("]")) {
|
|
54999
|
+
const index = parseInt(lastPart.slice(1, -1), 10);
|
|
55000
|
+
const originalString = current[index];
|
|
55001
|
+
current[index] = originalString.replace(diagram.fullMatch, newCodeBlock);
|
|
55002
|
+
} else {
|
|
55003
|
+
const originalString = current[lastPart];
|
|
55004
|
+
current[lastPart] = originalString.replace(diagram.fullMatch, newCodeBlock);
|
|
55005
|
+
}
|
|
55006
|
+
}
|
|
55007
|
+
const modifiedJsonString = JSON.stringify(modifiedJson, null, 2);
|
|
55008
|
+
const trimmed = originalResponse.trim();
|
|
55009
|
+
if (trimmed.match(/```json\s*\n([\s\S]*?)\n```/)) {
|
|
55010
|
+
return originalResponse.replace(/```json\s*\n([\s\S]*?)\n```/, `\`\`\`json
|
|
55011
|
+
${modifiedJsonString}
|
|
55012
|
+
\`\`\``);
|
|
55013
|
+
} else if (trimmed.match(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/)) {
|
|
55014
|
+
return originalResponse.replace(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/, `\`\`\`
|
|
55015
|
+
${modifiedJsonString}
|
|
55016
|
+
\`\`\``);
|
|
55017
|
+
}
|
|
55018
|
+
return modifiedJsonString;
|
|
55019
|
+
}
|
|
54860
55020
|
function replaceMermaidDiagramsInMarkdown(originalResponse, correctedDiagrams) {
|
|
54861
55021
|
if (!originalResponse || typeof originalResponse !== "string") {
|
|
54862
55022
|
return originalResponse;
|
|
@@ -54864,6 +55024,10 @@ function replaceMermaidDiagramsInMarkdown(originalResponse, correctedDiagrams) {
|
|
|
54864
55024
|
if (!correctedDiagrams || correctedDiagrams.length === 0) {
|
|
54865
55025
|
return originalResponse;
|
|
54866
55026
|
}
|
|
55027
|
+
const hasJsonDiagrams = correctedDiagrams.some((d) => d.isInJson);
|
|
55028
|
+
if (hasJsonDiagrams) {
|
|
55029
|
+
return replaceMermaidDiagramsInJson(originalResponse, correctedDiagrams);
|
|
55030
|
+
}
|
|
54867
55031
|
let modifiedResponse = originalResponse;
|
|
54868
55032
|
const sortedDiagrams = [...correctedDiagrams].sort((a, b) => b.startIndex - a.startIndex);
|
|
54869
55033
|
for (const diagram of sortedDiagrams) {
|
|
@@ -58529,21 +58693,20 @@ You are working with a repository located at: ${searchDirectory}
|
|
|
58529
58693
|
if (assistantResponseContent) {
|
|
58530
58694
|
await this.processImageReferences(assistantResponseContent);
|
|
58531
58695
|
}
|
|
58532
|
-
const validTools = [
|
|
58533
|
-
|
|
58534
|
-
|
|
58535
|
-
|
|
58536
|
-
|
|
58537
|
-
|
|
58538
|
-
|
|
58539
|
-
|
|
58540
|
-
if (this.allowEdit) {
|
|
58696
|
+
const validTools = [];
|
|
58697
|
+
if (this.allowedTools.isEnabled("search")) validTools.push("search");
|
|
58698
|
+
if (this.allowedTools.isEnabled("query")) validTools.push("query");
|
|
58699
|
+
if (this.allowedTools.isEnabled("extract")) validTools.push("extract");
|
|
58700
|
+
if (this.allowedTools.isEnabled("listFiles")) validTools.push("listFiles");
|
|
58701
|
+
if (this.allowedTools.isEnabled("searchFiles")) validTools.push("searchFiles");
|
|
58702
|
+
if (this.allowedTools.isEnabled("attempt_completion")) validTools.push("attempt_completion");
|
|
58703
|
+
if (this.allowEdit && this.allowedTools.isEnabled("implement")) {
|
|
58541
58704
|
validTools.push("implement", "edit", "create");
|
|
58542
58705
|
}
|
|
58543
|
-
if (this.enableBash) {
|
|
58706
|
+
if (this.enableBash && this.allowedTools.isEnabled("bash")) {
|
|
58544
58707
|
validTools.push("bash");
|
|
58545
58708
|
}
|
|
58546
|
-
if (this.enableDelegate) {
|
|
58709
|
+
if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
|
|
58547
58710
|
validTools.push("delegate");
|
|
58548
58711
|
}
|
|
58549
58712
|
const nativeTools = validTools;
|
|
@@ -165,6 +165,74 @@ export function decodeHtmlEntities(text) {
|
|
|
165
165
|
return decoded;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Normalize JavaScript syntax to valid JSON syntax
|
|
170
|
+
* Converts single quotes to double quotes for strings in JSON-like structures
|
|
171
|
+
*
|
|
172
|
+
* @param {string} str - String that might contain JavaScript array/object syntax
|
|
173
|
+
* @returns {string} - String with single quotes normalized to double quotes
|
|
174
|
+
*/
|
|
175
|
+
function normalizeJsonQuotes(str) {
|
|
176
|
+
if (!str || typeof str !== 'string') {
|
|
177
|
+
return str;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Quick check: if there are no single quotes, no need to normalize
|
|
181
|
+
if (!str.includes("'")) {
|
|
182
|
+
return str;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let result = '';
|
|
186
|
+
let inDoubleQuote = false;
|
|
187
|
+
let inSingleQuote = false;
|
|
188
|
+
let escaped = false;
|
|
189
|
+
|
|
190
|
+
for (let i = 0; i < str.length; i++) {
|
|
191
|
+
const char = str[i];
|
|
192
|
+
const prevChar = i > 0 ? str[i - 1] : '';
|
|
193
|
+
|
|
194
|
+
// Handle escape sequences
|
|
195
|
+
if (escaped) {
|
|
196
|
+
result += char;
|
|
197
|
+
escaped = false;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (char === '\\') {
|
|
202
|
+
escaped = true;
|
|
203
|
+
result += char;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Track when we're inside double-quoted strings
|
|
208
|
+
if (char === '"' && !inSingleQuote) {
|
|
209
|
+
inDoubleQuote = !inDoubleQuote;
|
|
210
|
+
result += char;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Convert single quotes to double quotes (when not inside double quotes)
|
|
215
|
+
if (char === "'" && !inDoubleQuote) {
|
|
216
|
+
// Check if this is a single quote inside a string value (like "It's")
|
|
217
|
+
// If we're already in a single-quoted string, toggle the state
|
|
218
|
+
if (inSingleQuote) {
|
|
219
|
+
// Closing single quote - convert to double quote
|
|
220
|
+
result += '"';
|
|
221
|
+
inSingleQuote = false;
|
|
222
|
+
} else {
|
|
223
|
+
// Opening single quote - convert to double quote
|
|
224
|
+
result += '"';
|
|
225
|
+
inSingleQuote = true;
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
result += char;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
168
236
|
/**
|
|
169
237
|
* Clean AI response by extracting JSON content when response contains JSON
|
|
170
238
|
* Only processes responses that contain JSON structures { or [
|
|
@@ -189,29 +257,36 @@ export function cleanSchemaResponse(response) {
|
|
|
189
257
|
// Try with json language specifier
|
|
190
258
|
const jsonBlockMatch = trimmed.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
191
259
|
if (jsonBlockMatch) {
|
|
192
|
-
return jsonBlockMatch[1].trim();
|
|
260
|
+
return normalizeJsonQuotes(jsonBlockMatch[1].trim());
|
|
193
261
|
}
|
|
194
262
|
|
|
195
263
|
// Try any code block with JSON content
|
|
196
264
|
const anyBlockMatch = trimmed.match(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/);
|
|
197
265
|
if (anyBlockMatch) {
|
|
198
|
-
return anyBlockMatch[1].trim();
|
|
266
|
+
return normalizeJsonQuotes(anyBlockMatch[1].trim());
|
|
199
267
|
}
|
|
200
268
|
|
|
201
269
|
// Legacy patterns for more specific matching
|
|
202
270
|
const codeBlockPatterns = [
|
|
203
271
|
/```json\s*\n?([{\[][\s\S]*?[}\]])\s*\n?```/,
|
|
204
|
-
/```\s*\n?([{\[][\s\S]*?[}\]])\s*\n
|
|
205
|
-
/`([{\[][\s\S]*?[}\]])`/
|
|
272
|
+
/```\s*\n?([{\[][\s\S]*?[}\]])\s*\n?```/
|
|
206
273
|
];
|
|
207
274
|
|
|
208
275
|
for (const pattern of codeBlockPatterns) {
|
|
209
276
|
const match = trimmed.match(pattern);
|
|
210
277
|
if (match) {
|
|
211
|
-
return match[1].trim();
|
|
278
|
+
return normalizeJsonQuotes(match[1].trim());
|
|
212
279
|
}
|
|
213
280
|
}
|
|
214
281
|
|
|
282
|
+
// Single backtick pattern - ONLY if the entire input is just the code block
|
|
283
|
+
// This prevents extracting inline code from within JSON objects (e.g., `['*']` from markdown text)
|
|
284
|
+
const singleBacktickPattern = /^`([{\[][\s\S]*?[}\]])`$/;
|
|
285
|
+
const singleBacktickMatch = trimmed.match(singleBacktickPattern);
|
|
286
|
+
if (singleBacktickMatch) {
|
|
287
|
+
return normalizeJsonQuotes(singleBacktickMatch[1].trim());
|
|
288
|
+
}
|
|
289
|
+
|
|
215
290
|
// Look for code block start followed immediately by JSON
|
|
216
291
|
const codeBlockStartPattern = /```(?:json)?\s*\n?\s*([{\[])/;
|
|
217
292
|
const codeBlockMatch = trimmed.match(codeBlockStartPattern);
|
|
@@ -236,7 +311,7 @@ export function cleanSchemaResponse(response) {
|
|
|
236
311
|
}
|
|
237
312
|
|
|
238
313
|
if (bracketCount === 0) {
|
|
239
|
-
return trimmed.substring(startIndex, endIndex);
|
|
314
|
+
return normalizeJsonQuotes(trimmed.substring(startIndex, endIndex));
|
|
240
315
|
}
|
|
241
316
|
}
|
|
242
317
|
|
|
@@ -261,7 +336,8 @@ export function cleanSchemaResponse(response) {
|
|
|
261
336
|
const isJsonArray = firstChar === '[' && lastChar === ']';
|
|
262
337
|
|
|
263
338
|
if (isJsonObject || isJsonArray) {
|
|
264
|
-
|
|
339
|
+
// Normalize JavaScript syntax (single quotes) to valid JSON syntax (double quotes)
|
|
340
|
+
return normalizeJsonQuotes(cleaned);
|
|
265
341
|
}
|
|
266
342
|
|
|
267
343
|
return response; // Return original if no extractable JSON found
|
|
@@ -826,6 +902,76 @@ export function isMermaidSchema(schema) {
|
|
|
826
902
|
return mermaidIndicators.some(indicator => indicator);
|
|
827
903
|
}
|
|
828
904
|
|
|
905
|
+
/**
|
|
906
|
+
* Extract Mermaid diagrams from JSON string values
|
|
907
|
+
* Handles escaped newlines and backticks within JSON strings
|
|
908
|
+
* @param {string} response - Response that may contain JSON with mermaid blocks in string values
|
|
909
|
+
* @returns {Object} - {diagrams: Array, jsonPaths: Array, parsedJson: Object|null}
|
|
910
|
+
*/
|
|
911
|
+
export function extractMermaidFromJson(response) {
|
|
912
|
+
if (!response || typeof response !== 'string') {
|
|
913
|
+
return { diagrams: [], jsonPaths: [], parsedJson: null };
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Try to extract JSON from code blocks first
|
|
917
|
+
let jsonContent = response.trim();
|
|
918
|
+
const jsonBlockMatch = jsonContent.match(/```json\s*\n([\s\S]*?)\n```/);
|
|
919
|
+
if (jsonBlockMatch) {
|
|
920
|
+
jsonContent = jsonBlockMatch[1].trim();
|
|
921
|
+
} else {
|
|
922
|
+
const anyBlockMatch = jsonContent.match(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/);
|
|
923
|
+
if (anyBlockMatch) {
|
|
924
|
+
jsonContent = anyBlockMatch[1].trim();
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Try to parse as JSON
|
|
929
|
+
let parsedJson;
|
|
930
|
+
try {
|
|
931
|
+
parsedJson = JSON.parse(jsonContent);
|
|
932
|
+
} catch (e) {
|
|
933
|
+
return { diagrams: [], jsonPaths: [], parsedJson: null };
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const diagrams = [];
|
|
937
|
+
const jsonPaths = [];
|
|
938
|
+
|
|
939
|
+
// Recursively search for mermaid diagrams in JSON string values
|
|
940
|
+
function searchObject(obj, path = []) {
|
|
941
|
+
if (typeof obj === 'string') {
|
|
942
|
+
// Look for mermaid code blocks in the string value
|
|
943
|
+
// Handle both escaped (\n) and literal newlines
|
|
944
|
+
const mermaidPattern = /```mermaid([^\n`]*?)(?:\n|\\n)([\s\S]*?)```/gi;
|
|
945
|
+
let match;
|
|
946
|
+
|
|
947
|
+
while ((match = mermaidPattern.exec(obj)) !== null) {
|
|
948
|
+
const attributes = match[1] ? match[1].trim() : '';
|
|
949
|
+
// Unescape the content (replace \\n with actual newlines)
|
|
950
|
+
const content = match[2].replace(/\\n/g, '\n');
|
|
951
|
+
|
|
952
|
+
diagrams.push({
|
|
953
|
+
content: content,
|
|
954
|
+
fullMatch: match[0],
|
|
955
|
+
startIndex: match.index,
|
|
956
|
+
endIndex: match.index + match[0].length,
|
|
957
|
+
attributes: attributes,
|
|
958
|
+
isInJson: true,
|
|
959
|
+
jsonPath: path.join('.')
|
|
960
|
+
});
|
|
961
|
+
jsonPaths.push(path.join('.'));
|
|
962
|
+
}
|
|
963
|
+
} else if (Array.isArray(obj)) {
|
|
964
|
+
obj.forEach((item, index) => searchObject(item, [...path, `[${index}]`]));
|
|
965
|
+
} else if (obj && typeof obj === 'object') {
|
|
966
|
+
Object.entries(obj).forEach(([key, value]) => searchObject(value, [...path, key]));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
searchObject(parsedJson);
|
|
971
|
+
|
|
972
|
+
return { diagrams, jsonPaths, parsedJson };
|
|
973
|
+
}
|
|
974
|
+
|
|
829
975
|
/**
|
|
830
976
|
* Extract Mermaid diagrams from markdown code blocks with position tracking
|
|
831
977
|
* @param {string} response - Response that may contain markdown with mermaid blocks
|
|
@@ -836,6 +982,16 @@ export function extractMermaidFromMarkdown(response) {
|
|
|
836
982
|
return { diagrams: [], cleanedResponse: response };
|
|
837
983
|
}
|
|
838
984
|
|
|
985
|
+
// First check if this looks like a JSON response - if so, use JSON-aware extraction
|
|
986
|
+
const trimmed = response.trim();
|
|
987
|
+
if ((trimmed.startsWith('{') || trimmed.startsWith('[')) ||
|
|
988
|
+
trimmed.includes('```json')) {
|
|
989
|
+
const jsonResult = extractMermaidFromJson(response);
|
|
990
|
+
if (jsonResult.diagrams.length > 0) {
|
|
991
|
+
return { diagrams: jsonResult.diagrams, cleanedResponse: response };
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
839
995
|
// Find all mermaid code blocks with enhanced regex to capture more variations
|
|
840
996
|
// This regex captures optional attributes on same line as ```mermaid, and all diagram content
|
|
841
997
|
const mermaidBlockRegex = /```mermaid([^\n]*)\n([\s\S]*?)```/gi;
|
|
@@ -854,7 +1010,8 @@ export function extractMermaidFromMarkdown(response) {
|
|
|
854
1010
|
fullMatch: match[0],
|
|
855
1011
|
startIndex: match.index,
|
|
856
1012
|
endIndex: match.index + match[0].length,
|
|
857
|
-
attributes: attributes
|
|
1013
|
+
attributes: attributes,
|
|
1014
|
+
isInJson: false
|
|
858
1015
|
});
|
|
859
1016
|
}
|
|
860
1017
|
|
|
@@ -862,9 +1019,84 @@ export function extractMermaidFromMarkdown(response) {
|
|
|
862
1019
|
return { diagrams, cleanedResponse: response };
|
|
863
1020
|
}
|
|
864
1021
|
|
|
1022
|
+
/**
|
|
1023
|
+
* Replace mermaid diagrams in JSON string values with corrected versions
|
|
1024
|
+
* @param {string} originalResponse - Original response with JSON
|
|
1025
|
+
* @param {Array} correctedDiagrams - Array of corrected diagram objects with jsonPath
|
|
1026
|
+
* @returns {string} - Response with corrected diagrams properly escaped in JSON
|
|
1027
|
+
*/
|
|
1028
|
+
export function replaceMermaidDiagramsInJson(originalResponse, correctedDiagrams) {
|
|
1029
|
+
if (!originalResponse || typeof originalResponse !== 'string') {
|
|
1030
|
+
return originalResponse;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (!correctedDiagrams || correctedDiagrams.length === 0) {
|
|
1034
|
+
return originalResponse;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Extract and parse JSON
|
|
1038
|
+
const jsonResult = extractMermaidFromJson(originalResponse);
|
|
1039
|
+
if (!jsonResult.parsedJson) {
|
|
1040
|
+
return originalResponse;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
let modifiedJson = jsonResult.parsedJson;
|
|
1044
|
+
|
|
1045
|
+
// Replace diagrams in the JSON object
|
|
1046
|
+
for (const diagram of correctedDiagrams) {
|
|
1047
|
+
if (!diagram.jsonPath || !diagram.isInJson) {
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Navigate to the path and replace the content
|
|
1052
|
+
const pathParts = diagram.jsonPath.split('.').filter(p => p);
|
|
1053
|
+
let current = modifiedJson;
|
|
1054
|
+
|
|
1055
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
1056
|
+
const part = pathParts[i];
|
|
1057
|
+
if (part.startsWith('[') && part.endsWith(']')) {
|
|
1058
|
+
const index = parseInt(part.slice(1, -1), 10);
|
|
1059
|
+
current = current[index];
|
|
1060
|
+
} else {
|
|
1061
|
+
current = current[part];
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// Get the last key/index
|
|
1066
|
+
const lastPart = pathParts[pathParts.length - 1];
|
|
1067
|
+
const attributesStr = diagram.attributes ? ` ${diagram.attributes}` : '';
|
|
1068
|
+
const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${diagram.content}\n\`\`\``;
|
|
1069
|
+
|
|
1070
|
+
if (lastPart.startsWith('[') && lastPart.endsWith(']')) {
|
|
1071
|
+
const index = parseInt(lastPart.slice(1, -1), 10);
|
|
1072
|
+
const originalString = current[index];
|
|
1073
|
+
// The fullMatch from extraction has unescaped newlines, so we need to match that
|
|
1074
|
+
current[index] = originalString.replace(diagram.fullMatch, newCodeBlock);
|
|
1075
|
+
} else {
|
|
1076
|
+
const originalString = current[lastPart];
|
|
1077
|
+
// The fullMatch from extraction has unescaped newlines, so we need to match that
|
|
1078
|
+
current[lastPart] = originalString.replace(diagram.fullMatch, newCodeBlock);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Reconstruct the response with modified JSON
|
|
1083
|
+
const modifiedJsonString = JSON.stringify(modifiedJson, null, 2);
|
|
1084
|
+
|
|
1085
|
+
// Check if original was in a code block
|
|
1086
|
+
const trimmed = originalResponse.trim();
|
|
1087
|
+
if (trimmed.match(/```json\s*\n([\s\S]*?)\n```/)) {
|
|
1088
|
+
return originalResponse.replace(/```json\s*\n([\s\S]*?)\n```/, `\`\`\`json\n${modifiedJsonString}\n\`\`\``);
|
|
1089
|
+
} else if (trimmed.match(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/)) {
|
|
1090
|
+
return originalResponse.replace(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/, `\`\`\`\n${modifiedJsonString}\n\`\`\``);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
return modifiedJsonString;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
865
1096
|
/**
|
|
866
1097
|
* Replace mermaid diagrams in original markdown with corrected versions
|
|
867
|
-
*
|
|
1098
|
+
* Automatically detects JSON vs markdown format and uses appropriate replacement
|
|
1099
|
+
* @param {string} originalResponse - Original response with markdown or JSON
|
|
868
1100
|
* @param {Array} correctedDiagrams - Array of corrected diagram objects
|
|
869
1101
|
* @returns {string} - Response with corrected diagrams in original format
|
|
870
1102
|
*/
|
|
@@ -877,22 +1109,28 @@ export function replaceMermaidDiagramsInMarkdown(originalResponse, correctedDiag
|
|
|
877
1109
|
return originalResponse;
|
|
878
1110
|
}
|
|
879
1111
|
|
|
1112
|
+
// Check if any diagrams are in JSON format
|
|
1113
|
+
const hasJsonDiagrams = correctedDiagrams.some(d => d.isInJson);
|
|
1114
|
+
if (hasJsonDiagrams) {
|
|
1115
|
+
return replaceMermaidDiagramsInJson(originalResponse, correctedDiagrams);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
880
1118
|
let modifiedResponse = originalResponse;
|
|
881
|
-
|
|
1119
|
+
|
|
882
1120
|
// Sort diagrams by start index in reverse order to preserve indices during replacement
|
|
883
1121
|
const sortedDiagrams = [...correctedDiagrams].sort((a, b) => b.startIndex - a.startIndex);
|
|
884
|
-
|
|
1122
|
+
|
|
885
1123
|
for (const diagram of sortedDiagrams) {
|
|
886
1124
|
// Reconstruct the code block with original attributes if they existed
|
|
887
1125
|
const attributesStr = diagram.attributes ? ` ${diagram.attributes}` : '';
|
|
888
1126
|
const newCodeBlock = `\`\`\`mermaid${attributesStr}\n${diagram.content}\n\`\`\``;
|
|
889
|
-
|
|
1127
|
+
|
|
890
1128
|
// Replace the original code block
|
|
891
|
-
modifiedResponse = modifiedResponse.slice(0, diagram.startIndex) +
|
|
892
|
-
newCodeBlock +
|
|
1129
|
+
modifiedResponse = modifiedResponse.slice(0, diagram.startIndex) +
|
|
1130
|
+
newCodeBlock +
|
|
893
1131
|
modifiedResponse.slice(diagram.endIndex);
|
|
894
1132
|
}
|
|
895
|
-
|
|
1133
|
+
|
|
896
1134
|
return modifiedResponse;
|
|
897
1135
|
}
|
|
898
1136
|
|