@nickyzj2023/ai 1.1.3 → 1.3.0
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 -3
- package/dist/cli.mjs +210 -37
- package/dist/index.d.mts +74 -96
- package/dist/index.mjs +1 -1
- package/dist/{src-B0tl6AT5.mjs → src-Dm0rwYe2.mjs} +117 -186
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ai
|
|
2
2
|
|
|
3
|
-
男生自用MyPi Coding Agent
|
|
3
|
+
男生自用MyPi Coding Agent
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
@@ -46,10 +46,15 @@ for await (const e of runAgent(model, messages, tools)) {
|
|
|
46
46
|
### 在终端里使用
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
#
|
|
50
|
-
|
|
49
|
+
# 首次使用:交互式配置BASE_URL / APIKEY / MODEL / ...
|
|
50
|
+
ai setup
|
|
51
|
+
|
|
52
|
+
# 启动对话
|
|
53
|
+
ai
|
|
51
54
|
```
|
|
52
55
|
|
|
56
|
+
配置保存在 `~/.@nickyzj2023/ai/config.json`,任意目录下执行`ai`都能读取到
|
|
57
|
+
|
|
53
58
|
## License
|
|
54
59
|
|
|
55
60
|
ISC
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,85 @@
|
|
|
1
|
-
import { i as defineModel, n as get_weather_default, o as runAgent, r as get_time_default } from "./src-
|
|
2
|
-
import {
|
|
3
|
-
import { loadEnvFile } from "node:process";
|
|
1
|
+
import { a as defineTool, i as defineModel, n as get_weather_default, o as runAgent, r as get_time_default } from "./src-Dm0rwYe2.mjs";
|
|
2
|
+
import { extractErrorMessage, isObject, omit } from "@nickyzj2023/utils";
|
|
4
3
|
import readline from "node:readline";
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
8
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
9
|
+
//#region src/utils/config.ts
|
|
10
|
+
/**
|
|
11
|
+
* 获取全局配置文件路径(Windows/macOS/Linux通用)
|
|
12
|
+
* @returns (Windows下)C:/Users/Administrator/.@nickyzj2023/ai/config.json
|
|
13
|
+
*/
|
|
14
|
+
const getConfigPath = () => {
|
|
15
|
+
return join(homedir(), ".@nickyzj2023", "ai", "config.json");
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 读取配置文件
|
|
19
|
+
* @returns 正常返回Config,配置不存在或损坏时返回null
|
|
20
|
+
*/
|
|
21
|
+
const loadConfig = () => {
|
|
22
|
+
try {
|
|
23
|
+
const config = JSON.parse(readFileSync(getConfigPath(), "utf8"));
|
|
24
|
+
if (typeof config.baseUrl !== "string" || typeof config.apiKey !== "string" || typeof config.model !== "string") return null;
|
|
25
|
+
return config;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 将配置写回全局文件(自动创建目录)
|
|
32
|
+
* @remarks 写入失败会抛异常
|
|
33
|
+
*/
|
|
34
|
+
const saveConfig = (config) => {
|
|
35
|
+
const configPath = getConfigPath();
|
|
36
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
37
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/interfaces/setup.ts
|
|
41
|
+
/**
|
|
42
|
+
* 临时创建readline接口,问答结束就关闭
|
|
43
|
+
* @param question 提问
|
|
44
|
+
* @returns 用户输入的回答
|
|
45
|
+
*/
|
|
46
|
+
const ask = (question) => {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const rl = readline.createInterface({
|
|
49
|
+
input: process.stdin,
|
|
50
|
+
output: process.stdout
|
|
51
|
+
});
|
|
52
|
+
rl.question(question, (answer) => {
|
|
53
|
+
rl.close();
|
|
54
|
+
resolve(answer);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* setup入口:依次询问BASE_URL / MODEL / APIKEY,确认后写入全局配置
|
|
60
|
+
*/
|
|
61
|
+
async function runSetup() {
|
|
62
|
+
const config = loadConfig();
|
|
63
|
+
if (config) {
|
|
64
|
+
console.log(`当前配置:BASE_URL ${config.baseUrl},APIKEY ${config.apiKey},MODEL ${config.model}`);
|
|
65
|
+
console.log("直接回车可沿用当前值。\n");
|
|
66
|
+
}
|
|
67
|
+
const baseUrl = await ask(`BASE_URL [当前为${config?.baseUrl}]: `) || config?.baseUrl;
|
|
68
|
+
const apiKey = await ask(`APIKEY [当前为${config?.apiKey}]: `) || config?.apiKey;
|
|
69
|
+
const model = await ask(`MODEL [当前为${config?.model}]: `) || config?.model;
|
|
70
|
+
try {
|
|
71
|
+
saveConfig({
|
|
72
|
+
baseUrl,
|
|
73
|
+
apiKey,
|
|
74
|
+
model
|
|
75
|
+
});
|
|
76
|
+
console.log(`配置已保存到${getConfigPath()},直接运行ai即可开始对话`);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error(`保存配置失败:${extractErrorMessage(e)}`);
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
5
83
|
//#region src/interfaces/tui.ts
|
|
6
84
|
var TUI = class {
|
|
7
85
|
rl = null;
|
|
@@ -86,47 +164,142 @@ var TUI = class {
|
|
|
86
164
|
this.preparePrint("tool_result");
|
|
87
165
|
process.stdout.write(this.colorize(`[工具结果:${name}] ${result}`, "90"));
|
|
88
166
|
}
|
|
167
|
+
formatter = new Intl.NumberFormat("en-US", {
|
|
168
|
+
notation: "compact",
|
|
169
|
+
maximumFractionDigits: 1
|
|
170
|
+
});
|
|
171
|
+
format(number) {
|
|
172
|
+
return this.formatter.format(number);
|
|
173
|
+
}
|
|
89
174
|
/** 打印轮次结束原因、token消耗 */
|
|
90
175
|
printFinish(finishReason, usage) {
|
|
91
176
|
this.preparePrint("done");
|
|
92
|
-
process.stdout.write(this.colorize(`[本轮结束:${finishReason}] ${usage ? `输入${usage.prompt_tokens}
|
|
177
|
+
process.stdout.write(this.colorize(`[本轮结束:${finishReason}] ${usage ? `输入${this.format(usage.prompt_tokens)}token,输出${this.format(usage.completion_tokens)}token,总共${this.format(usage.total_tokens)}token` : ""}${finishReason === "stop" ? "\n\n" : "\n"}`, "90"));
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/tools/mcp.ts
|
|
182
|
+
/** 全局单例MCP加载器 */
|
|
183
|
+
let router = null;
|
|
184
|
+
var MCPRouter = class {
|
|
185
|
+
entries = /* @__PURE__ */ new Map();
|
|
186
|
+
/** 注册一个新的MCP客户端 */
|
|
187
|
+
async addClient(name, url, options) {
|
|
188
|
+
if (this.entries.has(name)) return;
|
|
189
|
+
const transport = new StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: options?.headers } });
|
|
190
|
+
const client = new Client({
|
|
191
|
+
name,
|
|
192
|
+
version: "1.0.0"
|
|
193
|
+
});
|
|
194
|
+
await client.connect(transport);
|
|
195
|
+
const { tools } = await client.listTools();
|
|
196
|
+
const normalizedTools = tools.filter((tool) => !options?.ignoredToolNames?.includes(tool.name)).map((tool) => {
|
|
197
|
+
const _properties = { ...tool.inputSchema.properties ?? {} };
|
|
198
|
+
tool.inputSchema.required?.forEach((key) => {
|
|
199
|
+
if (isObject(_properties[key])) _properties[key] = {
|
|
200
|
+
..._properties[key],
|
|
201
|
+
required: true
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
return defineTool(tool.name, tool.description ?? "", _properties, (args) => client.callTool({
|
|
205
|
+
name: tool.name,
|
|
206
|
+
arguments: args
|
|
207
|
+
}));
|
|
208
|
+
});
|
|
209
|
+
this.entries.set(name, {
|
|
210
|
+
client,
|
|
211
|
+
tools: normalizedTools
|
|
212
|
+
});
|
|
213
|
+
return client;
|
|
93
214
|
}
|
|
215
|
+
/** 返回OpenAI API兼容的tools数组 */
|
|
216
|
+
async getTools() {
|
|
217
|
+
return [...this.entries.values()].flatMap((e) => e.tools);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* 把传入的MCPServer列表转换成OpenAI API兼容的tools数组
|
|
222
|
+
*/
|
|
223
|
+
const loadMCPTools = async (mcpServers) => {
|
|
224
|
+
router ||= new MCPRouter();
|
|
225
|
+
await Promise.allSettled(Object.entries(mcpServers).map(async ([name, server]) => {
|
|
226
|
+
try {
|
|
227
|
+
await router?.addClient(name, server.url, omit(server, ["type", "url"]));
|
|
228
|
+
console.log(`已加载MCP工具:${name}`);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.error(`MCP服务器${name}连接失败,跳过:${extractErrorMessage(e)}`);
|
|
231
|
+
}
|
|
232
|
+
}));
|
|
233
|
+
return router.getTools();
|
|
94
234
|
};
|
|
95
235
|
//#endregion
|
|
96
236
|
//#region src/cli.ts
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
237
|
+
/** 启动交互对话:配置来自全局配置文件,环境变量可临时覆盖 */
|
|
238
|
+
const startChat = async () => {
|
|
239
|
+
const config = loadConfig();
|
|
240
|
+
if (!config) {
|
|
241
|
+
console.error("请先运行 `ai setup` 配置一个模型");
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
const model = defineModel(config);
|
|
245
|
+
const messages = [];
|
|
246
|
+
const mcpTools = await loadMCPTools(config.mcpServers);
|
|
247
|
+
const tools = [
|
|
248
|
+
get_weather_default,
|
|
249
|
+
get_time_default,
|
|
250
|
+
...mcpTools
|
|
251
|
+
];
|
|
252
|
+
const tui = new TUI(async (input) => {
|
|
253
|
+
messages.push({
|
|
254
|
+
role: "user",
|
|
255
|
+
content: input
|
|
256
|
+
});
|
|
257
|
+
for await (const e of runAgent(model, messages, tools)) switch (e.type) {
|
|
258
|
+
case "reasoning_delta":
|
|
259
|
+
tui.printReasoning(e.delta);
|
|
260
|
+
break;
|
|
261
|
+
case "content_delta":
|
|
262
|
+
tui.printContent(e.delta);
|
|
263
|
+
break;
|
|
264
|
+
case "tool_call":
|
|
265
|
+
tui.printToolCall(e.name, e.args);
|
|
266
|
+
break;
|
|
267
|
+
case "tool_result":
|
|
268
|
+
tui.printToolResult(e.name, e.result);
|
|
269
|
+
break;
|
|
270
|
+
case "error":
|
|
271
|
+
tui.printContent(e.message);
|
|
272
|
+
break;
|
|
273
|
+
case "done": tui.printFinish(e.finishReason, e.usage);
|
|
274
|
+
}
|
|
113
275
|
});
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
276
|
+
tui.start();
|
|
277
|
+
};
|
|
278
|
+
/** 打印命令用法 */
|
|
279
|
+
const printHelp = () => {
|
|
280
|
+
console.log(`用法: ai [命令]
|
|
281
|
+
|
|
282
|
+
命令:
|
|
283
|
+
--help 显示帮助
|
|
284
|
+
setup 交互式配置模型APIKEY / BASE_URL / MODEL(保存到 ~/.@nickyzj2023/ai/config.json)
|
|
285
|
+
|
|
286
|
+
不带命令则启动对话`);
|
|
287
|
+
};
|
|
288
|
+
const command = process.argv[2];
|
|
289
|
+
switch (command) {
|
|
290
|
+
case void 0:
|
|
291
|
+
startChat();
|
|
292
|
+
break;
|
|
293
|
+
case "setup":
|
|
294
|
+
await runSetup();
|
|
295
|
+
break;
|
|
296
|
+
case "--help":
|
|
297
|
+
case "-h":
|
|
298
|
+
printHelp();
|
|
299
|
+
break;
|
|
300
|
+
default:
|
|
301
|
+
console.error(`未知命令:${command}(可以运行ai --help查看用法)`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
131
304
|
//#endregion
|
|
132
305
|
export {};
|
package/dist/index.d.mts
CHANGED
|
@@ -150,124 +150,102 @@ declare const _default$1: ToolDefinition;
|
|
|
150
150
|
//#endregion
|
|
151
151
|
//#region src/utils/compact/types.d.ts
|
|
152
152
|
declare namespace Compact {
|
|
153
|
-
type
|
|
154
|
-
|
|
153
|
+
type Options = {
|
|
154
|
+
/** 提供token消耗情况时,能更准确地判断上下文是否达到阈值 */
|
|
155
|
+
usage?: Usage;
|
|
156
|
+
/**
|
|
157
|
+
* 各种压缩方式统一保留的最近消息条数
|
|
158
|
+
* @default 10
|
|
159
|
+
* @remarks 压缩工具调用结果、压缩媒体消息、总结消息、硬删除兜底都会保留最近keepCount条消息不处理
|
|
160
|
+
*/
|
|
161
|
+
keepCount?: number;
|
|
162
|
+
/**
|
|
163
|
+
* 压缩工具/媒体消息时做个标记,防止下次重复压缩
|
|
164
|
+
* @default "[已压缩]"
|
|
165
|
+
*/
|
|
166
|
+
compactedMessageMark?: string;
|
|
167
|
+
/**
|
|
168
|
+
* 上下文>总上下文*ratio时压缩工具调用结果
|
|
169
|
+
* @default 0.6
|
|
170
|
+
*/
|
|
171
|
+
ratioToCompactToolResults?: number;
|
|
172
|
+
/**
|
|
173
|
+
* 如何压缩工具调用结果
|
|
174
|
+
* @default 让其他模型返回简化后的工具结果
|
|
175
|
+
*/
|
|
176
|
+
replacerOfToolResultContent?: Compact.ReplacerOfToolResultContent;
|
|
177
|
+
/**
|
|
178
|
+
* 上下文>总上下文*ratio时压缩图片/音频/视频消息
|
|
179
|
+
* @default 0.7
|
|
180
|
+
*/
|
|
181
|
+
ratioToCompactMedia?: number;
|
|
182
|
+
/**
|
|
183
|
+
* 如何压缩媒体消息
|
|
184
|
+
* @default 让其他模型用自然语言简短描述一遍
|
|
185
|
+
*/
|
|
186
|
+
replacerOfMediaContent?: Compact.ReplacerOfMediaContent;
|
|
187
|
+
/**
|
|
188
|
+
* 上下文>总上下文*ratio时总结消息
|
|
189
|
+
* @default 0.8
|
|
190
|
+
* @remarks 如果总结成功,会把keepCount以前的消息压成一条用户消息
|
|
191
|
+
*/
|
|
192
|
+
ratioToSummarize?: number;
|
|
193
|
+
/**
|
|
194
|
+
* 总结消息时的配置项
|
|
195
|
+
* @default { systemPrompt: "你现在的任务是总结历史消息" }
|
|
196
|
+
*/
|
|
197
|
+
summarizeOptions?: Partial<Compact.SummarizeOptions>;
|
|
198
|
+
};
|
|
199
|
+
type ReplacerOfToolResultContent = (content: Message["content"], options?: Record<string, any>) => Promise<string> | string;
|
|
200
|
+
type ReplacerOfMediaContent = (content: Message["content"], options?: Record<string, any>) => Promise<string> | string;
|
|
155
201
|
type SummarizeOptions = {
|
|
156
|
-
/**
|
|
157
|
-
model: Model;
|
|
158
|
-
/** 用于指导大模型如何总结消息的提示词 */
|
|
202
|
+
/** 指导大模型如何总结消息 */
|
|
159
203
|
systemPrompt: string;
|
|
204
|
+
model: Model;
|
|
160
205
|
};
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
* 例如 hasSummarized 为 true 只代表进入了总结流程,是否真的总结成功,
|
|
166
|
-
* 应由调用方检查消息数组里是否出现含 `<summary>` 标签的消息来判断。
|
|
167
|
-
*/
|
|
168
|
-
type CompactResult = {
|
|
169
|
-
/** 是否执行了压缩工具调用结果 */
|
|
170
|
-
hasCompactedToolResult: boolean;
|
|
171
|
-
/** 是否执行了压缩图片/音频/视频消息 */
|
|
206
|
+
type Result = {
|
|
207
|
+
/** 是否压缩了工具调用结果 */
|
|
208
|
+
hasCompactedToolResults: boolean;
|
|
209
|
+
/** 是否压缩了图片/音频/视频消息 */
|
|
172
210
|
hasCompactedMedia: boolean;
|
|
173
|
-
/**
|
|
174
|
-
hasClearedSoftDeletedMessages: boolean;
|
|
175
|
-
/** 是否执行了总结消息操作(是否真的总结,请检查消息中是否出现`<summary>`标签) */
|
|
211
|
+
/** 是否总结了消息 */
|
|
176
212
|
hasSummarized: boolean;
|
|
177
|
-
/**
|
|
178
|
-
|
|
213
|
+
/** 是否丢弃了一些旧消息(最终兜底策略) */
|
|
214
|
+
hasDiscardMessages: boolean;
|
|
179
215
|
};
|
|
180
216
|
}
|
|
181
217
|
//#endregion
|
|
182
218
|
//#region src/utils/compact/strategy.d.ts
|
|
183
|
-
|
|
219
|
+
/**
|
|
220
|
+
* @returns 实际处理了几条消息
|
|
221
|
+
*/
|
|
222
|
+
declare const compactToolResults: (compressible: Message[], options: {
|
|
184
223
|
replacer?: Compact.ReplacerOfToolResultContent;
|
|
185
224
|
mark: string;
|
|
186
225
|
model?: Model;
|
|
187
|
-
}) => Promise<
|
|
188
|
-
|
|
226
|
+
}) => Promise<number>;
|
|
227
|
+
/**
|
|
228
|
+
* @returns 实际处理了几条消息
|
|
229
|
+
*/
|
|
230
|
+
declare const compactMediaMessages: (compressible: Message[], options: {
|
|
189
231
|
replacer?: Compact.ReplacerOfMediaContent;
|
|
190
|
-
mark: string;
|
|
191
232
|
model?: Model;
|
|
192
|
-
}) => Promise<
|
|
233
|
+
}) => Promise<number>;
|
|
234
|
+
declare const summarizeMessages: (compressible: Message[], options: Compact.SummarizeOptions) => Promise<number>;
|
|
193
235
|
/**
|
|
194
|
-
*
|
|
195
|
-
* @param messages 可压缩区消息数组,直接原地删除
|
|
196
|
-
* @param softDeleted 本轮被软删过的消息引用集合
|
|
197
|
-
* @remarks
|
|
198
|
-
* tool消息不能单独删除:OpenAI API要求assistant(tool_calls)与tool消息按tool_call_id配对,
|
|
199
|
-
* 单独删掉tool会让assistant(tool_calls)变成孤立消息,后续请求返回400。
|
|
200
|
-
* 因此遇到被软删的tool消息时,必须把整个assistant(tool_calls) + tool配对组一起删除。
|
|
201
|
-
* 组内多条tool被软删时,集合去重后只会删除一次。
|
|
236
|
+
* 最终的兜底压缩策略,从头删除旧消息,直到第二个回调函数返回true(达成目标)
|
|
202
237
|
*/
|
|
203
|
-
declare const
|
|
204
|
-
declare const summarizeMessages: (messages: Message[], options: Compact.SummarizeOptions) => Promise<void>;
|
|
205
|
-
declare const hardDeleteOldMessages: (messages: Message[]) => void;
|
|
238
|
+
declare const discardMessagesUntil: (compressible: Message[], until: (compressible: Message[]) => boolean) => number;
|
|
206
239
|
//#endregion
|
|
207
240
|
//#region src/utils/compact/index.d.ts
|
|
208
241
|
/**
|
|
209
242
|
* 自动优化上下文,类似AI Coding Agent的/compact命令
|
|
210
243
|
*/
|
|
211
|
-
declare const compact: ((messages: Message[], model: Model, options?: {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* 各种压缩方式统一保留的最近消息条数
|
|
216
|
-
* @default 10
|
|
217
|
-
* @remarks 压缩工具调用结果、压缩媒体消息、总结消息、硬删除兜底都会保留最近keepCount条消息不处理
|
|
218
|
-
*/
|
|
219
|
-
keepCount?: number;
|
|
220
|
-
/**
|
|
221
|
-
* 上下文>总上下文*ratio时压缩工具调用结果
|
|
222
|
-
* @default 0.5
|
|
223
|
-
*/
|
|
224
|
-
ratioToCompactToolResult?: number;
|
|
225
|
-
/**
|
|
226
|
-
* 如何压缩工具调用结果,例如让其他模型返回精简后的工具结果
|
|
227
|
-
* @default (content) => "(已被消费)"
|
|
228
|
-
*/
|
|
229
|
-
replacerOfToolResultContent?: Compact.ReplacerOfToolResultContent;
|
|
230
|
-
/**
|
|
231
|
-
* 上下文>总上下文*ratio时压缩图片/音频/视频消息
|
|
232
|
-
* @default 0.6
|
|
233
|
-
*/
|
|
234
|
-
ratioToCompactMedia?: number;
|
|
235
|
-
/**
|
|
236
|
-
* 如何压缩媒体消息,例如让其他模型用自然语言简短描述一遍
|
|
237
|
-
* @default (content) => "(已被丢弃)"
|
|
238
|
-
*/
|
|
239
|
-
replacerOfMediaContent?: Compact.ReplacerOfMediaContent;
|
|
240
|
-
/**
|
|
241
|
-
* 用于标记哪些消息被软删除了
|
|
242
|
-
*/
|
|
243
|
-
softDeletedMessageMark?: string;
|
|
244
|
-
/**
|
|
245
|
-
* 上下文>总上下文*ratio时清理软删除残留的占位消息
|
|
246
|
-
* @default 0.7
|
|
247
|
-
* @remarks
|
|
248
|
-
* 软删除(ratioToCompactToolResult/ratioToCompactMedia)只替换content不删消息,
|
|
249
|
-
* 该选项负责把残留的占位消息真正移除。信息在软删除时已丢失,清理不损失任何额外信息。
|
|
250
|
-
* 阈值应介于ratioToCompactMedia与ratioToSummarize之间:
|
|
251
|
-
* 太早则媒体软删还没执行、无残留可清;太晚则总结/硬删除兜底已处理整个压缩区,清理失去意义
|
|
252
|
-
*/
|
|
253
|
-
ratioToClearSoftDeletedMessages?: number;
|
|
254
|
-
/**
|
|
255
|
-
* 上下文>总上下文*ratio时总结消息
|
|
256
|
-
* @default 0.8
|
|
257
|
-
* @remarks 如果总结成功,会把keepCount(默认10,见顶层选项)以前的消息压成一条消息;如果总结失败,会采取兜底压缩方法:硬删除keepCount以前的消息
|
|
258
|
-
*/
|
|
259
|
-
ratioToSummarize?: number;
|
|
260
|
-
/**
|
|
261
|
-
* 总结消息时的配置项
|
|
262
|
-
* @default { model: undefined, systemPrompt: "总结历史消息" }
|
|
263
|
-
*/
|
|
264
|
-
summarizeOptions?: Partial<Compact.SummarizeOptions>;
|
|
265
|
-
}) => Promise<Compact.CompactResult>) & {
|
|
244
|
+
declare const compact: ((messages: Message[], model: Model, options?: Compact.Options) => Promise<Compact.Result>) & {
|
|
245
|
+
compactToolResults: typeof compactToolResults;
|
|
246
|
+
compactMediaMessages: typeof compactMediaMessages;
|
|
266
247
|
summarizeMessages: typeof summarizeMessages;
|
|
267
|
-
|
|
268
|
-
softDeleteOldMediaMessages: typeof softDeleteOldMediaMessages;
|
|
269
|
-
hardDeleteSoftMessages: typeof hardDeleteSoftMessages;
|
|
270
|
-
hardDeleteOldMessages: typeof hardDeleteOldMessages;
|
|
248
|
+
discardMessagesUntil: typeof discardMessagesUntil;
|
|
271
249
|
};
|
|
272
250
|
//#endregion
|
|
273
251
|
//#region src/utils/helper.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as defineTool, i as defineModel, n as get_weather_default, o as runAgent, r as get_time_default, t as compact } from "./src-
|
|
1
|
+
import { a as defineTool, i as defineModel, n as get_weather_default, o as runAgent, r as get_time_default, t as compact } from "./src-Dm0rwYe2.mjs";
|
|
2
2
|
export { compact, defineModel, defineTool, get_time_default as getTime, get_weather_default as getWeather, runAgent };
|
|
@@ -130,7 +130,7 @@ async function* runAgent(model, messages, tools) {
|
|
|
130
130
|
if (!tool) result = `不存在工具“${name}”`;
|
|
131
131
|
else {
|
|
132
132
|
const [error, response] = await to(tool.execute(JSON.parse(args)));
|
|
133
|
-
result = error ? `工具“${name}”执行出错:${error.message}` :
|
|
133
|
+
result = error ? `工具“${name}”执行出错:${error.message}` : JSON.stringify(response);
|
|
134
134
|
}
|
|
135
135
|
messages.push({
|
|
136
136
|
role: "tool",
|
|
@@ -212,27 +212,12 @@ const estimateTokens = (messages) => {
|
|
|
212
212
|
};
|
|
213
213
|
//#endregion
|
|
214
214
|
//#region src/tools/get-time.ts
|
|
215
|
-
const weekdayNames = {
|
|
216
|
-
Sunday: "星期日",
|
|
217
|
-
Monday: "星期一",
|
|
218
|
-
Tuesday: "星期二",
|
|
219
|
-
Wednesday: "星期三",
|
|
220
|
-
Thursday: "星期四",
|
|
221
|
-
Friday: "星期五",
|
|
222
|
-
Saturday: "星期六"
|
|
223
|
-
};
|
|
224
215
|
var get_time_default = defineTool("get_time", "查询指定时区的当前时间", { timezone: {
|
|
225
216
|
type: "string",
|
|
226
217
|
description: "完整的IANA时区名称,如Europe/Amsterdam。用户未明确提及时,传入对方语言对应的时区,例如对方使用中文时可传入Asia/Shanghai。",
|
|
227
218
|
required: true
|
|
228
219
|
} }, async ({ timezone }) => {
|
|
229
|
-
|
|
230
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
231
|
-
return [
|
|
232
|
-
`时区:${data.timeZone}${data.dstActive ? "(夏令时已生效)" : ""}`,
|
|
233
|
-
`日期:${data.year}-${pad(data.month)}-${pad(data.day)}(${weekdayNames[data.dayOfWeek] ?? data.dayOfWeek})`,
|
|
234
|
-
`时间:${pad(data.hour)}:${pad(data.minute)}:${pad(data.seconds)}`
|
|
235
|
-
].join("\n");
|
|
220
|
+
return fetcher("https://timeapi.io/api", { params: { timezone } }).get("/time/current/zone");
|
|
236
221
|
});
|
|
237
222
|
//#endregion
|
|
238
223
|
//#region src/tools/get-weather.ts
|
|
@@ -241,260 +226,206 @@ var get_weather_default = defineTool("get_weather", "查询指定城市的天气
|
|
|
241
226
|
description: "城市名,如shanghai、tokyo",
|
|
242
227
|
required: true
|
|
243
228
|
} }, async ({ city }) => {
|
|
244
|
-
|
|
245
|
-
const nearestArea = data.nearest_area[0];
|
|
246
|
-
const currentCondition = data.current_condition[0];
|
|
247
|
-
return [
|
|
248
|
-
`位置:${nearestArea.country[0].value}-${nearestArea.region[0].value}-${nearestArea.areaName[0].value}`,
|
|
249
|
-
`当前温度: ${currentCondition.temp_C}°C`,
|
|
250
|
-
`体感温度: ${currentCondition.FeelsLikeC}°C`,
|
|
251
|
-
`天气状况: ${currentCondition.weatherDesc[0].value}`,
|
|
252
|
-
`湿度: ${currentCondition.humidity}%`
|
|
253
|
-
].join("\n");
|
|
229
|
+
return fetcher("https://wttr.in", { params: { format: "j1" } }).get(`/${city}`);
|
|
254
230
|
});
|
|
255
231
|
//#endregion
|
|
256
232
|
//#region src/utils/compact/helper.ts
|
|
257
233
|
/**
|
|
258
|
-
*
|
|
234
|
+
* 校验assistant(tool_calls)消息
|
|
235
|
+
*/
|
|
236
|
+
const isToolCalls = (message) => {
|
|
237
|
+
return message?.role === "assistant" && Array.isArray(message.tool_calls);
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* 校验多模态消息
|
|
259
241
|
*/
|
|
260
|
-
const
|
|
242
|
+
const isMediaMessage = (message) => {
|
|
243
|
+
const MEDIA_TYPES = [
|
|
244
|
+
"image_url",
|
|
245
|
+
"input_audio",
|
|
246
|
+
"video_url"
|
|
247
|
+
];
|
|
248
|
+
return message && Array.isArray(message.content) && message.content.some((part) => MEDIA_TYPES.includes(part.type));
|
|
249
|
+
};
|
|
261
250
|
/**
|
|
262
|
-
* 查找
|
|
251
|
+
* 查找assistant(tool_calls) + tool配对组范围
|
|
263
252
|
* @param messages 消息数组
|
|
264
|
-
* @param toolIndex tool
|
|
265
|
-
* @returns
|
|
266
|
-
* 找不到配对组头时返回 null
|
|
267
|
-
* @remarks 配对组约定:组头是assistant(tool_calls),组内是紧随其后的连续tool消息。
|
|
268
|
-
* 删除或切分tool消息时都应整组处理,否则会留下孤立消息导致OpenAI API返回400。
|
|
269
|
-
* 组内任意一条tool消息都能定位到整组,供上层按整组范围批量操作
|
|
253
|
+
* @param toolIndex 组内任意tool消息下标
|
|
254
|
+
* @returns 下标数组[组头assistant(tool_calls), 组内最后一个tool),找不到组头时返回null
|
|
270
255
|
*/
|
|
271
256
|
const findToolGroupRange = (messages, toolIndex) => {
|
|
272
257
|
let groupStart = toolIndex;
|
|
273
258
|
while (groupStart > 0 && messages[groupStart - 1]?.role === "tool") groupStart--;
|
|
274
|
-
if (!
|
|
259
|
+
if (!isToolCalls(messages[groupStart - 1])) return null;
|
|
275
260
|
let groupEnd = groupStart;
|
|
276
261
|
while (groupEnd < messages.length && messages[groupEnd]?.role === "tool") groupEnd++;
|
|
277
262
|
return [groupStart - 1, groupEnd];
|
|
278
263
|
};
|
|
279
264
|
//#endregion
|
|
280
265
|
//#region src/utils/compact/strategy.ts
|
|
281
|
-
/**
|
|
266
|
+
/** 默认的压缩工具结果策略:让大模型精简消息内容 */
|
|
282
267
|
const defaultReplacerOfToolResultContent = async (content, options) => {
|
|
283
|
-
const {
|
|
268
|
+
const { model } = options ?? {};
|
|
284
269
|
const messages = [{
|
|
285
270
|
role: "user",
|
|
286
271
|
content
|
|
287
272
|
}, {
|
|
288
273
|
role: "user",
|
|
289
|
-
content: "
|
|
274
|
+
content: "请用一两句话简述上条消息"
|
|
290
275
|
}];
|
|
291
|
-
let simplifiedContent =
|
|
276
|
+
let simplifiedContent = "";
|
|
292
277
|
for await (const e of runAgent(model, messages, [])) if (e.type === "content_delta") simplifiedContent += e.delta;
|
|
278
|
+
else if (e.type === "error") throw new Error(e.message);
|
|
293
279
|
return simplifiedContent;
|
|
294
280
|
};
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
281
|
+
/**
|
|
282
|
+
* @returns 实际处理了几条消息
|
|
283
|
+
*/
|
|
284
|
+
const compactToolResults = async (compressible, options) => {
|
|
285
|
+
const { replacer, mark, model } = options ?? {};
|
|
286
|
+
if (!replacer && !model) return 0;
|
|
287
|
+
const _replacer = replacer || defaultReplacerOfToolResultContent;
|
|
288
|
+
let count = 0;
|
|
289
|
+
for (const message of compressible) if (message?.role === "tool" && typeof message.content === "string") {
|
|
300
290
|
if (message.content.startsWith(mark)) continue;
|
|
301
|
-
message.content = await
|
|
302
|
-
|
|
303
|
-
model
|
|
304
|
-
});
|
|
305
|
-
softDeleted.push(message);
|
|
291
|
+
message.content = mark + await _replacer(message.content, { model });
|
|
292
|
+
count++;
|
|
306
293
|
}
|
|
307
|
-
if (
|
|
308
|
-
return
|
|
294
|
+
if (count > 0) logger(`压缩了${count}条工具调用结果`);
|
|
295
|
+
return count;
|
|
309
296
|
};
|
|
310
|
-
/**
|
|
297
|
+
/** 默认的压缩多模态消息策略:让大模型精简消息内容 */
|
|
311
298
|
const defaultReplacerOfMediaContent = async (content, options) => {
|
|
312
|
-
const {
|
|
299
|
+
const { model } = options ?? {};
|
|
313
300
|
const messages = [{
|
|
314
301
|
role: "user",
|
|
315
302
|
content
|
|
316
303
|
}, {
|
|
317
304
|
role: "user",
|
|
318
|
-
content: "
|
|
305
|
+
content: "请用一两句话简述上方的多模态消息"
|
|
319
306
|
}];
|
|
320
|
-
let simplifiedContent =
|
|
307
|
+
let simplifiedContent = "";
|
|
321
308
|
for await (const e of runAgent(model, messages, [])) if (e.type === "content_delta") simplifiedContent += e.delta;
|
|
309
|
+
else if (e.type === "error") throw new Error(e.message);
|
|
322
310
|
return simplifiedContent;
|
|
323
311
|
};
|
|
324
|
-
const softDeleteOldMediaMessages = async (messages, options) => {
|
|
325
|
-
const { replacer = defaultReplacerOfMediaContent, mark, model } = options ?? {};
|
|
326
|
-
if (!replacer && !model) return [];
|
|
327
|
-
const mediaTypes = [
|
|
328
|
-
"image_url",
|
|
329
|
-
"input_audio",
|
|
330
|
-
"video_url"
|
|
331
|
-
];
|
|
332
|
-
const softDeleted = [];
|
|
333
|
-
for (const message of messages) if (message && Array.isArray(message.content) && message.content.some((part) => mediaTypes.includes(part.type))) {
|
|
334
|
-
message.content = await replacer(message.content, {
|
|
335
|
-
mark,
|
|
336
|
-
model
|
|
337
|
-
});
|
|
338
|
-
softDeleted.push(message);
|
|
339
|
-
}
|
|
340
|
-
if (softDeleted.length > 0) logger(`软删除了${softDeleted.length}条旧图片/音频/视频消息`);
|
|
341
|
-
return softDeleted;
|
|
342
|
-
};
|
|
343
312
|
/**
|
|
344
|
-
*
|
|
345
|
-
* @param messages 可压缩区消息数组,直接原地删除
|
|
346
|
-
* @param softDeleted 本轮被软删过的消息引用集合
|
|
347
|
-
* @remarks
|
|
348
|
-
* tool消息不能单独删除:OpenAI API要求assistant(tool_calls)与tool消息按tool_call_id配对,
|
|
349
|
-
* 单独删掉tool会让assistant(tool_calls)变成孤立消息,后续请求返回400。
|
|
350
|
-
* 因此遇到被软删的tool消息时,必须把整个assistant(tool_calls) + tool配对组一起删除。
|
|
351
|
-
* 组内多条tool被软删时,集合去重后只会删除一次。
|
|
313
|
+
* @returns 实际处理了几条消息
|
|
352
314
|
*/
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
for (let k = start; k < end; k++) deleteIndices.add(k);
|
|
363
|
-
} else deleteIndices.add(i);
|
|
364
|
-
} else deleteIndices.add(i);
|
|
315
|
+
const compactMediaMessages = async (compressible, options) => {
|
|
316
|
+
const { replacer, model } = options ?? {};
|
|
317
|
+
if (!replacer && !model) return 0;
|
|
318
|
+
const _replacer = replacer || defaultReplacerOfMediaContent;
|
|
319
|
+
let count = 0;
|
|
320
|
+
for (const message of compressible) if (isMediaMessage(message)) {
|
|
321
|
+
const compacted = await _replacer(message.content, { model });
|
|
322
|
+
message.content = createXMLText("media", compacted);
|
|
323
|
+
count++;
|
|
365
324
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (deleteIndices.size > 0) logger(`清理了${deleteIndices.size}条软删除残留消息`);
|
|
325
|
+
if (count > 0) logger(`压缩了${count}条多模态消息`);
|
|
326
|
+
return count;
|
|
369
327
|
};
|
|
370
|
-
const summarizeMessages = async (
|
|
328
|
+
const summarizeMessages = async (compressible, options) => {
|
|
371
329
|
const { model, systemPrompt } = options ?? {};
|
|
372
|
-
|
|
330
|
+
const summarizable = compressible.slice(1);
|
|
331
|
+
if (summarizable.length === 0) {
|
|
373
332
|
logger("消息太少,无需总结");
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
const summarizableIndices = [];
|
|
377
|
-
const summarizingMessages = [];
|
|
378
|
-
for (let i = 0; i < messages.length; i++) {
|
|
379
|
-
const message = messages[i];
|
|
380
|
-
if (!message) continue;
|
|
381
|
-
if (i === 0 && message.role === "system") continue;
|
|
382
|
-
if (hasToolCalls(message)) {
|
|
383
|
-
let j = i + 1;
|
|
384
|
-
while (j < messages.length && messages[j]?.role === "tool") j++;
|
|
385
|
-
const group = messages.slice(i, j);
|
|
386
|
-
for (let k = i; k < j; k++) summarizableIndices.push(k);
|
|
387
|
-
summarizingMessages.push(...group);
|
|
388
|
-
i = j - 1;
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
if (message.role === "tool") continue;
|
|
392
|
-
summarizableIndices.push(i);
|
|
393
|
-
summarizingMessages.push(message);
|
|
333
|
+
return 0;
|
|
394
334
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
summarizingMessages.push({
|
|
335
|
+
const count = summarizable.length;
|
|
336
|
+
summarizable.push({
|
|
400
337
|
role: "system",
|
|
401
338
|
content: systemPrompt
|
|
402
339
|
}, {
|
|
403
340
|
role: "user",
|
|
404
|
-
content: "
|
|
341
|
+
content: "开始总结上下文"
|
|
405
342
|
});
|
|
406
343
|
let summarized = "";
|
|
407
344
|
let usage;
|
|
408
|
-
for await (const e of runAgent(model,
|
|
345
|
+
for await (const e of runAgent(model, summarizable, [])) if (e.type === "content_delta") summarized += e.delta;
|
|
409
346
|
else if (e.type === "done") usage = e.usage;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
for (let i = summarizableIndices.length - 1; i >= 0; i--) {
|
|
413
|
-
const index = summarizableIndices[i];
|
|
414
|
-
if (index !== void 0) messages.splice(index, 1);
|
|
415
|
-
}
|
|
416
|
-
messages.splice(firstIndex, 0, {
|
|
347
|
+
else if (e.type === "error") throw new Error(e.message);
|
|
348
|
+
compressible.splice(1, Infinity, {
|
|
417
349
|
role: "user",
|
|
418
350
|
content: createXMLText("summary", summarized)
|
|
419
351
|
});
|
|
352
|
+
logger(`总结了${count}条消息,消耗:`, usage);
|
|
353
|
+
return count;
|
|
420
354
|
};
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
355
|
+
/**
|
|
356
|
+
* 最终的兜底压缩策略,从头删除旧消息,直到第二个回调函数返回true(达成目标)
|
|
357
|
+
*/
|
|
358
|
+
const discardMessagesUntil = (compressible, until) => {
|
|
359
|
+
let count = 0;
|
|
360
|
+
while (compressible.length > 0 && !until(compressible)) {
|
|
361
|
+
let endIndex = Math.max(1, Math.floor(compressible.length / 10));
|
|
362
|
+
if (compressible[endIndex]?.role === "tool") {
|
|
363
|
+
const range = findToolGroupRange(compressible, endIndex);
|
|
364
|
+
if (range) endIndex = range[1];
|
|
365
|
+
else while (compressible[endIndex]?.role === "tool") endIndex++;
|
|
366
|
+
}
|
|
367
|
+
count += compressible.splice(0, endIndex).length;
|
|
426
368
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
logger(`硬删除了${deletedCount}条较早的消息`);
|
|
369
|
+
logger(`丢弃了${count}条旧消息`);
|
|
370
|
+
return count;
|
|
430
371
|
};
|
|
431
372
|
/**
|
|
432
373
|
* 自动优化上下文,类似AI Coding Agent的/compact命令
|
|
433
374
|
*/
|
|
434
375
|
const compact = Object.assign(async (messages, model, options) => {
|
|
435
|
-
const { usage, keepCount = 10,
|
|
376
|
+
const { usage, keepCount = 10, compactedMessageMark = "[已简化]", ratioToCompactToolResults = .6, replacerOfToolResultContent, ratioToCompactMedia = .7, replacerOfMediaContent, ratioToSummarize = .8, summarizeOptions } = options ?? {};
|
|
436
377
|
const context = model?.context ?? 131072;
|
|
437
378
|
const tokens = usage?.total_tokens ?? estimateTokens(messages);
|
|
438
379
|
const result = {
|
|
439
|
-
|
|
380
|
+
hasCompactedToolResults: false,
|
|
440
381
|
hasCompactedMedia: false,
|
|
441
|
-
hasClearedSoftDeletedMessages: false,
|
|
442
382
|
hasSummarized: false,
|
|
443
|
-
|
|
383
|
+
hasDiscardMessages: false
|
|
444
384
|
};
|
|
445
|
-
|
|
385
|
+
const startIndex = 1;
|
|
386
|
+
let endIndex = Math.max(startIndex, messages.length - keepCount);
|
|
446
387
|
if (messages[endIndex]?.role === "tool") {
|
|
447
388
|
const range = findToolGroupRange(messages, endIndex);
|
|
448
389
|
if (range) endIndex = range[1];
|
|
449
390
|
else while (messages[endIndex]?.role === "tool") endIndex++;
|
|
450
391
|
}
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const softDeletedMessages = await softDeleteOldMediaMessages(compressible, {
|
|
465
|
-
replacer: replacerOfMediaContent,
|
|
466
|
-
mark: softDeletedMessageMark,
|
|
467
|
-
model
|
|
468
|
-
});
|
|
469
|
-
for (const message of softDeletedMessages) softDeleted.add(message);
|
|
470
|
-
result.hasCompactedMedia = true;
|
|
471
|
-
}
|
|
472
|
-
if (tokens > context * ratioToClearSoftDeletedMessages) {
|
|
473
|
-
hardDeleteSoftMessages(compressible, softDeleted);
|
|
474
|
-
result.hasClearedSoftDeletedMessages = true;
|
|
475
|
-
messages.splice(0, messages.length, ...compressible, ...reserved);
|
|
476
|
-
}
|
|
392
|
+
endIndex = Math.max(startIndex, endIndex);
|
|
393
|
+
const reservedStart = messages.slice(0, startIndex);
|
|
394
|
+
const compressible = messages.slice(startIndex, endIndex);
|
|
395
|
+
const reservedEnd = messages.slice(endIndex);
|
|
396
|
+
if (tokens > context * ratioToCompactToolResults) result.hasCompactedToolResults = await compactToolResults(compressible, {
|
|
397
|
+
replacer: replacerOfToolResultContent,
|
|
398
|
+
mark: compactedMessageMark,
|
|
399
|
+
model
|
|
400
|
+
}) > 0;
|
|
401
|
+
if (tokens > context * ratioToCompactMedia) result.hasCompactedMedia = await compactMediaMessages(compressible, {
|
|
402
|
+
replacer: replacerOfMediaContent,
|
|
403
|
+
model
|
|
404
|
+
}) > 0;
|
|
477
405
|
if (tokens > context * ratioToSummarize) {
|
|
478
406
|
const { systemPrompt = "你现在的任务是总结历史消息" } = summarizeOptions ?? {};
|
|
479
|
-
const [error] = await to(summarizeMessages(compressible, {
|
|
407
|
+
const [error, count] = await to(summarizeMessages(compressible, {
|
|
480
408
|
model,
|
|
481
409
|
systemPrompt
|
|
482
410
|
}));
|
|
483
|
-
result.hasSummarized =
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
411
|
+
if (!error) result.hasSummarized = count > 0;
|
|
412
|
+
else result.hasDiscardMessages = discardMessagesUntil(compressible, (compressible) => {
|
|
413
|
+
if (compressible.length === 0) return true;
|
|
414
|
+
const tempMessages = [
|
|
415
|
+
...reservedStart,
|
|
416
|
+
...compressible,
|
|
417
|
+
...reservedEnd
|
|
418
|
+
];
|
|
419
|
+
return estimateTokens(tempMessages) < context * ratioToSummarize;
|
|
420
|
+
}) > 0;
|
|
421
|
+
messages.splice(0, Infinity, ...reservedStart, ...compressible, ...reservedEnd);
|
|
490
422
|
}
|
|
491
423
|
return result;
|
|
492
424
|
}, {
|
|
425
|
+
compactToolResults,
|
|
426
|
+
compactMediaMessages,
|
|
493
427
|
summarizeMessages,
|
|
494
|
-
|
|
495
|
-
softDeleteOldMediaMessages,
|
|
496
|
-
hardDeleteSoftMessages,
|
|
497
|
-
hardDeleteOldMessages
|
|
428
|
+
discardMessagesUntil
|
|
498
429
|
});
|
|
499
430
|
//#endregion
|
|
500
431
|
export { defineTool as a, defineModel as i, get_weather_default as n, runAgent as o, get_time_default as r, compact as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nickyzj2023/ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "我的“pi”,参考了pi-from-scratch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -26,12 +26,13 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@biomejs/biome": "^2.5.
|
|
30
|
-
"@types/node": "^26.4.
|
|
29
|
+
"@biomejs/biome": "^2.5.12",
|
|
30
|
+
"@types/node": "^26.4.1",
|
|
31
31
|
"tsdown": "^0.22.14",
|
|
32
32
|
"typescript": "^7.0.2"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
35
36
|
"@nickyzj2023/utils": "link:../utils"
|
|
36
37
|
},
|
|
37
38
|
"scripts": {
|