@saber2pr/ai-agent 0.0.14 → 0.0.16

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/README.md CHANGED
@@ -80,39 +80,93 @@ await agent.chat("Scan for hardcoded colors and submit a review report.");
80
80
 
81
81
  ## 🔧 Extending with Private LLMs
82
82
 
83
- To use your own API protocol, extend the `BaseChatModel` from `@langchain/core`:
83
+ ### Using AgentChainModel (Recommended)
84
+
85
+ For LangChain mode, extend `AgentChainModel` which provides a simplified interface for integrating custom LLMs:
84
86
 
85
87
  ```javascript
86
- const { BaseChatModel } = require("@langchain/core/language_models/chat_models");
88
+ const { AgentChainModel } = require("@saber2pr/ai-agent");
89
+
90
+ class MyPrivateLLM extends AgentChainModel {
91
+ constructor(fields) {
92
+ super(fields || {});
93
+ }
87
94
 
88
- class MyPrivateLLM extends BaseChatModel {
89
- async _generate(messages) {
95
+ async generateAgentChainResponse(messages) {
90
96
  const lastMessage = messages[messages.length - 1];
91
- const response = await fetch("https://your-api.com/v1/chat", {
97
+ const queryText = lastMessage.content;
98
+
99
+ const response = await fetch("https://your-api-gateway.com/api/completions", {
92
100
  method: 'POST',
93
- body: JSON.stringify({ query: lastMessage.content }),
94
- headers: { 'Authorization': `Bearer ${token}` }
101
+ body: JSON.stringify({ query: queryText, stream: false }),
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ Authorization: `Bearer YOUR_API_KEY`,
105
+ }
95
106
  });
107
+
108
+ if (!response.ok) {
109
+ const errorText = await response.text();
110
+ throw new Error(`HTTP Error: ${response.status}, ${errorText}`);
111
+ }
112
+
96
113
  const data = await response.json();
97
- return {
98
- generations: [{ text: data.text, message: { content: data.text, role: "assistant" } }]
99
- };
114
+ let text = data.text || "";
115
+
116
+ // Handle special response formats if needed
117
+ if (text.includes("Action:") && text.includes("Final Answer:")) {
118
+ text = text.split("Final Answer:")[0].trim();
119
+ }
120
+
121
+ return text;
100
122
  }
101
- _llmType() { return "private_llm"; }
102
123
  }
103
-
104
124
  ```
105
125
 
126
+ **Key Points:**
127
+ - `AgentChainModel` abstracts away LangChain's internal message handling
128
+ - You only need to implement `generateAgentChainResponse(messages)` which receives an array of messages
129
+ - The method should return a plain string response
130
+ - The base class handles conversion to LangChain's expected format
131
+
106
132
  ---
107
133
 
108
134
  ## 📦 Built-in Toolset
109
135
 
110
- | Tool | Description |
111
- | ----------------- | ------------------------------------------------------------------------ |
112
- | `generate_review` | Finalizes the process by submitting a structured violation report. |
113
- | `get_repo_map` | Generates a high-level map of the project files and exports. |
114
- | `read_text_file` | Reads file content with line numbers for precise auditing. |
115
- | `read_skeleton` | Extracts class/function signatures without full logic (Token efficient). |
136
+ The toolkit provides a comprehensive set of built-in tools organized into two categories: **Filesystem Tools** and **Code Analysis Tools**. All tools operate within the `targetDir` scope for security.
137
+
138
+ ### Filesystem Tools
139
+
140
+ | Tool | Description | Parameters |
141
+ |------|-------------|------------|
142
+ | `read_text_file` | Read complete file contents as text. Supports `head` and `tail` parameters for partial reading. Handles various text encodings. | `path` (required), `head?`, `tail?` |
143
+ | `read_multiple_files` | Read multiple files simultaneously for efficient batch analysis. Individual failures won't stop the operation. | `paths` (array, required) |
144
+ | `write_file` | Create a new file or completely overwrite an existing file. Use with caution as it overwrites without warning. | `path` (required), `content` (required) |
145
+ | `edit_file` | Make line-based edits to a text file. Replaces exact line sequences with new content. Returns git-style diff. Supports `dryRun` mode for preview. | `path` (required), `edits` (array, required), `dryRun?` |
146
+ | `get_directory_tree` | Get a recursive tree view of files and directories as JSON. Supports `excludePatterns` for filtering (minimatch patterns). Essential for understanding project structure. | `path` (required), `excludePatterns?` (array) |
147
+ | `list_directory` | List all files and directories in a specified path. Results distinguish files and directories with `[FILE]` and `[DIR]` prefixes. | `path` (required) |
148
+ | `list_directory_with_sizes` | List directory contents with file sizes. Supports sorting by name or size. Includes summary statistics. | `path` (required), `sortBy?` ("name" \| "size") |
149
+ | `search_files` | Search for files matching a glob pattern. Supports exclude patterns for filtering. | `path` (required), `pattern` (required), `excludePatterns?` (array) |
150
+ | `move_file` | Move or rename files and directories. Can move between directories and rename in a single operation. | `source` (required), `destination` (required) |
151
+ | `create_directory` | Create a new directory or ensure it exists. Can create multiple nested directories recursively. | `path` (required) |
152
+ | `get_file_info` | Get detailed metadata about a file: size, last modified time, type, etc. | `path` (required) |
153
+
154
+ ### Code Analysis Tools
155
+
156
+ | Tool | Description | Parameters |
157
+ |------|-------------|------------|
158
+ | `get_repo_map` | Generate a high-level map of project files and exports. Extracts export definitions to understand module relationships. Use this first to understand project structure. | None |
159
+ | `read_skeleton` | Extract structural definitions (interfaces, classes, method signatures) without implementation details. Token-efficient for code analysis. | `filePath` (required) |
160
+ | `analyze_deps` | Analyze dependency relationships for a specific file. Supports TypeScript path alias resolution via tsconfig. | `filePath` (required) |
161
+ | `get_method_body` | Get the complete implementation code for a specific method or function within a file. | `filePath` (required), `methodName` (required) |
162
+
163
+ ### Tool Usage Tips
164
+
165
+ 1. **Start with `get_directory_tree`**: Always begin by understanding the project structure before reading files.
166
+ 2. **Use `read_skeleton` before `read_text_file`**: Extract signatures first to save tokens, then read full content only when needed.
167
+ 3. **Leverage `excludePatterns`**: Use minimatch patterns to exclude `node_modules`, `.git`, build artifacts, etc.
168
+ 4. **Batch operations**: Use `read_multiple_files` when analyzing multiple files to improve efficiency.
169
+ 5. **Preview changes**: Use `edit_file` with `dryRun: true` to preview changes before applying them.
116
170
 
117
171
  ---
118
172
 
@@ -1,7 +1,6 @@
1
1
  import { AgentOptions } from '../types/type';
2
2
  export default class McpChainAgent {
3
3
  private allTools;
4
- private messages;
5
4
  private encoder;
6
5
  private extraTools;
7
6
  private maxTokens;
@@ -9,17 +8,13 @@ export default class McpChainAgent {
9
8
  private apiConfig;
10
9
  private maxIterations;
11
10
  private apiModel?;
12
- private targetDir;
13
- private verbose;
11
+ private memory?;
12
+ private systemPrompt;
14
13
  private runningTokenCounter;
14
+ private verbose;
15
15
  constructor(options?: AgentOptions);
16
- /**
17
- * 工具处理器包装逻辑:增加日志打印和 Token 监控
18
- */
19
- private wrapHandler;
20
16
  private initTools;
21
- private calculateTokens;
22
- private pruneMessages;
17
+ private wrapHandler;
23
18
  init(): Promise<void>;
24
19
  chat(input: string): Promise<string>;
25
20
  private showLoading;
@@ -39,222 +39,162 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const fs_1 = __importDefault(require("fs"));
40
40
  const js_tiktoken_1 = require("js-tiktoken");
41
41
  const agents_1 = require("langchain/agents");
42
+ const memory_1 = require("langchain/memory");
42
43
  const readline = __importStar(require("readline"));
43
44
  const prompts_1 = require("@langchain/core/prompts");
44
45
  const tools_1 = require("@langchain/core/tools");
45
46
  const openai_1 = require("@langchain/openai");
46
47
  const config_1 = require("../config/config");
47
- const jsonSchemaToZod_1 = require("../utils/jsonSchemaToZod");
48
48
  const builtin_1 = require("../tools/builtin");
49
+ const jsonSchemaToZod_1 = require("../utils/jsonSchemaToZod");
49
50
  class McpChainAgent {
50
51
  constructor(options) {
51
52
  this.allTools = [];
52
- this.messages = [];
53
53
  this.encoder = (0, js_tiktoken_1.getEncoding)("cl100k_base");
54
54
  this.extraTools = [];
55
- this.runningTokenCounter = 0; // 新增:用于追踪当前 Task 的动态消耗
55
+ this.runningTokenCounter = 0;
56
56
  this.extraTools = (options === null || options === void 0 ? void 0 : options.tools) || [];
57
57
  this.maxTokens = (options === null || options === void 0 ? void 0 : options.maxTokens) || 100000;
58
58
  this.apiConfig = options === null || options === void 0 ? void 0 : options.apiConfig;
59
59
  this.maxIterations = (options === null || options === void 0 ? void 0 : options.maxIterations) || 20;
60
60
  this.apiModel = options === null || options === void 0 ? void 0 : options.apiModel;
61
- this.targetDir = (options === null || options === void 0 ? void 0 : options.targetDir) || process.cwd();
62
61
  this.verbose = (options === null || options === void 0 ? void 0 : options.verbose) || false;
63
- const baseSystemPrompt = `你是一个专业的 AI 代码架构师,具备深度的源码分析与工程化处理能力。
64
-
65
- ### 核心操作规范:
66
- 1. **全局扫描(强制首选)**:在开始任何分析任务前,你【必须】首先调用 'get_repo_map'。这是理解项目结构、技术栈及模块关系的唯一来源。
67
- 2. **循序渐进的分析路径**:
68
- - 优先使用 'read_skeleton' 提取接口和函数签名。
69
- - 仅在需要分析具体逻辑或准备修复代码时,才使用 'read_text_file'。
70
- 3. **真实性原则**:所有的代码分析必须基于工具返回的真实内容,严禁虚假猜测。`;
71
- this.messages.push({
72
- role: "system",
73
- content: (options === null || options === void 0 ? void 0 : options.extraSystemPrompt)
74
- ? `${baseSystemPrompt}\n\n[额外指令]:\n${JSON.stringify(options.extraSystemPrompt)}`
75
- : baseSystemPrompt,
76
- });
62
+ const baseSystemPrompt = `你是一个专业的代码架构师。
63
+ 你的目标是理解并分析用户项目,请务必遵循以下工作流:
64
+
65
+ ### 第一阶段:全景感知 (The "Where" Phase)
66
+ 1. **必须首先调用 'get_directory_tree'**:获取项目完整文件列表,包括样式文件 (.less, .css) 和资源文件。
67
+ 2. 结合目录结构,观察项目架构(如 Monorepo 结构或 src 布局)。
68
+
69
+ ### 第二阶段:逻辑映射 (The "What" Phase)
70
+ 1. **调用 'get_repo_map'**:针对代码文件提取导出定义,理解模块间的调用关系。
71
+ 2. 如果需要查看具体的样式定义,直接使用 'read_text_file' 读取 .less 或 .css 文件。
72
+
73
+ ### 核心原则:
74
+ - 不要猜测文件是否存在,先看目录树。
75
+ - 优先查看 Skeleton(骨架),只有需要修复逻辑时才读取完整 Text(全文)。
76
+ - 始终以中文回答思考过程。`;
77
+ this.systemPrompt = (options === null || options === void 0 ? void 0 : options.extraSystemPrompt)
78
+ ? `${baseSystemPrompt}\n\n[额外指令]:\n${JSON.stringify(options.extraSystemPrompt)}`
79
+ : baseSystemPrompt;
77
80
  this.initTools(options);
78
81
  }
79
- /**
80
- * 工具处理器包装逻辑:增加日志打印和 Token 监控
81
- */
82
+ initTools(options) {
83
+ const allTools = [...(0, builtin_1.createDefaultBuiltinTools)({ options: { ...options, ...this } }), ...this.extraTools];
84
+ this.allTools = allTools.map((t) => ({
85
+ ...t,
86
+ _handler: this.wrapHandler(t.function.name, t._handler),
87
+ }));
88
+ }
82
89
  wrapHandler(name, handler) {
83
90
  return async (args) => {
84
- // 1. 打印工具执行日志
85
91
  console.log(`\n [工具调用]: ${name}`);
86
- if (args === null || args === void 0 ? void 0 : args.filePath) {
87
- console.log(` [目标文件]: ${args.filePath}`);
88
- }
89
- // 2. 执行逻辑
90
92
  const result = await handler(args);
91
93
  const content = typeof result === "string" ? result : JSON.stringify(result);
92
- // 3. 统计 Token 消耗
93
- const outputTokens = this.encoder.encode(content).length;
94
- console.log(` [输出长度]: ${outputTokens} tokens`);
95
- // 2. 更新动态计数器:
96
- // 这里的逻辑是:当前基础 Context + 本次工具调用的输出
97
- // 随着迭代增加,这个值会一直累加,直到任务结束存入 messages
98
- const baseTokens = this.calculateTokens();
99
- this.runningTokenCounter = baseTokens + outputTokens;
100
- // 3. 打印正确且递增的状态
101
- console.log(` 📊 状态: Context ${this.runningTokenCounter} / Limit ${this.maxTokens} tokens`);
94
+ this.runningTokenCounter += this.encoder.encode(content).length;
102
95
  return content;
103
96
  };
104
97
  }
105
- initTools(options) {
106
- const allTools = [
107
- // 注册内置工具
108
- ...(0, builtin_1.createDefaultBuiltinTools)({
109
- options: {
110
- ...options,
111
- ...this
112
- }
113
- }),
114
- ...this.extraTools
115
- ];
116
- if (allTools === null || allTools === void 0 ? void 0 : allTools.length) {
117
- this.allTools.push(...allTools.map((t) => ({
118
- type: t.type,
119
- function: t.function,
120
- _handler: this.wrapHandler(t.function.name, t._handler),
121
- })));
122
- }
123
- }
124
- calculateTokens() {
125
- return this.messages.reduce((acc, msg) => acc + this.encoder.encode(String(msg.content || "")).length, 0);
126
- }
127
- pruneMessages() {
128
- const current = this.calculateTokens();
129
- if (current > this.maxTokens) {
130
- console.log(`\n⚠️ 上下文达到限制 (${current} tokens),正在裁剪旧消息...`);
131
- // 保留 system prompt (index 0),移除后续消息
132
- while (this.calculateTokens() > this.maxTokens * 0.8 && this.messages.length > 2) {
133
- this.messages.splice(1, 1);
134
- }
135
- console.log(`✅ 裁剪完成,当前: ${this.calculateTokens()} tokens`);
136
- }
137
- }
138
98
  async init() {
139
99
  if (this.executor)
140
100
  return;
141
101
  let model;
142
102
  if (this.apiModel) {
143
- console.log("ℹ️ 使用自定义 API Model 实例");
144
103
  model = this.apiModel;
145
104
  }
146
105
  else {
147
- // 降级方案:使用配置创建默认的 ChatOpenAI
148
106
  const apiConfig = await this.ensureApiConfig();
149
- console.log(`ℹ️ 使用默认 ChatOpenAI (${apiConfig.model})`);
150
107
  model = new openai_1.ChatOpenAI({
151
108
  configuration: { baseURL: apiConfig.baseURL, apiKey: apiConfig.apiKey },
152
109
  modelName: apiConfig.model,
153
110
  temperature: 0,
154
- streaming: false
155
111
  });
156
112
  }
157
- const langchainTools = this.allTools.map(t => {
158
- return new tools_1.DynamicStructuredTool({
159
- name: t.function.name,
160
- description: t.function.description || "",
161
- // 定义 schema 告诉 LangChain 这是一个对象输入
162
- // passthrough() 允许接收未在 schema 中显式定义的其他字段
163
- schema: (0, jsonSchemaToZod_1.jsonSchemaToZod)(t.function.parameters),
164
- func: async (args) => {
165
- // 这里的 args 已经被 LangChain 自动解析为对象
166
- return await t._handler(args);
167
- },
168
- });
113
+ // 1. 初始化 SummaryBufferMemory
114
+ // maxTokenLimit 决定了当对话历史超过多少 Token 时触发“自动总结”
115
+ this.memory = new memory_1.ConversationSummaryBufferMemory({
116
+ llm: model,
117
+ maxTokenLimit: 2000,
118
+ memoryKey: "chat_history",
119
+ returnMessages: true,
120
+ // 必须添加下面这两行显式声明:
121
+ inputKey: "input", // 对应 invoke 里的 input 字段
122
+ outputKey: "output", // 对应 Agent 输出的字段
123
+ chatHistory: new memory_1.ChatMessageHistory(), // 👈 显式指定 history 实例
169
124
  });
170
- // src/core/agent-chain.ts 中的 prompt 修改
171
- const prompt = prompts_1.PromptTemplate.fromTemplate(`
172
- {system_prompt}
125
+ const langchainTools = this.allTools.map(t => new tools_1.DynamicStructuredTool({
126
+ name: t.function.name,
127
+ description: t.function.description || "",
128
+ schema: (0, jsonSchemaToZod_1.jsonSchemaToZod)(t.function.parameters),
129
+ func: async (args) => await t._handler(args),
130
+ }));
131
+ const prompt = prompts_1.PromptTemplate.fromTemplate(`{system_prompt}
173
132
 
174
- ### 🛠 可用工具列表 (TOOLS)
175
- --------------------
133
+ ### 🛠 可用工具:
176
134
  {tools}
177
135
 
178
- 工具名称列表: [{tool_names}]
136
+ ### 🛠 工具名称:
137
+ [{tool_names}]
138
+
139
+ ### 📝 历史记录:
140
+ {chat_history}
179
141
 
180
- ### 📝 交互协议格式 (PROTOCOL)
181
- --------------------
182
- 你必须严格遵守以下回复格式:
142
+ ### ⚠️ 回复规范(严格遵守):
143
+ 1. 首先输出 **Thought:**,用中文详细说明你的分析思路。
144
+ 2. 接着输出一个 **JSON Action 代码块**。
183
145
 
184
- Thought: 首先,我会[此处用中文简述你的分析思路和下一步目标]。
146
+ 示例格式:
147
+ Thought: 正在查看目录。
185
148
  \`\`\`json
186
149
  {{
187
- "action": "工具名称",
188
- "action_input": {{ "参数名": "参数值" }}
150
+ "action": "directory_tree",
151
+ "action_input": {{}}
189
152
  }}
190
153
  \`\`\`
191
154
 
192
- 注意:
193
- - 严禁直接输出 JSON,必须先写 Thought。
194
- - Thought 必须包含具体的分析意图,不少于 10 个字。
195
-
196
155
  Begin!
197
156
  Question: {input}
198
- Thought: {agent_scratchpad}`);
157
+ {agent_scratchpad}`); // 👈 强制以 Thought: 开头,解决断更问题
199
158
  const agent = await (0, agents_1.createStructuredChatAgent)({ llm: model, tools: langchainTools, prompt });
200
159
  this.executor = new agents_1.AgentExecutor({
201
160
  agent,
202
161
  tools: langchainTools,
203
- verbose: this.verbose, // 我们已经有了 wrapHandler 日志,关闭原生 verbose 以保持整洁
204
- handleParsingErrors: true,
205
- maxIterations: this.maxIterations
162
+ memory: this.memory, // 挂载内存模块
163
+ verbose: this.verbose,
164
+ maxIterations: this.maxIterations,
165
+ handleParsingErrors: (e) => {
166
+ // 简化报错,不要再给 AI 错误的 JSON 示例
167
+ return `格式不正确。请确保你输出了一个正确的 Markdown JSON 代码块,例如:\n\`\`\`json\n{ "action": "...", "action_input": { ... } }\n\`\`\``;
168
+ },
206
169
  });
207
170
  }
208
171
  async chat(input) {
209
- var _a;
210
172
  if (!this.executor)
211
173
  await this.init();
212
- this.messages.push({ role: "user", content: input });
213
- this.pruneMessages();
214
- console.log(`\n📊 状态: Context ${this.calculateTokens()} / Limit ${this.maxTokens} tokens`);
215
- const stopLoading = this.showLoading("🤖 Agent 正在思考并执行工具...");
174
+ this.runningTokenCounter = this.encoder.encode(input).length;
175
+ const stopLoading = this.showLoading("🤖 Agent 正在思考并管理上下文...");
216
176
  try {
177
+ // 执行请求,AgentExecutor 会自动:
178
+ // 1. 从 memory 加载历史 (chat_history)
179
+ // 2. 将这次对话的结果 saveContext 到 memory
217
180
  const response = await this.executor.invoke({
218
181
  input: input,
219
- system_prompt: this.messages[0].content,
182
+ system_prompt: this.systemPrompt,
220
183
  }, {
221
- // --- 新增:使用回调函数捕获 Thought ---
222
184
  callbacks: [{
223
- handleAgentAction: (action, runId) => {
224
- if (action.log) {
225
- const log = action.log.trim();
226
- // 1. 提取 Thought 部分:取 Thought: 之后,直到遇到 ```json{ 之前的内容
227
- const thoughtMatch = log.match(/Thought:\s*([\s\S]*?)(?=(?:```json|\{|Action:|$))/i);
228
- let thought = "";
229
- if (thoughtMatch && thoughtMatch[1]) {
230
- thought = thoughtMatch[1].trim();
231
- }
232
- else {
233
- // 备选方案:如果没有 Thought 标签,直接截取 JSON 之前的文本
234
- thought = log.split(/```json|\{/)[0].replace(/Thought:/i, "").trim();
235
- }
236
- // 2. 只有当 thought 真的有文字内容(且不是 JSON)时才打印
237
- if (thought && thought.length > 0 && !thought.startsWith('{')) {
238
- // 进一步清洗:如果 thought 包含多行,只取非空的第一行,避免打印太长
239
- const displayThought = thought.split('\n').find(line => line.trim().length > 0);
240
- if (displayThought) {
241
- console.log(`\n💭 [思考]: ${displayThought}`);
242
- }
243
- }
185
+ handleAgentAction: (action) => {
186
+ const rawLog = action.log || "";
187
+ // 兼容 Thought: Thought: [内容]
188
+ let thought = rawLog.split(/```json|\{/)[0]
189
+ .replace(/Thought:/gi, "") // 全局替换掉所有 Thought: 标签
190
+ .trim();
191
+ if (thought) {
192
+ console.log(`\n💭 [思考]: ${thought}`);
244
193
  }
245
194
  }
246
195
  }]
247
196
  });
248
- // 修复点:确保 output 是字符串
249
- let output = typeof response.output === 'string'
250
- ? response.output
251
- : JSON.stringify(response.output);
252
- // 清洗 ReAct 冗余标签
253
- if (output.includes("Final Answer:")) {
254
- output = ((_a = output.split("Final Answer:").pop()) === null || _a === void 0 ? void 0 : _a.trim()) || output;
255
- }
256
- this.messages.push({ role: "assistant", content: output });
257
- return output;
197
+ return typeof response.output === 'string' ? response.output : JSON.stringify(response.output);
258
198
  }
259
199
  finally {
260
200
  stopLoading();
@@ -267,16 +207,12 @@ Thought: {agent_scratchpad}`);
267
207
  process.stdout.write(`\r${chars[i]} ${text}`);
268
208
  i = (i + 1) % chars.length;
269
209
  }, 80);
270
- return () => {
271
- clearInterval(timer);
272
- process.stdout.write('\r\x1b[K');
273
- };
210
+ return () => { clearInterval(timer); process.stdout.write('\r\x1b[K'); };
274
211
  }
275
212
  async start() {
276
213
  await this.init();
277
214
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
278
- console.log(`\n🚀 AI 助手启动 (LangChain 核心)`);
279
- console.log(`📂 目标目录: ${this.targetDir}`);
215
+ console.log(`\n🚀 AI 助手 (Summary+Window 模式) 已启动`);
280
216
  const chatLoop = () => {
281
217
  rl.question("\n👤 你: ", async (input) => {
282
218
  if (!input.trim())
@@ -284,8 +220,8 @@ Thought: {agent_scratchpad}`);
284
220
  if (input.toLowerCase() === "exit")
285
221
  process.exit(0);
286
222
  try {
287
- const result = await this.chat(input);
288
- console.log(`\n🤖 Agent: ${result}`);
223
+ const res = await this.chat(input);
224
+ console.log(`\n🤖 Agent: ${res}`);
289
225
  }
290
226
  catch (err) {
291
227
  console.error("\n❌ 系统错误:", err.message);
@@ -298,20 +234,11 @@ Thought: {agent_scratchpad}`);
298
234
  async ensureApiConfig() {
299
235
  if (this.apiConfig)
300
236
  return this.apiConfig;
301
- if (fs_1.default.existsSync(config_1.CONFIG_FILE)) {
237
+ if (fs_1.default.existsSync(config_1.CONFIG_FILE))
302
238
  return JSON.parse(fs_1.default.readFileSync(config_1.CONFIG_FILE, "utf-8"));
303
- }
304
- const rl = readline.createInterface({
305
- input: process.stdin,
306
- output: process.stdout,
307
- });
239
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
308
240
  const question = (q) => new Promise((res) => rl.question(q, res));
309
- console.log("\n🔑 配置 API 凭据:");
310
- const config = {
311
- baseURL: await question("? API Base URL (如 https://api.openai.com/v1): "),
312
- apiKey: await question("? API Key: "),
313
- model: await question("? Model Name (如 gpt-4o): "),
314
- };
241
+ const config = { baseURL: await question("? API Base URL: "), apiKey: await question("? API Key: "), model: await question("? Model Name: ") };
315
242
  fs_1.default.writeFileSync(config_1.CONFIG_FILE, JSON.stringify(config, null, 2));
316
243
  rl.close();
317
244
  return config;
package/lib/core/agent.js CHANGED
@@ -57,24 +57,21 @@ class McpAgent {
57
57
  this.extraTools = (options === null || options === void 0 ? void 0 : options.tools) || []; // 接收外部传入的工具
58
58
  this.maxTokens = (options === null || options === void 0 ? void 0 : options.maxTokens) || 100000; // 默认 100k
59
59
  this.apiConfig = options === null || options === void 0 ? void 0 : options.apiConfig;
60
- let baseSystemPrompt = `你是一个专业的 AI 代码架构师,具备深度的源码分析与工程化处理能力。
60
+ let baseSystemPrompt = `你是一个专业的代码架构师。
61
+ 你的目标是理解并分析用户项目,请务必遵循以下工作流:
61
62
 
62
- ### 核心操作规范:
63
- 1. **全局扫描(强制首选)**:在开始任何分析任务前,你【必须】首先调用 'get_repo_map'。这是你理解项目目录结构、技术栈及模块关系的唯一权威来源。
64
- 2. **循序渐进的分析路径**:
65
- - 优先使用 'read_skeleton' 提取接口、类和函数签名,以最低的 Token 成本建立代码逻辑视图。
66
- - 仅在需要深入分析具体业务逻辑、提取精准代码块或进行代码修改建议时,才使用 'read_text_file' 'get_method_body'。
67
- 3. **真实性原则**:
68
- - 所有的代码分析、行号定位和逻辑推断必须基于工具返回的真实内容,严禁基于文件名进行虚假猜测。
69
- - 如果工具返回结果为空或报错,应尝试调整路径或更换工具。
70
-
71
- ### 技术能力:
72
- - 精通 TypeScript/JavaScript 及其 AST 结构,能准确识别各种复杂的声明与调用关系。
73
- - 能够理解代码间的依赖链路,并结合项目上下文给出合理的架构建议。
74
-
75
- ### 执行准则:
76
- - **任务导向**:直接通过工具链解决问题,减少不必要的中间对话。
77
- - **自主决策**:根据任务需求自主选择最合适的工具组合,无需每一步都向用户请示。`;
63
+ ### 第一阶段:全景感知 (The "Where" Phase)
64
+ 1. **必须首先调用 'get_directory_tree'**:获取项目完整文件列表,包括样式文件 (.less, .css) 和资源文件。
65
+ 2. 结合目录结构,观察项目架构(如 Monorepo 结构或 src 布局)。
66
+
67
+ ### 第二阶段:逻辑映射 (The "What" Phase)
68
+ 1. **调用 'get_repo_map'**:针对代码文件提取导出定义,理解模块间的调用关系。
69
+ 2. 如果需要查看具体的样式定义,直接使用 'read_text_file' 读取 .less 或 .css 文件。
70
+
71
+ ### 核心原则:
72
+ - 不要猜测文件是否存在,先看目录树。
73
+ - 优先查看 Skeleton(骨架),只有需要修复逻辑时才读取完整 Text(全文)。
74
+ - 始终以中文回答思考过程。`;
78
75
  // 2. 拼接额外指令
79
76
  if (options === null || options === void 0 ? void 0 : options.extraSystemPrompt) {
80
77
  const extra = typeof options.extraSystemPrompt === 'string'
package/lib/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './core/agent';
2
2
  export { default as McpChainAgent } from './core/agent-chain';
3
3
  export { default } from './core/agent';
4
4
  export { createTool } from './utils/createTool';
5
+ export { AgentChainModel } from './model/AgentChainModel';
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.createTool = exports.default = exports.McpChainAgent = void 0;
20
+ exports.AgentChainModel = exports.createTool = exports.default = exports.McpChainAgent = void 0;
21
21
  __exportStar(require("./core/agent"), exports);
22
22
  var agent_chain_1 = require("./core/agent-chain");
23
23
  Object.defineProperty(exports, "McpChainAgent", { enumerable: true, get: function () { return __importDefault(agent_chain_1).default; } });
@@ -25,3 +25,5 @@ var agent_1 = require("./core/agent");
25
25
  Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(agent_1).default; } });
26
26
  var createTool_1 = require("./utils/createTool");
27
27
  Object.defineProperty(exports, "createTool", { enumerable: true, get: function () { return createTool_1.createTool; } });
28
+ var AgentChainModel_1 = require("./model/AgentChainModel");
29
+ Object.defineProperty(exports, "AgentChainModel", { enumerable: true, get: function () { return AgentChainModel_1.AgentChainModel; } });
@@ -0,0 +1,18 @@
1
+ import { BaseChatModel } from "@langchain/core/language_models/chat_models";
2
+ import { AIMessage, MessageFieldWithRole } from "@langchain/core/messages";
3
+ interface AgentChainModelImpl {
4
+ generateAgentChainResponse: (messages: MessageFieldWithRole[]) => Promise<string>;
5
+ }
6
+ export declare abstract class AgentChainModel extends BaseChatModel implements AgentChainModelImpl {
7
+ bind(args: any): any;
8
+ constructor(fields?: any);
9
+ generateAgentChainResponse(messages: MessageFieldWithRole[]): Promise<string>;
10
+ _generate(messages: any): Promise<{
11
+ generations: {
12
+ text: string;
13
+ message: AIMessage;
14
+ }[];
15
+ }>;
16
+ _llmType(): string;
17
+ }
18
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentChainModel = void 0;
4
+ const chat_models_1 = require("@langchain/core/language_models/chat_models");
5
+ const messages_1 = require("@langchain/core/messages");
6
+ class AgentChainModel extends chat_models_1.BaseChatModel {
7
+ bind(args) {
8
+ // 逻辑上调用基类(即使基类在类型上说没有,运行时通常是有的)
9
+ // 如果运行时也没有,这里就返回 this 本身
10
+ // @ts-ignore
11
+ return (super.bind ? super.bind(args) : this);
12
+ }
13
+ constructor(fields) { super(fields || {}); }
14
+ async generateAgentChainResponse(messages) {
15
+ return '';
16
+ }
17
+ async _generate(messages) {
18
+ let text = await this.generateAgentChainResponse(messages);
19
+ return { generations: [{ text, message: new messages_1.AIMessage(text) }] };
20
+ }
21
+ _llmType() { return "my_private_llm"; }
22
+ }
23
+ exports.AgentChainModel = AgentChainModel;
@@ -7,6 +7,7 @@ exports.getFilesystemTools = void 0;
7
7
  const zod_1 = require("zod");
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const path_1 = __importDefault(require("path"));
10
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
10
11
  const createTool_1 = require("../../utils/createTool");
11
12
  const minimatch_1 = require("minimatch");
12
13
  const lib_1 = require("./lib");
@@ -89,7 +90,7 @@ const getFilesystemTools = (targetDir) => {
89
90
  "the contents of a single file. Use the 'head' parameter to read only " +
90
91
  "the first N lines of a file, or the 'tail' parameter to read only " +
91
92
  "the last N lines of a file. Operates on the file as text regardless of extension.",
92
- parameters: ReadTextFileArgsSchema.toJSONSchema(),
93
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(ReadTextFileArgsSchema, 'ReadTextFileArgsSchema'),
93
94
  validateParams: ["path"],
94
95
  handler: readTextFileHandler
95
96
  });
@@ -100,7 +101,7 @@ const getFilesystemTools = (targetDir) => {
100
101
  "or compare multiple files. Each file's content is returned with its " +
101
102
  "path as a reference. Failed reads for individual files won't stop " +
102
103
  "the entire operation. Only works within allowed directories.",
103
- parameters: ReadMultipleFilesArgsSchema.toJSONSchema(),
104
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(ReadMultipleFilesArgsSchema, 'ReadMultipleFilesArgsSchema'),
104
105
  validateParams: ["paths"],
105
106
  handler: async (args) => {
106
107
  const results = await Promise.all(args.paths.map(async (filePath) => {
@@ -123,7 +124,7 @@ const getFilesystemTools = (targetDir) => {
123
124
  description: "Create a new file or completely overwrite an existing file with new content. " +
124
125
  "Use with caution as it will overwrite existing files without warning. " +
125
126
  "Handles text content with proper encoding. Only works within allowed directories.",
126
- parameters: WriteFileArgsSchema.toJSONSchema(),
127
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(WriteFileArgsSchema, 'WriteFileArgsSchema'),
127
128
  validateParams: ["path", "content"],
128
129
  handler: async (args) => {
129
130
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -137,7 +138,7 @@ const getFilesystemTools = (targetDir) => {
137
138
  description: "Make line-based edits to a text file. Each edit replaces exact line sequences " +
138
139
  "with new content. Returns a git-style diff showing the changes made. " +
139
140
  "Only works within allowed directories.",
140
- parameters: EditFileArgsSchema.toJSONSchema(),
141
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(EditFileArgsSchema, 'EditFileArgsSchema'),
141
142
  validateParams: ["path", "edits"],
142
143
  handler: async (args) => {
143
144
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -151,7 +152,7 @@ const getFilesystemTools = (targetDir) => {
151
152
  "nested directories in one operation. If the directory already exists, " +
152
153
  "this operation will succeed silently. Perfect for setting up directory " +
153
154
  "structures for projects or ensuring required paths exist. Only works within allowed directories.",
154
- parameters: CreateDirectoryArgsSchema.toJSONSchema(),
155
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(CreateDirectoryArgsSchema, 'CreateDirectoryArgsSchema'),
155
156
  validateParams: ["path"],
156
157
  handler: async (args) => {
157
158
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -166,7 +167,7 @@ const getFilesystemTools = (targetDir) => {
166
167
  "Results clearly distinguish between files and directories with [FILE] and [DIR] " +
167
168
  "prefixes. This tool is essential for understanding directory structure and " +
168
169
  "finding specific files within a directory. Only works within allowed directories.",
169
- parameters: ListDirectoryArgsSchema.toJSONSchema(),
170
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(ListDirectoryArgsSchema, 'ListDirectoryArgsSchema'),
170
171
  validateParams: ["path"],
171
172
  handler: async (args) => {
172
173
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -183,7 +184,7 @@ const getFilesystemTools = (targetDir) => {
183
184
  "Results clearly distinguish between files and directories with [FILE] and [DIR] " +
184
185
  "prefixes. This tool is useful for understanding directory structure and " +
185
186
  "finding specific files within a directory. Only works within allowed directories.",
186
- parameters: ListDirectoryWithSizesArgsSchema.toJSONSchema(),
187
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(ListDirectoryWithSizesArgsSchema, 'ListDirectoryWithSizesArgsSchema'),
187
188
  validateParams: ["path"],
188
189
  handler: async (args) => {
189
190
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -238,7 +239,7 @@ const getFilesystemTools = (targetDir) => {
238
239
  "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " +
239
240
  "Files have no children array, while directories always have a children array (which may be empty). " +
240
241
  "The output is formatted with 2-space indentation for readability. Only works within allowed directories.",
241
- parameters: DirectoryTreeArgsSchema.toJSONSchema(),
242
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(DirectoryTreeArgsSchema, 'DirectoryTreeArgsSchema'),
242
243
  validateParams: ["path"],
243
244
  handler: async (args) => {
244
245
  const rootPath = args.path;
@@ -283,7 +284,7 @@ const getFilesystemTools = (targetDir) => {
283
284
  "and rename them in a single operation. If the destination exists, the " +
284
285
  "operation will fail. Works across different directories and can be used " +
285
286
  "for simple renaming within the same directory. Both source and destination must be within allowed directories.",
286
- parameters: MoveFileArgsSchema.toJSONSchema(),
287
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(MoveFileArgsSchema, 'MoveFileArgsSchema'),
287
288
  validateParams: ["source", "destination"],
288
289
  handler: async (args) => {
289
290
  const validSourcePath = await (0, lib_1.validatePath)(targetDir, args.source);
@@ -297,7 +298,7 @@ const getFilesystemTools = (targetDir) => {
297
298
  name: "search_files",
298
299
  description: "Search for files matching a specific pattern in a specified path. " +
299
300
  "Returns a list of files that match the pattern. Only works within allowed directories.",
300
- parameters: SearchFilesArgsSchema.toJSONSchema(),
301
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(SearchFilesArgsSchema, 'SearchFilesArgsSchema'),
301
302
  validateParams: ["path", "pattern"],
302
303
  handler: async (args) => {
303
304
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -310,7 +311,7 @@ const getFilesystemTools = (targetDir) => {
310
311
  name: "get_file_info",
311
312
  description: "Get detailed information about a file, including its size, last modified time, and type. " +
312
313
  "Only works within allowed directories.",
313
- parameters: GetFileInfoArgsSchema.toJSONSchema(),
314
+ parameters: (0, zod_to_json_schema_1.zodToJsonSchema)(GetFileInfoArgsSchema, 'GetFileInfoArgsSchema'),
314
315
  validateParams: ["path"],
315
316
  handler: async (args) => {
316
317
  const validPath = await (0, lib_1.validatePath)(targetDir, args.path);
@@ -1,4 +1,3 @@
1
- import { BaseChatModel } from "@langchain/core/language_models/chat_models";
2
1
  import { Client } from "@modelcontextprotocol/sdk/client/index";
3
2
  export interface ApiConfig {
4
3
  baseURL: string;
@@ -27,12 +26,24 @@ export interface McpConfig {
27
26
  }
28
27
  export interface AgentOptions {
29
28
  targetDir?: string;
30
- /** 外部传入的内置工具列表,不传则使用默认的 registerBuiltinTools */
31
29
  tools?: ToolInfo[];
32
30
  extraSystemPrompt?: any;
33
31
  maxTokens?: number;
32
+ /**
33
+ * only for chain agent
34
+ */
34
35
  apiConfig?: ApiConfig;
35
- apiModel?: BaseChatModel;
36
+ /**
37
+ * only for chain agent
38
+ * extends BaseChatModel
39
+ */
40
+ apiModel?: any;
41
+ /**
42
+ * only for chain agent
43
+ */
36
44
  maxIterations?: number;
45
+ /**
46
+ * only for chain agent
47
+ */
37
48
  verbose?: boolean;
38
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saber2pr/ai-agent",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "AI Assistant CLI.",
5
5
  "author": "saber2pr",
6
6
  "license": "ISC",
@@ -22,16 +22,21 @@
22
22
  "prepublishOnly": "tsc"
23
23
  },
24
24
  "dependencies": {
25
- "@langchain/core": "^1.1.18",
26
- "@langchain/openai": "^1.2.4",
27
25
  "@modelcontextprotocol/sdk": "^1.25.3",
28
- "@saber2pr/ts-context-mcp": "^0.0.6",
29
- "js-tiktoken": "^1.0.21",
30
- "langchain": "~0.3",
31
- "openai": "^6.16.0",
26
+ "@saber2pr/ts-context-mcp": "^0.0.8",
32
27
  "diff": "^8.0.3",
33
28
  "glob": "^10.5.0",
34
- "minimatch": "^10.0.1"
29
+ "js-tiktoken": "^1.0.21",
30
+ "minimatch": "^10.0.1",
31
+ "openai": "^6.16.0",
32
+ "zod-to-json-schema": "3.23.2",
33
+ "langchain": "0.3.15",
34
+ "@langchain/core": "0.3.39",
35
+ "@langchain/openai": "0.4.0",
36
+ "zod": "3.23.8"
37
+ },
38
+ "resolutions": {
39
+ "@langchain/core": "0.3.39"
35
40
  },
36
41
  "devDependencies": {
37
42
  "@types/node": "^16.3.3",