@saber2pr/ai-agent 0.0.9 → 0.0.10
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 +8 -0
- package/lib/cli-chain.js +3 -3
- package/lib/cli.js +3 -3
- package/lib/config/config.d.ts +1 -0
- package/lib/config/config.js +9 -0
- package/lib/{agent-chain.d.ts → core/agent-chain.d.ts} +3 -23
- package/lib/{agent-chain.js → core/agent-chain.js} +80 -102
- package/lib/{agent.d.ts → core/agent.d.ts} +3 -25
- package/lib/{agent.js → core/agent.js} +29 -147
- package/lib/index.d.ts +3 -3
- package/lib/index.js +3 -3
- package/lib/tools/builtin.d.ts +7 -0
- package/lib/tools/builtin.js +98 -0
- package/lib/types/type.d.ts +45 -0
- package/lib/types/type.js +2 -0
- package/lib/utils/createTool.d.ts +11 -0
- package/lib/utils/createTool.js +39 -0
- package/lib/utils/jsonSchemaToZod.d.ts +2 -0
- package/lib/utils/jsonSchemaToZod.js +33 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,14 @@ A high-performance AI Agent toolkit designed for automated code auditing, reposi
|
|
|
19
19
|
## 🛠️ Installation
|
|
20
20
|
|
|
21
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
|
+
|
|
22
30
|
# Clone the repository
|
|
23
31
|
git clone https://github.com/saber2pr/ai-agent.git
|
|
24
32
|
cd ai-agent
|
package/lib/cli-chain.js
CHANGED
|
@@ -4,6 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const agent_chain_1 = __importDefault(require("./agent-chain"));
|
|
8
|
-
const
|
|
9
|
-
|
|
7
|
+
const agent_chain_1 = __importDefault(require("./core/agent-chain"));
|
|
8
|
+
const agent = new agent_chain_1.default();
|
|
9
|
+
agent.start();
|
package/lib/cli.js
CHANGED
|
@@ -4,6 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const agent_1 = __importDefault(require("./agent"));
|
|
8
|
-
const
|
|
9
|
-
|
|
7
|
+
const agent_1 = __importDefault(require("./core/agent"));
|
|
8
|
+
const agent = new agent_1.default();
|
|
9
|
+
agent.start();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CONFIG_FILE: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CONFIG_FILE = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
exports.CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
|
|
@@ -1,28 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
interface ApiConfig {
|
|
3
|
-
baseURL: string;
|
|
4
|
-
apiKey: string;
|
|
5
|
-
model: string;
|
|
6
|
-
}
|
|
7
|
-
export interface CustomTool {
|
|
8
|
-
name: string;
|
|
9
|
-
description: string;
|
|
10
|
-
parameters: any;
|
|
11
|
-
handler: (args: any) => Promise<any>;
|
|
12
|
-
}
|
|
13
|
-
export interface AgentOptions {
|
|
14
|
-
targetDir?: string;
|
|
15
|
-
tools?: CustomTool[];
|
|
16
|
-
extraSystemPrompt?: any;
|
|
17
|
-
maxTokens?: number;
|
|
18
|
-
apiConfig?: ApiConfig;
|
|
19
|
-
apiModel?: BaseChatModel;
|
|
20
|
-
maxIterations?: number;
|
|
21
|
-
}
|
|
1
|
+
import { AgentOptions } from '../types/type';
|
|
22
2
|
export default class McpChainAgent {
|
|
23
3
|
private allTools;
|
|
24
4
|
private messages;
|
|
25
|
-
private engine;
|
|
26
5
|
private encoder;
|
|
27
6
|
private extraTools;
|
|
28
7
|
private maxTokens;
|
|
@@ -30,6 +9,8 @@ export default class McpChainAgent {
|
|
|
30
9
|
private apiConfig;
|
|
31
10
|
private maxIterations;
|
|
32
11
|
private apiModel?;
|
|
12
|
+
private targetDir;
|
|
13
|
+
private verbose;
|
|
33
14
|
constructor(options?: AgentOptions);
|
|
34
15
|
/**
|
|
35
16
|
* 工具处理器包装逻辑:增加日志打印和 Token 监控
|
|
@@ -45,4 +26,3 @@ export default class McpChainAgent {
|
|
|
45
26
|
start(): Promise<void>;
|
|
46
27
|
private ensureApiConfig;
|
|
47
28
|
}
|
|
48
|
-
export {};
|
|
@@ -37,28 +37,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
const fs_1 = __importDefault(require("fs"));
|
|
40
|
-
const path_1 = __importDefault(require("path"));
|
|
41
|
-
const os_1 = __importDefault(require("os"));
|
|
42
|
-
const readline = __importStar(require("readline"));
|
|
43
|
-
const ts_context_mcp_1 = require("@saber2pr/ts-context-mcp");
|
|
44
40
|
const js_tiktoken_1 = require("js-tiktoken");
|
|
45
|
-
const openai_1 = require("@langchain/openai");
|
|
46
|
-
const tools_1 = require("@langchain/core/tools");
|
|
47
41
|
const agents_1 = require("langchain/agents");
|
|
42
|
+
const readline = __importStar(require("readline"));
|
|
48
43
|
const prompts_1 = require("@langchain/core/prompts");
|
|
49
|
-
const
|
|
44
|
+
const tools_1 = require("@langchain/core/tools");
|
|
45
|
+
const openai_1 = require("@langchain/openai");
|
|
46
|
+
const builtin_1 = require("../tools/builtin");
|
|
47
|
+
const config_1 = require("../config/config");
|
|
48
|
+
const jsonSchemaToZod_1 = require("../utils/jsonSchemaToZod");
|
|
50
49
|
class McpChainAgent {
|
|
51
50
|
constructor(options) {
|
|
51
|
+
var _a;
|
|
52
52
|
this.allTools = [];
|
|
53
53
|
this.messages = [];
|
|
54
54
|
this.encoder = (0, js_tiktoken_1.getEncoding)("cl100k_base");
|
|
55
55
|
this.extraTools = [];
|
|
56
|
-
this.engine = new ts_context_mcp_1.PromptEngine((options === null || options === void 0 ? void 0 : options.targetDir) || process.cwd());
|
|
57
56
|
this.extraTools = (options === null || options === void 0 ? void 0 : options.tools) || [];
|
|
58
57
|
this.maxTokens = (options === null || options === void 0 ? void 0 : options.maxTokens) || 100000;
|
|
59
58
|
this.apiConfig = options === null || options === void 0 ? void 0 : options.apiConfig;
|
|
60
59
|
this.maxIterations = (options === null || options === void 0 ? void 0 : options.maxIterations) || 20;
|
|
61
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
|
+
this.verbose = (options === null || options === void 0 ? void 0 : options.verbose) || false;
|
|
62
63
|
const baseSystemPrompt = `你是一个专业的 AI 代码架构师,具备深度的源码分析与工程化处理能力。
|
|
63
64
|
|
|
64
65
|
### 核心操作规范:
|
|
@@ -73,7 +74,19 @@ class McpChainAgent {
|
|
|
73
74
|
? `${baseSystemPrompt}\n\n[额外指令]:\n${JSON.stringify(options.extraSystemPrompt)}`
|
|
74
75
|
: baseSystemPrompt,
|
|
75
76
|
});
|
|
76
|
-
|
|
77
|
+
if ((_a = options === null || options === void 0 ? void 0 : options.builtinTools) === null || _a === void 0 ? void 0 : _a.length) {
|
|
78
|
+
this.allTools.push(...options.builtinTools.map((t) => ({
|
|
79
|
+
type: t.type,
|
|
80
|
+
function: t.function,
|
|
81
|
+
_handler: this.wrapHandler(t.function.name, t._handler),
|
|
82
|
+
})));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.registerBuiltinTools({
|
|
86
|
+
...options,
|
|
87
|
+
...this,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
77
90
|
this.injectCustomTools();
|
|
78
91
|
}
|
|
79
92
|
/**
|
|
@@ -95,64 +108,16 @@ class McpChainAgent {
|
|
|
95
108
|
return content;
|
|
96
109
|
};
|
|
97
110
|
}
|
|
98
|
-
registerBuiltinTools() {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{
|
|
109
|
-
type: "function",
|
|
110
|
-
function: {
|
|
111
|
-
name: "read_skeleton",
|
|
112
|
-
description: "读取代码骨架(接口、类定义等),非常节省 Token",
|
|
113
|
-
parameters: { type: "object", properties: { filePath: { type: "string" } } },
|
|
114
|
-
},
|
|
115
|
-
_handler: this.wrapHandler("read_skeleton", async ({ filePath }) => this.engine.getSkeleton(filePath)),
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
type: "function",
|
|
119
|
-
function: {
|
|
120
|
-
name: "read_full_code",
|
|
121
|
-
description: "读取完整源码。注意:仅在需要具体行号或精细逻辑时使用",
|
|
122
|
-
parameters: { type: "object", properties: { filePath: { type: "string" } } },
|
|
123
|
-
},
|
|
124
|
-
_handler: this.wrapHandler("read_full_code", async ({ filePath }) => {
|
|
125
|
-
// --- 新增:Token 守卫 ---
|
|
126
|
-
const currentTokens = this.calculateTokens();
|
|
127
|
-
if (currentTokens > this.maxTokens) {
|
|
128
|
-
return `[SYSTEM WARNING]: 当前上下文已达到 ${currentTokens} tokens (上限 ${this.maxTokens})。为了保证系统稳定,已拦截 read_full_code。请立即根据已知信息进行总结或停止阅读更多代码。`;
|
|
129
|
-
}
|
|
130
|
-
try {
|
|
131
|
-
if (typeof filePath !== 'string' || !filePath) {
|
|
132
|
-
return "Error: filePath 不能为空";
|
|
133
|
-
}
|
|
134
|
-
// 拼合绝对路径
|
|
135
|
-
const fullPath = path_1.default.resolve(this.engine.getRootDir(), filePath);
|
|
136
|
-
// 安全检查:防止 AI 尝试读取项目外的敏感文件
|
|
137
|
-
if (!fullPath.startsWith(this.engine.getRootDir())) {
|
|
138
|
-
return "Error: 权限拒绝,禁止访问项目目录外的文件。";
|
|
139
|
-
}
|
|
140
|
-
if (!fs_1.default.existsSync(fullPath)) {
|
|
141
|
-
return `Error: 文件不存在: ${filePath}`;
|
|
142
|
-
}
|
|
143
|
-
const content = fs_1.default.readFileSync(fullPath, "utf-8");
|
|
144
|
-
// 加上行号,AI 就能在 generate_review 里给出准确的 line 参数
|
|
145
|
-
return content.split('\n')
|
|
146
|
-
.map((line, i) => `${i + 1} | ${line}`)
|
|
147
|
-
.join('\n');
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
return `Error: 读取文件失败: ${err.message}`;
|
|
151
|
-
}
|
|
152
|
-
}),
|
|
153
|
-
}
|
|
154
|
-
];
|
|
155
|
-
this.allTools.push(...builtinTools);
|
|
111
|
+
registerBuiltinTools(options) {
|
|
112
|
+
const defaults = (0, builtin_1.createDefaultBuiltinTools)({
|
|
113
|
+
options,
|
|
114
|
+
getCurrentTokens: () => this.calculateTokens(),
|
|
115
|
+
});
|
|
116
|
+
this.allTools.push(...defaults.map((t) => ({
|
|
117
|
+
type: t.type,
|
|
118
|
+
function: t.function,
|
|
119
|
+
_handler: this.wrapHandler(t.function.name, t._handler),
|
|
120
|
+
})));
|
|
156
121
|
}
|
|
157
122
|
injectCustomTools() {
|
|
158
123
|
for (const tool of this.extraTools) {
|
|
@@ -196,11 +161,19 @@ class McpChainAgent {
|
|
|
196
161
|
streaming: false
|
|
197
162
|
});
|
|
198
163
|
}
|
|
199
|
-
const langchainTools = this.allTools.map(t =>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
164
|
+
const langchainTools = this.allTools.map(t => {
|
|
165
|
+
return new tools_1.DynamicStructuredTool({
|
|
166
|
+
name: t.function.name,
|
|
167
|
+
description: t.function.description || "",
|
|
168
|
+
// 定义 schema 告诉 LangChain 这是一个对象输入
|
|
169
|
+
// passthrough() 允许接收未在 schema 中显式定义的其他字段
|
|
170
|
+
schema: (0, jsonSchemaToZod_1.jsonSchemaToZod)(t.function.parameters),
|
|
171
|
+
func: async (args) => {
|
|
172
|
+
// 这里的 args 已经被 LangChain 自动解析为对象
|
|
173
|
+
return await t._handler(args);
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
});
|
|
204
177
|
const prompt = prompts_1.PromptTemplate.fromTemplate(`
|
|
205
178
|
{system_prompt}
|
|
206
179
|
|
|
@@ -208,39 +181,32 @@ class McpChainAgent {
|
|
|
208
181
|
--------------------
|
|
209
182
|
{tools}
|
|
210
183
|
|
|
184
|
+
工具名称列表: [{tool_names}]
|
|
185
|
+
|
|
211
186
|
### 📝 交互协议格式 (PROTOCOL)
|
|
212
187
|
--------------------
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
1. **思考与行动阶段 (Thought & Action)**:
|
|
216
|
-
Thought: 我需要执行什么操作?
|
|
217
|
-
Action: 工具名称 (必须是 [{tool_names}] 之一)
|
|
218
|
-
Action Input: 工具的 JSON 参数 (例如: {{"filePath": "src/index.ts"}})
|
|
219
|
-
|
|
220
|
-
【⚠️ 极其重要】:当你输出 "Action Input" 后,必须【立即停止】输出,静静等待 Observation(工具返回结果)。严禁在此阶段输出 "Final Answer"。
|
|
188
|
+
你必须严格遵守以下回复格式:
|
|
221
189
|
|
|
222
|
-
|
|
223
|
-
|
|
190
|
+
Thought: [此处写下你的思考过程]
|
|
191
|
+
\`\`\`json
|
|
192
|
+
{{
|
|
193
|
+
"action": "工具名称",
|
|
194
|
+
"action_input": {{ "参数名": "参数值" }}
|
|
195
|
+
}}
|
|
196
|
+
\`\`\`
|
|
224
197
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Final Answer: 任务总结陈述。
|
|
229
|
-
|
|
230
|
-
### 🚫 强制禁止行为 (STRICT PROHIBITIONS)
|
|
231
|
-
--------------------
|
|
232
|
-
- **严禁虚构**:禁止使用 'path/to/file' 等占位符,必须使用 'get_repo_map' 返回的真实路径。
|
|
233
|
-
- **严禁冲突**:严禁在同一次回复中同时出现 "Action" 和 "Final Answer"。
|
|
234
|
-
- **严禁对话**:不要向用户提问或进行闲聊,你的唯一目标是完成审计并调用 'generate_review'。
|
|
198
|
+
注意:
|
|
199
|
+
- 每次调用工具前必须先写 Thought。
|
|
200
|
+
- 最终结论请使用 "action": "Final Answer"。
|
|
235
201
|
|
|
236
202
|
Begin!
|
|
237
203
|
Question: {input}
|
|
238
204
|
Thought: {agent_scratchpad}`);
|
|
239
|
-
const agent = await (0, agents_1.
|
|
205
|
+
const agent = await (0, agents_1.createStructuredChatAgent)({ llm: model, tools: langchainTools, prompt });
|
|
240
206
|
this.executor = new agents_1.AgentExecutor({
|
|
241
207
|
agent,
|
|
242
208
|
tools: langchainTools,
|
|
243
|
-
verbose:
|
|
209
|
+
verbose: this.verbose, // 我们已经有了 wrapHandler 日志,关闭原生 verbose 以保持整洁
|
|
244
210
|
handleParsingErrors: true,
|
|
245
211
|
maxIterations: this.maxIterations
|
|
246
212
|
});
|
|
@@ -261,11 +227,23 @@ Thought: {agent_scratchpad}`);
|
|
|
261
227
|
// --- 新增:使用回调函数捕获 Thought ---
|
|
262
228
|
callbacks: [{
|
|
263
229
|
handleAgentAction: (action, runId) => {
|
|
264
|
-
//
|
|
230
|
+
// action.log 包含了 LLM 输出的所有文本(包含 Thought 和 JSON)
|
|
265
231
|
if (action.log) {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
232
|
+
const log = action.log.trim();
|
|
233
|
+
// 正则说明:匹配 Thought: 到 ```json 或 { 之间的内容
|
|
234
|
+
const thoughtMatch = log.match(/Thought:\s*([\s\S]*?)(?=(?:```json|\{|$))/i);
|
|
235
|
+
if (thoughtMatch && thoughtMatch[1]) {
|
|
236
|
+
const thought = thoughtMatch[1].trim();
|
|
237
|
+
if (thought) {
|
|
238
|
+
console.log(`\n💭 [思考]: ${thought}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else if (!log.startsWith('{') && !log.startsWith('```')) {
|
|
242
|
+
// 备选方案:如果没匹配到 Thought 标签,但有非 JSON 开头的文字,也打印出来
|
|
243
|
+
const rawThought = log.split(/```json|\{/)[0].trim();
|
|
244
|
+
if (rawThought) {
|
|
245
|
+
console.log(`\n💭 [思考]: ${rawThought}`);
|
|
246
|
+
}
|
|
269
247
|
}
|
|
270
248
|
}
|
|
271
249
|
}
|
|
@@ -299,7 +277,7 @@ Thought: {agent_scratchpad}`);
|
|
|
299
277
|
await this.init();
|
|
300
278
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
301
279
|
console.log(`\n🚀 AI 助手启动 (LangChain 核心)`);
|
|
302
|
-
console.log(`📂 目标目录: ${this.
|
|
280
|
+
console.log(`📂 目标目录: ${this.targetDir}`);
|
|
303
281
|
const chatLoop = () => {
|
|
304
282
|
rl.question("\n👤 你: ", async (input) => {
|
|
305
283
|
if (!input.trim())
|
|
@@ -321,8 +299,8 @@ Thought: {agent_scratchpad}`);
|
|
|
321
299
|
async ensureApiConfig() {
|
|
322
300
|
if (this.apiConfig)
|
|
323
301
|
return this.apiConfig;
|
|
324
|
-
if (fs_1.default.existsSync(CONFIG_FILE)) {
|
|
325
|
-
return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, "utf-8"));
|
|
302
|
+
if (fs_1.default.existsSync(config_1.CONFIG_FILE)) {
|
|
303
|
+
return JSON.parse(fs_1.default.readFileSync(config_1.CONFIG_FILE, "utf-8"));
|
|
326
304
|
}
|
|
327
305
|
const rl = readline.createInterface({
|
|
328
306
|
input: process.stdin,
|
|
@@ -335,7 +313,7 @@ Thought: {agent_scratchpad}`);
|
|
|
335
313
|
apiKey: await question("? API Key: "),
|
|
336
314
|
model: await question("? Model Name (如 gpt-4o): "),
|
|
337
315
|
};
|
|
338
|
-
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
316
|
+
fs_1.default.writeFileSync(config_1.CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
339
317
|
rl.close();
|
|
340
318
|
return config;
|
|
341
319
|
}
|
|
@@ -1,34 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
baseURL: string;
|
|
3
|
-
apiKey: string;
|
|
4
|
-
model: string;
|
|
5
|
-
}
|
|
6
|
-
export interface CustomTool {
|
|
7
|
-
name: string;
|
|
8
|
-
description: string;
|
|
9
|
-
parameters: any;
|
|
10
|
-
handler: (args: any) => Promise<any>;
|
|
11
|
-
}
|
|
12
|
-
export interface AgentOptions {
|
|
13
|
-
targetDir?: string;
|
|
14
|
-
/** 自定义工具扩展 */
|
|
15
|
-
tools?: any[];
|
|
16
|
-
/** 注入到 System Prompt 中的额外指令/规则/上下文 */
|
|
17
|
-
extraSystemPrompt?: any;
|
|
18
|
-
maxTokens?: number;
|
|
19
|
-
apiConfig?: ApiConfig;
|
|
20
|
-
}
|
|
1
|
+
import { AgentOptions } from '../types/type';
|
|
21
2
|
export default class McpAgent {
|
|
22
3
|
private openai;
|
|
23
4
|
private modelName;
|
|
24
|
-
private clients;
|
|
25
5
|
private allTools;
|
|
26
6
|
private messages;
|
|
27
|
-
private engine;
|
|
28
7
|
private encoder;
|
|
29
8
|
private extraTools;
|
|
30
9
|
private maxTokens;
|
|
31
10
|
private apiConfig;
|
|
11
|
+
private targetDir;
|
|
32
12
|
constructor(options?: AgentOptions);
|
|
33
13
|
/**
|
|
34
14
|
* 计算当前消息列表的总 Token 消耗
|
|
@@ -37,8 +17,7 @@ export default class McpAgent {
|
|
|
37
17
|
private calculateTokens;
|
|
38
18
|
private injectCustomTools;
|
|
39
19
|
/**
|
|
40
|
-
*
|
|
41
|
-
* 这里的逻辑直接调用 PromptEngine,不走网络请求,效率极高
|
|
20
|
+
* 核心功能:内置代码分析工具(基于 engine/targetDir,可被外部通过 createDefaultBuiltinTools 替代)
|
|
42
21
|
*/
|
|
43
22
|
private registerBuiltinTools;
|
|
44
23
|
private ensureApiConfig;
|
|
@@ -62,4 +41,3 @@ export default class McpAgent {
|
|
|
62
41
|
chat(input: string): Promise<string>;
|
|
63
42
|
start(): Promise<void>;
|
|
64
43
|
}
|
|
65
|
-
export {};
|
|
@@ -36,25 +36,25 @@ 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"));
|
|
40
|
-
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
41
|
-
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
42
39
|
const fs_1 = __importDefault(require("fs"));
|
|
43
|
-
const
|
|
40
|
+
const js_tiktoken_1 = require("js-tiktoken");
|
|
41
|
+
const openai_1 = __importDefault(require("openai"));
|
|
44
42
|
const os_1 = __importDefault(require("os"));
|
|
43
|
+
const path_1 = __importDefault(require("path"));
|
|
45
44
|
const readline = __importStar(require("readline"));
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
45
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
46
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
47
|
+
const builtin_1 = require("../tools/builtin");
|
|
48
|
+
const config_1 = require("../config/config");
|
|
49
49
|
class McpAgent {
|
|
50
50
|
constructor(options) {
|
|
51
|
+
var _a;
|
|
51
52
|
this.modelName = "";
|
|
52
|
-
this.clients = [];
|
|
53
53
|
this.allTools = [];
|
|
54
54
|
this.messages = [];
|
|
55
55
|
this.encoder = (0, js_tiktoken_1.getEncoding)("cl100k_base");
|
|
56
56
|
this.extraTools = [];
|
|
57
|
-
this.
|
|
57
|
+
this.targetDir = (options === null || options === void 0 ? void 0 : options.targetDir) || process.cwd();
|
|
58
58
|
this.extraTools = (options === null || options === void 0 ? void 0 : options.tools) || []; // 接收外部传入的工具
|
|
59
59
|
this.maxTokens = (options === null || options === void 0 ? void 0 : options.maxTokens) || 100000; // 默认 100k
|
|
60
60
|
this.apiConfig = options === null || options === void 0 ? void 0 : options.apiConfig;
|
|
@@ -87,8 +87,16 @@ class McpAgent {
|
|
|
87
87
|
role: "system",
|
|
88
88
|
content: baseSystemPrompt,
|
|
89
89
|
});
|
|
90
|
-
//
|
|
91
|
-
|
|
90
|
+
// 初始化内置工具:外部传入则使用,否则走默认 registerBuiltinTools
|
|
91
|
+
if ((_a = options === null || options === void 0 ? void 0 : options.builtinTools) === null || _a === void 0 ? void 0 : _a.length) {
|
|
92
|
+
this.allTools.push(...options.builtinTools);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.registerBuiltinTools({
|
|
96
|
+
...options,
|
|
97
|
+
...this,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
92
100
|
this.injectCustomTools(); // 注入外部工具
|
|
93
101
|
}
|
|
94
102
|
/**
|
|
@@ -147,146 +155,20 @@ class McpAgent {
|
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
157
|
/**
|
|
150
|
-
*
|
|
151
|
-
* 这里的逻辑直接调用 PromptEngine,不走网络请求,效率极高
|
|
158
|
+
* 核心功能:内置代码分析工具(基于 engine/targetDir,可被外部通过 createDefaultBuiltinTools 替代)
|
|
152
159
|
*/
|
|
153
|
-
registerBuiltinTools() {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
name: "get_repo_map",
|
|
159
|
-
description: "获取项目全局文件结构及导出清单,用于快速定位代码",
|
|
160
|
-
parameters: { type: "object", properties: {} },
|
|
161
|
-
},
|
|
162
|
-
_handler: async () => {
|
|
163
|
-
this.engine.refresh();
|
|
164
|
-
return this.engine.getRepoMap();
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
type: "function",
|
|
169
|
-
function: {
|
|
170
|
-
name: "analyze_deps",
|
|
171
|
-
description: "分析指定文件的依赖关系,支持 tsconfig 路径别名解析",
|
|
172
|
-
parameters: {
|
|
173
|
-
type: "object",
|
|
174
|
-
properties: {
|
|
175
|
-
filePath: { type: "string", description: "文件相对路径" },
|
|
176
|
-
},
|
|
177
|
-
required: ["filePath"],
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
_handler: async ({ filePath }) => this.engine.getDeps(filePath),
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
type: "function",
|
|
184
|
-
function: {
|
|
185
|
-
name: "read_skeleton",
|
|
186
|
-
description: "提取文件的结构定义(接口、类、方法签名),忽略具体实现以节省 Token",
|
|
187
|
-
parameters: {
|
|
188
|
-
type: "object",
|
|
189
|
-
properties: {
|
|
190
|
-
filePath: { type: "string", description: "文件相对路径" },
|
|
191
|
-
},
|
|
192
|
-
required: ["filePath"],
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
_handler: async (args) => {
|
|
196
|
-
// 1. 严格路径守卫:防止 undefined 或空字符串进入 path 模块
|
|
197
|
-
const pathArg = args === null || args === void 0 ? void 0 : args.filePath;
|
|
198
|
-
if (typeof pathArg !== 'string' || pathArg.trim() === '') {
|
|
199
|
-
return `Error: 参数 'filePath' 无效。收到的是: ${JSON.stringify(pathArg)}`;
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
// 2. 刷新引擎状态,确保分析的是最新的文件内容
|
|
203
|
-
this.engine.refresh();
|
|
204
|
-
// 3. 执行获取
|
|
205
|
-
const result = this.engine.getSkeleton(pathArg);
|
|
206
|
-
// 4. 空值回退:防止 getSkeleton 返回 null 导致后续统计 Token 时崩溃
|
|
207
|
-
return result || `// Warning: 文件 ${pathArg} 存在但未找到任何可提取的结构。`;
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
// 5. 捕获 AST 级别的 match 错误
|
|
211
|
-
return `Error: 解析文件 ${pathArg} 时发生内部错误: ${error.message}`;
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
type: "function",
|
|
217
|
-
function: {
|
|
218
|
-
name: "get_method_body",
|
|
219
|
-
description: "获取指定文件内某个方法或函数的完整实现代码",
|
|
220
|
-
parameters: {
|
|
221
|
-
type: "object",
|
|
222
|
-
properties: {
|
|
223
|
-
filePath: { type: "string", description: "文件路径" },
|
|
224
|
-
methodName: { type: "string", description: "方法名或函数名" },
|
|
225
|
-
},
|
|
226
|
-
required: ["filePath", "methodName"],
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
_handler: async ({ filePath, methodName }) => {
|
|
230
|
-
// --- 新增:同样的 Token 守卫 ---
|
|
231
|
-
if (this.calculateTokens() > this.maxTokens) {
|
|
232
|
-
return `[SYSTEM WARNING]: Token 消耗已达上限,禁止获取详细方法体。请利用已获取的 Skeleton 信息进行分析。`;
|
|
233
|
-
}
|
|
234
|
-
return this.engine.getMethodImplementation(filePath, methodName);
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
type: "function",
|
|
239
|
-
function: {
|
|
240
|
-
name: "read_full_code",
|
|
241
|
-
description: "读取指定文件的完整源代码内容。当需要分析具体实现逻辑或查找硬编码字符串时使用。",
|
|
242
|
-
parameters: {
|
|
243
|
-
type: "object",
|
|
244
|
-
properties: {
|
|
245
|
-
filePath: { type: "string", description: "文件相对路径" },
|
|
246
|
-
},
|
|
247
|
-
required: ["filePath"],
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
// 核心实现:直接利用 fs 读取
|
|
251
|
-
_handler: async ({ filePath }) => {
|
|
252
|
-
// --- 新增:Token 守卫 ---
|
|
253
|
-
const currentTokens = this.calculateTokens();
|
|
254
|
-
if (currentTokens > this.maxTokens) {
|
|
255
|
-
return `[SYSTEM WARNING]: 当前上下文已达到 ${currentTokens} tokens (上限 ${this.maxTokens})。为了保证系统稳定,已拦截 read_full_code。请立即根据已知信息进行总结或停止阅读更多代码。`;
|
|
256
|
-
}
|
|
257
|
-
try {
|
|
258
|
-
if (typeof filePath !== 'string' || !filePath) {
|
|
259
|
-
return "Error: filePath 不能为空";
|
|
260
|
-
}
|
|
261
|
-
// 拼合绝对路径
|
|
262
|
-
const fullPath = path_1.default.resolve(this.engine.getRootDir(), filePath);
|
|
263
|
-
// 安全检查:防止 AI 尝试读取项目外的敏感文件
|
|
264
|
-
if (!fullPath.startsWith(this.engine.getRootDir())) {
|
|
265
|
-
return "Error: 权限拒绝,禁止访问项目目录外的文件。";
|
|
266
|
-
}
|
|
267
|
-
if (!fs_1.default.existsSync(fullPath)) {
|
|
268
|
-
return `Error: 文件不存在: ${filePath}`;
|
|
269
|
-
}
|
|
270
|
-
const content = fs_1.default.readFileSync(fullPath, "utf-8");
|
|
271
|
-
// 加上行号,AI 就能在 generate_review 里给出准确的 line 参数
|
|
272
|
-
return content.split('\n')
|
|
273
|
-
.map((line, i) => `${i + 1} | ${line}`)
|
|
274
|
-
.join('\n');
|
|
275
|
-
}
|
|
276
|
-
catch (err) {
|
|
277
|
-
return `Error: 读取文件失败: ${err.message}`;
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
];
|
|
282
|
-
this.allTools.push(...builtinTools);
|
|
160
|
+
registerBuiltinTools(options) {
|
|
161
|
+
this.allTools.push(...(0, builtin_1.createDefaultBuiltinTools)({
|
|
162
|
+
options,
|
|
163
|
+
getCurrentTokens: () => this.calculateTokens(),
|
|
164
|
+
}));
|
|
283
165
|
}
|
|
284
166
|
// --- 初始化与环境准备 (API Config & MCP Servers) ---
|
|
285
167
|
async ensureApiConfig() {
|
|
286
168
|
if (this.apiConfig)
|
|
287
169
|
return this.apiConfig;
|
|
288
|
-
if (fs_1.default.existsSync(CONFIG_FILE)) {
|
|
289
|
-
return JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, "utf-8"));
|
|
170
|
+
if (fs_1.default.existsSync(config_1.CONFIG_FILE)) {
|
|
171
|
+
return JSON.parse(fs_1.default.readFileSync(config_1.CONFIG_FILE, "utf-8"));
|
|
290
172
|
}
|
|
291
173
|
const rl = readline.createInterface({
|
|
292
174
|
input: process.stdin,
|
|
@@ -299,7 +181,7 @@ class McpAgent {
|
|
|
299
181
|
apiKey: await question("? API Key: "),
|
|
300
182
|
model: await question("? Model Name (如 gpt-4o): "),
|
|
301
183
|
};
|
|
302
|
-
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
184
|
+
fs_1.default.writeFileSync(config_1.CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
303
185
|
rl.close();
|
|
304
186
|
return config;
|
|
305
187
|
}
|
|
@@ -483,7 +365,7 @@ class McpAgent {
|
|
|
483
365
|
input: process.stdin,
|
|
484
366
|
output: process.stdout,
|
|
485
367
|
});
|
|
486
|
-
console.log(`\n🚀 代码助手已启动 (目标目录: ${this.
|
|
368
|
+
console.log(`\n🚀 代码助手已启动 (目标目录: ${this.targetDir})`);
|
|
487
369
|
const chatLoop = () => {
|
|
488
370
|
rl.question("\n👤 你: ", async (input) => {
|
|
489
371
|
if (input.toLowerCase() === "exit")
|
package/lib/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './agent';
|
|
2
|
-
export { default as McpChainAgent } from './agent-chain';
|
|
3
|
-
export { default } from './agent';
|
|
1
|
+
export * from './core/agent';
|
|
2
|
+
export { default as McpChainAgent } from './core/agent-chain';
|
|
3
|
+
export { default } from './core/agent';
|
package/lib/index.js
CHANGED
|
@@ -18,8 +18,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
exports.default = exports.McpChainAgent = void 0;
|
|
21
|
-
__exportStar(require("./agent"), exports);
|
|
22
|
-
var agent_chain_1 = require("./agent-chain");
|
|
21
|
+
__exportStar(require("./core/agent"), exports);
|
|
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; } });
|
|
24
|
-
var agent_1 = require("./agent");
|
|
24
|
+
var agent_1 = require("./core/agent");
|
|
25
25
|
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(agent_1).default; } });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AgentOptions, ToolInfo } from '../types/type';
|
|
2
|
+
export interface BuiltinToolsContext {
|
|
3
|
+
options?: AgentOptions;
|
|
4
|
+
/** 可选:获取当前已用 token 数 */
|
|
5
|
+
getCurrentTokens?: () => number;
|
|
6
|
+
}
|
|
7
|
+
export declare function createDefaultBuiltinTools(context: BuiltinToolsContext): ToolInfo[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createDefaultBuiltinTools = createDefaultBuiltinTools;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const ts_context_mcp_1 = require("@saber2pr/ts-context-mcp");
|
|
10
|
+
const createTool_1 = require("../utils/createTool");
|
|
11
|
+
function createDefaultBuiltinTools(context) {
|
|
12
|
+
const { options, getCurrentTokens } = context;
|
|
13
|
+
const engine = new ts_context_mcp_1.PromptEngine((options === null || options === void 0 ? void 0 : options.targetDir) || process.cwd());
|
|
14
|
+
const maxTokens = (options === null || options === void 0 ? void 0 : options.maxTokens) || 100000;
|
|
15
|
+
const rootDir = () => engine.getRootDir();
|
|
16
|
+
return [
|
|
17
|
+
(0, createTool_1.createTool)({
|
|
18
|
+
maxTokens,
|
|
19
|
+
getCurrentTokens,
|
|
20
|
+
name: "get_repo_map",
|
|
21
|
+
description: "获取项目全局文件结构及导出清单,用于快速定位代码",
|
|
22
|
+
parameters: { type: "object", properties: {} },
|
|
23
|
+
handler: async () => {
|
|
24
|
+
engine.refresh();
|
|
25
|
+
return engine.getRepoMap();
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
(0, createTool_1.createTool)({
|
|
29
|
+
maxTokens,
|
|
30
|
+
getCurrentTokens,
|
|
31
|
+
name: "analyze_deps",
|
|
32
|
+
description: "分析指定文件的依赖关系,支持 tsconfig 路径别名解析",
|
|
33
|
+
parameters: { type: "object", properties: { filePath: { type: "string", description: "文件相对路径", required: true } } },
|
|
34
|
+
validateParams: ["filePath"],
|
|
35
|
+
handler: async ({ filePath }) => engine.getDeps(filePath),
|
|
36
|
+
}),
|
|
37
|
+
(0, createTool_1.createTool)({
|
|
38
|
+
maxTokens,
|
|
39
|
+
getCurrentTokens,
|
|
40
|
+
name: "read_skeleton",
|
|
41
|
+
description: "提取文件的结构定义(接口、类、方法签名),忽略具体实现以节省 Token",
|
|
42
|
+
parameters: { type: "object", properties: { filePath: { type: "string", description: "文件相对路径" } } },
|
|
43
|
+
handler: async (args) => {
|
|
44
|
+
const pathArg = args === null || args === void 0 ? void 0 : args.filePath;
|
|
45
|
+
if (typeof pathArg !== "string" || pathArg.trim() === "") {
|
|
46
|
+
return `Error: 参数 'filePath' 无效。收到的是: ${JSON.stringify(pathArg)}`;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
engine.refresh();
|
|
50
|
+
const result = engine.getSkeleton(pathArg);
|
|
51
|
+
return result || `// Warning: 文件 ${pathArg} 存在但未找到任何可提取的结构。`;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return `Error: 解析文件 ${pathArg} 时发生内部错误: ${error.message}`;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
(0, createTool_1.createTool)({
|
|
59
|
+
maxTokens,
|
|
60
|
+
getCurrentTokens,
|
|
61
|
+
name: "get_method_body",
|
|
62
|
+
description: "获取指定文件内某个方法或函数的完整实现代码",
|
|
63
|
+
parameters: { type: "object", properties: { filePath: { type: "string", description: "文件相对路径" }, methodName: { type: "string", description: "方法名或函数名" } } },
|
|
64
|
+
handler: async ({ filePath, methodName }) => {
|
|
65
|
+
return engine.getMethodImplementation(filePath, methodName);
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
(0, createTool_1.createTool)({
|
|
69
|
+
maxTokens,
|
|
70
|
+
getCurrentTokens,
|
|
71
|
+
name: "read_full_code",
|
|
72
|
+
description: "读取指定文件的完整源代码内容。当需要分析具体实现逻辑或查找硬编码字符串时使用。",
|
|
73
|
+
parameters: { type: "object", properties: { filePath: { type: "string", description: "文件相对路径" } } },
|
|
74
|
+
handler: async ({ filePath }) => {
|
|
75
|
+
try {
|
|
76
|
+
if (typeof filePath !== "string" || !filePath) {
|
|
77
|
+
return "Error: filePath 不能为空";
|
|
78
|
+
}
|
|
79
|
+
const fullPath = path_1.default.resolve(rootDir(), filePath);
|
|
80
|
+
if (!fullPath.startsWith(rootDir())) {
|
|
81
|
+
return "Error: 权限拒绝,禁止访问项目目录外的文件。";
|
|
82
|
+
}
|
|
83
|
+
if (!fs_1.default.existsSync(fullPath)) {
|
|
84
|
+
return `Error: 文件不存在: ${filePath}`;
|
|
85
|
+
}
|
|
86
|
+
const content = fs_1.default.readFileSync(fullPath, "utf-8");
|
|
87
|
+
return content
|
|
88
|
+
.split("\n")
|
|
89
|
+
.map((line, i) => `${i + 1} | ${line}`)
|
|
90
|
+
.join("\n");
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
return `Error: 读取文件失败: ${err.message}`;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index";
|
|
3
|
+
export interface ApiConfig {
|
|
4
|
+
baseURL: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
model: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ToolInfo {
|
|
9
|
+
type: "function";
|
|
10
|
+
function: {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
parameters: any;
|
|
14
|
+
};
|
|
15
|
+
_handler?: (args: any) => Promise<any>;
|
|
16
|
+
_client?: Client;
|
|
17
|
+
_originalName?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CustomTool {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
parameters: any;
|
|
23
|
+
handler: (args: any) => Promise<any>;
|
|
24
|
+
}
|
|
25
|
+
export interface McpConfig {
|
|
26
|
+
mcpServers: {
|
|
27
|
+
[key: string]: {
|
|
28
|
+
command: string;
|
|
29
|
+
args?: string[];
|
|
30
|
+
env?: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface AgentOptions {
|
|
35
|
+
targetDir?: string;
|
|
36
|
+
/** 外部传入的内置工具列表,不传则使用默认的 registerBuiltinTools */
|
|
37
|
+
builtinTools?: ToolInfo[];
|
|
38
|
+
tools?: CustomTool[];
|
|
39
|
+
extraSystemPrompt?: any;
|
|
40
|
+
maxTokens?: number;
|
|
41
|
+
apiConfig?: ApiConfig;
|
|
42
|
+
apiModel?: BaseChatModel;
|
|
43
|
+
maxIterations?: number;
|
|
44
|
+
verbose?: boolean;
|
|
45
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ToolInfo } from "../types/type";
|
|
2
|
+
export interface CreateToolOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: any;
|
|
6
|
+
handler: (args: any) => Promise<any>;
|
|
7
|
+
maxTokens: number;
|
|
8
|
+
getCurrentTokens: () => number;
|
|
9
|
+
validateParams?: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function createTool(options: CreateToolOptions): ToolInfo;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTool = createTool;
|
|
4
|
+
function createTool(options) {
|
|
5
|
+
return {
|
|
6
|
+
type: "function",
|
|
7
|
+
function: {
|
|
8
|
+
name: options.name,
|
|
9
|
+
description: options.description,
|
|
10
|
+
parameters: options.parameters,
|
|
11
|
+
},
|
|
12
|
+
_handler: async (input) => {
|
|
13
|
+
var _a;
|
|
14
|
+
if (options.getCurrentTokens && options.maxTokens != null && options.getCurrentTokens() > options.maxTokens) {
|
|
15
|
+
return `[SYSTEM WARNING]: Token 消耗已达上限,禁止获取详细方法体。请利用已获取的 Skeleton 信息进行分析。`;
|
|
16
|
+
}
|
|
17
|
+
// 兼容处理:如果 input 是字符串,尝试解析为 JSON 对象
|
|
18
|
+
let args = input;
|
|
19
|
+
if (typeof input === 'string') {
|
|
20
|
+
try {
|
|
21
|
+
args = JSON.parse(input);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
args = input;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (((_a = options.validateParams) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
28
|
+
for (const arg in args) {
|
|
29
|
+
if (options.validateParams.includes(arg)) {
|
|
30
|
+
if (typeof args[arg] !== "string" || args[arg].trim() === "") {
|
|
31
|
+
return `Error: 参数 '${arg}' 无效。收到的是: ${JSON.stringify(args[arg])}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return options.handler(args);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jsonSchemaToZod = jsonSchemaToZod;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
// 将 JSON Schema 转换为 Zod Schema 的简易转换器
|
|
6
|
+
function jsonSchemaToZod(parameters) {
|
|
7
|
+
var _a;
|
|
8
|
+
if (!parameters || !parameters.properties) {
|
|
9
|
+
return zod_1.z.object({}).loose();
|
|
10
|
+
}
|
|
11
|
+
const obj = {};
|
|
12
|
+
const properties = parameters.properties;
|
|
13
|
+
for (const key in properties) {
|
|
14
|
+
const prop = properties[key];
|
|
15
|
+
let schema = zod_1.z.any();
|
|
16
|
+
if (prop.type === "string") {
|
|
17
|
+
schema = zod_1.z.string();
|
|
18
|
+
}
|
|
19
|
+
else if (prop.type === "number") {
|
|
20
|
+
schema = zod_1.z.number();
|
|
21
|
+
}
|
|
22
|
+
else if (prop.type === "boolean") {
|
|
23
|
+
schema = zod_1.z.boolean();
|
|
24
|
+
}
|
|
25
|
+
if (prop.description) {
|
|
26
|
+
schema = schema.describe(prop.description);
|
|
27
|
+
}
|
|
28
|
+
// 默认都设为 optional 以防 Agent 报错,除非在 JSON Schema 的 required 数组中
|
|
29
|
+
const isRequired = (_a = parameters.required) === null || _a === void 0 ? void 0 : _a.includes(key);
|
|
30
|
+
obj[key] = isRequired ? schema : schema.optional();
|
|
31
|
+
}
|
|
32
|
+
return zod_1.z.object(obj).loose();
|
|
33
|
+
}
|