codeant-cli 0.1.5 → 0.1.7
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/dist/239.index.js +1 -0
- package/dist/701.index.js +1 -0
- package/dist/935.index.js +1 -0
- package/dist/index.js +20 -0
- package/dist/package.json +3 -0
- package/package.json +12 -4
- package/src/commands/getBaseUrl.js +0 -25
- package/src/commands/login.js +0 -124
- package/src/commands/logout.js +0 -30
- package/src/commands/review.js +0 -239
- package/src/commands/secrets.js +0 -268
- package/src/commands/securityAnalysis.js +0 -284
- package/src/commands/setBaseUrl.js +0 -37
- package/src/commands/staticAnalysis.js +0 -405
- package/src/components/ReviewUI.js +0 -127
- package/src/components/Welcome.js +0 -143
- package/src/index.js +0 -202
- package/src/tools/bashTool.js +0 -17
- package/src/tools/executeTool.js +0 -35
- package/src/tools/globTool.js +0 -9
- package/src/tools/grepTool.js +0 -18
- package/src/tools/lsTool.js +0 -7
- package/src/tools/readTool.js +0 -12
- package/src/utils/commonApiHelper.js +0 -101
- package/src/utils/config.js +0 -42
- package/src/utils/fetchApi.js +0 -42
- package/src/utils/gitDiffHelper.js +0 -774
- package/src/utils/reviewApiHelper.js +0 -139
- package/src/utils/secretsApiHelper.js +0 -134
- package/src/utils/securityAnalysisApiHelper.js +0 -119
- package/src/utils/staticAnalysisApiHelper.js +0 -119
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeant-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Code review CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"codeant": "./
|
|
7
|
+
"codeant": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "node src/index.js"
|
|
10
|
+
"start": "node src/index.js",
|
|
11
|
+
"build": "node build.js",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest"
|
|
11
14
|
},
|
|
12
15
|
"keywords": [
|
|
13
16
|
"cli",
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
"url": ""
|
|
22
25
|
},
|
|
23
26
|
"files": [
|
|
24
|
-
"
|
|
27
|
+
"dist"
|
|
25
28
|
],
|
|
26
29
|
"dependencies": {
|
|
27
30
|
"commander": "^12.1.0",
|
|
@@ -29,5 +32,10 @@
|
|
|
29
32
|
"minimatch": "^10.1.1",
|
|
30
33
|
"open": "^10.1.0",
|
|
31
34
|
"react": "^18.3.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@vercel/ncc": "^0.38.4",
|
|
38
|
+
"javascript-obfuscator": "^4.1.1",
|
|
39
|
+
"vitest": "^1.6.1"
|
|
32
40
|
}
|
|
33
41
|
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import { Text, Box, useApp } from 'ink';
|
|
3
|
-
import { getConfigValue } from '../utils/config.js';
|
|
4
|
-
|
|
5
|
-
export default function GetBaseUrl() {
|
|
6
|
-
const { exit } = useApp();
|
|
7
|
-
|
|
8
|
-
const envUrl = process.env.CODEANT_API_URL;
|
|
9
|
-
const configUrl = getConfigValue('baseUrl');
|
|
10
|
-
const defaultUrl = 'https://api.codeant.ai';
|
|
11
|
-
|
|
12
|
-
const activeUrl = envUrl || configUrl || defaultUrl;
|
|
13
|
-
const source = envUrl ? 'env' : configUrl ? 'config' : 'default';
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
exit();
|
|
17
|
-
}, []);
|
|
18
|
-
|
|
19
|
-
return React.createElement(
|
|
20
|
-
Box,
|
|
21
|
-
{ flexDirection: 'column', padding: 1 },
|
|
22
|
-
React.createElement(Text, { bold: true }, 'Base URL: ', activeUrl),
|
|
23
|
-
React.createElement(Text, { color: 'gray' }, 'Source: ', source)
|
|
24
|
-
);
|
|
25
|
-
}
|
package/src/commands/login.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Text, Box, useApp } from 'ink';
|
|
3
|
-
import { getConfigValue, setConfigValue } from '../utils/config.js';
|
|
4
|
-
import { randomUUID } from 'crypto';
|
|
5
|
-
|
|
6
|
-
const POLL_INTERVAL = 10000; // 10 seconds
|
|
7
|
-
const TIMEOUT = 10 * 60 * 1000; // 10 minutes
|
|
8
|
-
|
|
9
|
-
export default function Login() {
|
|
10
|
-
const { exit } = useApp();
|
|
11
|
-
const [status, setStatus] = useState('opening');
|
|
12
|
-
const [error, setError] = useState(null);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
// Check if already logged in
|
|
16
|
-
const existingToken = getConfigValue('apiKey');
|
|
17
|
-
if (existingToken) {
|
|
18
|
-
setStatus('already_logged_in');
|
|
19
|
-
setTimeout(() => exit(), 100);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const token = randomUUID();
|
|
24
|
-
const baseUrl = getConfigValue('baseUrl') || 'https://api.codeant.ai';
|
|
25
|
-
const loginUrl = `https://app.codeant.ai?ideLoginToken=${token}`;
|
|
26
|
-
const pollUrl = `${baseUrl}/extension/login/status?apiKey=${token}`;
|
|
27
|
-
|
|
28
|
-
// Open browser
|
|
29
|
-
import('open').then(({ default: open }) => {
|
|
30
|
-
open(loginUrl);
|
|
31
|
-
setStatus('waiting');
|
|
32
|
-
}).catch(() => {
|
|
33
|
-
// Fallback: just show the URL
|
|
34
|
-
console.log(`\nOpen this URL in your browser:\n${loginUrl}\n`);
|
|
35
|
-
setStatus('waiting');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Poll for login status
|
|
39
|
-
let timeoutId;
|
|
40
|
-
const intervalId = setInterval(async () => {
|
|
41
|
-
try {
|
|
42
|
-
const response = await fetch(pollUrl);
|
|
43
|
-
const data = await response.json();
|
|
44
|
-
|
|
45
|
-
if (data.status === 'yes') {
|
|
46
|
-
clearInterval(intervalId);
|
|
47
|
-
clearTimeout(timeoutId);
|
|
48
|
-
|
|
49
|
-
// Save the API key
|
|
50
|
-
setConfigValue('apiKey', token);
|
|
51
|
-
|
|
52
|
-
// Check for custom API URL based on email domain
|
|
53
|
-
if (data.item?.email?.includes('commvault.com')) {
|
|
54
|
-
setConfigValue('baseUrl', 'https://codeant-api.commvault.com');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
setStatus('success');
|
|
58
|
-
setTimeout(() => exit(), 100);
|
|
59
|
-
}
|
|
60
|
-
} catch (err) {
|
|
61
|
-
// Silently continue polling
|
|
62
|
-
}
|
|
63
|
-
}, POLL_INTERVAL);
|
|
64
|
-
|
|
65
|
-
// Timeout after 10 minutes
|
|
66
|
-
timeoutId = setTimeout(() => {
|
|
67
|
-
clearInterval(intervalId);
|
|
68
|
-
setError('Login timed out. Please try again.');
|
|
69
|
-
setStatus('error');
|
|
70
|
-
setTimeout(() => exit(new Error('Login timed out')), 100);
|
|
71
|
-
}, TIMEOUT);
|
|
72
|
-
|
|
73
|
-
// Cleanup
|
|
74
|
-
return () => {
|
|
75
|
-
clearInterval(intervalId);
|
|
76
|
-
clearTimeout(timeoutId);
|
|
77
|
-
};
|
|
78
|
-
}, []);
|
|
79
|
-
|
|
80
|
-
if (status === 'already_logged_in') {
|
|
81
|
-
return React.createElement(
|
|
82
|
-
Box,
|
|
83
|
-
{ flexDirection: 'column', padding: 1 },
|
|
84
|
-
React.createElement(Text, { color: 'yellow' }, 'Already logged in.'),
|
|
85
|
-
React.createElement(Text, { color: 'gray' }, 'Run "codeant logout" first to re-authenticate.')
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (status === 'opening') {
|
|
90
|
-
return React.createElement(
|
|
91
|
-
Box,
|
|
92
|
-
{ flexDirection: 'column', padding: 1 },
|
|
93
|
-
React.createElement(Text, null, 'Opening browser...')
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (status === 'waiting') {
|
|
98
|
-
return React.createElement(
|
|
99
|
-
Box,
|
|
100
|
-
{ flexDirection: 'column', padding: 1 },
|
|
101
|
-
React.createElement(Text, { color: 'cyan' }, 'Waiting for login...'),
|
|
102
|
-
React.createElement(Text, { color: 'gray' }, 'Complete the login in your browser.'),
|
|
103
|
-
React.createElement(Text, { color: 'gray' }, 'Checking every 10 seconds. Timeout in 10 minutes.')
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (status === 'success') {
|
|
108
|
-
return React.createElement(
|
|
109
|
-
Box,
|
|
110
|
-
{ flexDirection: 'column', padding: 1 },
|
|
111
|
-
React.createElement(Text, { color: 'green' }, '✓ Login successful!')
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (status === 'error') {
|
|
116
|
-
return React.createElement(
|
|
117
|
-
Box,
|
|
118
|
-
{ flexDirection: 'column', padding: 1 },
|
|
119
|
-
React.createElement(Text, { color: 'red' }, '✗ ', error)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return null;
|
|
124
|
-
}
|
package/src/commands/logout.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import { Text, Box, useApp } from 'ink';
|
|
3
|
-
import { getConfigValue, setConfigValue } from '../utils/config.js';
|
|
4
|
-
|
|
5
|
-
export default function Logout() {
|
|
6
|
-
const { exit } = useApp();
|
|
7
|
-
|
|
8
|
-
const wasLoggedIn = !!getConfigValue('apiKey');
|
|
9
|
-
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
if (wasLoggedIn) {
|
|
12
|
-
setConfigValue('apiKey', null);
|
|
13
|
-
}
|
|
14
|
-
exit();
|
|
15
|
-
}, []);
|
|
16
|
-
|
|
17
|
-
if (!wasLoggedIn) {
|
|
18
|
-
return React.createElement(
|
|
19
|
-
Box,
|
|
20
|
-
{ flexDirection: 'column', padding: 1 },
|
|
21
|
-
React.createElement(Text, { color: 'yellow' }, 'Not logged in.')
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return React.createElement(
|
|
26
|
-
Box,
|
|
27
|
-
{ flexDirection: 'column', padding: 1 },
|
|
28
|
-
React.createElement(Text, { color: 'green' }, '✓ Logged out successfully.')
|
|
29
|
-
);
|
|
30
|
-
}
|
package/src/commands/review.js
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { useApp } from 'ink';
|
|
3
|
-
import { getConfigValue } from '../utils/config.js';
|
|
4
|
-
import ReviewApiHelper from '../utils/reviewApiHelper.js';
|
|
5
|
-
import { fetchApi } from '../utils/fetchApi.js';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { executeTool } from '../tools/executeTool.js';
|
|
9
|
-
import {
|
|
10
|
-
renderInitializing,
|
|
11
|
-
renderFetchingDiff,
|
|
12
|
-
renderReviewing,
|
|
13
|
-
renderNoFiles,
|
|
14
|
-
renderError,
|
|
15
|
-
renderNotLoggedIn,
|
|
16
|
-
renderDone,
|
|
17
|
-
mapLabelToSeverity,
|
|
18
|
-
} from '../components/ReviewUI.js';
|
|
19
|
-
|
|
20
|
-
const MAX_TURNS = 5;
|
|
21
|
-
|
|
22
|
-
export default function Review({ scanType = 'staged-only', lastNCommits = 1, failOn = 'CRITICAL' }) {
|
|
23
|
-
const { exit } = useApp();
|
|
24
|
-
const [status, setStatus] = useState('initializing');
|
|
25
|
-
const [error, setError] = useState(null);
|
|
26
|
-
const [currentMessage, setCurrentMessage] = useState('');
|
|
27
|
-
const [issues, setIssues] = useState([]);
|
|
28
|
-
const [summary, setSummary] = useState(null);
|
|
29
|
-
|
|
30
|
-
const apiKey = getConfigValue('apiKey');
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (!apiKey) {
|
|
34
|
-
if (process.stdin.isTTY) {
|
|
35
|
-
// Interactive session: don't exit immediately, let component render the error
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const id = setTimeout(() => exit(new Error('Not logged in')), 0);
|
|
39
|
-
return () => clearTimeout(id);
|
|
40
|
-
}
|
|
41
|
-
}, [apiKey, exit]);
|
|
42
|
-
|
|
43
|
-
if (!apiKey) {
|
|
44
|
-
return renderNotLoggedIn();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
let cancelled = false;
|
|
49
|
-
|
|
50
|
-
async function runReview() {
|
|
51
|
-
try {
|
|
52
|
-
// ── Fetch diff ─────────────────────────────────────────────────────
|
|
53
|
-
setStatus('fetching_diff');
|
|
54
|
-
|
|
55
|
-
const helper = new ReviewApiHelper(process.cwd());
|
|
56
|
-
await helper.init();
|
|
57
|
-
const gitRoot = helper.getGitRoot() || process.cwd();
|
|
58
|
-
const requestBody = await helper.buildReviewApiRequest(scanType, [], [], { lastNCommits });
|
|
59
|
-
|
|
60
|
-
if (!requestBody?.diff_content?.length) {
|
|
61
|
-
if (!cancelled) setStatus('no_files');
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (cancelled) return;
|
|
66
|
-
setStatus('reviewing');
|
|
67
|
-
|
|
68
|
-
// ── Normal agent turn loop ─────────────────────────────────────────
|
|
69
|
-
setCurrentMessage('Analyzing code...');
|
|
70
|
-
const { finalMessage: reviewFinal } = await runTurnLoop(
|
|
71
|
-
requestBody,
|
|
72
|
-
gitRoot,
|
|
73
|
-
() => cancelled,
|
|
74
|
-
(msg) => { if (!cancelled) setCurrentMessage(msg); },
|
|
75
|
-
false
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
if (cancelled) return;
|
|
79
|
-
|
|
80
|
-
// ── Reflector loop ─────────────────────────────────────────────────
|
|
81
|
-
setCurrentMessage('Running reflector...');
|
|
82
|
-
const reflectorPayload = {
|
|
83
|
-
diff_content: requestBody.diff_content,
|
|
84
|
-
prompt_template_name: 'reflector',
|
|
85
|
-
extra_variables: {
|
|
86
|
-
suggestion_str: reviewFinal?.content || '',
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const { finalOutput: reflectorOutput } = await runTurnLoop(
|
|
91
|
-
reflectorPayload,
|
|
92
|
-
gitRoot,
|
|
93
|
-
() => cancelled,
|
|
94
|
-
(msg) => { if (!cancelled) setCurrentMessage(msg); },
|
|
95
|
-
true
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
if (cancelled) return;
|
|
99
|
-
|
|
100
|
-
const reviewIssues = (reflectorOutput?.code_suggestions || []).map(issue => ({
|
|
101
|
-
issue_content: issue.issue_content || '',
|
|
102
|
-
relevant_file: issue.relevant_file || 'Unknown',
|
|
103
|
-
start_line: issue.start_line || 0,
|
|
104
|
-
label: issue.label || 'Code Quality',
|
|
105
|
-
}));
|
|
106
|
-
setIssues(reviewIssues);
|
|
107
|
-
setSummary(null);
|
|
108
|
-
setStatus('done');
|
|
109
|
-
|
|
110
|
-
} catch (err) {
|
|
111
|
-
if (!cancelled) {
|
|
112
|
-
setError(err.message);
|
|
113
|
-
setStatus('error');
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
runReview();
|
|
119
|
-
return () => { cancelled = true; };
|
|
120
|
-
}, [scanType, lastNCommits, apiKey]);
|
|
121
|
-
|
|
122
|
-
// Handle exit on terminal states
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (status === 'done') {
|
|
125
|
-
const severityOrder = ['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'];
|
|
126
|
-
const failOnIndex = severityOrder.indexOf(failOn);
|
|
127
|
-
if (failOnIndex === -1) {
|
|
128
|
-
setTimeout(() => { process.exitCode = 1; exit(new Error(`Invalid failOn value: ${failOn}`)); }, 100);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const hasBlocking = issues.some(issue =>
|
|
132
|
-
severityOrder.indexOf(mapLabelToSeverity(issue.label)) >= failOnIndex
|
|
133
|
-
);
|
|
134
|
-
if (hasBlocking) {
|
|
135
|
-
setTimeout(() => { process.exitCode = 1; exit(new Error('Issues detected')); }, 100);
|
|
136
|
-
} else {
|
|
137
|
-
setTimeout(() => exit(), 100);
|
|
138
|
-
}
|
|
139
|
-
} else if (status === 'no_files') {
|
|
140
|
-
setTimeout(() => exit(), 100);
|
|
141
|
-
} else if (status === 'error') {
|
|
142
|
-
setTimeout(() => exit(new Error(error)), 100);
|
|
143
|
-
}
|
|
144
|
-
}, [status, issues, failOn, exit, error]);
|
|
145
|
-
|
|
146
|
-
// ── Renders ──────────────────────────────────────────────────────────────
|
|
147
|
-
|
|
148
|
-
if (status === 'initializing') return renderInitializing();
|
|
149
|
-
if (status === 'fetching_diff') return renderFetchingDiff();
|
|
150
|
-
if (status === 'reviewing') return renderReviewing(currentMessage);
|
|
151
|
-
if (status === 'no_files') return renderNoFiles(scanType, lastNCommits);
|
|
152
|
-
if (status === 'error') return renderError(error);
|
|
153
|
-
if (status === 'done') return renderDone(issues, summary, failOn);
|
|
154
|
-
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ── Turn loop ──────────────────────────────────────────────────────────────
|
|
159
|
-
|
|
160
|
-
async function runTurnLoop(initialPayload, gitRoot, isCancelled, onMessage, isReflectorLoop) {
|
|
161
|
-
let nextPayload = initialPayload;
|
|
162
|
-
let finalMessage = null;
|
|
163
|
-
let finalOutput = null;
|
|
164
|
-
|
|
165
|
-
for (let turn = 0; turn < MAX_TURNS; turn+=1) {
|
|
166
|
-
if (isCancelled()) break;
|
|
167
|
-
|
|
168
|
-
onMessage('Analyzing code...');
|
|
169
|
-
|
|
170
|
-
const resp = await fetchApi('/extension/pr-review/agent/turn', 'POST', nextPayload);
|
|
171
|
-
|
|
172
|
-
if (!resp || typeof resp !== 'object') {
|
|
173
|
-
throw new Error('Invalid response from agent API');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const sessionId = resp.session_id;
|
|
177
|
-
const assistantMsg = resp.response;
|
|
178
|
-
const done = resp.done;
|
|
179
|
-
|
|
180
|
-
if (!sessionId) {
|
|
181
|
-
throw new Error('Missing session_id in agent response');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
finalMessage = assistantMsg;
|
|
185
|
-
if (resp.output) finalOutput = resp.output;
|
|
186
|
-
|
|
187
|
-
if (done) {
|
|
188
|
-
// Handle parsing errors in final message by dumping response content (only on reflector loop)
|
|
189
|
-
if (isReflectorLoop && resp.parsing_error) {
|
|
190
|
-
const dumpDir = path.join(gitRoot, '.codeant', 'codeSuggestions');
|
|
191
|
-
const dumpFile = path.join(dumpDir, `code_suggestions_${Date.now()}.txt`);
|
|
192
|
-
let dumpContent = resp.response?.content || JSON.stringify(resp, null, 2);
|
|
193
|
-
|
|
194
|
-
// Ensure dumpContent is a string
|
|
195
|
-
if (typeof dumpContent !== 'string') {
|
|
196
|
-
dumpContent = JSON.stringify(dumpContent, null, 2);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Unescape all serialization escape sequences
|
|
200
|
-
dumpContent = dumpContent
|
|
201
|
-
.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
202
|
-
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
203
|
-
.replace(/\\n/g, '\n')
|
|
204
|
-
.replace(/\\r/g, '\r')
|
|
205
|
-
.replace(/\\t/g, '\t')
|
|
206
|
-
.replace(/\\"/g, '"')
|
|
207
|
-
.replace(/\\'/g, "'")
|
|
208
|
-
.replace(/\\\\/g, '\\');
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
// Create directories if they don't exist
|
|
212
|
-
await fs.promises.mkdir(dumpDir, { recursive: true });
|
|
213
|
-
await fs.promises.writeFile(dumpFile, dumpContent, 'utf8');
|
|
214
|
-
console.error(`⚠ Parsing error encountered. Full response dumped to: ${dumpFile}`);
|
|
215
|
-
} catch (writeErr) {
|
|
216
|
-
console.error(`⚠ Parsing error encountered (could not dump to file: ${writeErr.message})`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const toolCalls = assistantMsg?.tool_calls || [];
|
|
223
|
-
if (toolCalls.length === 0) break;
|
|
224
|
-
|
|
225
|
-
const toolResults = [];
|
|
226
|
-
for (const tc of toolCalls) {
|
|
227
|
-
const content = await executeTool(tc, gitRoot);
|
|
228
|
-
toolResults.push({
|
|
229
|
-
tool_call_id: tc.id,
|
|
230
|
-
content,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
nextPayload = { session_id: sessionId, tool_results: toolResults };
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return { finalMessage, finalOutput };
|
|
238
|
-
}
|
|
239
|
-
|