@saber2pr/ai-agent 0.0.27 → 0.0.29
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 +57 -161
- package/lib/core/agent-graph.d.ts +15 -5
- package/lib/core/agent-graph.js +141 -16
- package/lib/core/agent.d.ts +1 -0
- package/lib/core/agent.js +24 -2
- package/lib/index.d.ts +0 -2
- package/lib/index.js +1 -5
- package/lib/types/type.d.ts +6 -34
- package/lib/utils/createTool.d.ts +1 -1
- package/lib/utils/jsonSchemaToZod.js +2 -52
- package/package.json +6 -3
- package/lib/cli-chain.d.ts +0 -2
- package/lib/cli-chain.js +0 -9
- package/lib/core/agent-chain.d.ts +0 -23
- package/lib/core/agent-chain.js +0 -246
- package/lib/model/AgentChainModel.d.ts +0 -18
- package/lib/model/AgentChainModel.js +0 -23
package/README.md
CHANGED
|
@@ -1,205 +1,101 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🤖 AI-Agent
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A professional code architect assistant built with **Model Context Protocol (MCP)** and **LangGraph**. It bridges Large Language Models (LLMs) with local file systems and remote services through standardized workflows.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 🌟 Core Modes
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
* **Standard Mode**: Lightweight, fast, and uses direct OpenAI-compatible API calls.
|
|
9
|
-
* **LangChain Mode**: Orchestrated via ReAct agents, supporting complex tool-chains and custom model extensions.
|
|
7
|
+
This project provides two core interaction modes to adapt to different LLM capabilities:
|
|
10
8
|
|
|
9
|
+
### 1. 🚀 Standard Agent (`sagent`)
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
* **Repository Intelligence**: Integrated `PromptEngine` for generating project maps and code skeletons without exhausting tokens.
|
|
14
|
-
* **Automated Audit Workflow**: Specialized tools for locating code violations, providing line-specific fixes, and generating structured JSON reports.
|
|
15
|
-
* **Private LLM Gateway**: Easily adapt to non-standard API protocols (e.g., Jarvis, internal enterprise gateways) by extending the `BaseChatModel`.
|
|
11
|
+
**Best for**: Modern models that support native `tools` parameters (e.g., GPT-4o, Claude 3.5, Qwen-Max).
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## 🛠️ Installation
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
sudo npm i -g @saber2pr/ai-agent
|
|
23
|
-
|
|
24
|
-
# call openapi
|
|
25
|
-
sagent
|
|
26
|
-
|
|
27
|
-
# call third api
|
|
28
|
-
sagent-chain
|
|
29
|
-
|
|
30
|
-
# Clone the repository
|
|
31
|
-
git clone https://github.com/saber2pr/ai-agent.git
|
|
32
|
-
cd ai-agent
|
|
13
|
+
* **Native Function Calling**: Leverages the model's built-in tool-calling capabilities for precise parameter parsing and low latency.
|
|
14
|
+
* **Intuitive Interaction**: A classic Chat loop suitable for instant Q&A, quick code lookups, or single-file refactoring tasks.
|
|
33
15
|
|
|
34
|
-
|
|
35
|
-
npm install
|
|
16
|
+
### 2. 🏗️ Graph Agent (`sagent-graph`)
|
|
36
17
|
|
|
37
|
-
|
|
38
|
-
npm run build
|
|
18
|
+
**Best for**: Long-running automated audits or scenarios where the API does not support native `tools`.
|
|
39
19
|
|
|
40
|
-
|
|
20
|
+
* **State Machine Architecture**: Built on **LangGraph** to implement an automated "Think-Act-Observe-Summarize" loop.
|
|
21
|
+
* **Audit Tracking**: Built-in progress manager that automatically records analyzed files to prevent redundant analysis in complex projects.
|
|
22
|
+
* **Auto-Settlement**: Automatically summarizes and prints **Total Tokens** and execution duration upon task completion or manual exit.
|
|
41
23
|
|
|
42
24
|
---
|
|
43
25
|
|
|
44
|
-
##
|
|
26
|
+
## 🛠️ Built-in Toolset
|
|
45
27
|
|
|
46
|
-
|
|
28
|
+
The Agent comes pre-installed with a suite of tools specifically customized for **code architecture analysis**:
|
|
47
29
|
|
|
48
|
-
|
|
30
|
+
| Category | Tool Name | Description |
|
|
31
|
+
| ---------------- | ----------------- | ------------------------------------------------------------------------- |
|
|
32
|
+
| | `list_directory` | Lists files in a specified directory. |
|
|
33
|
+
| | `analyze_deps` | Analyzes import dependencies of a specific file. |
|
|
34
|
+
| | `read_skeleton` | Reads only code skeletons (class names/signatures) to save tokens. |
|
|
35
|
+
| | `edit_file` | Precise Diff-based editing to avoid rewriting large files. |
|
|
36
|
+
| | `get_method_body` | Precisely extracts the implementation of a specific method. |
|
|
37
|
+
| | `get_file_info` | Gets file metadata like size, permissions, and timestamps. |
|
|
38
|
+
| **Architecture** | `get_repo_map` | **Core**: Extracts export definitions and builds a module dependency map. |
|
|
39
|
+
| **General** | `search_files` | Performs a global keyword search across the project. |
|
|
40
|
+
| **Navigation** | `directory_tree` | Recursively gets the project structure (The entry point for analysis). |
|
|
41
|
+
| **Operations** | `read_text_file` | Reads file content (supports pagination via head/tail). |
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
import McpAgent from "@saber2pr/ai-agent";
|
|
43
|
+
---
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
targetDir: "/path/to/project"
|
|
55
|
-
});
|
|
45
|
+
## 💻 CLI Guide
|
|
56
46
|
|
|
57
|
-
|
|
47
|
+
### Installation & Build
|
|
58
48
|
|
|
49
|
+
```bash
|
|
50
|
+
yarn
|
|
51
|
+
yarn build
|
|
59
52
|
```
|
|
60
53
|
|
|
61
|
-
###
|
|
62
|
-
|
|
63
|
-
Best for complex tasks like "Audit the whole project and fix bugs." It supports autonomous tool usage.
|
|
64
|
-
|
|
65
|
-
```javascript
|
|
66
|
-
import { McpChainAgent } from "@saber2pr/ai-agent";
|
|
67
|
-
import { MyPrivateLLM } from "./your-custom-llm";
|
|
54
|
+
### Usage
|
|
68
55
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
maxIterations: 15,
|
|
72
|
-
targetDir: "/path/to/project"
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await agent.chat("Scan for hardcoded colors and submit a review report.");
|
|
56
|
+
```sh
|
|
57
|
+
sudo npm i -g @saber2pr/ai-agent
|
|
76
58
|
|
|
77
59
|
```
|
|
78
60
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
### Using AgentChainModel (Recommended)
|
|
84
|
-
|
|
85
|
-
For LangChain mode, extend `AgentChainModel` which provides a simplified interface for integrating custom LLMs:
|
|
86
|
-
|
|
87
|
-
```javascript
|
|
88
|
-
import { AgentChainModel } from "@saber2pr/ai-agent";
|
|
89
|
-
|
|
90
|
-
class MyPrivateLLM extends AgentChainModel {
|
|
91
|
-
constructor(fields) {
|
|
92
|
-
super(fields || {});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async generateAgentChainResponse(messages) {
|
|
96
|
-
const lastMessage = messages[messages.length - 1];
|
|
97
|
-
const queryText = lastMessage.content;
|
|
98
|
-
|
|
99
|
-
const response = await fetch("https://your-api-gateway.com/api/completions", {
|
|
100
|
-
method: 'POST',
|
|
101
|
-
body: JSON.stringify({ query: queryText, stream: false }),
|
|
102
|
-
headers: {
|
|
103
|
-
'Content-Type': 'application/json',
|
|
104
|
-
Authorization: `Bearer YOUR_API_KEY`,
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
const errorText = await response.text();
|
|
110
|
-
throw new Error(`HTTP Error: ${response.status}, ${errorText}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const data = await response.json();
|
|
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;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
61
|
+
* **Start Standard Chat**:
|
|
62
|
+
```bash
|
|
63
|
+
sagent
|
|
124
64
|
```
|
|
125
65
|
|
|
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
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 📦 Built-in Toolset
|
|
135
|
-
|
|
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
66
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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.
|
|
67
|
+
* **Start Automated Audit**:
|
|
68
|
+
```bash
|
|
69
|
+
sagent-graph
|
|
70
|
+
```
|
|
170
71
|
|
|
171
72
|
---
|
|
172
73
|
|
|
173
|
-
##
|
|
174
|
-
|
|
175
|
-
You can pass structured rules via the `extraSystemPrompt`:
|
|
74
|
+
## ⚙️ Configuration
|
|
176
75
|
|
|
177
|
-
|
|
178
|
-
import McpAgent from "@saber2pr/ai-agent";
|
|
76
|
+
On the first run, the program will prompt you to configure `~/.saber2pr-agent.json`. You can define your API keys and dynamically connect **MCP Servers** here:
|
|
179
77
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
});
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"baseURL": "https://api.example.com/v1",
|
|
81
|
+
"apiKey": "sk-your-key",
|
|
82
|
+
"model": "gpt-4o"
|
|
83
|
+
}
|
|
188
84
|
|
|
189
85
|
```
|
|
190
86
|
|
|
191
87
|
---
|
|
192
88
|
|
|
193
|
-
##
|
|
89
|
+
## 🔄 Professional Audit Workflow
|
|
194
90
|
|
|
195
|
-
|
|
91
|
+
Regardless of the mode, the Agent follows a standardized logic chain:
|
|
196
92
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
93
|
+
1. **Phase 1: Panoramic Perception (Where)**: Uses `directory_tree` to identify project layout (e.g., Monorepo vs. traditional src structure).
|
|
94
|
+
2. **Phase 2: Logic Mapping (What)**: Uses `get_repo_map` to establish logical relationships—understanding "who calls whom."
|
|
95
|
+
3. **Phase 3: Source Diving (How)**: Locates critical implementations and performs detailed auditing via `read_text_file` or specific extraction tools.
|
|
200
96
|
|
|
201
97
|
---
|
|
202
98
|
|
|
203
|
-
##
|
|
99
|
+
## 📄 License
|
|
204
100
|
|
|
205
101
|
ISC
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BaseMessage } from
|
|
2
|
-
import {
|
|
1
|
+
import { BaseMessage } from '@langchain/core/messages';
|
|
2
|
+
import { GraphAgentOptions } from '../types/type';
|
|
3
3
|
export declare const CONFIG_FILE: string;
|
|
4
4
|
interface TokenUsage {
|
|
5
5
|
total: number;
|
|
@@ -23,7 +23,17 @@ export default class McpGraphAgent {
|
|
|
23
23
|
private verbose;
|
|
24
24
|
private alwaysSystem;
|
|
25
25
|
private recursionLimit;
|
|
26
|
-
|
|
26
|
+
private apiConfig;
|
|
27
|
+
private maxTargetCount;
|
|
28
|
+
private maxTokens;
|
|
29
|
+
private mcpClients;
|
|
30
|
+
constructor(options?: GraphAgentOptions);
|
|
31
|
+
private printLoadedTools;
|
|
32
|
+
private loadMcpConfigs;
|
|
33
|
+
private initMcpTools;
|
|
34
|
+
private prepareTools;
|
|
35
|
+
private ensureInitialized;
|
|
36
|
+
private closeMcpClients;
|
|
27
37
|
private showLoading;
|
|
28
38
|
private startLoading;
|
|
29
39
|
private stopLoading;
|
|
@@ -33,7 +43,7 @@ export default class McpGraphAgent {
|
|
|
33
43
|
start(): Promise<void>;
|
|
34
44
|
private renderOutput;
|
|
35
45
|
callModel(state: typeof AgentState.State): Promise<{
|
|
36
|
-
messages:
|
|
46
|
+
messages: any[];
|
|
37
47
|
tokenUsage: {
|
|
38
48
|
total: number;
|
|
39
49
|
};
|
|
@@ -58,7 +68,7 @@ export default class McpGraphAgent {
|
|
|
58
68
|
mode: import("@langchain/langgraph").BinaryOperatorAggregate<"chat" | "auto", "chat" | "auto">;
|
|
59
69
|
tokenUsage: import("@langchain/langgraph").BinaryOperatorAggregate<TokenUsage, TokenUsage>;
|
|
60
70
|
totalDuration: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
|
|
61
|
-
}>, "tools" | "
|
|
71
|
+
}>, "tools" | "__start__" | "agent" | "progress", {
|
|
62
72
|
messages: import("@langchain/langgraph").BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
63
73
|
auditedFiles: import("@langchain/langgraph").BinaryOperatorAggregate<string[], string[]>;
|
|
64
74
|
targetCount: import("@langchain/langgraph").BinaryOperatorAggregate<number, number>;
|
package/lib/core/agent-graph.js
CHANGED
|
@@ -4,18 +4,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.CONFIG_FILE = void 0;
|
|
7
|
-
const
|
|
7
|
+
const events_1 = require("events");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const readline_1 = __importDefault(require("readline"));
|
|
8
12
|
const messages_1 = require("@langchain/core/messages");
|
|
13
|
+
const prompts_1 = require("@langchain/core/prompts");
|
|
9
14
|
const langgraph_1 = require("@langchain/langgraph");
|
|
10
15
|
const prebuilt_1 = require("@langchain/langgraph/prebuilt");
|
|
11
|
-
const
|
|
12
|
-
const readline_1 = __importDefault(require("readline"));
|
|
13
|
-
const fs_1 = __importDefault(require("fs"));
|
|
14
|
-
const path_1 = __importDefault(require("path"));
|
|
15
|
-
const os_1 = __importDefault(require("os"));
|
|
16
|
-
const events_1 = require("events");
|
|
16
|
+
const openai_1 = require("@langchain/openai");
|
|
17
17
|
const builtin_1 = require("../tools/builtin");
|
|
18
18
|
const convertToLangChainTool_1 = require("../utils/convertToLangChainTool");
|
|
19
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
20
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
21
|
+
const jsonSchemaToZod_1 = require("../utils/jsonSchemaToZod");
|
|
19
22
|
exports.CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
|
|
20
23
|
// ✅ 全局设置:修复 AbortSignal 监听器数量警告
|
|
21
24
|
// LangChain 的 HTTP 客户端会创建多个 AbortSignal,需要增加默认限制
|
|
@@ -56,28 +59,139 @@ class McpGraphAgent {
|
|
|
56
59
|
this.checkpointer = new langgraph_1.MemorySaver();
|
|
57
60
|
this.langchainTools = [];
|
|
58
61
|
this.stopLoadingFunc = null;
|
|
62
|
+
this.mcpClients = [];
|
|
59
63
|
this.options = options;
|
|
60
64
|
this.verbose = options.verbose || false;
|
|
61
65
|
this.alwaysSystem = options.alwaysSystem || true;
|
|
62
66
|
this.targetDir = options.targetDir || process.cwd();
|
|
63
|
-
this.recursionLimit = options.recursionLimit ||
|
|
67
|
+
this.recursionLimit = options.recursionLimit || 80;
|
|
68
|
+
this.apiConfig = options.apiConfig;
|
|
69
|
+
this.maxTargetCount = options.maxTargetCount || 4;
|
|
70
|
+
this.maxTokens = options.maxTokens || 8000;
|
|
64
71
|
process.setMaxListeners(100);
|
|
65
72
|
// ✅ 修复 AbortSignal 监听器数量警告
|
|
66
73
|
// LangChain 的 HTTP 客户端会创建多个 AbortSignal,需要增加默认限制
|
|
67
74
|
// 设置 EventEmitter 的默认 maxListeners,这会影响所有事件发射器(包括 AbortSignal)
|
|
68
75
|
events_1.EventEmitter.defaultMaxListeners = 100;
|
|
69
|
-
const cleanup = () => {
|
|
76
|
+
const cleanup = async () => {
|
|
70
77
|
this.stopLoading();
|
|
78
|
+
await this.closeMcpClients(); // 清理 MCP 连接
|
|
71
79
|
process.stdout.write('\u001B[?25h');
|
|
72
80
|
process.exit(0);
|
|
73
81
|
};
|
|
74
82
|
process.on("SIGINT", cleanup);
|
|
75
83
|
process.on("SIGTERM", cleanup);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
}
|
|
85
|
+
printLoadedTools() {
|
|
86
|
+
console.log("\n🛠️ [Graph] 正在加载工具节点...");
|
|
87
|
+
this.langchainTools.forEach((tool) => {
|
|
88
|
+
// 工具名称
|
|
89
|
+
console.log(`\n🧰 工具名: ${tool.name}`);
|
|
90
|
+
// 提取参数结构 (LangChain Tool 的 schema 是 Zod 对象)
|
|
91
|
+
const schema = tool.schema;
|
|
92
|
+
if (schema && schema.shape) {
|
|
93
|
+
// 如果是 ZodObject,打印其内部 key
|
|
94
|
+
const keys = Object.keys(schema.shape);
|
|
95
|
+
console.log(` 参数结构: [ ${keys.join(", ")} ]`);
|
|
96
|
+
}
|
|
97
|
+
else if (schema && schema._def) {
|
|
98
|
+
// 兼容其他 Zod 类型
|
|
99
|
+
console.log(` 参数类型: ${schema._def.typeName}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// 降级:如果已经是 JSON 对象
|
|
103
|
+
console.log(` 参数结构:`, JSON.stringify(schema, null, 2));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
console.log(`\n✅ Graph 节点就绪,总计加载 ${this.langchainTools.length} 个工具。\n`);
|
|
107
|
+
}
|
|
108
|
+
loadMcpConfigs() {
|
|
109
|
+
const combined = { mcpServers: {} };
|
|
110
|
+
const paths = [
|
|
111
|
+
path_1.default.join(os_1.default.homedir(), ".cursor", "mcp.json"),
|
|
112
|
+
path_1.default.join(os_1.default.homedir(), ".vscode", "mcp.json"),
|
|
113
|
+
];
|
|
114
|
+
paths.forEach((p) => {
|
|
115
|
+
if (fs_1.default.existsSync(p)) {
|
|
116
|
+
const content = JSON.parse(fs_1.default.readFileSync(p, "utf-8"));
|
|
117
|
+
Object.assign(combined.mcpServers, content.mcpServers);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return combined;
|
|
121
|
+
}
|
|
122
|
+
async initMcpTools() {
|
|
123
|
+
const mcpConfig = this.loadMcpConfigs();
|
|
124
|
+
const mcpServers = mcpConfig.mcpServers || {};
|
|
125
|
+
const mcpToolInfos = [];
|
|
126
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
127
|
+
try {
|
|
128
|
+
const transport = new stdio_js_1.StdioClientTransport({
|
|
129
|
+
command: config.command,
|
|
130
|
+
args: config.args,
|
|
131
|
+
env: { ...process.env, ...(config.env || {}) },
|
|
132
|
+
});
|
|
133
|
+
const client = new index_js_1.Client({ name: "mcp-graph-client", version: "1.0.0" }, { capabilities: {} });
|
|
134
|
+
await client.connect(transport);
|
|
135
|
+
this.mcpClients.push(client);
|
|
136
|
+
const { tools } = await client.listTools();
|
|
137
|
+
tools.forEach((tool) => {
|
|
138
|
+
mcpToolInfos.push({
|
|
139
|
+
type: "function",
|
|
140
|
+
function: {
|
|
141
|
+
name: tool.name,
|
|
142
|
+
description: tool.description,
|
|
143
|
+
parameters: (0, jsonSchemaToZod_1.jsonSchemaToZod)(tool.inputSchema), // MCP 使用 JSON Schema
|
|
144
|
+
},
|
|
145
|
+
_handler: async (args) => {
|
|
146
|
+
const result = await client.callTool({
|
|
147
|
+
name: tool.name,
|
|
148
|
+
arguments: args,
|
|
149
|
+
});
|
|
150
|
+
return result.content;
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
console.log(`\n✅ 已连接 MCP 服务 [${name}]: 加载了 ${tools.length} 个工具`);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(`\n❌ 连接 MCP 服务 [${name}] 失败:`, error.message);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return mcpToolInfos;
|
|
161
|
+
}
|
|
162
|
+
async prepareTools() {
|
|
163
|
+
const builtinToolInfos = (0, builtin_1.createDefaultBuiltinTools)({ options: this.options });
|
|
164
|
+
const mcpToolInfos = await this.initMcpTools();
|
|
165
|
+
// 合并内置、手动传入和 MCP 工具
|
|
166
|
+
const allToolInfos = [
|
|
167
|
+
...builtinToolInfos,
|
|
168
|
+
...(this.options.tools || []),
|
|
169
|
+
...mcpToolInfos
|
|
170
|
+
];
|
|
171
|
+
this.langchainTools = allToolInfos.map((t) => (0, convertToLangChainTool_1.convertToLangChainTool)(t));
|
|
79
172
|
this.toolNode = new prebuilt_1.ToolNode(this.langchainTools);
|
|
80
173
|
}
|
|
174
|
+
// ✅ 修改:初始化逻辑
|
|
175
|
+
async ensureInitialized() {
|
|
176
|
+
if (this.model && this.langchainTools.length > 0)
|
|
177
|
+
return;
|
|
178
|
+
// 1. 加载所有工具(含 MCP)
|
|
179
|
+
await this.prepareTools();
|
|
180
|
+
// 2. 初始化模型
|
|
181
|
+
await this.getModel();
|
|
182
|
+
// 3. 打印工具状态
|
|
183
|
+
this.printLoadedTools();
|
|
184
|
+
}
|
|
185
|
+
// ✅ 新增:关闭连接
|
|
186
|
+
async closeMcpClients() {
|
|
187
|
+
for (const client of this.mcpClients) {
|
|
188
|
+
try {
|
|
189
|
+
await client.close();
|
|
190
|
+
}
|
|
191
|
+
catch (e) { }
|
|
192
|
+
}
|
|
193
|
+
this.mcpClients = [];
|
|
194
|
+
}
|
|
81
195
|
showLoading(text) {
|
|
82
196
|
const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
83
197
|
let i = 0;
|
|
@@ -113,13 +227,15 @@ class McpGraphAgent {
|
|
|
113
227
|
configuration: { baseURL: config.baseURL },
|
|
114
228
|
modelName: config.model,
|
|
115
229
|
temperature: 0,
|
|
230
|
+
maxTokens: this.maxTokens,
|
|
116
231
|
});
|
|
117
232
|
}
|
|
118
|
-
// 绑定工具,使模型具备调用能力
|
|
119
233
|
this.model = modelInstance.bindTools(this.langchainTools);
|
|
120
234
|
return this.model;
|
|
121
235
|
}
|
|
122
236
|
async askForConfig() {
|
|
237
|
+
if (this.apiConfig)
|
|
238
|
+
return this.apiConfig;
|
|
123
239
|
let config = {};
|
|
124
240
|
if (fs_1.default.existsSync(exports.CONFIG_FILE)) {
|
|
125
241
|
try {
|
|
@@ -139,17 +255,19 @@ class McpGraphAgent {
|
|
|
139
255
|
return config;
|
|
140
256
|
}
|
|
141
257
|
async chat(query = "开始代码审计") {
|
|
258
|
+
await this.ensureInitialized();
|
|
142
259
|
await this.getModel();
|
|
143
260
|
const app = await this.createGraph();
|
|
144
261
|
const stream = await app.stream({
|
|
145
262
|
messages: [new messages_1.HumanMessage(query)],
|
|
146
263
|
mode: "auto",
|
|
147
|
-
targetCount:
|
|
264
|
+
targetCount: this.maxTargetCount,
|
|
148
265
|
}, { configurable: { thread_id: "auto_worker" }, recursionLimit: this.recursionLimit, debug: this.verbose, });
|
|
149
266
|
for await (const output of stream)
|
|
150
267
|
this.renderOutput(output);
|
|
151
268
|
}
|
|
152
269
|
async start() {
|
|
270
|
+
await this.ensureInitialized();
|
|
153
271
|
await this.getModel();
|
|
154
272
|
const app = await this.createGraph();
|
|
155
273
|
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -165,7 +283,7 @@ class McpGraphAgent {
|
|
|
165
283
|
rl.close();
|
|
166
284
|
return;
|
|
167
285
|
}
|
|
168
|
-
const stream = await app.stream({ messages: [new messages_1.HumanMessage(input)], mode: "chat" }, { configurable: { thread_id: "session" }, recursionLimit:
|
|
286
|
+
const stream = await app.stream({ messages: [new messages_1.HumanMessage(input)], mode: "chat" }, { configurable: { thread_id: "session" }, recursionLimit: this.recursionLimit, debug: this.verbose, });
|
|
169
287
|
for await (const output of stream)
|
|
170
288
|
this.renderOutput(output);
|
|
171
289
|
ask();
|
|
@@ -351,6 +469,13 @@ class McpGraphAgent {
|
|
|
351
469
|
const messages = state.messages;
|
|
352
470
|
const lastMsg = messages[messages.length - 1];
|
|
353
471
|
const content = lastMsg.content || "";
|
|
472
|
+
// 🛑 新增:全局 Token 熔断保护
|
|
473
|
+
// 如果已消耗 Token 超过了 options 中设置的 maxTokens (假设是总限额)
|
|
474
|
+
if (this.options.maxTokens && state.tokenUsage.total >= this.options.maxTokens) {
|
|
475
|
+
console.warn("⚠️ [警告] 已达到最大 Token 限制,强制结束任务。");
|
|
476
|
+
this.printFinalSummary(state);
|
|
477
|
+
return langgraph_1.END;
|
|
478
|
+
}
|
|
354
479
|
// 1. 如果 AI 想要调用工具,去 tools 节点
|
|
355
480
|
if (lastMsg.tool_calls && lastMsg.tool_calls.length > 0) {
|
|
356
481
|
return "tools";
|
|
@@ -359,7 +484,7 @@ class McpGraphAgent {
|
|
|
359
484
|
// - 模式是 auto 且审计完成
|
|
360
485
|
// - 或者 AI 明确输出了结束语
|
|
361
486
|
// - 或者 AI 输出了普通内容且没有工具调用(针对问答模式)
|
|
362
|
-
const isAutoFinished = state.mode === "auto" && state.auditedFiles.length
|
|
487
|
+
const isAutoFinished = state.mode === "auto" && state.auditedFiles.length > state.targetCount;
|
|
363
488
|
const isFinalAnswer = content.includes("Final Answer");
|
|
364
489
|
// ✅ 修复核心:如果 AI 只是在聊天(没有工具调用),直接结束,不要跳回 agent
|
|
365
490
|
if (isAutoFinished || isFinalAnswer || state.mode === "chat") {
|
package/lib/core/agent.d.ts
CHANGED
package/lib/core/agent.js
CHANGED
|
@@ -46,6 +46,7 @@ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
|
46
46
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
47
47
|
const config_1 = require("../config/config");
|
|
48
48
|
const builtin_1 = require("../tools/builtin");
|
|
49
|
+
const zod_to_json_schema_1 = require("zod-to-json-schema");
|
|
49
50
|
const jsonSchemaToZod_1 = require("../utils/jsonSchemaToZod");
|
|
50
51
|
class McpAgent {
|
|
51
52
|
constructor(options) {
|
|
@@ -215,6 +216,25 @@ class McpAgent {
|
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
218
|
}
|
|
219
|
+
getToolsForOpenAIAPI() {
|
|
220
|
+
return this.allTools.map((tool) => {
|
|
221
|
+
const { _handler, _client, _originalName, ...rest } = tool;
|
|
222
|
+
let parameters = rest.function.parameters;
|
|
223
|
+
// 💡 核心逻辑:判断是否为 Zod 实例并转换
|
|
224
|
+
// Zod 对象通常包含 _def 属性,或者你可以用 instanceof z.ZodType
|
|
225
|
+
if (parameters && typeof parameters === 'object' && ('_def' in parameters || parameters.safeParse)) {
|
|
226
|
+
// 使用 zod-to-json-schema 转换为标准 JSON Schema
|
|
227
|
+
parameters = (0, zod_to_json_schema_1.zodToJsonSchema)(parameters);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
...rest,
|
|
231
|
+
function: {
|
|
232
|
+
...rest.function,
|
|
233
|
+
parameters: parameters, // 确保这里是 Plain Object
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
}
|
|
218
238
|
async processChat(userInput) {
|
|
219
239
|
var _a;
|
|
220
240
|
this.messages.push({ role: 'user', content: userInput });
|
|
@@ -237,7 +257,7 @@ class McpAgent {
|
|
|
237
257
|
response = await this.openai.chat.completions.create({
|
|
238
258
|
model: this.modelName,
|
|
239
259
|
messages: this.messages,
|
|
240
|
-
tools: this.
|
|
260
|
+
tools: this.getToolsForOpenAIAPI(),
|
|
241
261
|
tool_choice: 'auto'
|
|
242
262
|
});
|
|
243
263
|
}
|
|
@@ -262,7 +282,9 @@ class McpAgent {
|
|
|
262
282
|
if (args.filePath) {
|
|
263
283
|
console.log(` 📂 正在查看文件: ${args.filePath}`);
|
|
264
284
|
}
|
|
265
|
-
|
|
285
|
+
// 2. 打印操作信息和参数
|
|
286
|
+
console.log(`\n 🛠️ 执行工具: \x1b[36m${call.function.name}\x1b[0m`);
|
|
287
|
+
console.log(` 📦 传入参数: \x1b[2m${JSON.stringify(args)}\x1b[0m`);
|
|
266
288
|
let result;
|
|
267
289
|
if (tool === null || tool === void 0 ? void 0 : tool._handler) {
|
|
268
290
|
result = await tool._handler(args);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export * from './core/agent';
|
|
2
|
-
export { default as McpChainAgent } from './core/agent-chain';
|
|
3
2
|
export { default } from './core/agent';
|
|
4
3
|
export { createTool } from './utils/createTool';
|
|
5
|
-
export { AgentChainModel } from './model/AgentChainModel';
|
|
6
4
|
export { AgentGraphModel, AgentGraphLLMResponse } from './model/AgentGraphModel';
|
|
7
5
|
export { default as McpGraphAgent } from './core/agent-graph';
|
package/lib/index.js
CHANGED
|
@@ -17,16 +17,12 @@ 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.McpGraphAgent = exports.AgentGraphModel = exports.
|
|
20
|
+
exports.McpGraphAgent = exports.AgentGraphModel = exports.createTool = exports.default = void 0;
|
|
21
21
|
__exportStar(require("./core/agent"), exports);
|
|
22
|
-
var agent_chain_1 = require("./core/agent-chain");
|
|
23
|
-
Object.defineProperty(exports, "McpChainAgent", { enumerable: true, get: function () { return __importDefault(agent_chain_1).default; } });
|
|
24
22
|
var agent_1 = require("./core/agent");
|
|
25
23
|
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(agent_1).default; } });
|
|
26
24
|
var createTool_1 = require("./utils/createTool");
|
|
27
25
|
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; } });
|
|
30
26
|
var AgentGraphModel_1 = require("./model/AgentGraphModel");
|
|
31
27
|
Object.defineProperty(exports, "AgentGraphModel", { enumerable: true, get: function () { return AgentGraphModel_1.AgentGraphModel; } });
|
|
32
28
|
var agent_graph_1 = require("./core/agent-graph");
|
package/lib/types/type.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Client } from '@modelcontextprotocol/sdk/client/index';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { AgentGraphModel } from '../model/AgentGraphModel';
|
|
3
4
|
export interface ApiConfig {
|
|
4
5
|
baseURL: string;
|
|
5
6
|
apiKey: string;
|
|
@@ -30,41 +31,12 @@ export interface AgentOptions {
|
|
|
30
31
|
tools?: ToolInfo[];
|
|
31
32
|
extraSystemPrompt?: any;
|
|
32
33
|
maxTokens?: number;
|
|
33
|
-
/**
|
|
34
|
-
* only for chain agent
|
|
35
|
-
*/
|
|
36
|
-
apiConfig?: ApiConfig;
|
|
37
|
-
/**
|
|
38
|
-
* only for chain agent
|
|
39
|
-
* extends BaseChatModel
|
|
40
|
-
*/
|
|
41
|
-
apiModel?: any;
|
|
42
|
-
/**
|
|
43
|
-
* only for chain agent
|
|
44
|
-
*/
|
|
45
|
-
maxIterations?: number;
|
|
46
|
-
/**
|
|
47
|
-
* only for chain agent
|
|
48
|
-
*/
|
|
49
34
|
verbose?: boolean;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* only for graph agent
|
|
56
|
-
*/
|
|
57
|
-
apiKey?: string;
|
|
58
|
-
/**
|
|
59
|
-
* only for graph agent
|
|
60
|
-
*/
|
|
61
|
-
modelName?: string;
|
|
62
|
-
/**
|
|
63
|
-
* only for graph agent
|
|
64
|
-
*/
|
|
35
|
+
apiConfig?: ApiConfig;
|
|
36
|
+
}
|
|
37
|
+
export interface GraphAgentOptions extends AgentOptions {
|
|
38
|
+
apiModel?: AgentGraphModel;
|
|
65
39
|
alwaysSystem?: boolean;
|
|
66
|
-
/**
|
|
67
|
-
* only for graph agent
|
|
68
|
-
*/
|
|
69
40
|
recursionLimit?: number;
|
|
41
|
+
maxTargetCount?: number;
|
|
70
42
|
}
|
|
@@ -1,57 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.jsonSchemaToZod = jsonSchemaToZod;
|
|
4
|
-
const
|
|
4
|
+
const zod_from_json_schema_1 = require("zod-from-json-schema");
|
|
5
5
|
function jsonSchemaToZod(parameters) {
|
|
6
|
-
|
|
7
|
-
if (!parameters || typeof parameters !== 'object') {
|
|
8
|
-
return zod_1.z.object({}).passthrough();
|
|
9
|
-
}
|
|
10
|
-
// 辅助函数:递归转换单个属性
|
|
11
|
-
function convertProp(prop) {
|
|
12
|
-
let schema = zod_1.z.any();
|
|
13
|
-
if (prop.type === "string") {
|
|
14
|
-
schema = zod_1.z.string();
|
|
15
|
-
}
|
|
16
|
-
else if (prop.type === "number") {
|
|
17
|
-
schema = zod_1.z.number();
|
|
18
|
-
}
|
|
19
|
-
else if (prop.type === "boolean") {
|
|
20
|
-
schema = zod_1.z.boolean();
|
|
21
|
-
}
|
|
22
|
-
else if (prop.type === "array") {
|
|
23
|
-
// 递归处理数组项
|
|
24
|
-
if (prop.items) {
|
|
25
|
-
schema = zod_1.z.array(convertProp(prop.items));
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
schema = zod_1.z.array(zod_1.z.any());
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
else if (prop.type === "object") {
|
|
32
|
-
// 递归处理嵌套对象
|
|
33
|
-
const nestedObj = {};
|
|
34
|
-
if (prop.properties) {
|
|
35
|
-
for (const key in prop.properties) {
|
|
36
|
-
nestedObj[key] = convertProp(prop.properties[key]);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
schema = zod_1.z.object(nestedObj).passthrough();
|
|
40
|
-
}
|
|
41
|
-
if (prop.description) {
|
|
42
|
-
schema = schema.describe(prop.description);
|
|
43
|
-
}
|
|
44
|
-
return schema;
|
|
45
|
-
}
|
|
46
|
-
const obj = {};
|
|
47
|
-
const properties = parameters.properties || {};
|
|
48
|
-
for (const key in properties) {
|
|
49
|
-
const prop = properties[key];
|
|
50
|
-
let schema = convertProp(prop);
|
|
51
|
-
// 处理必填项
|
|
52
|
-
const isRequired = (_a = parameters.required) === null || _a === void 0 ? void 0 : _a.includes(key);
|
|
53
|
-
obj[key] = isRequired ? schema : schema.nullable();
|
|
54
|
-
}
|
|
55
|
-
// 使用 passthrough 或 loose 以增加容错性
|
|
56
|
-
return zod_1.z.object(obj);
|
|
6
|
+
return (0, zod_from_json_schema_1.convertJsonSchemaToZod)(parameters);
|
|
57
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saber2pr/ai-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29",
|
|
4
4
|
"description": "AI Assistant CLI.",
|
|
5
5
|
"author": "saber2pr",
|
|
6
6
|
"license": "ISC",
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
],
|
|
10
10
|
"bin": {
|
|
11
11
|
"sagent": "./lib/cli.js",
|
|
12
|
-
"sagent-chain": "./lib/cli-chain.js",
|
|
13
12
|
"sagent-graph": "./lib/cli-graph.js"
|
|
14
13
|
},
|
|
15
14
|
"publishConfig": {
|
|
@@ -20,6 +19,7 @@
|
|
|
20
19
|
"scripts": {
|
|
21
20
|
"test": "node lib/index",
|
|
22
21
|
"start": "tsc --watch",
|
|
22
|
+
"build": "tsc",
|
|
23
23
|
"prepublishOnly": "tsc"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
@@ -34,9 +34,12 @@
|
|
|
34
34
|
"langchain": "0.3.15",
|
|
35
35
|
"minimatch": "^10.0.1",
|
|
36
36
|
"openai": "^6.16.0",
|
|
37
|
-
"zod": "3.
|
|
37
|
+
"zod": "3.25",
|
|
38
|
+
"zod-from-json-schema": "^0.1.0",
|
|
39
|
+
"zod-to-json-schema": "3.23.2"
|
|
38
40
|
},
|
|
39
41
|
"resolutions": {
|
|
42
|
+
"zod": "3.25",
|
|
40
43
|
"@langchain/core": "0.3.39"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
package/lib/cli-chain.d.ts
DELETED
package/lib/cli-chain.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
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_chain_1 = __importDefault(require("./core/agent-chain"));
|
|
8
|
-
const agent = new agent_chain_1.default();
|
|
9
|
-
agent.start();
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { AgentOptions } from '../types/type';
|
|
2
|
-
export default class McpChainAgent {
|
|
3
|
-
private allTools;
|
|
4
|
-
private encoder;
|
|
5
|
-
private extraTools;
|
|
6
|
-
private maxTokens;
|
|
7
|
-
private executor?;
|
|
8
|
-
private apiConfig;
|
|
9
|
-
private maxIterations;
|
|
10
|
-
private apiModel?;
|
|
11
|
-
private memory?;
|
|
12
|
-
private systemPrompt;
|
|
13
|
-
private runningTokenCounter;
|
|
14
|
-
private verbose;
|
|
15
|
-
constructor(options?: AgentOptions);
|
|
16
|
-
private initTools;
|
|
17
|
-
private wrapHandler;
|
|
18
|
-
init(): Promise<void>;
|
|
19
|
-
chat(input: string): Promise<string>;
|
|
20
|
-
private showLoading;
|
|
21
|
-
start(): Promise<void>;
|
|
22
|
-
private ensureApiConfig;
|
|
23
|
-
}
|
package/lib/core/agent-chain.js
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
const fs_1 = __importDefault(require("fs"));
|
|
40
|
-
const js_tiktoken_1 = require("js-tiktoken");
|
|
41
|
-
const agents_1 = require("langchain/agents");
|
|
42
|
-
const memory_1 = require("langchain/memory");
|
|
43
|
-
const readline = __importStar(require("readline"));
|
|
44
|
-
const prompts_1 = require("@langchain/core/prompts");
|
|
45
|
-
const tools_1 = require("@langchain/core/tools");
|
|
46
|
-
const openai_1 = require("@langchain/openai");
|
|
47
|
-
const config_1 = require("../config/config");
|
|
48
|
-
const builtin_1 = require("../tools/builtin");
|
|
49
|
-
class McpChainAgent {
|
|
50
|
-
constructor(options) {
|
|
51
|
-
this.allTools = [];
|
|
52
|
-
this.encoder = (0, js_tiktoken_1.getEncoding)("cl100k_base");
|
|
53
|
-
this.extraTools = [];
|
|
54
|
-
this.runningTokenCounter = 0;
|
|
55
|
-
this.extraTools = (options === null || options === void 0 ? void 0 : options.tools) || [];
|
|
56
|
-
this.maxTokens = (options === null || options === void 0 ? void 0 : options.maxTokens) || 8000;
|
|
57
|
-
this.apiConfig = options === null || options === void 0 ? void 0 : options.apiConfig;
|
|
58
|
-
this.maxIterations = (options === null || options === void 0 ? void 0 : options.maxIterations) || 50;
|
|
59
|
-
this.apiModel = options === null || options === void 0 ? void 0 : options.apiModel;
|
|
60
|
-
this.verbose = (options === null || options === void 0 ? void 0 : options.verbose) || false;
|
|
61
|
-
const baseSystemPrompt = `你是一个专业的代码架构师。
|
|
62
|
-
你的目标是理解并分析用户项目,请务必遵循以下工作流:
|
|
63
|
-
|
|
64
|
-
### 第一阶段:全景感知 (The "Where" Phase)
|
|
65
|
-
1. **必须首先调用 'get_directory_tree'**:获取项目完整文件列表,包括样式文件 (.less, .css) 和资源文件。
|
|
66
|
-
2. 结合目录结构,观察项目架构(如 Monorepo 结构或 src 布局)。
|
|
67
|
-
|
|
68
|
-
### 第二阶段:逻辑映射 (The "What" Phase)
|
|
69
|
-
1. **调用 'get_repo_map'**:针对代码文件提取导出定义,理解模块间的调用关系。
|
|
70
|
-
2. 如果需要查看具体的样式定义,直接使用 'read_text_file' 读取 .less 或 .css 文件。
|
|
71
|
-
|
|
72
|
-
### 核心原则:
|
|
73
|
-
- 不要猜测文件是否存在,先看目录树。
|
|
74
|
-
- 优先查看 Skeleton(骨架),只有需要修复逻辑时才读取完整 Text(全文)。
|
|
75
|
-
- 始终以中文回答思考过程。`;
|
|
76
|
-
this.systemPrompt = (options === null || options === void 0 ? void 0 : options.extraSystemPrompt)
|
|
77
|
-
? `${baseSystemPrompt}\n\n[额外指令]:\n${JSON.stringify(options.extraSystemPrompt)}`
|
|
78
|
-
: baseSystemPrompt;
|
|
79
|
-
this.initTools(options);
|
|
80
|
-
}
|
|
81
|
-
initTools(options) {
|
|
82
|
-
const allTools = [...(0, builtin_1.createDefaultBuiltinTools)({ options: { ...options, ...this } }), ...this.extraTools];
|
|
83
|
-
this.allTools = allTools.map((t) => ({
|
|
84
|
-
...t,
|
|
85
|
-
_handler: this.wrapHandler(t.function.name, t._handler),
|
|
86
|
-
}));
|
|
87
|
-
}
|
|
88
|
-
wrapHandler(name, handler) {
|
|
89
|
-
return async (args) => {
|
|
90
|
-
console.log(`\n [工具调用]: ${name}`);
|
|
91
|
-
const result = await handler(args);
|
|
92
|
-
const content = typeof result === "string" ? result : JSON.stringify(result);
|
|
93
|
-
this.runningTokenCounter += this.encoder.encode(content).length;
|
|
94
|
-
return content;
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
async init() {
|
|
98
|
-
if (this.executor)
|
|
99
|
-
return;
|
|
100
|
-
let model;
|
|
101
|
-
if (this.apiModel) {
|
|
102
|
-
model = this.apiModel;
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
const apiConfig = await this.ensureApiConfig();
|
|
106
|
-
model = new openai_1.ChatOpenAI({
|
|
107
|
-
configuration: { baseURL: apiConfig.baseURL, apiKey: apiConfig.apiKey },
|
|
108
|
-
modelName: apiConfig.model,
|
|
109
|
-
temperature: 0,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
// 1. 初始化 SummaryBufferMemory
|
|
113
|
-
// maxTokenLimit 决定了当对话历史超过多少 Token 时触发“自动总结”
|
|
114
|
-
this.memory = new memory_1.ConversationSummaryBufferMemory({
|
|
115
|
-
llm: model,
|
|
116
|
-
maxTokenLimit: this.maxTokens,
|
|
117
|
-
memoryKey: "chat_history",
|
|
118
|
-
returnMessages: true,
|
|
119
|
-
// 必须添加下面这两行显式声明:
|
|
120
|
-
inputKey: "input", // 对应 invoke 里的 input 字段
|
|
121
|
-
outputKey: "output", // 对应 Agent 输出的字段
|
|
122
|
-
chatHistory: new memory_1.ChatMessageHistory(), // 👈 显式指定 history 实例
|
|
123
|
-
});
|
|
124
|
-
const langchainTools = this.allTools.map(t => new tools_1.DynamicStructuredTool({
|
|
125
|
-
name: t.function.name,
|
|
126
|
-
description: t.function.description || "",
|
|
127
|
-
schema: (t.function.parameters),
|
|
128
|
-
func: async (args) => await t._handler(args),
|
|
129
|
-
}));
|
|
130
|
-
const prompt = prompts_1.PromptTemplate.fromTemplate(`{system_prompt}
|
|
131
|
-
|
|
132
|
-
### 🛠 可用工具:
|
|
133
|
-
{tools}
|
|
134
|
-
|
|
135
|
-
### 🛠 工具名称:
|
|
136
|
-
[{tool_names}]
|
|
137
|
-
|
|
138
|
-
### 📝 历史记录:
|
|
139
|
-
{chat_history}
|
|
140
|
-
|
|
141
|
-
### ⚠️ 回复规范(严格遵守):
|
|
142
|
-
1. 首先输出 **Thought:**,用中文详细说明你的分析思路。
|
|
143
|
-
2. 接着输出一个 **JSON Action 代码块**。
|
|
144
|
-
|
|
145
|
-
示例格式:
|
|
146
|
-
Thought: 正在查看目录。
|
|
147
|
-
\`\`\`json
|
|
148
|
-
{{
|
|
149
|
-
"action": "directory_tree",
|
|
150
|
-
"action_input": {{}}
|
|
151
|
-
}}
|
|
152
|
-
\`\`\`
|
|
153
|
-
|
|
154
|
-
Begin!
|
|
155
|
-
Question: {input}
|
|
156
|
-
{agent_scratchpad}`); // 👈 强制以 Thought: 开头,解决断更问题
|
|
157
|
-
const agent = await (0, agents_1.createStructuredChatAgent)({ llm: model, tools: langchainTools, prompt });
|
|
158
|
-
this.executor = new agents_1.AgentExecutor({
|
|
159
|
-
agent,
|
|
160
|
-
tools: langchainTools,
|
|
161
|
-
memory: this.memory, // 挂载内存模块
|
|
162
|
-
verbose: this.verbose,
|
|
163
|
-
maxIterations: this.maxIterations,
|
|
164
|
-
handleParsingErrors: (e) => {
|
|
165
|
-
// 简化报错,不要再给 AI 错误的 JSON 示例
|
|
166
|
-
return `格式不正确。请确保你输出了一个正确的 Markdown JSON 代码块,例如:\n\`\`\`json\n{ "action": "...", "action_input": { ... } }\n\`\`\``;
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
async chat(input) {
|
|
171
|
-
if (!this.executor)
|
|
172
|
-
await this.init();
|
|
173
|
-
this.runningTokenCounter = this.encoder.encode(input).length;
|
|
174
|
-
const stopLoading = this.showLoading("🤖 Agent 正在思考并管理上下文...");
|
|
175
|
-
try {
|
|
176
|
-
// 执行请求,AgentExecutor 会自动:
|
|
177
|
-
// 1. 从 memory 加载历史 (chat_history)
|
|
178
|
-
// 2. 将这次对话的结果 saveContext 到 memory
|
|
179
|
-
const response = await this.executor.invoke({
|
|
180
|
-
input: input,
|
|
181
|
-
system_prompt: this.systemPrompt,
|
|
182
|
-
}, {
|
|
183
|
-
callbacks: [{
|
|
184
|
-
handleAgentAction: (action) => {
|
|
185
|
-
const rawLog = action.log || "";
|
|
186
|
-
// 兼容 Thought: Thought: [内容]
|
|
187
|
-
let thought = rawLog.split(/```json|\{/)[0]
|
|
188
|
-
.replace(/Thought:/gi, "") // 全局替换掉所有 Thought: 标签
|
|
189
|
-
.trim();
|
|
190
|
-
if (thought) {
|
|
191
|
-
console.log(`\n💭 [思考]: ${thought}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}]
|
|
195
|
-
});
|
|
196
|
-
return typeof response.output === 'string' ? response.output : JSON.stringify(response.output);
|
|
197
|
-
}
|
|
198
|
-
finally {
|
|
199
|
-
stopLoading();
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
showLoading(text) {
|
|
203
|
-
const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
204
|
-
let i = 0;
|
|
205
|
-
const timer = setInterval(() => {
|
|
206
|
-
process.stdout.write(`\r${chars[i]} ${text}`);
|
|
207
|
-
i = (i + 1) % chars.length;
|
|
208
|
-
}, 80);
|
|
209
|
-
return () => { clearInterval(timer); process.stdout.write('\r\x1b[K'); };
|
|
210
|
-
}
|
|
211
|
-
async start() {
|
|
212
|
-
await this.init();
|
|
213
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
214
|
-
console.log(`\n🚀 AI 助手 (Summary+Window 模式) 已启动`);
|
|
215
|
-
const chatLoop = () => {
|
|
216
|
-
rl.question("\n👤 你: ", async (input) => {
|
|
217
|
-
if (!input.trim())
|
|
218
|
-
return chatLoop();
|
|
219
|
-
if (input.toLowerCase() === "exit")
|
|
220
|
-
process.exit(0);
|
|
221
|
-
try {
|
|
222
|
-
const res = await this.chat(input);
|
|
223
|
-
console.log(`\n🤖 Agent: ${res}`);
|
|
224
|
-
}
|
|
225
|
-
catch (err) {
|
|
226
|
-
console.error("\n❌ 系统错误:", err.message);
|
|
227
|
-
}
|
|
228
|
-
chatLoop();
|
|
229
|
-
});
|
|
230
|
-
};
|
|
231
|
-
chatLoop();
|
|
232
|
-
}
|
|
233
|
-
async ensureApiConfig() {
|
|
234
|
-
if (this.apiConfig)
|
|
235
|
-
return this.apiConfig;
|
|
236
|
-
if (fs_1.default.existsSync(config_1.CONFIG_FILE))
|
|
237
|
-
return JSON.parse(fs_1.default.readFileSync(config_1.CONFIG_FILE, "utf-8"));
|
|
238
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
239
|
-
const question = (q) => new Promise((res) => rl.question(q, res));
|
|
240
|
-
const config = { baseURL: await question("? API Base URL: "), apiKey: await question("? API Key: "), model: await question("? Model Name: ") };
|
|
241
|
-
fs_1.default.writeFileSync(config_1.CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
242
|
-
rl.close();
|
|
243
|
-
return config;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
exports.default = McpChainAgent;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
2
|
-
import { AIMessage, MessageFieldWithRole } from '@langchain/core/messages';
|
|
3
|
-
interface AgentChainModelImpl {
|
|
4
|
-
generateResponse: (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
|
-
generateResponse(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 {};
|
|
@@ -1,23 +0,0 @@
|
|
|
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 generateResponse(messages) {
|
|
15
|
-
return '';
|
|
16
|
-
}
|
|
17
|
-
async _generate(messages) {
|
|
18
|
-
let text = await this.generateResponse(messages);
|
|
19
|
-
return { generations: [{ text, message: new messages_1.AIMessage(text) }] };
|
|
20
|
-
}
|
|
21
|
-
_llmType() { return "my_private_llm"; }
|
|
22
|
-
}
|
|
23
|
-
exports.AgentChainModel = AgentChainModel;
|