@saber2pr/ai-agent 0.0.4 → 0.0.6
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/lib/agent.d.ts +31 -4
- package/lib/agent.js +209 -30
- package/lib/cli.d.ts +2 -0
- package/lib/cli.js +9 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +18 -4
- package/package.json +4 -3
package/lib/agent.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
export interface CustomTool {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
parameters: any;
|
|
5
|
+
handler: (args: any) => Promise<any>;
|
|
6
|
+
}
|
|
7
|
+
export interface AgentOptions {
|
|
8
|
+
targetDir?: string;
|
|
9
|
+
/** 自定义工具扩展 */
|
|
10
|
+
tools?: any[];
|
|
11
|
+
/** 注入到 System Prompt 中的额外指令/规则/上下文 */
|
|
12
|
+
extraSystemPrompt?: any;
|
|
13
|
+
}
|
|
1
14
|
export default class McpAgent {
|
|
2
15
|
private openai;
|
|
3
16
|
private modelName;
|
|
@@ -5,7 +18,15 @@ export default class McpAgent {
|
|
|
5
18
|
private allTools;
|
|
6
19
|
private messages;
|
|
7
20
|
private engine;
|
|
8
|
-
|
|
21
|
+
private encoder;
|
|
22
|
+
private extraTools;
|
|
23
|
+
constructor(options?: AgentOptions);
|
|
24
|
+
/**
|
|
25
|
+
* 计算当前消息列表的总 Token 消耗
|
|
26
|
+
* 兼容多模态内容 (Content Parts) 和 工具调用 (Tool Calls)
|
|
27
|
+
*/
|
|
28
|
+
private calculateTokens;
|
|
29
|
+
private injectCustomTools;
|
|
9
30
|
/**
|
|
10
31
|
* 核心功能:内置代码分析工具
|
|
11
32
|
* 这里的逻辑直接调用 PromptEngine,不走网络请求,效率极高
|
|
@@ -14,10 +35,16 @@ export default class McpAgent {
|
|
|
14
35
|
private ensureApiConfig;
|
|
15
36
|
private loadMcpConfigs;
|
|
16
37
|
init(): Promise<void>;
|
|
38
|
+
private processChat;
|
|
17
39
|
/**
|
|
18
|
-
*
|
|
19
|
-
* 允许 AI 连续调用工具来解决复杂代码问题
|
|
40
|
+
* 简易 Loading 动画辅助函数
|
|
20
41
|
*/
|
|
21
|
-
private
|
|
42
|
+
private showLoading;
|
|
43
|
+
/**
|
|
44
|
+
* 编程式对话入口
|
|
45
|
+
* @param input 用户指令
|
|
46
|
+
* @returns AI 的最终答复内容
|
|
47
|
+
*/
|
|
48
|
+
chat(input: string): Promise<string>;
|
|
22
49
|
start(): Promise<void>;
|
|
23
50
|
}
|
package/lib/agent.js
CHANGED
|
@@ -44,24 +44,92 @@ const path_1 = __importDefault(require("path"));
|
|
|
44
44
|
const os_1 = __importDefault(require("os"));
|
|
45
45
|
const readline = __importStar(require("readline"));
|
|
46
46
|
const ts_context_mcp_1 = require("@saber2pr/ts-context-mcp"); // 引入我们的核心引擎
|
|
47
|
+
const js_tiktoken_1 = require("js-tiktoken");
|
|
47
48
|
const CONFIG_FILE = path_1.default.join(os_1.default.homedir(), ".saber2pr-agent.json");
|
|
48
49
|
class McpAgent {
|
|
49
|
-
constructor() {
|
|
50
|
+
constructor(options) {
|
|
50
51
|
this.modelName = "";
|
|
51
52
|
this.clients = [];
|
|
52
53
|
this.allTools = [];
|
|
53
54
|
this.messages = [];
|
|
54
|
-
|
|
55
|
-
this.
|
|
56
|
-
this.
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
this.encoder = (0, js_tiktoken_1.getEncoding)("cl100k_base");
|
|
56
|
+
this.extraTools = [];
|
|
57
|
+
this.engine = new ts_context_mcp_1.PromptEngine((options === null || options === void 0 ? void 0 : options.targetDir) || process.cwd());
|
|
58
|
+
this.extraTools = (options === null || options === void 0 ? void 0 : options.tools) || []; // 接收外部传入的工具
|
|
59
|
+
let baseSystemPrompt = `你是一个专业的 AI 代码架构师。
|
|
59
60
|
你可以访问本地文件系统并利用 AST (抽象语法树) 技术分析代码。
|
|
60
61
|
你的核心目标是提供准确的代码结构、依赖关系和逻辑分析。
|
|
61
|
-
|
|
62
|
+
请先使用 get_repo_map 查看项目整体代码结构。
|
|
63
|
+
请优先使用 read_skeleton 查看结构,只有在必要时才使用 read_full_code 或 get_method_body。`;
|
|
64
|
+
// 2. 拼接额外指令
|
|
65
|
+
if (options === null || options === void 0 ? void 0 : options.extraSystemPrompt) {
|
|
66
|
+
const extra = typeof options.extraSystemPrompt === 'string'
|
|
67
|
+
? options.extraSystemPrompt
|
|
68
|
+
: JSON.stringify(options.extraSystemPrompt, null, 2);
|
|
69
|
+
baseSystemPrompt += `\n\n[额外执行指令]:\n${extra}`;
|
|
70
|
+
}
|
|
71
|
+
this.messages.push({
|
|
72
|
+
role: "system",
|
|
73
|
+
content: baseSystemPrompt,
|
|
62
74
|
});
|
|
63
75
|
// 初始化内置工具
|
|
64
76
|
this.registerBuiltinTools();
|
|
77
|
+
this.injectCustomTools(); // 注入外部工具
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 计算当前消息列表的总 Token 消耗
|
|
81
|
+
* 兼容多模态内容 (Content Parts) 和 工具调用 (Tool Calls)
|
|
82
|
+
*/
|
|
83
|
+
calculateTokens() {
|
|
84
|
+
let total = 0;
|
|
85
|
+
for (const msg of this.messages) {
|
|
86
|
+
// 1. 处理消息内容 (Content)
|
|
87
|
+
if (msg.content) {
|
|
88
|
+
if (typeof msg.content === "string") {
|
|
89
|
+
// 普通文本消息
|
|
90
|
+
total += this.encoder.encode(msg.content).length;
|
|
91
|
+
}
|
|
92
|
+
else if (Array.isArray(msg.content)) {
|
|
93
|
+
// 多模态/复合内容消息 (ChatCompletionContentPart[])
|
|
94
|
+
for (const part of msg.content) {
|
|
95
|
+
if (part.type === "text" && "text" in part) {
|
|
96
|
+
total += this.encoder.encode(part.text || "").length;
|
|
97
|
+
}
|
|
98
|
+
// 注意:图片 (image_url) 的 Token 计算通常基于分辨率,tiktoken 无法计算
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// 2. 处理助手角色发出的工具调用请求 (Assistant Tool Calls)
|
|
103
|
+
// 这是为了统计 AI 发出的指令所占用的 Token
|
|
104
|
+
if (msg.role === "assistant" && msg.tool_calls) {
|
|
105
|
+
for (const call of msg.tool_calls) {
|
|
106
|
+
if (call.type === "function") {
|
|
107
|
+
// 统计函数名和参数字符串
|
|
108
|
+
total += this.encoder.encode(call.function.name).length;
|
|
109
|
+
total += this.encoder.encode(call.function.arguments).length;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 3. 处理工具返回的结果 (Tool Role)
|
|
114
|
+
// 在 processChat 中,我们确保了工具返回的 result 最终被转为了 string
|
|
115
|
+
if (msg.role === "tool" && typeof msg.content === "string") {
|
|
116
|
+
total += this.encoder.encode(msg.content).length;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return total;
|
|
120
|
+
}
|
|
121
|
+
injectCustomTools() {
|
|
122
|
+
for (const tool of this.extraTools) {
|
|
123
|
+
this.allTools.push({
|
|
124
|
+
type: "function",
|
|
125
|
+
function: {
|
|
126
|
+
name: tool.name,
|
|
127
|
+
description: tool.description,
|
|
128
|
+
parameters: tool.parameters,
|
|
129
|
+
},
|
|
130
|
+
_handler: tool.handler,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
65
133
|
}
|
|
66
134
|
/**
|
|
67
135
|
* 核心功能:内置代码分析工具
|
|
@@ -109,7 +177,25 @@ class McpAgent {
|
|
|
109
177
|
required: ["filePath"],
|
|
110
178
|
},
|
|
111
179
|
},
|
|
112
|
-
_handler: async (
|
|
180
|
+
_handler: async (args) => {
|
|
181
|
+
// 1. 严格路径守卫:防止 undefined 或空字符串进入 path 模块
|
|
182
|
+
const pathArg = args === null || args === void 0 ? void 0 : args.filePath;
|
|
183
|
+
if (typeof pathArg !== 'string' || pathArg.trim() === '') {
|
|
184
|
+
return `Error: 参数 'filePath' 无效。收到的是: ${JSON.stringify(pathArg)}`;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
// 2. 刷新引擎状态,确保分析的是最新的文件内容
|
|
188
|
+
this.engine.refresh();
|
|
189
|
+
// 3. 执行获取
|
|
190
|
+
const result = this.engine.getSkeleton(pathArg);
|
|
191
|
+
// 4. 空值回退:防止 getSkeleton 返回 null 导致后续统计 Token 时崩溃
|
|
192
|
+
return result || `// Warning: 文件 ${pathArg} 存在但未找到任何可提取的结构。`;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
// 5. 捕获 AST 级别的 match 错误
|
|
196
|
+
return `Error: 解析文件 ${pathArg} 时发生内部错误: ${error.message}`;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
113
199
|
},
|
|
114
200
|
{
|
|
115
201
|
type: "function",
|
|
@@ -127,6 +213,45 @@ class McpAgent {
|
|
|
127
213
|
},
|
|
128
214
|
_handler: async ({ filePath, methodName }) => this.engine.getMethodImplementation(filePath, methodName),
|
|
129
215
|
},
|
|
216
|
+
{
|
|
217
|
+
type: "function",
|
|
218
|
+
function: {
|
|
219
|
+
name: "read_full_code",
|
|
220
|
+
description: "读取指定文件的完整源代码内容。当需要分析具体实现逻辑或查找硬编码字符串时使用。",
|
|
221
|
+
parameters: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
filePath: { type: "string", description: "文件相对路径" },
|
|
225
|
+
},
|
|
226
|
+
required: ["filePath"],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
// 核心实现:直接利用 fs 读取
|
|
230
|
+
_handler: async ({ filePath }) => {
|
|
231
|
+
try {
|
|
232
|
+
if (typeof filePath !== 'string' || !filePath) {
|
|
233
|
+
return "Error: filePath 不能为空";
|
|
234
|
+
}
|
|
235
|
+
// 拼合绝对路径
|
|
236
|
+
const fullPath = path_1.default.resolve(this.engine.getRootDir(), filePath);
|
|
237
|
+
// 安全检查:防止 AI 尝试读取项目外的敏感文件
|
|
238
|
+
if (!fullPath.startsWith(this.engine.getRootDir())) {
|
|
239
|
+
return "Error: 权限拒绝,禁止访问项目目录外的文件。";
|
|
240
|
+
}
|
|
241
|
+
if (!fs_1.default.existsSync(fullPath)) {
|
|
242
|
+
return `Error: 文件不存在: ${filePath}`;
|
|
243
|
+
}
|
|
244
|
+
const content = fs_1.default.readFileSync(fullPath, "utf-8");
|
|
245
|
+
// 加上行号,AI 就能在 generate_review 里给出准确的 line 参数
|
|
246
|
+
return content.split('\n')
|
|
247
|
+
.map((line, i) => `${i + 1} | ${line}`)
|
|
248
|
+
.join('\n');
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
return `Error: 读取文件失败: ${err.message}`;
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
},
|
|
130
255
|
];
|
|
131
256
|
this.allTools.push(...builtinTools);
|
|
132
257
|
}
|
|
@@ -200,52 +325,105 @@ class McpAgent {
|
|
|
200
325
|
}
|
|
201
326
|
}
|
|
202
327
|
}
|
|
203
|
-
/**
|
|
204
|
-
* 核心交互循环 (Reasoning Loop)
|
|
205
|
-
* 允许 AI 连续调用工具来解决复杂代码问题
|
|
206
|
-
*/
|
|
207
328
|
async processChat(userInput) {
|
|
208
|
-
|
|
329
|
+
var _a;
|
|
330
|
+
this.messages.push({ role: 'user', content: userInput });
|
|
209
331
|
while (true) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
332
|
+
// 打印当前上下文的累计 Token
|
|
333
|
+
const currentInputTokens = this.calculateTokens();
|
|
334
|
+
console.log(`\n📊 当前上下文累计: ${currentInputTokens} tokens`);
|
|
335
|
+
const stopLoading = this.showLoading("🤖 Agent 正在思考...");
|
|
336
|
+
let response;
|
|
337
|
+
try {
|
|
338
|
+
response = await this.openai.chat.completions.create({
|
|
339
|
+
model: this.modelName,
|
|
340
|
+
messages: this.messages,
|
|
341
|
+
tools: this.allTools.map(({ _handler, _client, _originalName, ...rest }) => rest),
|
|
342
|
+
tool_choice: 'auto'
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
stopLoading();
|
|
347
|
+
}
|
|
216
348
|
const message = response.choices[0].message;
|
|
217
349
|
this.messages.push(message);
|
|
350
|
+
// 计算本次 AI 回复生成的 Token
|
|
351
|
+
const completionTokens = ((_a = response.usage) === null || _a === void 0 ? void 0 : _a.completion_tokens) ||
|
|
352
|
+
(message.content ? this.encoder.encode(message.content).length : 0);
|
|
353
|
+
console.log(`✨ AI 回复消耗: ${completionTokens} tokens`);
|
|
218
354
|
if (!message.tool_calls) {
|
|
219
355
|
console.log(`\n🤖 Agent: ${message.content}`);
|
|
220
356
|
break;
|
|
221
357
|
}
|
|
222
|
-
console.log(`\n⚙️
|
|
358
|
+
console.log(`\n⚙️ 正在执行 ${message.tool_calls.length} 个操作...`);
|
|
223
359
|
for (const call of message.tool_calls) {
|
|
224
|
-
const tool = this.allTools.find(
|
|
360
|
+
const tool = this.allTools.find(t => t.function.name === call.function.name);
|
|
361
|
+
const args = JSON.parse(call.function.arguments);
|
|
362
|
+
// 打印文件路径提示
|
|
363
|
+
if (args.filePath) {
|
|
364
|
+
console.log(` 📂 正在查看文件: ${args.filePath}`);
|
|
365
|
+
}
|
|
366
|
+
console.log(` 🛠️ 执行: ${call.function.name}`);
|
|
225
367
|
let result;
|
|
226
368
|
if (tool === null || tool === void 0 ? void 0 : tool._handler) {
|
|
227
|
-
|
|
228
|
-
result = await tool._handler(JSON.parse(call.function.arguments));
|
|
369
|
+
result = await tool._handler(args);
|
|
229
370
|
}
|
|
230
371
|
else if ((tool === null || tool === void 0 ? void 0 : tool._client) && tool._originalName) {
|
|
231
|
-
// 执行外部 MCP 工具
|
|
232
372
|
const mcpRes = await tool._client.callTool({
|
|
233
373
|
name: tool._originalName,
|
|
234
|
-
arguments:
|
|
374
|
+
arguments: args
|
|
235
375
|
});
|
|
236
376
|
result = mcpRes.content;
|
|
237
377
|
}
|
|
378
|
+
const resultContent = typeof result === "string" ? result : JSON.stringify(result);
|
|
379
|
+
// 打印工具返回结果的 Token 消耗
|
|
380
|
+
const toolResultTokens = this.encoder.encode(resultContent).length;
|
|
381
|
+
console.log(` 📝 工具输出: ${toolResultTokens} tokens`);
|
|
238
382
|
this.messages.push({
|
|
239
|
-
role:
|
|
383
|
+
role: 'tool',
|
|
240
384
|
tool_call_id: call.id,
|
|
241
|
-
content:
|
|
385
|
+
content: resultContent
|
|
242
386
|
});
|
|
243
|
-
console.log(`
|
|
387
|
+
console.log(` ✅ 完成: ${call.function.name}`);
|
|
244
388
|
}
|
|
245
389
|
}
|
|
246
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* 简易 Loading 动画辅助函数
|
|
393
|
+
*/
|
|
394
|
+
showLoading(text) {
|
|
395
|
+
const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
396
|
+
let i = 0;
|
|
397
|
+
const timer = setInterval(() => {
|
|
398
|
+
process.stdout.write(`\r${chars[i]} ${text}`);
|
|
399
|
+
i = (i + 1) % chars.length;
|
|
400
|
+
}, 80);
|
|
401
|
+
return () => {
|
|
402
|
+
clearInterval(timer);
|
|
403
|
+
process.stdout.write('\r\x1b[K'); // 清除当前行
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* 编程式对话入口
|
|
408
|
+
* @param input 用户指令
|
|
409
|
+
* @returns AI 的最终答复内容
|
|
410
|
+
*/
|
|
411
|
+
async chat(input) {
|
|
412
|
+
if (!this.openai) {
|
|
413
|
+
await this.init();
|
|
414
|
+
}
|
|
415
|
+
// 调用现有的处理逻辑
|
|
416
|
+
// 假设你的 processChat 已经处理了所有的 tool_calls 循环
|
|
417
|
+
await this.processChat(input);
|
|
418
|
+
// 返回消息列表中的最后一条 AI 回复
|
|
419
|
+
const lastMsg = this.messages[this.messages.length - 1];
|
|
420
|
+
return lastMsg.role === 'assistant' ? lastMsg.content : '';
|
|
421
|
+
}
|
|
422
|
+
// 修改原来的 start 方法,使其内部也调用 chat
|
|
247
423
|
async start() {
|
|
248
|
-
|
|
424
|
+
if (!this.openai) {
|
|
425
|
+
await this.init();
|
|
426
|
+
}
|
|
249
427
|
const rl = readline.createInterface({
|
|
250
428
|
input: process.stdin,
|
|
251
429
|
output: process.stdout,
|
|
@@ -256,7 +434,8 @@ class McpAgent {
|
|
|
256
434
|
if (input.toLowerCase() === "exit")
|
|
257
435
|
process.exit(0);
|
|
258
436
|
try {
|
|
259
|
-
|
|
437
|
+
// 这里统一调用 chat 或核心逻辑
|
|
438
|
+
await this.chat(input);
|
|
260
439
|
}
|
|
261
440
|
catch (err) {
|
|
262
441
|
console.error("\n❌ 系统错误:", err.message);
|
package/lib/cli.d.ts
ADDED
package/lib/cli.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
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_1 = __importDefault(require("./agent"));
|
|
8
|
+
const manager = new agent_1.default();
|
|
9
|
+
manager.start();
|
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export {};
|
|
1
|
+
export * from './agent';
|
|
2
|
+
export { default } from './agent';
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
3
16
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
18
|
};
|
|
6
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
20
|
+
exports.default = void 0;
|
|
21
|
+
__exportStar(require("./agent"), exports);
|
|
22
|
+
var agent_1 = require("./agent");
|
|
23
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(agent_1).default; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saber2pr/ai-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "AI Assistant CLI.",
|
|
5
5
|
"author": "saber2pr",
|
|
6
6
|
"license": "ISC",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"lib"
|
|
9
9
|
],
|
|
10
10
|
"bin": {
|
|
11
|
-
"sagent": "./lib/
|
|
11
|
+
"sagent": "./lib/cli.js"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
25
|
-
"@saber2pr/ts-context-mcp": "^0.0.
|
|
25
|
+
"@saber2pr/ts-context-mcp": "^0.0.6",
|
|
26
|
+
"js-tiktoken": "^1.0.21",
|
|
26
27
|
"openai": "^6.16.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|