@saber2pr/ai-agent 0.0.5 → 0.0.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/lib/agent.d.ts +29 -1
- package/lib/agent.js +111 -12
- package/lib/cli.d.ts +2 -0
- package/lib/cli.js +9 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +18 -4
- package/package.json +2 -2
package/lib/agent.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
export interface CustomTool {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
parameters: any;
|
|
5
|
+
handler: (args: any) => Promise<any>;
|
|
6
|
+
}
|
|
7
|
+
export interface AgentOptions {
|
|
8
|
+
targetDir?: string;
|
|
9
|
+
/** 自定义工具扩展 */
|
|
10
|
+
tools?: any[];
|
|
11
|
+
/** 注入到 System Prompt 中的额外指令/规则/上下文 */
|
|
12
|
+
extraSystemPrompt?: any;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
}
|
|
1
15
|
export default class McpAgent {
|
|
2
16
|
private openai;
|
|
3
17
|
private modelName;
|
|
@@ -6,12 +20,15 @@ export default class McpAgent {
|
|
|
6
20
|
private messages;
|
|
7
21
|
private engine;
|
|
8
22
|
private encoder;
|
|
9
|
-
|
|
23
|
+
private extraTools;
|
|
24
|
+
private maxTokens;
|
|
25
|
+
constructor(options?: AgentOptions);
|
|
10
26
|
/**
|
|
11
27
|
* 计算当前消息列表的总 Token 消耗
|
|
12
28
|
* 兼容多模态内容 (Content Parts) 和 工具调用 (Tool Calls)
|
|
13
29
|
*/
|
|
14
30
|
private calculateTokens;
|
|
31
|
+
private injectCustomTools;
|
|
15
32
|
/**
|
|
16
33
|
* 核心功能:内置代码分析工具
|
|
17
34
|
* 这里的逻辑直接调用 PromptEngine,不走网络请求,效率极高
|
|
@@ -21,9 +38,20 @@ export default class McpAgent {
|
|
|
21
38
|
private loadMcpConfigs;
|
|
22
39
|
init(): Promise<void>;
|
|
23
40
|
private processChat;
|
|
41
|
+
/**
|
|
42
|
+
* 裁剪上下文消息列表
|
|
43
|
+
* 保留第一条 System 消息,并移除中间的旧消息直到低于阈值
|
|
44
|
+
*/
|
|
45
|
+
private pruneMessages;
|
|
24
46
|
/**
|
|
25
47
|
* 简易 Loading 动画辅助函数
|
|
26
48
|
*/
|
|
27
49
|
private showLoading;
|
|
50
|
+
/**
|
|
51
|
+
* 编程式对话入口
|
|
52
|
+
* @param input 用户指令
|
|
53
|
+
* @returns AI 的最终答复内容
|
|
54
|
+
*/
|
|
55
|
+
chat(input: string): Promise<string>;
|
|
28
56
|
start(): Promise<void>;
|
|
29
57
|
}
|
package/lib/agent.js
CHANGED
|
@@ -47,24 +47,48 @@ const ts_context_mcp_1 = require("@saber2pr/ts-context-mcp"); // 引入我们的
|
|
|
47
47
|
const js_tiktoken_1 = require("js-tiktoken");
|
|
48
48
|
const CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
|
|
49
49
|
class McpAgent {
|
|
50
|
-
constructor() {
|
|
50
|
+
constructor(options) {
|
|
51
51
|
this.modelName = "";
|
|
52
52
|
this.clients = [];
|
|
53
53
|
this.allTools = [];
|
|
54
54
|
this.messages = [];
|
|
55
55
|
this.encoder = (0, js_tiktoken_1.getEncoding)("cl100k_base");
|
|
56
|
-
|
|
57
|
-
this.engine = new ts_context_mcp_1.PromptEngine(process.cwd());
|
|
56
|
+
this.extraTools = [];
|
|
57
|
+
this.engine = new ts_context_mcp_1.PromptEngine((options === null || options === void 0 ? void 0 : options.targetDir) || process.cwd());
|
|
58
|
+
this.extraTools = (options === null || options === void 0 ? void 0 : options.tools) || []; // 接收外部传入的工具
|
|
59
|
+
this.maxTokens = (options === null || options === void 0 ? void 0 : options.maxTokens) || 100000; // 默认 100k
|
|
60
|
+
let baseSystemPrompt = `你是一个专业的 AI 代码架构师,具备深度的源码分析与工程化处理能力。
|
|
61
|
+
|
|
62
|
+
### 核心操作规范:
|
|
63
|
+
1. **全局扫描(强制首选)**:在开始任何分析任务前,你【必须】首先调用 'get_repo_map'。这是你理解项目目录结构、技术栈及模块关系的唯一权威来源。
|
|
64
|
+
2. **循序渐进的分析路径**:
|
|
65
|
+
- 优先使用 'read_skeleton' 提取接口、类和函数签名,以最低的 Token 成本建立代码逻辑视图。
|
|
66
|
+
- 仅在需要深入分析具体业务逻辑、提取精准代码块或进行代码修改建议时,才使用 'read_full_code' 或 'get_method_body'。
|
|
67
|
+
3. **真实性原则**:
|
|
68
|
+
- 所有的代码分析、行号定位和逻辑推断必须基于工具返回的真实内容,严禁基于文件名进行虚假猜测。
|
|
69
|
+
- 如果工具返回结果为空或报错,应尝试调整路径或更换工具。
|
|
70
|
+
|
|
71
|
+
### 技术能力:
|
|
72
|
+
- 精通 TypeScript/JavaScript 及其 AST 结构,能准确识别各种复杂的声明与调用关系。
|
|
73
|
+
- 能够理解代码间的依赖链路,并结合项目上下文给出合理的架构建议。
|
|
74
|
+
|
|
75
|
+
### 执行准则:
|
|
76
|
+
- **任务导向**:直接通过工具链解决问题,减少不必要的中间对话。
|
|
77
|
+
- **自主决策**:根据任务需求自主选择最合适的工具组合,无需每一步都向用户请示。`;
|
|
78
|
+
// 2. 拼接额外指令
|
|
79
|
+
if (options === null || options === void 0 ? void 0 : options.extraSystemPrompt) {
|
|
80
|
+
const extra = typeof options.extraSystemPrompt === 'string'
|
|
81
|
+
? options.extraSystemPrompt
|
|
82
|
+
: JSON.stringify(options.extraSystemPrompt, null, 2);
|
|
83
|
+
baseSystemPrompt += `\n\n[额外执行指令]:\n${extra}`;
|
|
84
|
+
}
|
|
58
85
|
this.messages.push({
|
|
59
86
|
role: "system",
|
|
60
|
-
content:
|
|
61
|
-
你可以访问本地文件系统并利用 AST (抽象语法树) 技术分析代码。
|
|
62
|
-
你的核心目标是提供准确的代码结构、依赖关系和逻辑分析。
|
|
63
|
-
请先使用 get_repo_map 查看项目整体代码结构。
|
|
64
|
-
请优先使用 read_skeleton 查看结构,只有在必要时才使用 read_full_code 或 get_method_body。`,
|
|
87
|
+
content: baseSystemPrompt,
|
|
65
88
|
});
|
|
66
89
|
// 初始化内置工具
|
|
67
90
|
this.registerBuiltinTools();
|
|
91
|
+
this.injectCustomTools(); // 注入外部工具
|
|
68
92
|
}
|
|
69
93
|
/**
|
|
70
94
|
* 计算当前消息列表的总 Token 消耗
|
|
@@ -108,6 +132,19 @@ class McpAgent {
|
|
|
108
132
|
}
|
|
109
133
|
return total;
|
|
110
134
|
}
|
|
135
|
+
injectCustomTools() {
|
|
136
|
+
for (const tool of this.extraTools) {
|
|
137
|
+
this.allTools.push({
|
|
138
|
+
type: "function",
|
|
139
|
+
function: {
|
|
140
|
+
name: tool.name,
|
|
141
|
+
description: tool.description,
|
|
142
|
+
parameters: tool.parameters,
|
|
143
|
+
},
|
|
144
|
+
_handler: tool.handler,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
111
148
|
/**
|
|
112
149
|
* 核心功能:内置代码分析工具
|
|
113
150
|
* 这里的逻辑直接调用 PromptEngine,不走网络请求,效率极高
|
|
@@ -188,7 +225,13 @@ class McpAgent {
|
|
|
188
225
|
required: ["filePath", "methodName"],
|
|
189
226
|
},
|
|
190
227
|
},
|
|
191
|
-
_handler: async ({ filePath, methodName }) =>
|
|
228
|
+
_handler: async ({ filePath, methodName }) => {
|
|
229
|
+
// --- 新增:同样的 Token 守卫 ---
|
|
230
|
+
if (this.calculateTokens() > this.maxTokens) {
|
|
231
|
+
return `[SYSTEM WARNING]: Token 消耗已达上限,禁止获取详细方法体。请利用已获取的 Skeleton 信息进行分析。`;
|
|
232
|
+
}
|
|
233
|
+
return this.engine.getMethodImplementation(filePath, methodName);
|
|
234
|
+
},
|
|
192
235
|
},
|
|
193
236
|
{
|
|
194
237
|
type: "function",
|
|
@@ -205,6 +248,11 @@ class McpAgent {
|
|
|
205
248
|
},
|
|
206
249
|
// 核心实现:直接利用 fs 读取
|
|
207
250
|
_handler: async ({ filePath }) => {
|
|
251
|
+
// --- 新增:Token 守卫 ---
|
|
252
|
+
const currentTokens = this.calculateTokens();
|
|
253
|
+
if (currentTokens > this.maxTokens) {
|
|
254
|
+
return `[SYSTEM WARNING]: 当前上下文已达到 ${currentTokens} tokens (上限 ${this.maxTokens})。为了保证系统稳定,已拦截 read_full_code。请立即根据已知信息进行总结或停止阅读更多代码。`;
|
|
255
|
+
}
|
|
208
256
|
try {
|
|
209
257
|
if (typeof filePath !== 'string' || !filePath) {
|
|
210
258
|
return "Error: filePath 不能为空";
|
|
@@ -218,7 +266,11 @@ class McpAgent {
|
|
|
218
266
|
if (!fs_1.default.existsSync(fullPath)) {
|
|
219
267
|
return `Error: 文件不存在: ${filePath}`;
|
|
220
268
|
}
|
|
221
|
-
|
|
269
|
+
const content = fs_1.default.readFileSync(fullPath, "utf-8");
|
|
270
|
+
// 加上行号,AI 就能在 generate_review 里给出准确的 line 参数
|
|
271
|
+
return content.split('\n')
|
|
272
|
+
.map((line, i) => `${i + 1} | ${line}`)
|
|
273
|
+
.join('\n');
|
|
222
274
|
}
|
|
223
275
|
catch (err) {
|
|
224
276
|
return `Error: 读取文件失败: ${err.message}`;
|
|
@@ -302,9 +354,18 @@ class McpAgent {
|
|
|
302
354
|
var _a;
|
|
303
355
|
this.messages.push({ role: 'user', content: userInput });
|
|
304
356
|
while (true) {
|
|
357
|
+
// --- 新增:发送请求前先检查并裁剪 ---
|
|
358
|
+
this.pruneMessages();
|
|
305
359
|
// 打印当前上下文的累计 Token
|
|
306
360
|
const currentInputTokens = this.calculateTokens();
|
|
307
361
|
console.log(`\n📊 当前上下文累计: ${currentInputTokens} tokens`);
|
|
362
|
+
// 如果接近上限(如 80%),在消息队列中插入一条隐含的系统指令
|
|
363
|
+
if (currentInputTokens > this.maxTokens * 0.8 && currentInputTokens <= this.maxTokens) {
|
|
364
|
+
this.messages.push({
|
|
365
|
+
role: "system",
|
|
366
|
+
content: "注意:上下文即将耗尽。请停止读取新文件,优先处理现有信息并尽快输出结果。"
|
|
367
|
+
});
|
|
368
|
+
}
|
|
308
369
|
const stopLoading = this.showLoading("🤖 Agent 正在思考...");
|
|
309
370
|
let response;
|
|
310
371
|
try {
|
|
@@ -361,6 +422,24 @@ class McpAgent {
|
|
|
361
422
|
}
|
|
362
423
|
}
|
|
363
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* 裁剪上下文消息列表
|
|
427
|
+
* 保留第一条 System 消息,并移除中间的旧消息直到低于阈值
|
|
428
|
+
*/
|
|
429
|
+
pruneMessages() {
|
|
430
|
+
const currentTokens = this.calculateTokens();
|
|
431
|
+
if (currentTokens <= this.maxTokens)
|
|
432
|
+
return;
|
|
433
|
+
console.log(`\n⚠️ 上下文达到限制 (${currentTokens} tokens),正在自动裁剪...`);
|
|
434
|
+
// 策略:保留索引 0 (System),从索引 1 开始删除
|
|
435
|
+
// 每次删除一对 (通常是助理请求 + 工具回复,或者用户提问 + 助理回答)
|
|
436
|
+
while (this.calculateTokens() > this.maxTokens && this.messages.length > 2) {
|
|
437
|
+
// 始终保留系统提示词 (index 0) 和最后一条消息 (保持对话连贯)
|
|
438
|
+
// 删除索引为 1 的消息
|
|
439
|
+
this.messages.splice(1, 1);
|
|
440
|
+
}
|
|
441
|
+
console.log(`✅ 裁剪完成,当前上下文: ${this.calculateTokens()} tokens`);
|
|
442
|
+
}
|
|
364
443
|
/**
|
|
365
444
|
* 简易 Loading 动画辅助函数
|
|
366
445
|
*/
|
|
@@ -376,8 +455,27 @@ class McpAgent {
|
|
|
376
455
|
process.stdout.write('\r\x1b[K'); // 清除当前行
|
|
377
456
|
};
|
|
378
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* 编程式对话入口
|
|
460
|
+
* @param input 用户指令
|
|
461
|
+
* @returns AI 的最终答复内容
|
|
462
|
+
*/
|
|
463
|
+
async chat(input) {
|
|
464
|
+
if (!this.openai) {
|
|
465
|
+
await this.init();
|
|
466
|
+
}
|
|
467
|
+
// 调用现有的处理逻辑
|
|
468
|
+
// 假设你的 processChat 已经处理了所有的 tool_calls 循环
|
|
469
|
+
await this.processChat(input);
|
|
470
|
+
// 返回消息列表中的最后一条 AI 回复
|
|
471
|
+
const lastMsg = this.messages[this.messages.length - 1];
|
|
472
|
+
return lastMsg.role === 'assistant' ? lastMsg.content : '';
|
|
473
|
+
}
|
|
474
|
+
// 修改原来的 start 方法,使其内部也调用 chat
|
|
379
475
|
async start() {
|
|
380
|
-
|
|
476
|
+
if (!this.openai) {
|
|
477
|
+
await this.init();
|
|
478
|
+
}
|
|
381
479
|
const rl = readline.createInterface({
|
|
382
480
|
input: process.stdin,
|
|
383
481
|
output: process.stdout,
|
|
@@ -388,7 +486,8 @@ class McpAgent {
|
|
|
388
486
|
if (input.toLowerCase() === "exit")
|
|
389
487
|
process.exit(0);
|
|
390
488
|
try {
|
|
391
|
-
|
|
489
|
+
// 这里统一调用 chat 或核心逻辑
|
|
490
|
+
await this.chat(input);
|
|
392
491
|
}
|
|
393
492
|
catch (err) {
|
|
394
493
|
console.error("\n❌ 系统错误:", err.message);
|
package/lib/cli.d.ts
ADDED
package/lib/cli.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const agent_1 = __importDefault(require("./agent"));
|
|
8
|
+
const manager = new agent_1.default();
|
|
9
|
+
manager.start();
|
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export {};
|
|
1
|
+
export * from './agent';
|
|
2
|
+
export { default } from './agent';
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
3
16
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
18
|
};
|
|
6
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
20
|
+
exports.default = void 0;
|
|
21
|
+
__exportStar(require("./agent"), exports);
|
|
22
|
+
var agent_1 = require("./agent");
|
|
23
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(agent_1).default; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saber2pr/ai-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "AI Assistant CLI.",
|
|
5
5
|
"author": "saber2pr",
|
|
6
6
|
"license": "ISC",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"lib"
|
|
9
9
|
],
|
|
10
10
|
"bin": {
|
|
11
|
-
"sagent": "./lib/
|
|
11
|
+
"sagent": "./lib/cli.js"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public",
|