vigthoria-cli 1.5.9 → 1.6.1

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.
@@ -1486,17 +1486,85 @@ export class AgenticTools {
1486
1486
  const calls: ToolCall[] = [];
1487
1487
  let match;
1488
1488
 
1489
+ // Helper to extract balanced JSON from a position (handles nested braces)
1490
+ const extractBalancedJson = (str: string, startIdx: number): string | null => {
1491
+ if (str[startIdx] !== '{') return null;
1492
+ let braceCount = 0;
1493
+ let inString = false;
1494
+ let escapeNext = false;
1495
+
1496
+ for (let i = startIdx; i < str.length; i++) {
1497
+ const char = str[i];
1498
+
1499
+ if (escapeNext) {
1500
+ escapeNext = false;
1501
+ continue;
1502
+ }
1503
+
1504
+ if (char === '\\') {
1505
+ escapeNext = true;
1506
+ continue;
1507
+ }
1508
+
1509
+ if (char === '"') {
1510
+ inString = !inString;
1511
+ continue;
1512
+ }
1513
+
1514
+ if (!inString) {
1515
+ if (char === '{') braceCount++;
1516
+ else if (char === '}') {
1517
+ braceCount--;
1518
+ if (braceCount === 0) {
1519
+ return str.substring(startIdx, i + 1);
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1524
+ return null;
1525
+ };
1526
+
1489
1527
  // Helper to fix common JSON issues from AI outputs
1528
+ // IMPORTANT: Don't blindly replace quotes - it breaks code content
1490
1529
  const fixJson = (jsonStr: string): string => {
1491
- return jsonStr
1492
- .replace(/'/g, '"') // Replace single quotes with double
1493
- .replace(/([{,]\s*)(\w+):/g, '$1"$2":') // Quote unquoted keys
1494
- .replace(/:\s*'([^']*)'\s*([,}])/g, ': "$1"$2') // Quote values with single quotes
1495
- .replace(/\n/g, '\\n') // Escape newlines
1530
+ // First, escape newlines and control characters
1531
+ let fixed = jsonStr
1496
1532
  .replace(/\r/g, '') // Remove carriage returns
1497
- .replace(/\t/g, '\\t') // Escape tabs
1498
- .replace(/,\s*}/g, '}') // Remove trailing commas in objects
1499
- .replace(/,\s*]/g, ']'); // Remove trailing commas in arrays
1533
+ .replace(/\t/g, '\\t'); // Escape tabs
1534
+
1535
+ // Escape literal newlines inside strings (but not \n which is already escaped)
1536
+ // We need to be careful - only escape newlines that are inside quoted strings
1537
+ const parts: string[] = [];
1538
+ let inString = false;
1539
+ let currentPart = '';
1540
+
1541
+ for (let i = 0; i < fixed.length; i++) {
1542
+ const char = fixed[i];
1543
+ const prevChar = i > 0 ? fixed[i - 1] : '';
1544
+
1545
+ if (char === '"' && prevChar !== '\\') {
1546
+ inString = !inString;
1547
+ currentPart += char;
1548
+ } else if (char === '\n') {
1549
+ if (inString) {
1550
+ currentPart += '\\n'; // Escape the newline
1551
+ } else {
1552
+ currentPart += char; // Keep as-is outside strings
1553
+ }
1554
+ } else {
1555
+ currentPart += char;
1556
+ }
1557
+ }
1558
+ fixed = currentPart;
1559
+
1560
+ // Quote unquoted keys (only outside strings)
1561
+ fixed = fixed.replace(/([{,]\s*)(\w+)\s*:/g, '$1"$2":');
1562
+
1563
+ // Remove trailing commas
1564
+ fixed = fixed.replace(/,\s*}/g, '}');
1565
+ fixed = fixed.replace(/,\s*]/g, ']');
1566
+
1567
+ return fixed;
1500
1568
  };
1501
1569
 
1502
1570
  // Normalize tool name from various formats
@@ -1580,6 +1648,58 @@ export class AgenticTools {
1580
1648
  }
1581
1649
  }
1582
1650
 
1651
+ // ROBUST PARSER: Find {"tool": at any position and extract balanced JSON
1652
+ // This handles multi-line content in write_file and nested structures
1653
+ const toolMarkerRegex = /\{"tool"\s*:/g;
1654
+ while ((match = toolMarkerRegex.exec(text)) !== null) {
1655
+ const startIdx = match.index;
1656
+ const jsonStr = extractBalancedJson(text, startIdx);
1657
+
1658
+ if (jsonStr) {
1659
+ try {
1660
+ const parsed = JSON.parse(jsonStr);
1661
+ if (parsed.tool && parsed.args) {
1662
+ parsed.tool = normalizeToolName(parsed.tool);
1663
+ // Prevent duplicates
1664
+ if (!calls.some(c => c.tool === parsed.tool && JSON.stringify(c.args) === JSON.stringify(parsed.args))) {
1665
+ calls.push(parsed);
1666
+ }
1667
+ }
1668
+ } catch (e) {
1669
+ // Invalid JSON, try to fix it
1670
+ try {
1671
+ const fixed = fixJson(jsonStr);
1672
+ const parsed = JSON.parse(fixed);
1673
+ if (parsed.tool && parsed.args) {
1674
+ parsed.tool = normalizeToolName(parsed.tool);
1675
+ if (!calls.some(c => c.tool === parsed.tool && JSON.stringify(c.args) === JSON.stringify(parsed.args))) {
1676
+ calls.push(parsed);
1677
+ }
1678
+ }
1679
+ } catch (e2) {
1680
+ // Still invalid - try more aggressive fixing for write_file
1681
+ if (jsonStr.includes('"write_file"') || jsonStr.includes('"content"')) {
1682
+ try {
1683
+ // More aggressive: escape all control characters
1684
+ const aggressiveFix = jsonStr
1685
+ .replace(/[\x00-\x1F]/g, (c) => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))
1686
+ .replace(/'/g, '"');
1687
+ const parsed = JSON.parse(aggressiveFix);
1688
+ if (parsed.tool && parsed.args) {
1689
+ parsed.tool = normalizeToolName(parsed.tool);
1690
+ if (!calls.some(c => c.tool === parsed.tool && JSON.stringify(c.args) === JSON.stringify(parsed.args))) {
1691
+ calls.push(parsed);
1692
+ }
1693
+ }
1694
+ } catch (e3) {
1695
+ // Still invalid, skip
1696
+ }
1697
+ }
1698
+ }
1699
+ }
1700
+ }
1701
+ }
1702
+
1583
1703
  // Match inline JSON with "tool" key (various formats - tool before args)
1584
1704
  const inlineToolRegex = /\{[^{}]*"?tool"?\s*:\s*["']?([^"',}]+)["']?[^{}]*"?args"?\s*:\s*\{([^{}]*)\}[^{}]*\}/gi;
1585
1705
  while ((match = inlineToolRegex.exec(text)) !== null) {
package/test-parse.js ADDED
@@ -0,0 +1,105 @@
1
+ const { AgenticTools } = require("./dist/utils/tools.js");
2
+
3
+ // Test with multi-line write_file content - matching what AI actually outputs
4
+ const text = `Here is the code I created:
5
+
6
+ {"tool": "write_file", "args": {"path": "test.jsx", "content": "import React from 'react';
7
+
8
+ export function Test() {
9
+ return <div>Hello</div>;
10
+ }"}}
11
+
12
+ And then:
13
+ {"tool": "bash", "args": {"command": "npm test"}}
14
+ `;
15
+
16
+ console.log("Input text:");
17
+ console.log(text);
18
+ console.log("\n" + "=".repeat(50) + "\n");
19
+
20
+ // Debug: test the balanced extractor directly
21
+ function extractBalancedJson(str, startIdx) {
22
+ if (str[startIdx] !== '{') return null;
23
+ let braceCount = 0;
24
+ let inString = false;
25
+ let escapeNext = false;
26
+
27
+ for (let i = startIdx; i < str.length; i++) {
28
+ const char = str[i];
29
+
30
+ if (escapeNext) {
31
+ escapeNext = false;
32
+ continue;
33
+ }
34
+
35
+ if (char === '\\') {
36
+ escapeNext = true;
37
+ continue;
38
+ }
39
+
40
+ if (char === '"') {
41
+ inString = !inString;
42
+ continue;
43
+ }
44
+
45
+ if (!inString) {
46
+ if (char === '{') braceCount++;
47
+ else if (char === '}') {
48
+ braceCount--;
49
+ if (braceCount === 0) {
50
+ return str.substring(startIdx, i + 1);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+
58
+ function fixJson(jsonStr) {
59
+ return jsonStr
60
+ .replace(/'/g, '"')
61
+ .replace(/([{,]\s*)(\w+):/g, '$1"$2":')
62
+ .replace(/:\s*'([^']*)'\s*([,}])/g, ': "$1"$2')
63
+ .replace(/\n/g, '\\n')
64
+ .replace(/\r/g, '')
65
+ .replace(/\t/g, '\\t')
66
+ .replace(/,\s*}/g, '}')
67
+ .replace(/,\s*]/g, ']');
68
+ }
69
+
70
+ // Find the write_file call
71
+ const regex = /\{"tool"\s*:/g;
72
+ let match;
73
+ while ((match = regex.exec(text)) !== null) {
74
+ const extracted = extractBalancedJson(text, match.index);
75
+ if (extracted && extracted.includes("write_file")) {
76
+ console.log("Extracted write_file JSON:");
77
+ console.log(extracted);
78
+ console.log("\n--- After fixJson: ---");
79
+ const fixed = fixJson(extracted);
80
+ console.log(fixed);
81
+ console.log("\n--- Trying to parse: ---");
82
+ try {
83
+ const parsed = JSON.parse(fixed);
84
+ console.log("SUCCESS:", parsed.tool);
85
+ } catch (e) {
86
+ console.log("FAILED:", e.message);
87
+
88
+ // Try aggressive fix
89
+ console.log("\n--- Aggressive fix: ---");
90
+ const aggressive = extracted
91
+ .replace(/[\x00-\x1F]/g, (c) => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))
92
+ .replace(/'/g, '"');
93
+ console.log(aggressive.substring(0, 200));
94
+ try {
95
+ const parsed2 = JSON.parse(aggressive);
96
+ console.log("AGGRESSIVE SUCCESS:", parsed2.tool);
97
+ } catch (e2) {
98
+ console.log("AGGRESSIVE FAILED:", e2.message);
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ const calls = AgenticTools.parseToolCalls(text);
105
+ console.log("\n\nFinal result:", calls.length, "tool calls found");
package/test-parse2.js ADDED
@@ -0,0 +1,35 @@
1
+ const { AgenticTools } = require("./dist/utils/tools.js");
2
+
3
+ // Test with complex multi-line content
4
+ const text = `Here is the code:
5
+
6
+ {"tool": "write_file", "args": {"path": "src/App.jsx", "content": "import React from 'react';
7
+
8
+ function App() {
9
+ const [count, setCount] = useState(0);
10
+
11
+ return (
12
+ <div>
13
+ <h1>Hello World</h1>
14
+ <button onClick={() => setCount(c => c + 1)}>
15
+ Count: {count}
16
+ </button>
17
+ </div>
18
+ );
19
+ }
20
+
21
+ export default App;"}}
22
+
23
+ And run:
24
+ {"tool": "bash", "args": {"command": "npm run dev"}}
25
+ `;
26
+
27
+ const calls = AgenticTools.parseToolCalls(text);
28
+ console.log("Found", calls.length, "tool calls:");
29
+ for (const call of calls) {
30
+ console.log("\n Tool:", call.tool);
31
+ console.log(" Path:", call.args.path || call.args.command);
32
+ if (call.args.content) {
33
+ console.log(" Content preview:", call.args.content.substring(0, 80) + "...");
34
+ }
35
+ }