@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 +72 -18
- package/lib/core/agent-chain.d.ts +4 -9
- package/lib/core/agent-chain.js +90 -163
- package/lib/core/agent.js +14 -17
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -1
- package/lib/model/AgentChainModel.d.ts +18 -0
- package/lib/model/AgentChainModel.js +23 -0
- package/lib/tools/filesystem/index.js +12 -11
- package/lib/types/type.d.ts +14 -3
- package/package.json +13 -8
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
|
-
|
|
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 {
|
|
88
|
+
const { AgentChainModel } = require("@saber2pr/ai-agent");
|
|
89
|
+
|
|
90
|
+
class MyPrivateLLM extends AgentChainModel {
|
|
91
|
+
constructor(fields) {
|
|
92
|
+
super(fields || {});
|
|
93
|
+
}
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
async _generate(messages) {
|
|
95
|
+
async generateAgentChainResponse(messages) {
|
|
90
96
|
const lastMessage = messages[messages.length - 1];
|
|
91
|
-
const
|
|
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:
|
|
94
|
-
headers: {
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
|
115
|
-
|
|
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
|
|
13
|
-
private
|
|
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
|
|
22
|
-
private pruneMessages;
|
|
17
|
+
private wrapHandler;
|
|
23
18
|
init(): Promise<void>;
|
|
24
19
|
chat(input: string): Promise<string>;
|
|
25
20
|
private showLoading;
|
package/lib/core/agent-chain.js
CHANGED
|
@@ -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;
|
|
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 =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
### 🛠
|
|
175
|
-
--------------------
|
|
133
|
+
### 🛠 可用工具:
|
|
176
134
|
{tools}
|
|
177
135
|
|
|
178
|
-
|
|
136
|
+
### 🛠 工具名称:
|
|
137
|
+
[{tool_names}]
|
|
138
|
+
|
|
139
|
+
### 📝 历史记录:
|
|
140
|
+
{chat_history}
|
|
179
141
|
|
|
180
|
-
###
|
|
181
|
-
|
|
182
|
-
|
|
142
|
+
### ⚠️ 回复规范(严格遵守):
|
|
143
|
+
1. 首先输出 **Thought:**,用中文详细说明你的分析思路。
|
|
144
|
+
2. 接着输出一个 **JSON Action 代码块**。
|
|
183
145
|
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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.
|
|
213
|
-
this.
|
|
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.
|
|
182
|
+
system_prompt: this.systemPrompt,
|
|
220
183
|
}, {
|
|
221
|
-
// --- 新增:使用回调函数捕获 Thought ---
|
|
222
184
|
callbacks: [{
|
|
223
|
-
handleAgentAction: (action
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
288
|
-
console.log(`\n🤖 Agent: ${
|
|
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
|
-
|
|
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 =
|
|
60
|
+
let baseSystemPrompt = `你是一个专业的代码架构师。
|
|
61
|
+
你的目标是理解并分析用户项目,请务必遵循以下工作流:
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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);
|
package/lib/types/type.d.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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",
|