@saber2pr/ai-agent 0.0.3 → 0.0.4
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 +6 -14
- package/lib/agent.js +133 -122
- package/package.json +2 -1
package/lib/agent.d.ts
CHANGED
|
@@ -4,28 +4,20 @@ export default class McpAgent {
|
|
|
4
4
|
private clients;
|
|
5
5
|
private allTools;
|
|
6
6
|
private messages;
|
|
7
|
+
private engine;
|
|
7
8
|
constructor();
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* 核心功能:内置代码分析工具
|
|
11
|
+
* 这里的逻辑直接调用 PromptEngine,不走网络请求,效率极高
|
|
11
12
|
*/
|
|
13
|
+
private registerBuiltinTools;
|
|
12
14
|
private ensureApiConfig;
|
|
13
|
-
/**
|
|
14
|
-
* 2. Load MCP server configs from common IDE paths
|
|
15
|
-
*/
|
|
16
15
|
private loadMcpConfigs;
|
|
17
|
-
/**
|
|
18
|
-
* 3. Initialization
|
|
19
|
-
* Validates API credentials and connects to MCP servers.
|
|
20
|
-
*/
|
|
21
16
|
init(): Promise<void>;
|
|
22
17
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
18
|
+
* 核心交互循环 (Reasoning Loop)
|
|
19
|
+
* 允许 AI 连续调用工具来解决复杂代码问题
|
|
25
20
|
*/
|
|
26
21
|
private processChat;
|
|
27
|
-
/**
|
|
28
|
-
* 5. Start the Interactive Shell
|
|
29
|
-
*/
|
|
30
22
|
start(): Promise<void>;
|
|
31
23
|
}
|
package/lib/agent.js
CHANGED
|
@@ -36,124 +36,154 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const openai_1 = __importDefault(require("openai"));
|
|
39
40
|
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
40
41
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
41
|
-
const openai_1 = __importDefault(require("openai"));
|
|
42
42
|
const fs_1 = __importDefault(require("fs"));
|
|
43
43
|
const path_1 = __importDefault(require("path"));
|
|
44
44
|
const os_1 = __importDefault(require("os"));
|
|
45
45
|
const readline = __importStar(require("readline"));
|
|
46
|
+
const ts_context_mcp_1 = require("@saber2pr/ts-context-mcp"); // 引入我们的核心引擎
|
|
46
47
|
const CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
|
|
47
|
-
// --- Core Class ---
|
|
48
48
|
class McpAgent {
|
|
49
49
|
constructor() {
|
|
50
50
|
this.modelName = "";
|
|
51
51
|
this.clients = [];
|
|
52
52
|
this.allTools = [];
|
|
53
53
|
this.messages = [];
|
|
54
|
+
// 默认以当前工作目录为分析目标
|
|
55
|
+
this.engine = new ts_context_mcp_1.PromptEngine(process.cwd());
|
|
54
56
|
this.messages.push({
|
|
55
57
|
role: "system",
|
|
56
|
-
content:
|
|
58
|
+
content: `你是一个专业的 AI 代码架构师。
|
|
59
|
+
你可以访问本地文件系统并利用 AST (抽象语法树) 技术分析代码。
|
|
60
|
+
你的核心目标是提供准确的代码结构、依赖关系和逻辑分析。
|
|
61
|
+
请优先使用 read_skeleton 查看结构,只有在必要时才使用 read_full_code 或 get_method_body。`,
|
|
57
62
|
});
|
|
63
|
+
// 初始化内置工具
|
|
64
|
+
this.registerBuiltinTools();
|
|
58
65
|
}
|
|
59
66
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
67
|
+
* 核心功能:内置代码分析工具
|
|
68
|
+
* 这里的逻辑直接调用 PromptEngine,不走网络请求,效率极高
|
|
62
69
|
*/
|
|
70
|
+
registerBuiltinTools() {
|
|
71
|
+
const builtinTools = [
|
|
72
|
+
{
|
|
73
|
+
type: "function",
|
|
74
|
+
function: {
|
|
75
|
+
name: "get_repo_map",
|
|
76
|
+
description: "获取项目全局文件结构及导出清单,用于快速定位代码",
|
|
77
|
+
parameters: { type: "object", properties: {} },
|
|
78
|
+
},
|
|
79
|
+
_handler: async () => {
|
|
80
|
+
this.engine.refresh();
|
|
81
|
+
return this.engine.getRepoMap();
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "function",
|
|
86
|
+
function: {
|
|
87
|
+
name: "analyze_deps",
|
|
88
|
+
description: "分析指定文件的依赖关系,支持 tsconfig 路径别名解析",
|
|
89
|
+
parameters: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
filePath: { type: "string", description: "文件相对路径" },
|
|
93
|
+
},
|
|
94
|
+
required: ["filePath"],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
_handler: async ({ filePath }) => this.engine.getDeps(filePath),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: "function",
|
|
101
|
+
function: {
|
|
102
|
+
name: "read_skeleton",
|
|
103
|
+
description: "提取文件的结构定义(接口、类、方法签名),忽略具体实现以节省 Token",
|
|
104
|
+
parameters: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
filePath: { type: "string", description: "文件相对路径" },
|
|
108
|
+
},
|
|
109
|
+
required: ["filePath"],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
_handler: async ({ filePath }) => this.engine.getSkeleton(filePath),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: "function",
|
|
116
|
+
function: {
|
|
117
|
+
name: "get_method_body",
|
|
118
|
+
description: "获取指定文件内某个方法或函数的完整实现代码",
|
|
119
|
+
parameters: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
filePath: { type: "string", description: "文件路径" },
|
|
123
|
+
methodName: { type: "string", description: "方法名或函数名" },
|
|
124
|
+
},
|
|
125
|
+
required: ["filePath", "methodName"],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
_handler: async ({ filePath, methodName }) => this.engine.getMethodImplementation(filePath, methodName),
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
this.allTools.push(...builtinTools);
|
|
132
|
+
}
|
|
133
|
+
// --- 初始化与环境准备 (API Config & MCP Servers) ---
|
|
63
134
|
async ensureApiConfig() {
|
|
64
135
|
if (fs_1.default.existsSync(CONFIG_FILE)) {
|
|
65
|
-
|
|
66
|
-
const config = JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, "utf-8"));
|
|
67
|
-
if (config.baseURL && config.apiKey && config.model) {
|
|
68
|
-
return config;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch (e) {
|
|
72
|
-
console.error(`[Error] Failed to read ${CONFIG_FILE}, re-initializing...`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
76
|
-
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
77
|
-
console.log("\n🔑 API Configuration not found. Please provide the following details:");
|
|
78
|
-
const baseURL = await question("? API Base URL: ");
|
|
79
|
-
const apiKey = await question("? API Key: ");
|
|
80
|
-
const model = await question("? Model Name: ");
|
|
81
|
-
if (!baseURL || !apiKey || !model) {
|
|
82
|
-
console.error("❌ Error: All fields (Base URL, API Key, Model Name) are required!");
|
|
83
|
-
process.exit(1);
|
|
136
|
+
return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, "utf-8"));
|
|
84
137
|
}
|
|
85
|
-
const
|
|
138
|
+
const rl = readline.createInterface({
|
|
139
|
+
input: process.stdin,
|
|
140
|
+
output: process.stdout,
|
|
141
|
+
});
|
|
142
|
+
const question = (q) => new Promise((res) => rl.question(q, res));
|
|
143
|
+
console.log("\n🔑 配置 API 凭据:");
|
|
144
|
+
const config = {
|
|
145
|
+
baseURL: await question("? API Base URL (如 https://api.openai.com/v1): "),
|
|
146
|
+
apiKey: await question("? API Key: "),
|
|
147
|
+
model: await question("? Model Name (如 gpt-4o): "),
|
|
148
|
+
};
|
|
86
149
|
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
87
|
-
console.log(`✅ Configuration saved to ${CONFIG_FILE}\n`);
|
|
88
150
|
rl.close();
|
|
89
151
|
return config;
|
|
90
152
|
}
|
|
91
|
-
/**
|
|
92
|
-
* 2. Load MCP server configs from common IDE paths
|
|
93
|
-
*/
|
|
94
153
|
loadMcpConfigs() {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
154
|
+
const combined = { mcpServers: {} };
|
|
155
|
+
const paths = [
|
|
97
156
|
path_1.default.join(os_1.default.homedir(), ".cursor", "mcp.json"),
|
|
98
157
|
path_1.default.join(os_1.default.homedir(), ".vscode", "mcp.json"),
|
|
99
158
|
];
|
|
100
|
-
|
|
159
|
+
paths.forEach((p) => {
|
|
101
160
|
if (fs_1.default.existsSync(p)) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (content.mcpServers) {
|
|
105
|
-
combinedConfig.mcpServers = { ...combinedConfig.mcpServers, ...content.mcpServers };
|
|
106
|
-
console.log(`[MCP] Config loaded from: ${p}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch (e) {
|
|
110
|
-
console.error(`[Error] Failed to parse MCP config ${p}:`, e);
|
|
111
|
-
}
|
|
161
|
+
const content = JSON.parse(fs_1.default.readFileSync(p, "utf-8"));
|
|
162
|
+
Object.assign(combined.mcpServers, content.mcpServers);
|
|
112
163
|
}
|
|
113
|
-
}
|
|
114
|
-
return
|
|
164
|
+
});
|
|
165
|
+
return combined;
|
|
115
166
|
}
|
|
116
|
-
/**
|
|
117
|
-
* 3. Initialization
|
|
118
|
-
* Validates API credentials and connects to MCP servers.
|
|
119
|
-
*/
|
|
120
167
|
async init() {
|
|
121
|
-
// A. Setup & Validate OpenAI
|
|
122
168
|
const apiConfig = await this.ensureApiConfig();
|
|
123
169
|
this.openai = new openai_1.default({
|
|
124
170
|
baseURL: apiConfig.baseURL,
|
|
125
171
|
apiKey: apiConfig.apiKey,
|
|
126
172
|
});
|
|
127
173
|
this.modelName = apiConfig.model;
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
// Perform a lightweight check to verify URL and Key
|
|
131
|
-
await this.openai.models.list();
|
|
132
|
-
console.log("✅ API validation successful.");
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
console.error("\n❌ API Connection Failed!");
|
|
136
|
-
console.error(`Reason: ${e.message}`);
|
|
137
|
-
console.log(`\nSuggestion: If you made a mistake, please delete or edit: ${CONFIG_FILE}`);
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
// B. Setup MCP Clients
|
|
174
|
+
// 链接外部 MCP Server (如 Google Search, Filesystem 等)
|
|
141
175
|
const mcpConfig = this.loadMcpConfigs();
|
|
142
|
-
const
|
|
143
|
-
if (serverEntries.length === 0) {
|
|
144
|
-
console.warn("⚠️ No MCP server configurations found.");
|
|
145
|
-
}
|
|
146
|
-
for (const [name, server] of serverEntries) {
|
|
176
|
+
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
147
177
|
try {
|
|
148
178
|
const transport = new stdio_js_1.StdioClientTransport({
|
|
149
179
|
command: server.command,
|
|
150
180
|
args: server.args || [],
|
|
151
|
-
env: { ...process.env, ...
|
|
181
|
+
env: { ...process.env, ...server.env },
|
|
152
182
|
});
|
|
153
183
|
const client = new index_js_1.Client({ name, version: "1.0.0" }, { capabilities: {} });
|
|
154
184
|
await client.connect(transport);
|
|
155
185
|
const { tools } = await client.listTools();
|
|
156
|
-
|
|
186
|
+
this.allTools.push(...tools.map((t) => ({
|
|
157
187
|
type: "function",
|
|
158
188
|
function: {
|
|
159
189
|
name: `${name}__${t.name}`,
|
|
@@ -162,93 +192,74 @@ class McpAgent {
|
|
|
162
192
|
},
|
|
163
193
|
_originalName: t.name,
|
|
164
194
|
_client: client,
|
|
165
|
-
}));
|
|
166
|
-
|
|
167
|
-
this.clients.push(client);
|
|
168
|
-
console.log(`✅ [${name}] Connected, loaded ${tools.length} tools`);
|
|
195
|
+
})));
|
|
196
|
+
console.log(`✅ [${name}] 加载成功`);
|
|
169
197
|
}
|
|
170
198
|
catch (e) {
|
|
171
|
-
console.error(`❌ [${name}]
|
|
199
|
+
console.error(`❌ [${name}] 启动失败`);
|
|
172
200
|
}
|
|
173
201
|
}
|
|
174
202
|
}
|
|
175
203
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
204
|
+
* 核心交互循环 (Reasoning Loop)
|
|
205
|
+
* 允许 AI 连续调用工具来解决复杂代码问题
|
|
178
206
|
*/
|
|
179
207
|
async processChat(userInput) {
|
|
180
208
|
this.messages.push({ role: "user", content: userInput });
|
|
181
|
-
|
|
182
|
-
while (isThinking) {
|
|
183
|
-
const apiTools = this.allTools.map(({ _originalName, _client, ...rest }) => rest);
|
|
209
|
+
while (true) {
|
|
184
210
|
const response = await this.openai.chat.completions.create({
|
|
185
211
|
model: this.modelName,
|
|
186
212
|
messages: this.messages,
|
|
187
|
-
tools:
|
|
213
|
+
tools: this.allTools.map(({ _handler, _client, _originalName, ...rest }) => rest),
|
|
188
214
|
tool_choice: "auto",
|
|
189
215
|
});
|
|
190
216
|
const message = response.choices[0].message;
|
|
191
|
-
|
|
192
|
-
if (!message.tool_calls
|
|
193
|
-
this.messages.push(message);
|
|
217
|
+
this.messages.push(message);
|
|
218
|
+
if (!message.tool_calls) {
|
|
194
219
|
console.log(`\n🤖 Agent: ${message.content}`);
|
|
195
|
-
isThinking = false;
|
|
196
220
|
break;
|
|
197
221
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
tool_call_id: toolCall.id,
|
|
214
|
-
content: JSON.stringify(result.content),
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
console.error(` - Execution failed: ${error.message}`);
|
|
219
|
-
this.messages.push({
|
|
220
|
-
role: "tool",
|
|
221
|
-
tool_call_id: toolCall.id,
|
|
222
|
-
content: `Error: ${error.message}`,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
222
|
+
console.log(`\n⚙️ 正在思考并执行 ${message.tool_calls.length} 个操作...`);
|
|
223
|
+
for (const call of message.tool_calls) {
|
|
224
|
+
const tool = this.allTools.find((t) => t.function.name === call.function.name);
|
|
225
|
+
let result;
|
|
226
|
+
if (tool === null || tool === void 0 ? void 0 : tool._handler) {
|
|
227
|
+
// 执行内置 PromptEngine 工具
|
|
228
|
+
result = await tool._handler(JSON.parse(call.function.arguments));
|
|
229
|
+
}
|
|
230
|
+
else if ((tool === null || tool === void 0 ? void 0 : tool._client) && tool._originalName) {
|
|
231
|
+
// 执行外部 MCP 工具
|
|
232
|
+
const mcpRes = await tool._client.callTool({
|
|
233
|
+
name: tool._originalName,
|
|
234
|
+
arguments: JSON.parse(call.function.arguments),
|
|
235
|
+
});
|
|
236
|
+
result = mcpRes.content;
|
|
225
237
|
}
|
|
238
|
+
this.messages.push({
|
|
239
|
+
role: "tool",
|
|
240
|
+
tool_call_id: call.id,
|
|
241
|
+
content: typeof result === "string" ? result : JSON.stringify(result),
|
|
242
|
+
});
|
|
243
|
+
console.log(` - 完成: ${call.function.name}`);
|
|
226
244
|
}
|
|
227
245
|
}
|
|
228
246
|
}
|
|
229
|
-
/**
|
|
230
|
-
* 5. Start the Interactive Shell
|
|
231
|
-
*/
|
|
232
247
|
async start() {
|
|
233
248
|
await this.init();
|
|
234
249
|
const rl = readline.createInterface({
|
|
235
250
|
input: process.stdin,
|
|
236
251
|
output: process.stdout,
|
|
237
252
|
});
|
|
238
|
-
console.log(`\n🚀
|
|
253
|
+
console.log(`\n🚀 代码助手已启动 (目标目录: ${this.engine.getRootDir()})`);
|
|
239
254
|
const chatLoop = () => {
|
|
240
|
-
rl.question("\n👤
|
|
241
|
-
if (input.toLowerCase() === "exit")
|
|
242
|
-
console.log("Goodbye!");
|
|
243
|
-
rl.close();
|
|
255
|
+
rl.question("\n👤 你: ", async (input) => {
|
|
256
|
+
if (input.toLowerCase() === "exit")
|
|
244
257
|
process.exit(0);
|
|
245
|
-
}
|
|
246
258
|
try {
|
|
247
259
|
await this.processChat(input);
|
|
248
260
|
}
|
|
249
261
|
catch (err) {
|
|
250
|
-
console.error("\n❌
|
|
251
|
-
console.log("Try checking your API configuration or network connection.");
|
|
262
|
+
console.error("\n❌ 系统错误:", err.message);
|
|
252
263
|
}
|
|
253
264
|
chatLoop();
|
|
254
265
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saber2pr/ai-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "AI Assistant CLI.",
|
|
5
5
|
"author": "saber2pr",
|
|
6
6
|
"license": "ISC",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
25
|
+
"@saber2pr/ts-context-mcp": "^0.0.5",
|
|
25
26
|
"openai": "^6.16.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|