lcagent-cli 0.1.5 → 0.1.6
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/README.md +8 -0
- package/dist/bin/cli.js +28 -30
- package/dist/tools/editFile.d.ts +7 -3
- package/dist/tools/editFile.js +20 -7
- package/dist/tools/execute.js +9 -1
- package/dist/tools/grep.d.ts +5 -1
- package/dist/tools/grep.js +22 -9
- package/dist/tools/readFile.d.ts +5 -1
- package/dist/tools/readFile.js +17 -4
- package/dist/tools/runShell.d.ts +2 -1
- package/dist/tools/runShell.js +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,6 +139,14 @@ npm run start -- model
|
|
|
139
139
|
npm run start -- doctor
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
当模型触发工具时,CLI 会打印:
|
|
143
|
+
|
|
144
|
+
- `[tool-call] 工具名`:后面跟模型传入的原始 JSON 参数
|
|
145
|
+
- `[tool-result] 工具名`:显示工具执行结果
|
|
146
|
+
- `[tool-result] 工具名 (error)`:显示工具失败原因
|
|
147
|
+
|
|
148
|
+
这样可以直接看出是模型参数字段名不对、路径不对,还是工具执行本身报错。
|
|
149
|
+
|
|
142
150
|
## 设计目标
|
|
143
151
|
|
|
144
152
|
- 保留 Claude Code 式的核心代理循环
|
package/dist/bin/cli.js
CHANGED
|
@@ -46,6 +46,32 @@ function questionAsync(rl, prompt) {
|
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
+
function formatToolInput(input) {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify(input, null, 2);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return String(input);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function printEvent(event) {
|
|
58
|
+
switch (event.type) {
|
|
59
|
+
case 'status':
|
|
60
|
+
console.log(`\n[status] ${event.message}`);
|
|
61
|
+
break;
|
|
62
|
+
case 'assistant_text':
|
|
63
|
+
console.log(`\n${event.text}`);
|
|
64
|
+
break;
|
|
65
|
+
case 'tool_call':
|
|
66
|
+
console.log(`\n[tool-call] ${event.toolName}`);
|
|
67
|
+
console.log(formatToolInput(event.input));
|
|
68
|
+
break;
|
|
69
|
+
case 'tool_result':
|
|
70
|
+
console.log(`\n[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
|
|
71
|
+
console.log(event.result);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
49
75
|
async function probeUrl(url) {
|
|
50
76
|
try {
|
|
51
77
|
const response = await fetchWithTimeout(url, {
|
|
@@ -205,21 +231,7 @@ async function runDoctor() {
|
|
|
205
231
|
async function printAgentRun(prompt) {
|
|
206
232
|
const { engine } = await createApp(process.cwd());
|
|
207
233
|
for await (const event of engine.submit(prompt)) {
|
|
208
|
-
|
|
209
|
-
case 'status':
|
|
210
|
-
console.log(`\n[status] ${event.message}`);
|
|
211
|
-
break;
|
|
212
|
-
case 'assistant_text':
|
|
213
|
-
console.log(`\n${event.text}`);
|
|
214
|
-
break;
|
|
215
|
-
case 'tool_call':
|
|
216
|
-
console.log(`\n[tool] ${event.toolName} ${JSON.stringify(event.input)}`);
|
|
217
|
-
break;
|
|
218
|
-
case 'tool_result':
|
|
219
|
-
console.log(`\n[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
|
|
220
|
-
console.log(event.result);
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
234
|
+
printEvent(event);
|
|
223
235
|
}
|
|
224
236
|
}
|
|
225
237
|
async function runChat() {
|
|
@@ -237,21 +249,7 @@ async function runChat() {
|
|
|
237
249
|
break;
|
|
238
250
|
}
|
|
239
251
|
for await (const event of engine.submit(line)) {
|
|
240
|
-
|
|
241
|
-
case 'status':
|
|
242
|
-
console.log(`[status] ${event.message}`);
|
|
243
|
-
break;
|
|
244
|
-
case 'assistant_text':
|
|
245
|
-
console.log(event.text);
|
|
246
|
-
break;
|
|
247
|
-
case 'tool_call':
|
|
248
|
-
console.log(`[tool] ${event.toolName} ${JSON.stringify(event.input)}`);
|
|
249
|
-
break;
|
|
250
|
-
case 'tool_result':
|
|
251
|
-
console.log(`[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
|
|
252
|
-
console.log(event.result);
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
252
|
+
printEvent(event);
|
|
255
253
|
}
|
|
256
254
|
}
|
|
257
255
|
}
|
package/dist/tools/editFile.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolDefinition } from './types.js';
|
|
3
3
|
declare const inputSchema: z.ZodObject<{
|
|
4
|
-
path: z.ZodString
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
path: z.ZodOptional<z.ZodString>;
|
|
5
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
6
|
+
file_path: z.ZodOptional<z.ZodString>;
|
|
7
|
+
oldText: z.ZodOptional<z.ZodString>;
|
|
8
|
+
old_text: z.ZodOptional<z.ZodString>;
|
|
9
|
+
newText: z.ZodOptional<z.ZodString>;
|
|
10
|
+
new_text: z.ZodOptional<z.ZodString>;
|
|
7
11
|
}, z.core.$strip>;
|
|
8
12
|
export declare const editFileTool: ToolDefinition<z.infer<typeof inputSchema>>;
|
|
9
13
|
export {};
|
package/dist/tools/editFile.js
CHANGED
|
@@ -2,9 +2,13 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
const inputSchema = z.object({
|
|
5
|
-
path: z.string().min(1),
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
path: z.string().min(1).optional(),
|
|
6
|
+
filePath: z.string().min(1).optional(),
|
|
7
|
+
file_path: z.string().min(1).optional(),
|
|
8
|
+
oldText: z.string().optional(),
|
|
9
|
+
old_text: z.string().optional(),
|
|
10
|
+
newText: z.string().optional(),
|
|
11
|
+
new_text: z.string().optional(),
|
|
8
12
|
});
|
|
9
13
|
function resolvePath(cwd, filePath) {
|
|
10
14
|
return isAbsolute(filePath) ? filePath : resolve(cwd, filePath);
|
|
@@ -31,18 +35,27 @@ export const editFileTool = {
|
|
|
31
35
|
content: 'edit_file is blocked in manual approval mode. Switch config approvalMode to auto to enable file edits.',
|
|
32
36
|
};
|
|
33
37
|
}
|
|
34
|
-
const
|
|
38
|
+
const normalizedPath = input.path ?? input.filePath ?? input.file_path;
|
|
39
|
+
const oldText = input.oldText ?? input.old_text;
|
|
40
|
+
const newText = input.newText ?? input.new_text;
|
|
41
|
+
if (!normalizedPath || oldText === undefined || newText === undefined) {
|
|
42
|
+
return {
|
|
43
|
+
isError: true,
|
|
44
|
+
content: 'Missing required fields: path, oldText, or newText',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const absolutePath = resolvePath(context.cwd, normalizedPath);
|
|
35
48
|
const current = await readFile(absolutePath, 'utf8');
|
|
36
|
-
if (!current.includes(
|
|
49
|
+
if (!current.includes(oldText)) {
|
|
37
50
|
return {
|
|
38
51
|
isError: true,
|
|
39
52
|
content: 'oldText was not found in the target file.',
|
|
40
53
|
};
|
|
41
54
|
}
|
|
42
|
-
const next = current.replace(
|
|
55
|
+
const next = current.replace(oldText, newText);
|
|
43
56
|
await writeFile(absolutePath, next, 'utf8');
|
|
44
57
|
return {
|
|
45
|
-
content: `Updated ${
|
|
58
|
+
content: `Updated ${normalizedPath}`,
|
|
46
59
|
};
|
|
47
60
|
},
|
|
48
61
|
};
|
package/dist/tools/execute.js
CHANGED
|
@@ -13,5 +13,13 @@ export async function executeToolCall(toolUse, tools, context) {
|
|
|
13
13
|
content: parsed.error.message,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
return await tool.execute(parsed.data, context);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return {
|
|
21
|
+
isError: true,
|
|
22
|
+
content: error instanceof Error ? error.message : String(error),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
17
25
|
}
|
package/dist/tools/grep.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolDefinition } from './types.js';
|
|
3
3
|
declare const inputSchema: z.ZodObject<{
|
|
4
|
-
pattern: z.ZodString
|
|
4
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
5
|
+
query: z.ZodOptional<z.ZodString>;
|
|
5
6
|
path: z.ZodOptional<z.ZodString>;
|
|
7
|
+
directory: z.ZodOptional<z.ZodString>;
|
|
6
8
|
maxResults: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
max_results: z.ZodOptional<z.ZodNumber>;
|
|
7
10
|
isRegex: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
is_regex: z.ZodOptional<z.ZodBoolean>;
|
|
8
12
|
}, z.core.$strip>;
|
|
9
13
|
export declare const grepTool: ToolDefinition<z.infer<typeof inputSchema>>;
|
|
10
14
|
export {};
|
package/dist/tools/grep.js
CHANGED
|
@@ -2,10 +2,14 @@ import { readdir, readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { extname, isAbsolute, join, resolve } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
const inputSchema = z.object({
|
|
5
|
-
pattern: z.string().min(1),
|
|
5
|
+
pattern: z.string().min(1).optional(),
|
|
6
|
+
query: z.string().min(1).optional(),
|
|
6
7
|
path: z.string().optional(),
|
|
8
|
+
directory: z.string().optional(),
|
|
7
9
|
maxResults: z.number().int().positive().max(200).optional(),
|
|
10
|
+
max_results: z.number().int().positive().max(200).optional(),
|
|
8
11
|
isRegex: z.boolean().optional(),
|
|
12
|
+
is_regex: z.boolean().optional(),
|
|
9
13
|
});
|
|
10
14
|
const textExtensions = new Set([
|
|
11
15
|
'.ts', '.tsx', '.js', '.jsx', '.json', '.md', '.txt', '.yml', '.yaml', '.css', '.html', '.mjs', '.cjs', '.sh', '.py', '.java', '.go', '.rs'
|
|
@@ -44,17 +48,26 @@ export const grepTool = {
|
|
|
44
48
|
},
|
|
45
49
|
isReadOnly: true,
|
|
46
50
|
async execute(input, context) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
:
|
|
51
|
+
const pattern = input.pattern ?? input.query;
|
|
52
|
+
if (!pattern) {
|
|
53
|
+
return {
|
|
54
|
+
isError: true,
|
|
55
|
+
content: 'Missing required field: pattern',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const searchPath = input.path ?? input.directory;
|
|
59
|
+
const root = searchPath
|
|
60
|
+
? isAbsolute(searchPath)
|
|
61
|
+
? searchPath
|
|
62
|
+
: resolve(context.cwd, searchPath)
|
|
51
63
|
: context.cwd;
|
|
52
|
-
const
|
|
53
|
-
|
|
64
|
+
const isRegex = input.isRegex ?? input.is_regex;
|
|
65
|
+
const matcher = isRegex
|
|
66
|
+
? new RegExp(pattern, 'i')
|
|
54
67
|
: null;
|
|
55
68
|
const files = await walk(root);
|
|
56
69
|
const results = [];
|
|
57
|
-
const maxResults = input.maxResults ?? 50;
|
|
70
|
+
const maxResults = input.maxResults ?? input.max_results ?? 50;
|
|
58
71
|
for (const filePath of files) {
|
|
59
72
|
if (!textExtensions.has(extname(filePath))) {
|
|
60
73
|
continue;
|
|
@@ -68,7 +81,7 @@ export const grepTool = {
|
|
|
68
81
|
const line = lines[index] ?? '';
|
|
69
82
|
const matched = matcher
|
|
70
83
|
? matcher.test(line)
|
|
71
|
-
: line.toLowerCase().includes(
|
|
84
|
+
: line.toLowerCase().includes(pattern.toLowerCase());
|
|
72
85
|
if (!matched) {
|
|
73
86
|
continue;
|
|
74
87
|
}
|
package/dist/tools/readFile.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolDefinition } from './types.js';
|
|
3
3
|
declare const inputSchema: z.ZodObject<{
|
|
4
|
-
path: z.ZodString
|
|
4
|
+
path: z.ZodOptional<z.ZodString>;
|
|
5
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
6
|
+
file_path: z.ZodOptional<z.ZodString>;
|
|
5
7
|
startLine: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
start_line: z.ZodOptional<z.ZodNumber>;
|
|
6
9
|
endLine: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
end_line: z.ZodOptional<z.ZodNumber>;
|
|
7
11
|
}, z.core.$strip>;
|
|
8
12
|
export declare const readFileTool: ToolDefinition<z.infer<typeof inputSchema>>;
|
|
9
13
|
export {};
|
package/dist/tools/readFile.js
CHANGED
|
@@ -2,9 +2,13 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
const inputSchema = z.object({
|
|
5
|
-
path: z.string().min(1),
|
|
5
|
+
path: z.string().min(1).optional(),
|
|
6
|
+
filePath: z.string().min(1).optional(),
|
|
7
|
+
file_path: z.string().min(1).optional(),
|
|
6
8
|
startLine: z.number().int().positive().optional(),
|
|
9
|
+
start_line: z.number().int().positive().optional(),
|
|
7
10
|
endLine: z.number().int().positive().optional(),
|
|
11
|
+
end_line: z.number().int().positive().optional(),
|
|
8
12
|
});
|
|
9
13
|
function resolvePath(cwd, filePath) {
|
|
10
14
|
return isAbsolute(filePath) ? filePath : resolve(cwd, filePath);
|
|
@@ -25,11 +29,20 @@ export const readFileTool = {
|
|
|
25
29
|
},
|
|
26
30
|
isReadOnly: true,
|
|
27
31
|
async execute(input, context) {
|
|
28
|
-
const
|
|
32
|
+
const normalizedPath = input.path ?? input.filePath ?? input.file_path;
|
|
33
|
+
if (!normalizedPath) {
|
|
34
|
+
return {
|
|
35
|
+
isError: true,
|
|
36
|
+
content: 'Missing required field: path',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const startLine = input.startLine ?? input.start_line;
|
|
40
|
+
const endLine = input.endLine ?? input.end_line;
|
|
41
|
+
const absolutePath = resolvePath(context.cwd, normalizedPath);
|
|
29
42
|
const contents = await readFile(absolutePath, 'utf8');
|
|
30
43
|
const lines = contents.split(/\r?\n/);
|
|
31
|
-
const start = Math.max((
|
|
32
|
-
const end = Math.min(
|
|
44
|
+
const start = Math.max((startLine ?? 1) - 1, 0);
|
|
45
|
+
const end = Math.min(endLine ?? lines.length, lines.length);
|
|
33
46
|
const slice = lines.slice(start, end);
|
|
34
47
|
const numbered = slice
|
|
35
48
|
.map((line, index) => `${start + index + 1}: ${line}`)
|
package/dist/tools/runShell.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolDefinition } from './types.js';
|
|
3
3
|
declare const inputSchema: z.ZodObject<{
|
|
4
|
-
command: z.ZodString
|
|
4
|
+
command: z.ZodOptional<z.ZodString>;
|
|
5
|
+
cmd: z.ZodOptional<z.ZodString>;
|
|
5
6
|
}, z.core.$strip>;
|
|
6
7
|
export declare const runShellTool: ToolDefinition<z.infer<typeof inputSchema>>;
|
|
7
8
|
export {};
|
package/dist/tools/runShell.js
CHANGED
|
@@ -3,7 +3,8 @@ import { promisify } from 'node:util';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
const execAsync = promisify(exec);
|
|
5
5
|
const inputSchema = z.object({
|
|
6
|
-
command: z.string().min(1),
|
|
6
|
+
command: z.string().min(1).optional(),
|
|
7
|
+
cmd: z.string().min(1).optional(),
|
|
7
8
|
});
|
|
8
9
|
export const runShellTool = {
|
|
9
10
|
name: 'run_shell',
|
|
@@ -29,9 +30,16 @@ export const runShellTool = {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
try {
|
|
32
|
-
const
|
|
33
|
+
const command = input.command ?? input.cmd;
|
|
34
|
+
if (!command) {
|
|
35
|
+
return {
|
|
36
|
+
isError: true,
|
|
37
|
+
content: 'Missing required field: command',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
33
41
|
cwd: context.cwd,
|
|
34
|
-
shell: '/bin/sh',
|
|
42
|
+
shell: process.platform === 'win32' ? process.env.ComSpec || 'cmd.exe' : '/bin/sh',
|
|
35
43
|
maxBuffer: 1024 * 1024,
|
|
36
44
|
});
|
|
37
45
|
const output = [stdout.trim(), stderr.trim()].filter(Boolean).join('\n');
|