@saber2pr/ai-agent 0.0.20 → 0.0.22
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.
|
@@ -14,10 +14,13 @@ export default class McpGraphAgent {
|
|
|
14
14
|
private options;
|
|
15
15
|
private checkpointer;
|
|
16
16
|
private langchainTools;
|
|
17
|
-
private
|
|
17
|
+
private stopLoadingFunc;
|
|
18
18
|
constructor(options?: AgentOptions);
|
|
19
|
-
private
|
|
19
|
+
private showLoading;
|
|
20
|
+
private startLoading;
|
|
21
|
+
private stopLoading;
|
|
20
22
|
private getModel;
|
|
23
|
+
private askForConfig;
|
|
21
24
|
chat(query?: string): Promise<void>;
|
|
22
25
|
start(): Promise<void>;
|
|
23
26
|
private renderOutput;
|
package/lib/core/agent-graph.js
CHANGED
|
@@ -13,7 +13,6 @@ const readline_1 = __importDefault(require("readline"));
|
|
|
13
13
|
const fs_1 = __importDefault(require("fs"));
|
|
14
14
|
const path_1 = __importDefault(require("path"));
|
|
15
15
|
const os_1 = __importDefault(require("os"));
|
|
16
|
-
const ora_1 = __importDefault(require("ora")); // 用于显示 Loading 动画
|
|
17
16
|
const builtin_1 = require("../tools/builtin");
|
|
18
17
|
const convertToLangChainTool_1 = require("../utils/convertToLangChainTool");
|
|
19
18
|
exports.CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
|
|
@@ -40,15 +39,66 @@ class McpGraphAgent {
|
|
|
40
39
|
constructor(options = {}) {
|
|
41
40
|
this.checkpointer = new langgraph_1.MemorySaver();
|
|
42
41
|
this.langchainTools = [];
|
|
43
|
-
|
|
42
|
+
// ✅ 用于存储清理 loading 的函数
|
|
43
|
+
this.stopLoadingFunc = null;
|
|
44
44
|
this.options = options;
|
|
45
45
|
this.targetDir = options.targetDir || process.cwd();
|
|
46
|
-
process.setMaxListeners(
|
|
46
|
+
process.setMaxListeners(100);
|
|
47
|
+
// ✅ 退出清理
|
|
48
|
+
const cleanup = () => {
|
|
49
|
+
this.stopLoading();
|
|
50
|
+
process.stdout.write('\u001B[?25h'); // 显示光标
|
|
51
|
+
process.exit(0);
|
|
52
|
+
};
|
|
53
|
+
process.on("SIGINT", cleanup);
|
|
54
|
+
process.on("SIGTERM", cleanup);
|
|
47
55
|
const builtinToolInfos = (0, builtin_1.createDefaultBuiltinTools)({ options });
|
|
48
|
-
|
|
49
|
-
this.langchainTools = [...builtinToolInfos, ...externalToolInfos].map((t) => (0, convertToLangChainTool_1.convertToLangChainTool)(t));
|
|
56
|
+
this.langchainTools = [...builtinToolInfos, ...(options.tools || [])].map((t) => (0, convertToLangChainTool_1.convertToLangChainTool)(t));
|
|
50
57
|
this.toolNode = new prebuilt_1.ToolNode(this.langchainTools);
|
|
51
58
|
}
|
|
59
|
+
// ✅ 1. 复现你提供的 showLoading 效果
|
|
60
|
+
showLoading(text) {
|
|
61
|
+
const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
62
|
+
let i = 0;
|
|
63
|
+
// 隐藏光标,防止闪烁
|
|
64
|
+
process.stdout.write('\u001B[?25l');
|
|
65
|
+
const timer = setInterval(() => {
|
|
66
|
+
process.stdout.write(`\r\x1b[36m${chars[i]}\x1b[0m ${text}`);
|
|
67
|
+
i = (i + 1) % chars.length;
|
|
68
|
+
}, 80);
|
|
69
|
+
return () => {
|
|
70
|
+
clearInterval(timer);
|
|
71
|
+
process.stdout.write('\r\x1b[K'); // 清除这一行
|
|
72
|
+
process.stdout.write('\u001B[?25h'); // 恢复光标
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// ✅ 2. 封装内部调用方法
|
|
76
|
+
startLoading(text) {
|
|
77
|
+
this.stopLoading();
|
|
78
|
+
this.stopLoadingFunc = this.showLoading(text);
|
|
79
|
+
}
|
|
80
|
+
stopLoading() {
|
|
81
|
+
if (this.stopLoadingFunc) {
|
|
82
|
+
this.stopLoadingFunc();
|
|
83
|
+
this.stopLoadingFunc = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async getModel() {
|
|
87
|
+
if (this.model)
|
|
88
|
+
return this.model;
|
|
89
|
+
let modelInstance = this.options.apiModel;
|
|
90
|
+
if (!modelInstance) {
|
|
91
|
+
const config = await this.askForConfig();
|
|
92
|
+
modelInstance = new openai_1.ChatOpenAI({
|
|
93
|
+
openAIApiKey: config.apiKey,
|
|
94
|
+
configuration: { baseURL: config.baseURL },
|
|
95
|
+
modelName: config.model,
|
|
96
|
+
temperature: 0,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
this.model = modelInstance.bindTools(this.langchainTools);
|
|
100
|
+
return this.model;
|
|
101
|
+
}
|
|
52
102
|
async askForConfig() {
|
|
53
103
|
let config = {};
|
|
54
104
|
if (fs_1.default.existsSync(exports.CONFIG_FILE)) {
|
|
@@ -60,7 +110,6 @@ class McpGraphAgent {
|
|
|
60
110
|
if (!config.baseURL || !config.apiKey) {
|
|
61
111
|
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
62
112
|
const question = (q) => new Promise((res) => rl.question(q, res));
|
|
63
|
-
console.log(`💡 首次运行请配置信息:`);
|
|
64
113
|
config.baseURL = config.baseURL || await question(`? API Base URL: `);
|
|
65
114
|
config.apiKey = config.apiKey || await question(`? API Key: `);
|
|
66
115
|
config.model = config.model || await question(`? Model Name: `) || "gpt-4o";
|
|
@@ -69,22 +118,6 @@ class McpGraphAgent {
|
|
|
69
118
|
}
|
|
70
119
|
return config;
|
|
71
120
|
}
|
|
72
|
-
async getModel() {
|
|
73
|
-
if (this.model)
|
|
74
|
-
return this.model;
|
|
75
|
-
let modelInstance = this.options.apiModel;
|
|
76
|
-
if (!modelInstance) {
|
|
77
|
-
const config = await this.askForConfig();
|
|
78
|
-
modelInstance = new openai_1.ChatOpenAI({
|
|
79
|
-
openAIApiKey: config.apiKey,
|
|
80
|
-
configuration: { baseURL: config.baseURL },
|
|
81
|
-
modelName: config.model,
|
|
82
|
-
temperature: 0,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
this.model = modelInstance.bindTools(this.langchainTools);
|
|
86
|
-
return this.model;
|
|
87
|
-
}
|
|
88
121
|
async chat(query = "开始代码审计") {
|
|
89
122
|
await this.getModel();
|
|
90
123
|
const app = await this.createGraph();
|
|
@@ -95,21 +128,24 @@ class McpGraphAgent {
|
|
|
95
128
|
}, { configurable: { thread_id: "auto_worker" }, recursionLimit: 100 });
|
|
96
129
|
for await (const output of stream)
|
|
97
130
|
this.renderOutput(output);
|
|
98
|
-
console.log("✅ 任务执行完毕。");
|
|
99
131
|
}
|
|
100
132
|
async start() {
|
|
101
133
|
await this.getModel();
|
|
102
134
|
const app = await this.createGraph();
|
|
103
135
|
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
104
|
-
|
|
105
|
-
|
|
136
|
+
rl.on("SIGINT", () => {
|
|
137
|
+
this.stopLoading();
|
|
138
|
+
rl.close();
|
|
139
|
+
process.stdout.write('\u001B[?25h');
|
|
140
|
+
process.exit(0);
|
|
141
|
+
});
|
|
106
142
|
const ask = () => {
|
|
107
143
|
rl.question("> ", async (input) => {
|
|
108
144
|
if (input.toLowerCase() === "exit") {
|
|
109
145
|
rl.close();
|
|
110
146
|
return;
|
|
111
147
|
}
|
|
112
|
-
const stream = await app.stream({ messages: [new messages_1.HumanMessage(input)], mode: "chat" }, { configurable: { thread_id:
|
|
148
|
+
const stream = await app.stream({ messages: [new messages_1.HumanMessage(input)], mode: "chat" }, { configurable: { thread_id: "session" }, recursionLimit: 50 });
|
|
113
149
|
for await (const output of stream)
|
|
114
150
|
this.renderOutput(output);
|
|
115
151
|
ask();
|
|
@@ -119,21 +155,16 @@ class McpGraphAgent {
|
|
|
119
155
|
}
|
|
120
156
|
renderOutput(output) {
|
|
121
157
|
var _a, _b;
|
|
122
|
-
//
|
|
123
|
-
if (this.spinner.isSpinning)
|
|
124
|
-
this.spinner.stop();
|
|
158
|
+
this.stopLoading(); // 收到任何输出前,先关掉转圈圈
|
|
125
159
|
const agentNode = output.agent;
|
|
126
160
|
if (agentNode) {
|
|
127
161
|
const msg = agentNode.messages[0];
|
|
128
|
-
// 1. 打印思考过程
|
|
129
162
|
const reasoning = (_a = msg.additional_kwargs) === null || _a === void 0 ? void 0 : _a.reasoning;
|
|
130
163
|
if (reasoning) {
|
|
131
|
-
console.log("\n🧠 [思考过程]:\n" + "─".repeat(50) + "\n" + reasoning + "\n" + "─".repeat(50)
|
|
164
|
+
console.log("\n🧠 [思考过程]:\n" + "─".repeat(50) + "\n" + reasoning + "\n" + "─".repeat(50));
|
|
132
165
|
}
|
|
133
|
-
// 2. 打印正式回答
|
|
134
166
|
if (msg.content)
|
|
135
167
|
console.log("🤖 [AI]:", msg.content);
|
|
136
|
-
// 3. 打印工具调用
|
|
137
168
|
if ((_b = msg.tool_calls) === null || _b === void 0 ? void 0 : _b.length) {
|
|
138
169
|
msg.tool_calls.forEach((call) => {
|
|
139
170
|
console.log(`🛠️ [调用工具]: ${call.name} 📦 参数: ${JSON.stringify(call.args)}`);
|
|
@@ -141,27 +172,17 @@ class McpGraphAgent {
|
|
|
141
172
|
}
|
|
142
173
|
}
|
|
143
174
|
}
|
|
144
|
-
// --- 节点逻辑 ---
|
|
145
175
|
async callModel(state) {
|
|
146
|
-
// 处理变量序列化,防止 [object Object]
|
|
147
|
-
const auditedListStr = state.auditedFiles.length > 0
|
|
148
|
-
? state.auditedFiles.map(f => `\n - ${f}`).join("")
|
|
149
|
-
: "暂无";
|
|
150
|
-
const extraPromptStr = typeof this.options.extraSystemPrompt === 'object'
|
|
151
|
-
? JSON.stringify(this.options.extraSystemPrompt, null, 2)
|
|
152
|
-
: (this.options.extraSystemPrompt || "");
|
|
153
|
-
// 使用变量占位符 {extraPrompt} 避免内容中的 {} 引发模板解析错误
|
|
154
176
|
const prompt = prompts_1.ChatPromptTemplate.fromMessages([
|
|
155
|
-
["system",
|
|
156
|
-
当前模式:{mode}。
|
|
157
|
-
|
|
177
|
+
["system", `你是一个代码专家。工作目录:${this.targetDir}。
|
|
178
|
+
当前模式:{mode}。进度:{doneCount}/{targetCount}。
|
|
179
|
+
已审计文件:{auditedList}
|
|
158
180
|
|
|
159
|
-
# 附加指令
|
|
160
181
|
{extraPrompt}`],
|
|
161
182
|
new prompts_1.MessagesPlaceholder("messages"),
|
|
162
183
|
]);
|
|
163
|
-
// ✅
|
|
164
|
-
this.
|
|
184
|
+
// ✅ 调用 AI 前开启转圈圈
|
|
185
|
+
this.startLoading("AI 正在分析并思考中...");
|
|
165
186
|
try {
|
|
166
187
|
const chain = prompt.pipe(this.model);
|
|
167
188
|
const response = await chain.invoke({
|
|
@@ -169,29 +190,26 @@ KPI进度:{doneCount}/{targetCount}。已审计文件:{auditedList}
|
|
|
169
190
|
mode: state.mode,
|
|
170
191
|
targetCount: state.targetCount,
|
|
171
192
|
doneCount: state.auditedFiles.length,
|
|
172
|
-
auditedList:
|
|
173
|
-
extraPrompt:
|
|
193
|
+
auditedList: state.auditedFiles.join(", "),
|
|
194
|
+
extraPrompt: this.options.extraSystemPrompt || "",
|
|
174
195
|
});
|
|
175
|
-
this.
|
|
196
|
+
this.stopLoading();
|
|
176
197
|
return { messages: [response] };
|
|
177
198
|
}
|
|
178
199
|
catch (error) {
|
|
179
|
-
this.
|
|
200
|
+
this.stopLoading();
|
|
180
201
|
throw error;
|
|
181
202
|
}
|
|
182
203
|
}
|
|
183
204
|
async trackProgress(state) {
|
|
205
|
+
var _a;
|
|
184
206
|
const lastAiMsg = state.messages[state.messages.length - 1];
|
|
185
207
|
const newFiles = [];
|
|
186
|
-
if (lastAiMsg === null || lastAiMsg === void 0 ? void 0 : lastAiMsg.tool_calls) {
|
|
208
|
+
if ((_a = lastAiMsg === null || lastAiMsg === void 0 ? void 0 : lastAiMsg.tool_calls) === null || _a === void 0 ? void 0 : _a.length) {
|
|
187
209
|
for (const tc of lastAiMsg.tool_calls) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const file = tc.args.path || tc.args.filePath || tc.args.file;
|
|
192
|
-
if (file && typeof file === 'string')
|
|
193
|
-
newFiles.push(file);
|
|
194
|
-
}
|
|
210
|
+
const file = tc.args.path || tc.args.filePath || tc.args.file;
|
|
211
|
+
if (file && typeof file === 'string')
|
|
212
|
+
newFiles.push(file);
|
|
195
213
|
}
|
|
196
214
|
}
|
|
197
215
|
return { auditedFiles: newFiles };
|
|
@@ -207,7 +225,6 @@ KPI进度:{doneCount}/{targetCount}。已审计文件:{auditedList}
|
|
|
207
225
|
const lastMsg = state.messages[state.messages.length - 1];
|
|
208
226
|
if ((_a = lastMsg.tool_calls) === null || _a === void 0 ? void 0 : _a.length)
|
|
209
227
|
return "tools";
|
|
210
|
-
// 自动模式下且未达标时继续
|
|
211
228
|
if (state.mode === "auto" && state.auditedFiles.length < state.targetCount)
|
|
212
229
|
return "agent";
|
|
213
230
|
return langgraph_1.END;
|
|
@@ -62,6 +62,7 @@ const SearchFilesArgsSchema = zod_1.z.object({
|
|
|
62
62
|
const GetFileInfoArgsSchema = zod_1.z.object({
|
|
63
63
|
path: zod_1.z.string(),
|
|
64
64
|
});
|
|
65
|
+
console.log('🚀 ~ index ~ zodObjectToJsonSchema(ListDirectoryWithSizesArgsSchema,):', (0, jsonSchemaToZod_1.zodObjectToJsonSchema)(ListDirectoryWithSizesArgsSchema, 'ListDirectoryWithSizesArgsSchema'));
|
|
65
66
|
const getFilesystemTools = (targetDir) => {
|
|
66
67
|
(0, lib_1.setAllowedDirectories)([targetDir]);
|
|
67
68
|
// read_file (deprecated) and read_text_file
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
|
2
|
-
import {
|
|
3
|
-
export declare function convertToLangChainTool(info:
|
|
2
|
+
import { ToolInfo } from "../types/type";
|
|
3
|
+
export declare function convertToLangChainTool(info: ToolInfo): DynamicStructuredTool<import("zod").ZodObject<any, import("zod").UnknownKeysParam, import("zod").ZodTypeAny, {
|
|
4
|
+
[x: string]: any;
|
|
5
|
+
}, {
|
|
6
|
+
[x: string]: any;
|
|
7
|
+
}>>;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.convertToLangChainTool = convertToLangChainTool;
|
|
4
4
|
const tools_1 = require("@langchain/core/tools");
|
|
5
|
-
const
|
|
5
|
+
const jsonSchemaToZod_1 = require("./jsonSchemaToZod");
|
|
6
6
|
function convertToLangChainTool(info) {
|
|
7
7
|
return new tools_1.DynamicStructuredTool({
|
|
8
8
|
name: info.function.name,
|
|
9
9
|
description: info.function.description || "",
|
|
10
|
-
schema:
|
|
10
|
+
schema: (0, jsonSchemaToZod_1.jsonSchemaToZod)(info.function.parameters),
|
|
11
11
|
func: async (args) => {
|
|
12
12
|
if (info._handler)
|
|
13
13
|
return await info._handler(args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saber2pr/ai-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "AI Assistant CLI.",
|
|
5
5
|
"author": "saber2pr",
|
|
6
6
|
"license": "ISC",
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
"langchain": "0.3.15",
|
|
35
35
|
"minimatch": "^10.0.1",
|
|
36
36
|
"openai": "^6.16.0",
|
|
37
|
-
"ora": "^9.3.0",
|
|
38
37
|
"zod": "3.23.8",
|
|
39
38
|
"zod-to-json-schema": "3.23.2"
|
|
40
39
|
},
|