@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.
Files changed (3) hide show
  1. package/lib/agent.d.ts +6 -14
  2. package/lib/agent.js +133 -122
  3. 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
- * 1. API Configuration Management
10
- * Checks for existing config or prompts user for input.
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
- * 4. Core Chat Logic
24
- * Handles user input and recursive tool calls.
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: "You are a powerful local assistant. You can access local tools provided by the user via the MCP protocol. Please answer questions by combining tool outputs and context.",
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
- * 1. API Configuration Management
61
- * Checks for existing config or prompts user for input.
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
- try {
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 config = { baseURL, apiKey, model };
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 combinedConfig = { mcpServers: {} };
96
- const configPaths = [
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
- for (const p of configPaths) {
159
+ paths.forEach((p) => {
101
160
  if (fs_1.default.existsSync(p)) {
102
- try {
103
- const content = JSON.parse(fs_1.default.readFileSync(p, "utf-8"));
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 combinedConfig;
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
- console.log("🔍 Validating API configuration...");
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 serverEntries = Object.entries(mcpConfig.mcpServers);
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, ...(server.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
- const formatted = tools.map((t) => ({
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
- this.allTools.push(...formatted);
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}] Failed to start:`, e);
199
+ console.error(`❌ [${name}] 启动失败`);
172
200
  }
173
201
  }
174
202
  }
175
203
  /**
176
- * 4. Core Chat Logic
177
- * Handles user input and recursive tool calls.
204
+ * 核心交互循环 (Reasoning Loop)
205
+ * 允许 AI 连续调用工具来解决复杂代码问题
178
206
  */
179
207
  async processChat(userInput) {
180
208
  this.messages.push({ role: "user", content: userInput });
181
- let isThinking = true;
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: apiTools.length > 0 ? apiTools : undefined,
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
- // If no more tool calls, exit loop and show final response
192
- if (!message.tool_calls || message.tool_calls.length === 0) {
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
- // Handle tool calls requested by the model
199
- this.messages.push(message);
200
- console.log(`\n⚙️ Model requested ${message.tool_calls.length} tool calls...`);
201
- for (const toolCall of message.tool_calls) {
202
- const toolInfo = this.allTools.find((t) => t.function.name === toolCall.function.name);
203
- if (toolInfo) {
204
- const args = JSON.parse(toolCall.function.arguments);
205
- console.log(` - Executing: ${toolInfo.function.name}`);
206
- try {
207
- const result = await toolInfo._client.callTool({
208
- name: toolInfo._originalName,
209
- arguments: args,
210
- });
211
- this.messages.push({
212
- role: "tool",
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🚀 Agent Started (Model: ${this.modelName})! Type 'exit' to quit.`);
253
+ console.log(`\n🚀 代码助手已启动 (目标目录: ${this.engine.getRootDir()})`);
239
254
  const chatLoop = () => {
240
- rl.question("\n👤 You: ", async (input) => {
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❌ System Error during chat:", err.message);
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",
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": {