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.
- package/CLI_DIRECT_API_ARCHITECTURE.md +145 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/api.d.ts +7 -5
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js +46 -59
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +2 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/tools.d.ts.map +1 -1
- package/dist/utils/tools.js +122 -8
- package/dist/utils/tools.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +3 -1
- package/src/utils/api.ts +47 -61
- package/src/utils/config.ts +3 -0
- package/src/utils/tools.ts +128 -8
- package/test-parse.js +105 -0
- package/test-parse2.js +35 -0
package/src/utils/tools.ts
CHANGED
|
@@ -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
|
-
|
|
1492
|
-
|
|
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
|
-
|
|
1499
|
-
|
|
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
|
+
}
|