@gird/ccli 2.0.3 → 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 +47 -9
- package/dist/ai.js.map +1 -1
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/repl.js +64 -12
- package/dist/repl.js.map +1 -1
- package/dist/slash.js +93 -0
- package/dist/slash.js.map +1 -0
- 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;
|
|
@@ -18,27 +19,64 @@ export class AIClient {
|
|
|
18
19
|
});
|
|
19
20
|
this.model = config.model;
|
|
20
21
|
}
|
|
22
|
+
getModel() {
|
|
23
|
+
return this.model;
|
|
24
|
+
}
|
|
25
|
+
setModel(model) {
|
|
26
|
+
this.model = model;
|
|
27
|
+
}
|
|
28
|
+
/** 拉取后端可用模型列表(结果缓存,避免重复请求)。 */
|
|
29
|
+
modelCache;
|
|
30
|
+
async listModels() {
|
|
31
|
+
if (!this.modelCache) {
|
|
32
|
+
const list = await this.client.models.list();
|
|
33
|
+
this.modelCache = list.data.map((m) => m.id).sort();
|
|
34
|
+
}
|
|
35
|
+
return this.modelCache;
|
|
36
|
+
}
|
|
21
37
|
/**
|
|
22
38
|
* 发送整段对话历史,流式接收助手回复。
|
|
23
|
-
* @param messages 完整历史(含本轮 user
|
|
39
|
+
* @param messages 完整历史(含本轮 user 消息、以及可能的工具结果)
|
|
24
40
|
* @param onDelta 每收到一小段文本就回调(用于实时打印)
|
|
25
|
-
* @returns
|
|
41
|
+
* @returns 助手本回合的文本 + 它想调用的工具
|
|
26
42
|
*/
|
|
27
43
|
async chat(messages, onDelta) {
|
|
28
44
|
const stream = await this.client.chat.completions.create({
|
|
29
45
|
model: this.model,
|
|
30
46
|
messages,
|
|
47
|
+
tools: toolDefs(),
|
|
31
48
|
stream: true,
|
|
32
49
|
});
|
|
33
|
-
let
|
|
50
|
+
let content = '';
|
|
51
|
+
// 工具调用在流里是分片到达的,按 index 累加拼回完整的 id/name/arguments
|
|
52
|
+
const acc = {};
|
|
34
53
|
for await (const chunk of stream) {
|
|
35
|
-
const delta = chunk.choices[0]?.delta
|
|
36
|
-
if (delta)
|
|
37
|
-
|
|
38
|
-
|
|
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;
|
|
39
73
|
}
|
|
40
74
|
}
|
|
41
|
-
|
|
75
|
+
const toolCalls = Object.keys(acc)
|
|
76
|
+
.map(Number)
|
|
77
|
+
.sort((a, b) => a - b)
|
|
78
|
+
.map((i) => acc[i]);
|
|
79
|
+
return { content, toolCalls };
|
|
42
80
|
}
|
|
43
81
|
}
|
|
44
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/config.js
CHANGED
|
@@ -44,12 +44,15 @@ export function loadConfig(cwd = process.cwd()) {
|
|
|
44
44
|
const global = loadGlobalConfig();
|
|
45
45
|
// 按优先级取一个环境风格的键:环境变量 > .env
|
|
46
46
|
const get = (key) => process.env[key] ?? dotenv[key];
|
|
47
|
+
// 判断某个键的值来自哪个来源(用于诊断展示)
|
|
48
|
+
const sourceOf = (key) => process.env[key] !== undefined ? '环境变量' : '项目 .env';
|
|
47
49
|
// —— 后端识别(按 Key 出现顺序)——
|
|
48
50
|
// 1) Anthropic
|
|
49
51
|
const anthropicKey = get('ANTHROPIC_API_KEY');
|
|
50
52
|
if (anthropicKey) {
|
|
51
53
|
return {
|
|
52
54
|
ok: true,
|
|
55
|
+
source: sourceOf('ANTHROPIC_API_KEY'),
|
|
53
56
|
config: {
|
|
54
57
|
provider: 'anthropic',
|
|
55
58
|
apiKey: anthropicKey,
|
|
@@ -65,6 +68,7 @@ export function loadConfig(cwd = process.cwd()) {
|
|
|
65
68
|
if (qwenKey) {
|
|
66
69
|
return {
|
|
67
70
|
ok: true,
|
|
71
|
+
source: sourceOf('QWEN_API_KEY'),
|
|
68
72
|
config: {
|
|
69
73
|
provider: 'openai',
|
|
70
74
|
apiKey: qwenKey,
|
|
@@ -78,6 +82,7 @@ export function loadConfig(cwd = process.cwd()) {
|
|
|
78
82
|
if (openaiKey) {
|
|
79
83
|
return {
|
|
80
84
|
ok: true,
|
|
85
|
+
source: sourceOf('OPENAI_API_KEY'),
|
|
81
86
|
config: {
|
|
82
87
|
provider: 'openai',
|
|
83
88
|
apiKey: openaiKey,
|
|
@@ -92,6 +97,7 @@ export function loadConfig(cwd = process.cwd()) {
|
|
|
92
97
|
if (global.apiKey && global.provider) {
|
|
93
98
|
return {
|
|
94
99
|
ok: true,
|
|
100
|
+
source: '全局 ~/.ccli/config.json',
|
|
95
101
|
config: {
|
|
96
102
|
provider: global.provider,
|
|
97
103
|
apiKey: global.apiKey,
|
|
@@ -117,4 +123,24 @@ export function maskKey(key) {
|
|
|
117
123
|
return '****';
|
|
118
124
|
return `${key.slice(0, 4)}…${key.slice(-4)}`;
|
|
119
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* `ccli config` 诊断命令:打印解析出的最终配置(密钥打码),
|
|
128
|
+
* 让用户不进对话就能确认「读到了什么、来自哪里」。
|
|
129
|
+
* @returns 进程退出码:配置有效 0,缺失 1
|
|
130
|
+
*/
|
|
131
|
+
export function printConfig(cwd = process.cwd()) {
|
|
132
|
+
const result = loadConfig(cwd);
|
|
133
|
+
if (!result.ok) {
|
|
134
|
+
console.error(result.message);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
const { config, source } = result;
|
|
138
|
+
console.log('CCLI 配置诊断');
|
|
139
|
+
console.log(` 后端 (provider): ${config.provider}`);
|
|
140
|
+
console.log(` 模型 (model): ${config.model}`);
|
|
141
|
+
console.log(` 地址 (baseURL): ${config.baseURL ?? '(默认)'}`);
|
|
142
|
+
console.log(` 密钥 (apiKey): ${maskKey(config.apiKey)}`);
|
|
143
|
+
console.log(` 来源 (source): ${source}`);
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
120
146
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAYvC,+BAA+B;AAC/B,MAAM,aAAa,GAA6B;IAC5C,SAAS,EAAE,mBAAmB;IAC9B,MAAM,EAAE,WAAW;CACtB,CAAC;AAEF,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AAEnE,kDAAkD;AAClD,SAAS,gBAAgB;IACrB,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;QACjE,OAAO;YACH,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG,CAAC,KAAK;SACnB,CAAC;IACN,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACtB,MAAc,OAAO,CAAC,GAAG,EAAE;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAYvC,+BAA+B;AAC/B,MAAM,aAAa,GAA6B;IAC5C,SAAS,EAAE,mBAAmB;IAC9B,MAAM,EAAE,WAAW;CACtB,CAAC;AAEF,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AAEnE,kDAAkD;AAClD,SAAS,gBAAgB;IACrB,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;QACjE,OAAO;YACH,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG,CAAC,KAAK;SACnB,CAAC;IACN,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACtB,MAAc,OAAO,CAAC,GAAG,EAAE;IAI3B,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAElC,4BAA4B;IAC5B,MAAM,GAAG,GAAG,CAAC,GAAW,EAAsB,EAAE,CAC5C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;IAEpC,wBAAwB;IACxB,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAU,EAAE,CACrC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAExD,wBAAwB;IACxB,eAAe;IACf,MAAM,YAAY,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC9C,IAAI,YAAY,EAAE,CAAC;QACf,OAAO;YACH,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,QAAQ,CAAC,mBAAmB,CAAC;YACrC,MAAM,EAAE;gBACJ,QAAQ,EAAE,WAAW;gBACrB,MAAM,EAAE,YAAY;gBACpB,OAAO,EAAE,GAAG,CAAC,oBAAoB,CAAC;gBAClC,KAAK,EACD,GAAG,CAAC,iBAAiB,CAAC;oBACtB,GAAG,CAAC,YAAY,CAAC;oBACjB,aAAa,CAAC,SAAS;aAC9B;SACJ,CAAC;IACN,CAAC;IAED,+BAA+B;IAC/B,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC;IACpC,IAAI,OAAO,EAAE,CAAC;QACV,OAAO;YACH,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC;YAChC,MAAM,EAAE;gBACJ,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,GAAG,CAAC,eAAe,CAAC;gBAC7B,KAAK,EAAE,GAAG,CAAC,YAAY,CAAC,IAAI,aAAa,CAAC,MAAM;aACnD;SACJ,CAAC;IACN,CAAC;IAED,yCAAyC;IACzC,MAAM,SAAS,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACxC,IAAI,SAAS,EAAE,CAAC;QACZ,OAAO;YACH,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC;YAClC,MAAM,EAAE;gBACJ,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC;gBAC/B,KAAK,EACD,GAAG,CAAC,cAAc,CAAC;oBACnB,GAAG,CAAC,YAAY,CAAC;oBACjB,aAAa,CAAC,MAAM;aAC3B;SACJ,CAAC;IACN,CAAC;IAED,aAAa;IACb,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACnC,OAAO;YACH,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,wBAAwB;YAChC,MAAM,EAAE;gBACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC;aACxD;SACJ,CAAC;IACN,CAAC;IAED,yBAAyB;IACzB,OAAO;QACH,EAAE,EAAE,KAAK;QACT,OAAO,EAAE;YACL,sBAAsB;YACtB,0CAA0C;YAC1C,6DAA6D;YAC7D,oEAAoE;SACvE,CAAC,IAAI,CAAC,IAAI,CAAC;KACf,CAAC;AACN,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,OAAO,CAAC,GAAW;IAC/B,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACnC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACnD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAE/B,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,CAAC,CAAC;IACb,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACzB,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,oBAAoB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,CAAC;AACb,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11,17 +11,22 @@
|
|
|
11
11
|
import { parseArgs } from './args.js';
|
|
12
12
|
import { NAME, VERSION } from './version.js';
|
|
13
13
|
import { startRepl } from './repl.js';
|
|
14
|
+
import { printConfig } from './config.js';
|
|
14
15
|
const HELP = `${NAME} v${VERSION}
|
|
15
16
|
|
|
16
17
|
用法:
|
|
17
|
-
ccli [选项]
|
|
18
|
+
ccli [命令] [选项]
|
|
19
|
+
|
|
20
|
+
命令:
|
|
21
|
+
config 打印当前解析出的配置(诊断用,密钥打码)
|
|
18
22
|
|
|
19
23
|
选项:
|
|
20
24
|
-v, --version 打印版本号
|
|
21
25
|
-h, --help 打印本帮助
|
|
22
26
|
|
|
23
27
|
示例:
|
|
24
|
-
ccli
|
|
28
|
+
ccli 启动交互式对话
|
|
29
|
+
ccli config 查看当前生效的后端 / 模型 / 密钥来源
|
|
25
30
|
ccli --version 查看版本
|
|
26
31
|
`;
|
|
27
32
|
function main() {
|
|
@@ -40,6 +45,16 @@ function main() {
|
|
|
40
45
|
console.log(VERSION);
|
|
41
46
|
return;
|
|
42
47
|
}
|
|
48
|
+
// 子命令分发(位置参数的第一个)
|
|
49
|
+
const command = args.positionals[0];
|
|
50
|
+
if (command === 'config') {
|
|
51
|
+
process.exit(printConfig());
|
|
52
|
+
}
|
|
53
|
+
if (command) {
|
|
54
|
+
console.error(`未知命令: ${command}`);
|
|
55
|
+
console.error(`运行 "ccli --help" 查看可用命令。`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
43
58
|
// 无参数:进入交互式会话循环。
|
|
44
59
|
startRepl();
|
|
45
60
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,IAAI,GAAG,GAAG,IAAI,KAAK,OAAO;;;;;;;;;;;;;;;;CAgB/B,CAAC;AAEF,SAAS,IAAI;IACT,uCAAuC;IACvC,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9C,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACX,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO;IACX,CAAC;IAED,kBAAkB;IAClB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACpC,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,iBAAiB;IACjB,SAAS,EAAE,CAAC;AAChB,CAAC;AAED,IAAI,EAAE,CAAC"}
|
package/dist/repl.js
CHANGED
|
@@ -9,10 +9,51 @@ import { createInterface } from 'node:readline';
|
|
|
9
9
|
import { NAME, VERSION } from './version.js';
|
|
10
10
|
import { loadConfig, maskKey } from './config.js';
|
|
11
11
|
import { AIClient } from './ai.js';
|
|
12
|
+
import { handleSlash } from './slash.js';
|
|
13
|
+
import { executeTool } from './tools.js';
|
|
12
14
|
const PROMPT = '› ';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+
}
|
|
16
57
|
export function startRepl() {
|
|
17
58
|
// 启动即解析配置:缺失就明确报错退出,不进入循环
|
|
18
59
|
const result = loadConfig();
|
|
@@ -31,7 +72,7 @@ export function startRepl() {
|
|
|
31
72
|
});
|
|
32
73
|
console.log(`${NAME} v${VERSION}`);
|
|
33
74
|
console.log(`后端: ${config.provider} 模型: ${config.model} 密钥: ${maskKey(config.apiKey)}`);
|
|
34
|
-
console.log('进入交互式会话。输入 /exit
|
|
75
|
+
console.log('进入交互式会话。输入 /help 查看命令,/exit 退出。\n');
|
|
35
76
|
rl.prompt();
|
|
36
77
|
// 正在等待 AI 回复时置 true,避免这期间的输入被重复处理
|
|
37
78
|
let busy = false;
|
|
@@ -42,22 +83,33 @@ export function startRepl() {
|
|
|
42
83
|
rl.prompt();
|
|
43
84
|
return;
|
|
44
85
|
}
|
|
45
|
-
|
|
46
|
-
|
|
86
|
+
// 斜杠命令:交给 handleSlash,不发给 AI
|
|
87
|
+
// (/model 会联网列模型,故也置 busy 防止期间重复处理)
|
|
88
|
+
if (input.startsWith('/')) {
|
|
89
|
+
busy = true;
|
|
90
|
+
let exiting = false;
|
|
91
|
+
await handleSlash(input, {
|
|
92
|
+
history,
|
|
93
|
+
ai,
|
|
94
|
+
requestExit: () => {
|
|
95
|
+
exiting = true;
|
|
96
|
+
rl.close();
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
busy = false;
|
|
100
|
+
if (!exiting)
|
|
101
|
+
rl.prompt(); // 退出时不再打印提示符
|
|
47
102
|
return;
|
|
48
103
|
}
|
|
49
104
|
busy = true;
|
|
105
|
+
const mark = history.length; // 出错时回滚到这里,避免半截上下文
|
|
50
106
|
history.push({ role: 'user', content: input });
|
|
51
107
|
try {
|
|
52
|
-
|
|
53
|
-
const reply = await ai.chat(history, (delta) => process.stdout.write(delta));
|
|
54
|
-
process.stdout.write('\n'); // 回复末尾补个换行
|
|
55
|
-
history.push({ role: 'assistant', content: reply });
|
|
108
|
+
await runAgentTurn(ai, history);
|
|
56
109
|
}
|
|
57
110
|
catch (e) {
|
|
58
111
|
console.error(`\n[请求失败] ${e?.message ?? e}`);
|
|
59
|
-
|
|
60
|
-
history.pop();
|
|
112
|
+
history.length = mark; // 回滚本轮新增的所有消息
|
|
61
113
|
}
|
|
62
114
|
finally {
|
|
63
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;
|
|
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;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,MAAM,GAAG,IAAI,CAAC;AAEpB,MAAM,aAAa,GACf,uCAAuC;IACvC,6CAA6C,CAAC;AAElD,sCAAsC;AACtC,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B;;;;GAIG;AACH,KAAK,UAAU,YAAY,CAAC,EAAY,EAAE,OAAsB;IAC5D,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,eAAe,EAAE,KAAK,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAC9B,CAAC;QAEF,uBAAuB;QACvB,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3D,OAAO;QACX,CAAC;QAED,yCAAyC;QACzC,OAAO,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;YAC7B,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACpC,EAAE,EAAE,EAAE,CAAC,EAAE;gBACT,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE;aACvD,CAAC,CAAC;SACN,CAAC,CAAC;QAEH,8BAA8B;QAC9B,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,MAAM;gBACZ,YAAY,EAAE,EAAE,CAAC,EAAE;gBACnB,OAAO,EAAE,MAAM;aAClB,CAAC,CAAC;QACP,CAAC;QACD,sBAAsB;IAC1B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,SAAS;IACrB,0BAA0B;IAC1B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAE1B,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,+CAA+C;IAC/C,MAAM,OAAO,GAAkB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;IAE5E,MAAM,EAAE,GAAG,eAAe,CAAC;QACvB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,MAAM;KACjB,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CACP,OAAO,MAAM,CAAC,QAAQ,SAAS,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAC/E,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACjD,EAAE,CAAC,MAAM,EAAE,CAAC;IAEZ,kCAAkC;IAClC,IAAI,IAAI,GAAG,KAAK,CAAC;IAEjB,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAE1B,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,IAAI;gBAAE,EAAE,CAAC,MAAM,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QAED,6BAA6B;QAC7B,oCAAoC;QACpC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,GAAG,IAAI,CAAC;YACZ,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,MAAM,WAAW,CAAC,KAAK,EAAE;gBACrB,OAAO;gBACP,EAAE;gBACF,WAAW,EAAE,GAAG,EAAE;oBACd,OAAO,GAAG,IAAI,CAAC;oBACf,EAAE,CAAC,KAAK,EAAE,CAAC;gBACf,CAAC;aACJ,CAAC,CAAC;YACH,IAAI,GAAG,KAAK,CAAC;YACb,IAAI,CAAC,OAAO;gBAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,aAAa;YACxC,OAAO;QACX,CAAC;QAED,IAAI,GAAG,IAAI,CAAC;QACZ,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,mBAAmB;QAChD,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAE/C,IAAI,CAAC;YACD,MAAM,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,cAAc;QACzC,CAAC;gBAAS,CAAC;YACP,IAAI,GAAG,KAAK,CAAC;YACb,EAAE,CAAC,MAAM,EAAE,CAAC;QAChB,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,6BAA6B;IAC7B,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QAChB,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,0BAA0B;IAC1B,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACjB,EAAE,CAAC,KAAK,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;AACP,CAAC"}
|
package/dist/slash.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** 命令清单:名称 → 说明,供 /help 展示,单一数据源。 */
|
|
2
|
+
const COMMANDS = [
|
|
3
|
+
['/help', '列出所有命令'],
|
|
4
|
+
['/clear', '清空对话历史(保留系统提示)'],
|
|
5
|
+
['/model [id]', '查看或切换模型'],
|
|
6
|
+
['/exit, /quit', '退出会话'],
|
|
7
|
+
];
|
|
8
|
+
/** /model 搜索时最多列出多少个候选,避免刷屏。 */
|
|
9
|
+
const MAX_MODEL_LIST = 30;
|
|
10
|
+
/**
|
|
11
|
+
* 处理一条斜杠命令。
|
|
12
|
+
* @returns true 表示已作为命令处理(不应再发给 AI)
|
|
13
|
+
*/
|
|
14
|
+
export async function handleSlash(input, ctx) {
|
|
15
|
+
// 去掉开头的 '/',按空白拆成命令名 + 参数
|
|
16
|
+
const [name, ...rest] = input.slice(1).split(/\s+/);
|
|
17
|
+
const arg = rest.join(' ').trim();
|
|
18
|
+
switch (name) {
|
|
19
|
+
case 'help':
|
|
20
|
+
printHelp();
|
|
21
|
+
return true;
|
|
22
|
+
case 'clear':
|
|
23
|
+
// 保留第 0 条 system 提示,清掉其余历史
|
|
24
|
+
ctx.history.length = 1;
|
|
25
|
+
console.log('(已清空对话历史)');
|
|
26
|
+
return true;
|
|
27
|
+
case 'model':
|
|
28
|
+
await handleModel(arg, ctx);
|
|
29
|
+
return true;
|
|
30
|
+
case 'exit':
|
|
31
|
+
case 'quit':
|
|
32
|
+
ctx.requestExit();
|
|
33
|
+
return true;
|
|
34
|
+
default:
|
|
35
|
+
console.log(`未知命令:/${name},输入 /help 查看可用命令。`);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* /model:无参看当前模型 + 用法;带参先按完整名精确切换,
|
|
41
|
+
* 否则当作关键词搜索列出候选(复制完整名再切)。
|
|
42
|
+
*/
|
|
43
|
+
async function handleModel(arg, ctx) {
|
|
44
|
+
// 拉列表用于校验 / 搜索;后端不支持列模型时降级为「不校验直接切」
|
|
45
|
+
let models = null;
|
|
46
|
+
try {
|
|
47
|
+
models = await ctx.ai.listModels();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
models = null;
|
|
51
|
+
}
|
|
52
|
+
if (!arg) {
|
|
53
|
+
console.log(`当前模型:${ctx.ai.getModel()}`);
|
|
54
|
+
if (models) {
|
|
55
|
+
console.log(`可用模型共 ${models.length} 个。用 /model <关键词> 搜索,/model <完整名> 切换。`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log('(无法获取模型列表,可直接 /model <名称> 切换,但不校验)');
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!models) {
|
|
63
|
+
ctx.ai.setModel(arg);
|
|
64
|
+
console.log(`已切换模型:${arg}(未校验,若不存在下次请求会报错)`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// 完整名精确命中 → 真正切换
|
|
68
|
+
if (models.includes(arg)) {
|
|
69
|
+
ctx.ai.setModel(arg);
|
|
70
|
+
console.log(`已切换模型:${arg}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// 否则当关键词搜索,列出候选(不切换)
|
|
74
|
+
const kw = arg.toLowerCase();
|
|
75
|
+
const matches = models.filter((m) => m.toLowerCase().includes(kw));
|
|
76
|
+
if (matches.length === 0) {
|
|
77
|
+
console.log(`没有找到包含 “${arg}” 的模型(当前仍为 ${ctx.ai.getModel()})。`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log(`未精确匹配 “${arg}”,以下 ${matches.length} 个可选(复制完整名切换,当前仍为 ${ctx.ai.getModel()}):`);
|
|
81
|
+
for (const m of matches.slice(0, MAX_MODEL_LIST))
|
|
82
|
+
console.log(` ${m}`);
|
|
83
|
+
if (matches.length > MAX_MODEL_LIST) {
|
|
84
|
+
console.log(` … 还有 ${matches.length - MAX_MODEL_LIST} 个,请细化关键词`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function printHelp() {
|
|
88
|
+
console.log('可用命令:');
|
|
89
|
+
for (const [cmd, desc] of COMMANDS) {
|
|
90
|
+
console.log(` ${cmd.padEnd(14)} ${desc}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=slash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash.js","sourceRoot":"","sources":["../src/slash.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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"}
|