@gird/ccli 2.0.4 → 2.0.5
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/ai.js +32 -9
- package/dist/ai.js.map +1 -1
- package/dist/repl.js +46 -7
- package/dist/repl.js.map +1 -1
- package/dist/tools.js +207 -0
- package/dist/tools.js.map +1 -0
- package/package.json +1 -1
package/dist/ai.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI 客户端 —— 把配置变成「能对话的对象」。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 支持流式输出与工具调用(function calling)。
|
|
5
5
|
* 目前只对接 OpenAI 兼容后端(通义千问 / DeepSeek / GLM…);Anthropic 留待后续步骤。
|
|
6
6
|
*
|
|
7
7
|
* 流式(stream)的意义:模型边生成边返回,我们边收边打印,
|
|
8
8
|
* 用户不用干等一整段生成完,交互感更接近「打字机」。
|
|
9
9
|
*/
|
|
10
10
|
import OpenAI from 'openai';
|
|
11
|
+
import { toolDefs } from './tools.js';
|
|
11
12
|
export class AIClient {
|
|
12
13
|
client;
|
|
13
14
|
model;
|
|
@@ -35,25 +36,47 @@ export class AIClient {
|
|
|
35
36
|
}
|
|
36
37
|
/**
|
|
37
38
|
* 发送整段对话历史,流式接收助手回复。
|
|
38
|
-
* @param messages 完整历史(含本轮 user
|
|
39
|
+
* @param messages 完整历史(含本轮 user 消息、以及可能的工具结果)
|
|
39
40
|
* @param onDelta 每收到一小段文本就回调(用于实时打印)
|
|
40
|
-
* @returns
|
|
41
|
+
* @returns 助手本回合的文本 + 它想调用的工具
|
|
41
42
|
*/
|
|
42
43
|
async chat(messages, onDelta) {
|
|
43
44
|
const stream = await this.client.chat.completions.create({
|
|
44
45
|
model: this.model,
|
|
45
46
|
messages,
|
|
47
|
+
tools: toolDefs(),
|
|
46
48
|
stream: true,
|
|
47
49
|
});
|
|
48
|
-
let
|
|
50
|
+
let content = '';
|
|
51
|
+
// 工具调用在流里是分片到达的,按 index 累加拼回完整的 id/name/arguments
|
|
52
|
+
const acc = {};
|
|
49
53
|
for await (const chunk of stream) {
|
|
50
|
-
const delta = chunk.choices[0]?.delta
|
|
51
|
-
if (delta)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
const delta = chunk.choices[0]?.delta;
|
|
55
|
+
if (!delta)
|
|
56
|
+
continue;
|
|
57
|
+
if (delta.content) {
|
|
58
|
+
content += delta.content;
|
|
59
|
+
onDelta(delta.content);
|
|
60
|
+
}
|
|
61
|
+
for (const tc of delta.tool_calls ?? []) {
|
|
62
|
+
const cur = (acc[tc.index] ??= {
|
|
63
|
+
id: '',
|
|
64
|
+
name: '',
|
|
65
|
+
arguments: '',
|
|
66
|
+
});
|
|
67
|
+
if (tc.id)
|
|
68
|
+
cur.id = tc.id;
|
|
69
|
+
if (tc.function?.name)
|
|
70
|
+
cur.name = tc.function.name;
|
|
71
|
+
if (tc.function?.arguments)
|
|
72
|
+
cur.arguments += tc.function.arguments;
|
|
54
73
|
}
|
|
55
74
|
}
|
|
56
|
-
|
|
75
|
+
const toolCalls = Object.keys(acc)
|
|
76
|
+
.map(Number)
|
|
77
|
+
.sort((a, b) => a - b)
|
|
78
|
+
.map((i) => acc[i]);
|
|
79
|
+
return { content, toolCalls };
|
|
57
80
|
}
|
|
58
81
|
}
|
|
59
82
|
//# sourceMappingURL=ai.js.map
|
package/dist/ai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"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"}
|
package/dist/repl.js
CHANGED
|
@@ -10,8 +10,50 @@ import { NAME, VERSION } from './version.js';
|
|
|
10
10
|
import { loadConfig, maskKey } from './config.js';
|
|
11
11
|
import { AIClient } from './ai.js';
|
|
12
12
|
import { handleSlash } from './slash.js';
|
|
13
|
+
import { executeTool } from './tools.js';
|
|
13
14
|
const PROMPT = '› ';
|
|
14
|
-
const SYSTEM_PROMPT = '你是 CCLI,一个运行在终端里的 AI 助手。回答简洁、准确,使用中文。'
|
|
15
|
+
const SYSTEM_PROMPT = '你是 CCLI,一个运行在终端里的 AI 助手。回答简洁、准确,使用中文。' +
|
|
16
|
+
'你可以调用工具来读取文件;需要查看文件内容时请使用 read_file,不要凭空猜测。';
|
|
17
|
+
/** 单个用户回合内,最多允许模型连续调用工具的轮数,防止失控循环。 */
|
|
18
|
+
const MAX_TOOL_ROUNDS = 10;
|
|
19
|
+
/**
|
|
20
|
+
* 驱动一个用户回合:请求模型 → 若它要调用工具则执行并回传 → 再请求 …
|
|
21
|
+
* 直到模型不再调用工具(给出最终回答)或达到轮数上限。
|
|
22
|
+
* 这就是「代理循环」的雏形,本步只有 read_file 一个工具。
|
|
23
|
+
*/
|
|
24
|
+
async function runAgentTurn(ai, history) {
|
|
25
|
+
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
26
|
+
const turn = await ai.chat(history, (delta) => process.stdout.write(delta));
|
|
27
|
+
// 没有工具调用 → 这是最终回答,收尾退出
|
|
28
|
+
if (turn.toolCalls.length === 0) {
|
|
29
|
+
process.stdout.write('\n');
|
|
30
|
+
history.push({ role: 'assistant', content: turn.content });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// 有工具调用:先把带 tool_calls 的 assistant 消息入历史
|
|
34
|
+
history.push({
|
|
35
|
+
role: 'assistant',
|
|
36
|
+
content: turn.content || null,
|
|
37
|
+
tool_calls: turn.toolCalls.map((tc) => ({
|
|
38
|
+
id: tc.id,
|
|
39
|
+
type: 'function',
|
|
40
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
41
|
+
})),
|
|
42
|
+
});
|
|
43
|
+
// 逐个执行工具,结果以 role:'tool' 消息回传
|
|
44
|
+
for (const tc of turn.toolCalls) {
|
|
45
|
+
console.log(`\n⚙ ${tc.name}(${tc.arguments})`);
|
|
46
|
+
const result = await executeTool(tc.name, tc.arguments);
|
|
47
|
+
history.push({
|
|
48
|
+
role: 'tool',
|
|
49
|
+
tool_call_id: tc.id,
|
|
50
|
+
content: result,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// 继续下一轮,让模型基于工具结果接着回答
|
|
54
|
+
}
|
|
55
|
+
console.log('\n(已达到工具调用轮数上限)');
|
|
56
|
+
}
|
|
15
57
|
export function startRepl() {
|
|
16
58
|
// 启动即解析配置:缺失就明确报错退出,不进入循环
|
|
17
59
|
const result = loadConfig();
|
|
@@ -60,17 +102,14 @@ export function startRepl() {
|
|
|
60
102
|
return;
|
|
61
103
|
}
|
|
62
104
|
busy = true;
|
|
105
|
+
const mark = history.length; // 出错时回滚到这里,避免半截上下文
|
|
63
106
|
history.push({ role: 'user', content: input });
|
|
64
107
|
try {
|
|
65
|
-
|
|
66
|
-
const reply = await ai.chat(history, (delta) => process.stdout.write(delta));
|
|
67
|
-
process.stdout.write('\n'); // 回复末尾补个换行
|
|
68
|
-
history.push({ role: 'assistant', content: reply });
|
|
108
|
+
await runAgentTurn(ai, history);
|
|
69
109
|
}
|
|
70
110
|
catch (e) {
|
|
71
111
|
console.error(`\n[请求失败] ${e?.message ?? e}`);
|
|
72
|
-
|
|
73
|
-
history.pop();
|
|
112
|
+
history.length = mark; // 回滚本轮新增的所有消息
|
|
74
113
|
}
|
|
75
114
|
finally {
|
|
76
115
|
busy = false;
|
package/dist/repl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAoB,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,MAAM,GAAG,IAAI,CAAC;AAEpB,MAAM,aAAa,GACf,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具(Tools)—— 让模型能「做事」,而不只是「说话」。
|
|
3
|
+
*
|
|
4
|
+
* 每个工具声明三样东西:名字、给模型看的说明、参数的 JSON Schema。
|
|
5
|
+
* 模型据此决定「要不要调用、传什么参数」;我们负责真正执行并把结果回传。
|
|
6
|
+
*
|
|
7
|
+
* 本步是一套「只读勘探」工具,安全无副作用:
|
|
8
|
+
* read_file —— 按行分页读文件
|
|
9
|
+
* list_dir —— 列目录
|
|
10
|
+
* grep —— 在文件里按正则搜索
|
|
11
|
+
* 写入类工具(会改磁盘 / 跑命令)与权限确认留到后续步骤。
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
14
|
+
import { resolve, join, relative } from 'node:path';
|
|
15
|
+
// —— 一些体量上限,避免把过多内容塞进上下文 —— //
|
|
16
|
+
/** read_file 一次默认最多读多少行 */
|
|
17
|
+
const DEFAULT_LINES = 2000;
|
|
18
|
+
/** 单行过长时截断到多少字符(应对压缩过的代码) */
|
|
19
|
+
const MAX_LINE_CHARS = 1000;
|
|
20
|
+
/** grep 最多返回多少条命中 */
|
|
21
|
+
const MAX_MATCHES = 200;
|
|
22
|
+
/** grep 跳过的目录 */
|
|
23
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.ccli']);
|
|
24
|
+
/** 单行过长则截断。 */
|
|
25
|
+
function clampLine(line) {
|
|
26
|
+
return line.length > MAX_LINE_CHARS
|
|
27
|
+
? line.slice(0, MAX_LINE_CHARS) + ' …(行过长已截断)'
|
|
28
|
+
: line;
|
|
29
|
+
}
|
|
30
|
+
const readFileTool = {
|
|
31
|
+
name: 'read_file',
|
|
32
|
+
description: '读取文本文件内容(带行号)。默认从第 1 行读,最多读若干行;文件很大时用 offset/limit 分页续读。需要查看代码或文件真实内容时使用,不要凭空猜测。',
|
|
33
|
+
parameters: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
path: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: '要读取的文件路径,相对(相对当前工作目录)或绝对均可',
|
|
39
|
+
},
|
|
40
|
+
offset: {
|
|
41
|
+
type: 'integer',
|
|
42
|
+
description: '起始行号(从 1 开始),默认 1。用于分页续读大文件',
|
|
43
|
+
},
|
|
44
|
+
limit: {
|
|
45
|
+
type: 'integer',
|
|
46
|
+
description: `最多读取的行数,默认 ${DEFAULT_LINES}`,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: ['path'],
|
|
50
|
+
},
|
|
51
|
+
async run({ path, offset = 1, limit = DEFAULT_LINES, }) {
|
|
52
|
+
const abs = resolve(process.cwd(), path);
|
|
53
|
+
const lines = readFileSync(abs, 'utf8').split('\n');
|
|
54
|
+
const start = Math.max(1, Math.floor(offset)); // 1-based 起始行
|
|
55
|
+
const slice = lines.slice(start - 1, start - 1 + Math.max(1, limit));
|
|
56
|
+
const end = start - 1 + slice.length; // 实际读到的最后一行号
|
|
57
|
+
const body = slice
|
|
58
|
+
.map((line, i) => `${start + i}\t${clampLine(line)}`)
|
|
59
|
+
.join('\n');
|
|
60
|
+
// 还有后续内容 → 提示模型如何续读
|
|
61
|
+
const footer = end < lines.length
|
|
62
|
+
? `\n…(共 ${lines.length} 行,还剩 ${lines.length - end} 行,用 offset=${end + 1} 续读)`
|
|
63
|
+
: '';
|
|
64
|
+
return body + footer;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const listDirTool = {
|
|
68
|
+
name: 'list_dir',
|
|
69
|
+
description: '列出某个目录下的文件与子目录(子目录名带结尾斜杠 /)。用于了解项目结构、决定接下来读哪个文件。',
|
|
70
|
+
parameters: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
path: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: '目录路径,默认当前工作目录(.)',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: [],
|
|
79
|
+
},
|
|
80
|
+
async run({ path = '.' }) {
|
|
81
|
+
const abs = resolve(process.cwd(), path);
|
|
82
|
+
const entries = readdirSync(abs, { withFileTypes: true });
|
|
83
|
+
if (entries.length === 0)
|
|
84
|
+
return '(空目录)';
|
|
85
|
+
// 目录在前、文件在后,各自按名字排序
|
|
86
|
+
const dirs = entries
|
|
87
|
+
.filter((e) => e.isDirectory())
|
|
88
|
+
.map((e) => e.name + '/')
|
|
89
|
+
.sort();
|
|
90
|
+
const files = entries
|
|
91
|
+
.filter((e) => !e.isDirectory())
|
|
92
|
+
.map((e) => e.name)
|
|
93
|
+
.sort();
|
|
94
|
+
return [...dirs, ...files].join('\n');
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
/** 递归遍历目录下的所有文件(跳过 SKIP_DIRS)。 */
|
|
98
|
+
function* walkFiles(dir) {
|
|
99
|
+
let entries;
|
|
100
|
+
try {
|
|
101
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return; // 读不了的目录直接跳过
|
|
105
|
+
}
|
|
106
|
+
for (const e of entries) {
|
|
107
|
+
const full = join(dir, e.name);
|
|
108
|
+
if (e.isDirectory()) {
|
|
109
|
+
if (!SKIP_DIRS.has(e.name))
|
|
110
|
+
yield* walkFiles(full);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
yield full;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const grepTool = {
|
|
118
|
+
name: 'grep',
|
|
119
|
+
description: '在文件内容里按正则表达式搜索,返回命中的「文件:行号: 该行内容」。用于在项目里定位符号/关键字,比逐个读文件高效。path 为目录时递归搜索(自动跳过 node_modules/.git/dist)。',
|
|
120
|
+
parameters: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
pattern: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: '正则表达式(JavaScript 语法)',
|
|
126
|
+
},
|
|
127
|
+
path: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
description: '搜索范围:文件或目录,默认当前目录(.)',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ['pattern'],
|
|
133
|
+
},
|
|
134
|
+
async run({ pattern, path = '.', }) {
|
|
135
|
+
let re;
|
|
136
|
+
try {
|
|
137
|
+
re = new RegExp(pattern);
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
return `错误:非法正则表达式 ${pattern}(${e?.message})`;
|
|
141
|
+
}
|
|
142
|
+
const absRoot = resolve(process.cwd(), path);
|
|
143
|
+
const files = statSync(absRoot).isDirectory()
|
|
144
|
+
? [...walkFiles(absRoot)]
|
|
145
|
+
: [absRoot];
|
|
146
|
+
const out = [];
|
|
147
|
+
for (const f of files) {
|
|
148
|
+
let text;
|
|
149
|
+
try {
|
|
150
|
+
text = readFileSync(f, 'utf8');
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
continue; // 读不了就跳过
|
|
154
|
+
}
|
|
155
|
+
if (text.includes("\u0000"))
|
|
156
|
+
continue; // 含 NUL 字符,疑似二进制,跳过
|
|
157
|
+
const rel = relative(process.cwd(), f);
|
|
158
|
+
const flines = text.split('\n');
|
|
159
|
+
for (let i = 0; i < flines.length; i++) {
|
|
160
|
+
if (re.test(flines[i])) {
|
|
161
|
+
out.push(`${rel}:${i + 1}: ${clampLine(flines[i].trim())}`);
|
|
162
|
+
if (out.length >= MAX_MATCHES) {
|
|
163
|
+
out.push(`…(已达 ${MAX_MATCHES} 条上限,请缩小范围或细化正则)`);
|
|
164
|
+
return out.join('\n');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return out.length > 0 ? out.join('\n') : `没有匹配 “${pattern}” 的行。`;
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
/** 已注册的工具集合(后续步骤在此追加)。 */
|
|
173
|
+
export const tools = [readFileTool, listDirTool, grepTool];
|
|
174
|
+
/** 转成 OpenAI function-calling 需要的工具定义数组。 */
|
|
175
|
+
export function toolDefs() {
|
|
176
|
+
return tools.map((t) => ({
|
|
177
|
+
type: 'function',
|
|
178
|
+
function: {
|
|
179
|
+
name: t.name,
|
|
180
|
+
description: t.description,
|
|
181
|
+
parameters: t.parameters,
|
|
182
|
+
},
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 按名字执行工具,返回给模型的文本结果。
|
|
187
|
+
* 任何错误都转成文本返回(让模型看到失败原因并自行决定下一步),而不是抛异常。
|
|
188
|
+
*/
|
|
189
|
+
export async function executeTool(name, argsJson) {
|
|
190
|
+
const tool = tools.find((t) => t.name === name);
|
|
191
|
+
if (!tool)
|
|
192
|
+
return `错误:未知工具 ${name}`;
|
|
193
|
+
let args;
|
|
194
|
+
try {
|
|
195
|
+
args = JSON.parse(argsJson || '{}');
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return `错误:参数不是合法 JSON:${argsJson}`;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
return await tool.run(args);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
return `错误:${e?.message ?? String(e)}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|