mozi-bot 1.0.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/LICENSE +190 -0
- package/README.md +467 -0
- package/dist/agents/agent.d.ts +118 -0
- package/dist/agents/agent.d.ts.map +1 -0
- package/dist/agents/agent.js +686 -0
- package/dist/agents/agent.js.map +1 -0
- package/dist/agents/compaction.d.ts +78 -0
- package/dist/agents/compaction.d.ts.map +1 -0
- package/dist/agents/compaction.js +350 -0
- package/dist/agents/compaction.js.map +1 -0
- package/dist/agents/failover-error.d.ts +42 -0
- package/dist/agents/failover-error.d.ts.map +1 -0
- package/dist/agents/failover-error.js +171 -0
- package/dist/agents/failover-error.js.map +1 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +10 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/model-fallback.d.ts +54 -0
- package/dist/agents/model-fallback.d.ts.map +1 -0
- package/dist/agents/model-fallback.js +227 -0
- package/dist/agents/model-fallback.js.map +1 -0
- package/dist/agents/session-store.d.ts +53 -0
- package/dist/agents/session-store.d.ts.map +1 -0
- package/dist/agents/session-store.js +217 -0
- package/dist/agents/session-store.js.map +1 -0
- package/dist/agents/system-prompt.d.ts +29 -0
- package/dist/agents/system-prompt.d.ts.map +1 -0
- package/dist/agents/system-prompt.js +299 -0
- package/dist/agents/system-prompt.js.map +1 -0
- package/dist/channels/common/base.d.ts +40 -0
- package/dist/channels/common/base.d.ts.map +1 -0
- package/dist/channels/common/base.js +23 -0
- package/dist/channels/common/base.js.map +1 -0
- package/dist/channels/common/index.d.ts +21 -0
- package/dist/channels/common/index.d.ts.map +1 -0
- package/dist/channels/common/index.js +66 -0
- package/dist/channels/common/index.js.map +1 -0
- package/dist/channels/dingtalk/api.d.ts +30 -0
- package/dist/channels/dingtalk/api.d.ts.map +1 -0
- package/dist/channels/dingtalk/api.js +149 -0
- package/dist/channels/dingtalk/api.js.map +1 -0
- package/dist/channels/dingtalk/events.d.ts +80 -0
- package/dist/channels/dingtalk/events.d.ts.map +1 -0
- package/dist/channels/dingtalk/events.js +92 -0
- package/dist/channels/dingtalk/events.js.map +1 -0
- package/dist/channels/dingtalk/index.d.ts +51 -0
- package/dist/channels/dingtalk/index.d.ts.map +1 -0
- package/dist/channels/dingtalk/index.js +231 -0
- package/dist/channels/dingtalk/index.js.map +1 -0
- package/dist/channels/dingtalk/stream.d.ts +31 -0
- package/dist/channels/dingtalk/stream.d.ts.map +1 -0
- package/dist/channels/dingtalk/stream.js +116 -0
- package/dist/channels/dingtalk/stream.js.map +1 -0
- package/dist/channels/feishu/api.d.ts +33 -0
- package/dist/channels/feishu/api.d.ts.map +1 -0
- package/dist/channels/feishu/api.js +120 -0
- package/dist/channels/feishu/api.js.map +1 -0
- package/dist/channels/feishu/events.d.ts +87 -0
- package/dist/channels/feishu/events.d.ts.map +1 -0
- package/dist/channels/feishu/events.js +163 -0
- package/dist/channels/feishu/events.js.map +1 -0
- package/dist/channels/feishu/index.d.ts +46 -0
- package/dist/channels/feishu/index.d.ts.map +1 -0
- package/dist/channels/feishu/index.js +207 -0
- package/dist/channels/feishu/index.js.map +1 -0
- package/dist/channels/feishu/websocket.d.ts +32 -0
- package/dist/channels/feishu/websocket.d.ts.map +1 -0
- package/dist/channels/feishu/websocket.js +121 -0
- package/dist/channels/feishu/websocket.js.map +1 -0
- package/dist/channels/index.d.ts +9 -0
- package/dist/channels/index.d.ts.map +1 -0
- package/dist/channels/index.js +9 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +468 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/index.d.ts +54 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +171 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/config/index.d.ts +201 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +261 -0
- package/dist/config/index.js.map +1 -0
- package/dist/gateway/index.d.ts +5 -0
- package/dist/gateway/index.d.ts.map +1 -0
- package/dist/gateway/index.js +5 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/server.d.ts +42 -0
- package/dist/gateway/server.d.ts.map +1 -0
- package/dist/gateway/server.js +235 -0
- package/dist/gateway/server.js.map +1 -0
- package/dist/hooks/index.d.ts +168 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +148 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +94 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +282 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/plugins/index.d.ts +57 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +103 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/providers/anthropic-compatible.d.ts +48 -0
- package/dist/providers/anthropic-compatible.d.ts.map +1 -0
- package/dist/providers/anthropic-compatible.js +380 -0
- package/dist/providers/anthropic-compatible.js.map +1 -0
- package/dist/providers/base.d.ts +26 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +58 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/custom-openai.d.ts +66 -0
- package/dist/providers/custom-openai.d.ts.map +1 -0
- package/dist/providers/custom-openai.js +255 -0
- package/dist/providers/custom-openai.js.map +1 -0
- package/dist/providers/dashscope.d.ts +15 -0
- package/dist/providers/dashscope.d.ts.map +1 -0
- package/dist/providers/dashscope.js +237 -0
- package/dist/providers/dashscope.js.map +1 -0
- package/dist/providers/deepseek.d.ts +10 -0
- package/dist/providers/deepseek.d.ts.map +1 -0
- package/dist/providers/deepseek.js +57 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/index.d.ts +33 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +142 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/kimi.d.ts +10 -0
- package/dist/providers/kimi.d.ts.map +1 -0
- package/dist/providers/kimi.js +83 -0
- package/dist/providers/kimi.js.map +1 -0
- package/dist/providers/minimax.d.ts +20 -0
- package/dist/providers/minimax.d.ts.map +1 -0
- package/dist/providers/minimax.js +252 -0
- package/dist/providers/minimax.js.map +1 -0
- package/dist/providers/modelscope.d.ts +15 -0
- package/dist/providers/modelscope.d.ts.map +1 -0
- package/dist/providers/modelscope.js +237 -0
- package/dist/providers/modelscope.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +57 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -0
- package/dist/providers/openai-compatible.js +193 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/providers/stepfun.d.ts +10 -0
- package/dist/providers/stepfun.d.ts.map +1 -0
- package/dist/providers/stepfun.js +125 -0
- package/dist/providers/stepfun.js.map +1 -0
- package/dist/providers/zhipu.d.ts +15 -0
- package/dist/providers/zhipu.d.ts.map +1 -0
- package/dist/providers/zhipu.js +247 -0
- package/dist/providers/zhipu.js.map +1 -0
- package/dist/sessions/index.d.ts +6 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +6 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/store.d.ts +45 -0
- package/dist/sessions/store.d.ts.map +1 -0
- package/dist/sessions/store.js +268 -0
- package/dist/sessions/store.js.map +1 -0
- package/dist/sessions/types.d.ts +110 -0
- package/dist/sessions/types.d.ts.map +1 -0
- package/dist/sessions/types.js +6 -0
- package/dist/sessions/types.js.map +1 -0
- package/dist/tools/builtin/apply-patch.d.ts +8 -0
- package/dist/tools/builtin/apply-patch.d.ts.map +1 -0
- package/dist/tools/builtin/apply-patch.js +272 -0
- package/dist/tools/builtin/apply-patch.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +25 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +212 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/browser.d.ts +12 -0
- package/dist/tools/builtin/browser.d.ts.map +1 -0
- package/dist/tools/builtin/browser.js +693 -0
- package/dist/tools/builtin/browser.js.map +1 -0
- package/dist/tools/builtin/filesystem.d.ts +29 -0
- package/dist/tools/builtin/filesystem.d.ts.map +1 -0
- package/dist/tools/builtin/filesystem.js +484 -0
- package/dist/tools/builtin/filesystem.js.map +1 -0
- package/dist/tools/builtin/image.d.ts +13 -0
- package/dist/tools/builtin/image.d.ts.map +1 -0
- package/dist/tools/builtin/image.js +126 -0
- package/dist/tools/builtin/image.js.map +1 -0
- package/dist/tools/builtin/index.d.ts +30 -0
- package/dist/tools/builtin/index.d.ts.map +1 -0
- package/dist/tools/builtin/index.js +55 -0
- package/dist/tools/builtin/index.js.map +1 -0
- package/dist/tools/builtin/process-registry.d.ts +71 -0
- package/dist/tools/builtin/process-registry.d.ts.map +1 -0
- package/dist/tools/builtin/process-registry.js +172 -0
- package/dist/tools/builtin/process-registry.js.map +1 -0
- package/dist/tools/builtin/process-tool.d.ts +8 -0
- package/dist/tools/builtin/process-tool.d.ts.map +1 -0
- package/dist/tools/builtin/process-tool.js +279 -0
- package/dist/tools/builtin/process-tool.js.map +1 -0
- package/dist/tools/builtin/subagent.d.ts +33 -0
- package/dist/tools/builtin/subagent.d.ts.map +1 -0
- package/dist/tools/builtin/subagent.js +185 -0
- package/dist/tools/builtin/subagent.js.map +1 -0
- package/dist/tools/builtin/system.d.ts +11 -0
- package/dist/tools/builtin/system.d.ts.map +1 -0
- package/dist/tools/builtin/system.js +144 -0
- package/dist/tools/builtin/system.js.map +1 -0
- package/dist/tools/builtin/web.d.ts +9 -0
- package/dist/tools/builtin/web.d.ts.map +1 -0
- package/dist/tools/builtin/web.js +87 -0
- package/dist/tools/builtin/web.js.map +1 -0
- package/dist/tools/common.d.ts +45 -0
- package/dist/tools/common.d.ts.map +1 -0
- package/dist/tools/common.js +129 -0
- package/dist/tools/common.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +33 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +174 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +58 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +11 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/types/index.d.ts +280 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +34 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +35 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +114 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +94 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/web/index.d.ts +7 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +7 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/static.d.ts +12 -0
- package/dist/web/static.d.ts.map +1 -0
- package/dist/web/static.js +1260 -0
- package/dist/web/static.js.map +1 -0
- package/dist/web/types.d.ts +95 -0
- package/dist/web/types.d.ts.map +1 -0
- package/dist/web/types.js +5 -0
- package/dist/web/types.js.map +1 -0
- package/dist/web/websocket.d.ts +58 -0
- package/dist/web/websocket.d.ts.map +1 -0
- package/dist/web/websocket.js +371 -0
- package/dist/web/websocket.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent - 消息处理核心 (增强版)
|
|
3
|
+
* 支持 OpenAI 原生 function calling 和 tool 消息格式
|
|
4
|
+
*/
|
|
5
|
+
import { getProvider, findProviderForModel } from "../providers/index.js";
|
|
6
|
+
import { getChildLogger } from "../utils/logger.js";
|
|
7
|
+
import { generateId } from "../utils/index.js";
|
|
8
|
+
import { registerTools, getAllTools, filterToolsByPolicy, executeToolCalls, createBuiltinTools, } from "../tools/index.js";
|
|
9
|
+
import { estimateMessagesTokens, summarizeInStages, limitHistoryTurns, pruneHistoryForContextShare, } from "./compaction.js";
|
|
10
|
+
import { runWithModelFallback } from "./model-fallback.js";
|
|
11
|
+
import { buildSystemPrompt } from "./system-prompt.js";
|
|
12
|
+
import { createSessionStore } from "./session-store.js";
|
|
13
|
+
const logger = getChildLogger("agent");
|
|
14
|
+
// ============== Agent 类 ==============
|
|
15
|
+
/** Agent 类 */
|
|
16
|
+
export class Agent {
|
|
17
|
+
options;
|
|
18
|
+
tools = [];
|
|
19
|
+
openaiTools = [];
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = {
|
|
22
|
+
model: options.model,
|
|
23
|
+
provider: options.provider ?? "deepseek",
|
|
24
|
+
systemPrompt: options.systemPrompt ?? "",
|
|
25
|
+
temperature: options.temperature ?? 0.7,
|
|
26
|
+
maxTokens: options.maxTokens ?? 4096,
|
|
27
|
+
maxHistoryMessages: options.maxHistoryMessages ?? 50,
|
|
28
|
+
maxHistoryTurns: options.maxHistoryTurns ?? 20,
|
|
29
|
+
contextWindow: options.contextWindow ?? 32000,
|
|
30
|
+
enableTools: options.enableTools ?? true,
|
|
31
|
+
toolPolicy: options.toolPolicy ?? {},
|
|
32
|
+
enableCompaction: options.enableCompaction ?? true,
|
|
33
|
+
compactionThreshold: options.compactionThreshold ?? 64000,
|
|
34
|
+
fallbacks: options.fallbacks ?? [],
|
|
35
|
+
maxToolRounds: options.maxToolRounds ?? 10,
|
|
36
|
+
workingDirectory: options.workingDirectory ?? process.cwd(),
|
|
37
|
+
enableFunctionCalling: options.enableFunctionCalling ?? true,
|
|
38
|
+
sessionStore: options.sessionStore ?? createSessionStore(),
|
|
39
|
+
};
|
|
40
|
+
// 初始化工具
|
|
41
|
+
if (this.options.enableTools) {
|
|
42
|
+
this.initializeTools();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** 初始化工具 */
|
|
46
|
+
initializeTools() {
|
|
47
|
+
const builtinTools = createBuiltinTools({
|
|
48
|
+
filesystem: { allowedPaths: [this.options.workingDirectory] },
|
|
49
|
+
bash: { allowedPaths: [this.options.workingDirectory] },
|
|
50
|
+
enableBrowser: true,
|
|
51
|
+
});
|
|
52
|
+
registerTools(builtinTools);
|
|
53
|
+
this.tools = filterToolsByPolicy(getAllTools(), this.options.toolPolicy);
|
|
54
|
+
// 转换为 OpenAI 格式
|
|
55
|
+
this.openaiTools = this.tools.map((tool) => ({
|
|
56
|
+
type: "function",
|
|
57
|
+
function: {
|
|
58
|
+
name: tool.name,
|
|
59
|
+
description: tool.description,
|
|
60
|
+
parameters: tool.parameters,
|
|
61
|
+
},
|
|
62
|
+
}));
|
|
63
|
+
logger.info({ toolCount: this.tools.length }, "Tools initialized");
|
|
64
|
+
}
|
|
65
|
+
/** 注册自定义工具 */
|
|
66
|
+
registerTool(tool) {
|
|
67
|
+
registerTools([tool]);
|
|
68
|
+
this.tools = filterToolsByPolicy(getAllTools(), this.options.toolPolicy);
|
|
69
|
+
this.openaiTools = this.tools.map((t) => ({
|
|
70
|
+
type: "function",
|
|
71
|
+
function: {
|
|
72
|
+
name: t.name,
|
|
73
|
+
description: t.description,
|
|
74
|
+
parameters: t.parameters,
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
/** 处理消息 */
|
|
79
|
+
async processMessage(context) {
|
|
80
|
+
const sessionKey = this.getSessionKey(context);
|
|
81
|
+
logger.debug({ sessionKey, content: context.content.slice(0, 100) }, "Processing message");
|
|
82
|
+
// 获取会话历史
|
|
83
|
+
const history = this.getSessionHistory(sessionKey);
|
|
84
|
+
// 添加用户消息
|
|
85
|
+
history.messages.push({
|
|
86
|
+
role: "user",
|
|
87
|
+
content: context.content,
|
|
88
|
+
});
|
|
89
|
+
// 检查是否需要压缩
|
|
90
|
+
if (this.options.enableCompaction) {
|
|
91
|
+
await this.maybeCompactHistory(history);
|
|
92
|
+
}
|
|
93
|
+
// 构建请求消息
|
|
94
|
+
const messages = this.buildMessages(history);
|
|
95
|
+
// 执行对话 (可能包含多轮工具调用)
|
|
96
|
+
const response = await this.executeWithTools(messages, history);
|
|
97
|
+
// 添加助手回复到历史
|
|
98
|
+
history.messages.push({
|
|
99
|
+
role: "assistant",
|
|
100
|
+
content: response.content,
|
|
101
|
+
});
|
|
102
|
+
// 更新会话
|
|
103
|
+
history.lastUpdate = Date.now();
|
|
104
|
+
history.totalTokensUsed += response.usage?.totalTokens ?? 0;
|
|
105
|
+
this.trimHistory(history);
|
|
106
|
+
this.options.sessionStore.set(sessionKey, history);
|
|
107
|
+
logger.debug({ sessionKey, usage: response.usage }, "Message processed");
|
|
108
|
+
return response;
|
|
109
|
+
}
|
|
110
|
+
/** 执行对话 (包含原生 function calling) */
|
|
111
|
+
async executeWithTools(messages, history) {
|
|
112
|
+
let currentMessages = [...messages];
|
|
113
|
+
const allToolCalls = [];
|
|
114
|
+
let totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
115
|
+
let fallbackAttempts;
|
|
116
|
+
for (let round = 0; round < this.options.maxToolRounds; round++) {
|
|
117
|
+
// 调用模型
|
|
118
|
+
const result = await runWithModelFallback({
|
|
119
|
+
provider: this.options.provider,
|
|
120
|
+
model: this.options.model,
|
|
121
|
+
fallbacks: this.options.fallbacks,
|
|
122
|
+
run: async (provider, model) => {
|
|
123
|
+
const p = getProvider(provider);
|
|
124
|
+
if (!p)
|
|
125
|
+
throw new Error(`Provider not found: ${provider}`);
|
|
126
|
+
const request = {
|
|
127
|
+
model,
|
|
128
|
+
messages: currentMessages,
|
|
129
|
+
temperature: this.options.temperature,
|
|
130
|
+
maxTokens: this.options.maxTokens,
|
|
131
|
+
};
|
|
132
|
+
// 添加工具定义 (如果启用原生 function calling)
|
|
133
|
+
if (this.options.enableFunctionCalling && this.openaiTools.length > 0) {
|
|
134
|
+
request.tools = this.openaiTools;
|
|
135
|
+
request.tool_choice = "auto";
|
|
136
|
+
}
|
|
137
|
+
return p.chat(request);
|
|
138
|
+
},
|
|
139
|
+
onError: (attempt) => {
|
|
140
|
+
logger.warn({ ...attempt }, "Model call failed");
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
fallbackAttempts = result.attempts.length > 0 ? result.attempts : undefined;
|
|
144
|
+
// 累计 token 使用
|
|
145
|
+
if (result.result.usage) {
|
|
146
|
+
totalUsage.promptTokens += result.result.usage.promptTokens;
|
|
147
|
+
totalUsage.completionTokens += result.result.usage.completionTokens;
|
|
148
|
+
totalUsage.totalTokens += result.result.usage.totalTokens;
|
|
149
|
+
}
|
|
150
|
+
// 检查是否有工具调用
|
|
151
|
+
const toolCalls = result.result.toolCalls;
|
|
152
|
+
if (!toolCalls || toolCalls.length === 0) {
|
|
153
|
+
// 没有工具调用,返回结果
|
|
154
|
+
return {
|
|
155
|
+
content: result.result.content,
|
|
156
|
+
toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
|
|
157
|
+
usage: totalUsage,
|
|
158
|
+
provider: result.provider,
|
|
159
|
+
model: result.model,
|
|
160
|
+
fallbackAttempts,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// 添加 assistant 消息 (包含 tool_calls)
|
|
164
|
+
const assistantMessage = {
|
|
165
|
+
role: "assistant",
|
|
166
|
+
content: result.result.content || null,
|
|
167
|
+
tool_calls: toolCalls,
|
|
168
|
+
};
|
|
169
|
+
currentMessages.push(assistantMessage);
|
|
170
|
+
history.messages.push(assistantMessage);
|
|
171
|
+
// 执行工具调用
|
|
172
|
+
logger.debug({ toolCount: toolCalls.length, round }, "Executing tool calls");
|
|
173
|
+
const toolCallInputs = toolCalls.map((tc) => ({
|
|
174
|
+
id: tc.id,
|
|
175
|
+
name: tc.function.name,
|
|
176
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
177
|
+
}));
|
|
178
|
+
const toolResults = await executeToolCalls(toolCallInputs);
|
|
179
|
+
allToolCalls.push(...toolResults);
|
|
180
|
+
// 添加 tool 消息 (每个工具调用一个)
|
|
181
|
+
for (const tr of toolResults) {
|
|
182
|
+
const toolMessage = {
|
|
183
|
+
role: "tool",
|
|
184
|
+
content: this.formatToolResult(tr),
|
|
185
|
+
tool_call_id: tr.toolCallId,
|
|
186
|
+
name: tr.name,
|
|
187
|
+
};
|
|
188
|
+
currentMessages.push(toolMessage);
|
|
189
|
+
history.messages.push(toolMessage);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
throw new Error("Max tool rounds exceeded");
|
|
193
|
+
}
|
|
194
|
+
/** 解析工具参数 */
|
|
195
|
+
parseToolArguments(argsStr) {
|
|
196
|
+
// 处理空字符串或只有空白的情况 - 返回空对象
|
|
197
|
+
if (!argsStr || argsStr.trim() === "" || argsStr.trim() === "{}") {
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const parsed = JSON.parse(argsStr);
|
|
202
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
203
|
+
logger.warn({ argsStr }, "Tool arguments is not an object");
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
return parsed;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
// 尝试修复常见的 JSON 格式问题
|
|
210
|
+
logger.warn({ argsStr, error }, "Failed to parse tool arguments, attempting repair");
|
|
211
|
+
// 尝试修复常见问题:
|
|
212
|
+
// 1. 尾部多余逗号
|
|
213
|
+
// 2. 单引号替换为双引号
|
|
214
|
+
// 3. 未转义的换行符
|
|
215
|
+
// 4. 多个 JSON 对象拼接在一起 (claude-code-router bug workaround)
|
|
216
|
+
let repaired = argsStr
|
|
217
|
+
.replace(/,\s*}/g, "}")
|
|
218
|
+
.replace(/,\s*]/g, "]")
|
|
219
|
+
.replace(/'/g, '"')
|
|
220
|
+
.replace(/\n/g, "\\n")
|
|
221
|
+
.replace(/\r/g, "\\r")
|
|
222
|
+
.replace(/\t/g, "\\t");
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(repaired);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// 检测是否是多个 JSON 对象拼接在一起(如 {...}{...})
|
|
228
|
+
// 这是 claude-code-router 的一个 bug,它会把多个工具调用的参数发送到同一个 content block
|
|
229
|
+
const multiJsonMatch = repaired.match(/^(\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\})/);
|
|
230
|
+
if (multiJsonMatch && multiJsonMatch[1]) {
|
|
231
|
+
const firstJson = multiJsonMatch[1];
|
|
232
|
+
try {
|
|
233
|
+
logger.warn({ original: argsStr, extracted: firstJson }, "Detected concatenated JSON objects (claude-code-router bug), using first object");
|
|
234
|
+
return JSON.parse(firstJson);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// 继续尝试其他方法
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// 尝试提取第一个完整的 JSON 对象(简单的括号匹配)
|
|
241
|
+
let depth = 0;
|
|
242
|
+
let firstJsonEnd = -1;
|
|
243
|
+
for (let i = 0; i < repaired.length; i++) {
|
|
244
|
+
if (repaired[i] === "{")
|
|
245
|
+
depth++;
|
|
246
|
+
else if (repaired[i] === "}") {
|
|
247
|
+
depth--;
|
|
248
|
+
if (depth === 0) {
|
|
249
|
+
firstJsonEnd = i;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (firstJsonEnd > 0) {
|
|
255
|
+
const firstJson = repaired.slice(0, firstJsonEnd + 1);
|
|
256
|
+
try {
|
|
257
|
+
const parsed = JSON.parse(firstJson);
|
|
258
|
+
logger.warn({ original: argsStr, extracted: firstJson }, "Extracted first JSON object from concatenated string");
|
|
259
|
+
return parsed;
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// 仍然失败
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
logger.error({ argsStr, repaired }, "Failed to parse tool arguments after repair");
|
|
266
|
+
return {};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** 格式化工具结果为字符串 */
|
|
271
|
+
formatToolResult(result) {
|
|
272
|
+
const content = result.result.content
|
|
273
|
+
.filter((c) => c.type === "text")
|
|
274
|
+
.map((c) => c.text)
|
|
275
|
+
.join("\n");
|
|
276
|
+
if (result.isError) {
|
|
277
|
+
return `Error: ${content}`;
|
|
278
|
+
}
|
|
279
|
+
return content;
|
|
280
|
+
}
|
|
281
|
+
/** 获取工具参数预览 (Claude Code 风格) */
|
|
282
|
+
getToolArgsPreview(args) {
|
|
283
|
+
if (!args || Object.keys(args).length === 0)
|
|
284
|
+
return "";
|
|
285
|
+
// 常见工具的主参数提取
|
|
286
|
+
const mainArg = args.path ?? args.directory ?? args.command ?? args.query ?? args.pattern ?? args.content;
|
|
287
|
+
if (typeof mainArg === "string") {
|
|
288
|
+
const preview = mainArg.replace(/\n/g, " ").trim();
|
|
289
|
+
return preview.length > 40 ? preview.slice(0, 40) + "…" : preview;
|
|
290
|
+
}
|
|
291
|
+
// 其他情况显示简化的参数
|
|
292
|
+
const firstKey = Object.keys(args)[0];
|
|
293
|
+
if (firstKey) {
|
|
294
|
+
const firstVal = args[firstKey];
|
|
295
|
+
if (typeof firstVal === "string") {
|
|
296
|
+
const preview = firstVal.replace(/\n/g, " ").trim();
|
|
297
|
+
return preview.length > 30 ? preview.slice(0, 30) + "…" : preview;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return "";
|
|
301
|
+
}
|
|
302
|
+
/** 获取错误信息预览 */
|
|
303
|
+
getErrorPreview(result) {
|
|
304
|
+
const content = result.result.content
|
|
305
|
+
.filter((c) => c.type === "text")
|
|
306
|
+
.map((c) => c.text)
|
|
307
|
+
.join(" ");
|
|
308
|
+
// 提取第一行错误信息
|
|
309
|
+
const firstLine = content.split("\n")[0]?.trim() ?? "Unknown error";
|
|
310
|
+
return firstLine.length > 50 ? firstLine.slice(0, 50) + "…" : firstLine;
|
|
311
|
+
}
|
|
312
|
+
/** 流式处理消息 (支持原生 function calling) */
|
|
313
|
+
async *processMessageStream(context) {
|
|
314
|
+
const sessionKey = this.getSessionKey(context);
|
|
315
|
+
logger.debug({ sessionKey, content: context.content.slice(0, 100) }, "Processing message (stream)");
|
|
316
|
+
// 获取会话历史
|
|
317
|
+
const history = this.getSessionHistory(sessionKey);
|
|
318
|
+
// 添加用户消息
|
|
319
|
+
history.messages.push({
|
|
320
|
+
role: "user",
|
|
321
|
+
content: context.content,
|
|
322
|
+
});
|
|
323
|
+
// 检查是否需要压缩
|
|
324
|
+
if (this.options.enableCompaction) {
|
|
325
|
+
await this.maybeCompactHistory(history);
|
|
326
|
+
}
|
|
327
|
+
// 构建请求消息
|
|
328
|
+
let currentMessages = this.buildMessages(history);
|
|
329
|
+
// 获取提供商 (先按 provider ID 查找,再按 model ID 查找)
|
|
330
|
+
const provider = (this.options.provider
|
|
331
|
+
? getProvider(this.options.provider)
|
|
332
|
+
: undefined) ?? findProviderForModel(this.options.model);
|
|
333
|
+
if (!provider) {
|
|
334
|
+
throw new Error(`No provider found for model: ${this.options.model}`);
|
|
335
|
+
}
|
|
336
|
+
let fullContent = "";
|
|
337
|
+
let totalTokens = 0;
|
|
338
|
+
const allToolCalls = [];
|
|
339
|
+
// 工具调用循环
|
|
340
|
+
for (let round = 0; round < this.options.maxToolRounds; round++) {
|
|
341
|
+
let roundContent = "";
|
|
342
|
+
const pendingToolCalls = new Map();
|
|
343
|
+
// 构建请求
|
|
344
|
+
const request = {
|
|
345
|
+
model: this.options.model,
|
|
346
|
+
messages: currentMessages,
|
|
347
|
+
temperature: this.options.temperature,
|
|
348
|
+
maxTokens: this.options.maxTokens,
|
|
349
|
+
};
|
|
350
|
+
if (this.options.enableFunctionCalling && this.openaiTools.length > 0) {
|
|
351
|
+
request.tools = this.openaiTools;
|
|
352
|
+
request.tool_choice = "auto";
|
|
353
|
+
}
|
|
354
|
+
// 流式调用
|
|
355
|
+
for await (const chunk of provider.chatStream(request)) {
|
|
356
|
+
// 处理文本内容
|
|
357
|
+
if (chunk.delta) {
|
|
358
|
+
roundContent += chunk.delta;
|
|
359
|
+
yield chunk.delta;
|
|
360
|
+
}
|
|
361
|
+
// 处理工具调用增量
|
|
362
|
+
if (chunk.toolCallDeltas) {
|
|
363
|
+
for (const delta of chunk.toolCallDeltas) {
|
|
364
|
+
// 使用 id 作为 key (优先),fallback 到 index
|
|
365
|
+
const key = delta.id ?? `idx_${delta.index}`;
|
|
366
|
+
let tc = pendingToolCalls.get(key);
|
|
367
|
+
if (!tc) {
|
|
368
|
+
tc = {
|
|
369
|
+
id: delta.id ?? generateId("tc"),
|
|
370
|
+
type: "function",
|
|
371
|
+
function: { name: "", arguments: "" },
|
|
372
|
+
};
|
|
373
|
+
pendingToolCalls.set(key, tc);
|
|
374
|
+
}
|
|
375
|
+
if (delta.id && tc.id !== delta.id)
|
|
376
|
+
tc.id = delta.id;
|
|
377
|
+
// 只在名称为空时设置,避免重复累加
|
|
378
|
+
if (delta.function?.name && !tc.function.name)
|
|
379
|
+
tc.function.name = delta.function.name;
|
|
380
|
+
if (delta.function?.arguments)
|
|
381
|
+
tc.function.arguments += delta.function.arguments;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
fullContent += roundContent;
|
|
386
|
+
// 检查是否有完整的工具调用
|
|
387
|
+
const completedToolCalls = Array.from(pendingToolCalls.values()).filter((tc) => tc.function.name // 只要有函数名就认为有效,arguments 可以为空
|
|
388
|
+
);
|
|
389
|
+
// 调试日志
|
|
390
|
+
if (pendingToolCalls.size > 0) {
|
|
391
|
+
logger.debug({
|
|
392
|
+
pendingCount: pendingToolCalls.size,
|
|
393
|
+
completedCount: completedToolCalls.length,
|
|
394
|
+
toolCalls: completedToolCalls.map((tc) => ({
|
|
395
|
+
name: tc.function.name,
|
|
396
|
+
argsLength: tc.function.arguments.length,
|
|
397
|
+
argsPreview: tc.function.arguments.slice(0, 100),
|
|
398
|
+
})),
|
|
399
|
+
}, "Tool calls accumulated");
|
|
400
|
+
}
|
|
401
|
+
if (completedToolCalls.length === 0) {
|
|
402
|
+
// 没有工具调用,结束循环
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
// 添加 assistant 消息
|
|
406
|
+
const assistantMessage = {
|
|
407
|
+
role: "assistant",
|
|
408
|
+
content: roundContent || null,
|
|
409
|
+
tool_calls: completedToolCalls,
|
|
410
|
+
};
|
|
411
|
+
currentMessages.push(assistantMessage);
|
|
412
|
+
history.messages.push(assistantMessage);
|
|
413
|
+
// 执行工具调用
|
|
414
|
+
const toolCallInputs = completedToolCalls.map((tc) => ({
|
|
415
|
+
id: tc.id,
|
|
416
|
+
name: tc.function.name,
|
|
417
|
+
arguments: this.parseToolArguments(tc.function.arguments),
|
|
418
|
+
}));
|
|
419
|
+
const toolResults = await executeToolCalls(toolCallInputs);
|
|
420
|
+
allToolCalls.push(...toolResults);
|
|
421
|
+
// 添加 tool 消息并输出结果摘要 (Claude Code 风格)
|
|
422
|
+
for (const tr of toolResults) {
|
|
423
|
+
const toolMessage = {
|
|
424
|
+
role: "tool",
|
|
425
|
+
content: this.formatToolResult(tr),
|
|
426
|
+
tool_call_id: tr.toolCallId,
|
|
427
|
+
name: tr.name,
|
|
428
|
+
};
|
|
429
|
+
currentMessages.push(toolMessage);
|
|
430
|
+
history.messages.push(toolMessage);
|
|
431
|
+
// 简洁的工具输出格式
|
|
432
|
+
const argsPreview = this.getToolArgsPreview(toolCallInputs.find(t => t.id === tr.toolCallId)?.arguments);
|
|
433
|
+
if (tr.isError) {
|
|
434
|
+
const errorMsg = this.getErrorPreview(tr);
|
|
435
|
+
yield `\n⏺ ${tr.name}(${argsPreview}) ✗ ${errorMsg}`;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
yield `\n⏺ ${tr.name}(${argsPreview}) ✓`;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
yield `\n\n`;
|
|
442
|
+
}
|
|
443
|
+
// 估算 token
|
|
444
|
+
totalTokens = estimateMessagesTokens(currentMessages) + estimateMessagesTokens([
|
|
445
|
+
{ role: "assistant", content: fullContent }
|
|
446
|
+
]);
|
|
447
|
+
// 添加完整回复到历史
|
|
448
|
+
if (!history.messages.some(m => m.role === "assistant" && m.content === fullContent)) {
|
|
449
|
+
history.messages.push({
|
|
450
|
+
role: "assistant",
|
|
451
|
+
content: fullContent,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
// 更新会话
|
|
455
|
+
history.lastUpdate = Date.now();
|
|
456
|
+
history.totalTokensUsed += totalTokens;
|
|
457
|
+
this.trimHistory(history);
|
|
458
|
+
this.options.sessionStore.set(sessionKey, history);
|
|
459
|
+
return {
|
|
460
|
+
content: fullContent,
|
|
461
|
+
toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
|
|
462
|
+
usage: {
|
|
463
|
+
promptTokens: estimateMessagesTokens(currentMessages),
|
|
464
|
+
completionTokens: estimateMessagesTokens([{ role: "assistant", content: fullContent }]),
|
|
465
|
+
totalTokens,
|
|
466
|
+
},
|
|
467
|
+
provider: provider.id,
|
|
468
|
+
model: this.options.model,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/** 获取会话键 */
|
|
472
|
+
getSessionKey(context) {
|
|
473
|
+
if (context.chatType === "group") {
|
|
474
|
+
return `${context.channelId}:${context.chatId}`;
|
|
475
|
+
}
|
|
476
|
+
return `${context.channelId}:${context.senderId}`;
|
|
477
|
+
}
|
|
478
|
+
/** 获取会话历史 */
|
|
479
|
+
getSessionHistory(sessionKey) {
|
|
480
|
+
const cached = this.options.sessionStore.get(sessionKey);
|
|
481
|
+
if (cached) {
|
|
482
|
+
return cached;
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
messages: [],
|
|
486
|
+
lastUpdate: Date.now(),
|
|
487
|
+
totalTokensUsed: 0,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/** 构建消息列表 */
|
|
491
|
+
buildMessages(history) {
|
|
492
|
+
const messages = [];
|
|
493
|
+
// 构建系统提示
|
|
494
|
+
const systemContent = buildSystemPrompt({
|
|
495
|
+
basePrompt: this.options.systemPrompt,
|
|
496
|
+
workingDirectory: this.options.workingDirectory,
|
|
497
|
+
includeEnvironment: true,
|
|
498
|
+
includeDateTime: true,
|
|
499
|
+
includeToolRules: !this.options.enableFunctionCalling, // 使用原生 FC 时不需要文本规则
|
|
500
|
+
tools: this.options.enableFunctionCalling ? undefined : this.tools,
|
|
501
|
+
additionalContext: history.summary,
|
|
502
|
+
});
|
|
503
|
+
messages.push({ role: "system", content: systemContent });
|
|
504
|
+
// 验证并清理历史消息,确保 tool_calls 和 tool 消息配对
|
|
505
|
+
const validatedMessages = this.validateToolCallPairs(history.messages);
|
|
506
|
+
// 添加历史消息
|
|
507
|
+
messages.push(...validatedMessages);
|
|
508
|
+
return messages;
|
|
509
|
+
}
|
|
510
|
+
/** 验证 tool_calls 和 tool 消息配对,清理不完整的工具调用 */
|
|
511
|
+
validateToolCallPairs(messages) {
|
|
512
|
+
// 第一遍:收集所有有效的 tool_call_id
|
|
513
|
+
const validToolCallIds = new Set();
|
|
514
|
+
for (const msg of messages) {
|
|
515
|
+
if (msg.role === "assistant" && msg.tool_calls) {
|
|
516
|
+
for (const tc of msg.tool_calls) {
|
|
517
|
+
validToolCallIds.add(tc.id);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const result = [];
|
|
522
|
+
let i = 0;
|
|
523
|
+
while (i < messages.length) {
|
|
524
|
+
const msg = messages[i];
|
|
525
|
+
// 如果是 tool 消息,检查是否有对应的 tool_call
|
|
526
|
+
if (msg.role === "tool") {
|
|
527
|
+
if (msg.tool_call_id && validToolCallIds.has(msg.tool_call_id)) {
|
|
528
|
+
result.push(msg);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
// 孤立的 tool_result,跳过
|
|
532
|
+
logger.warn({ orphanToolCallId: msg.tool_call_id, messageIndex: i }, "Skipping orphan tool_result message");
|
|
533
|
+
}
|
|
534
|
+
i++;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
// 如果是包含 tool_calls 的 assistant 消息
|
|
538
|
+
if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
539
|
+
const toolCallIds = new Set(msg.tool_calls.map(tc => tc.id));
|
|
540
|
+
const toolResults = [];
|
|
541
|
+
// 查找后续的 tool 消息
|
|
542
|
+
let j = i + 1;
|
|
543
|
+
while (j < messages.length && messages[j]?.role === "tool") {
|
|
544
|
+
const toolMsg = messages[j];
|
|
545
|
+
if (toolMsg.tool_call_id && toolCallIds.has(toolMsg.tool_call_id)) {
|
|
546
|
+
toolResults.push(toolMsg);
|
|
547
|
+
toolCallIds.delete(toolMsg.tool_call_id);
|
|
548
|
+
}
|
|
549
|
+
j++;
|
|
550
|
+
}
|
|
551
|
+
// 检查是否所有 tool_calls 都有对应的 tool 结果
|
|
552
|
+
if (toolCallIds.size === 0) {
|
|
553
|
+
// 完整配对,添加 assistant 和所有 tool 消息
|
|
554
|
+
result.push(msg);
|
|
555
|
+
result.push(...toolResults);
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
// 不完整的工具调用,跳过整个 assistant 消息和相关 tool 消息
|
|
559
|
+
logger.warn({ missingToolResults: Array.from(toolCallIds), messageIndex: i }, "Skipping incomplete tool call sequence");
|
|
560
|
+
// 如果 assistant 消息有文本内容,保留文本内容但移除 tool_calls
|
|
561
|
+
if (msg.content) {
|
|
562
|
+
result.push({
|
|
563
|
+
role: "assistant",
|
|
564
|
+
content: msg.content,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
// 跳到 tool 消息之后
|
|
569
|
+
i = j;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
// 普通消息直接添加
|
|
573
|
+
result.push(msg);
|
|
574
|
+
i++;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
579
|
+
/** 裁剪历史消息 */
|
|
580
|
+
trimHistory(history) {
|
|
581
|
+
// 按轮次限制
|
|
582
|
+
history.messages = limitHistoryTurns({
|
|
583
|
+
messages: history.messages,
|
|
584
|
+
maxTurns: this.options.maxHistoryTurns,
|
|
585
|
+
preserveSystemMessage: false, // 系统消息在 buildMessages 中单独处理
|
|
586
|
+
});
|
|
587
|
+
// 按消息数限制
|
|
588
|
+
if (history.messages.length > this.options.maxHistoryMessages) {
|
|
589
|
+
history.messages = history.messages.slice(-this.options.maxHistoryMessages);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/** 检查并执行上下文压缩 */
|
|
593
|
+
async maybeCompactHistory(history) {
|
|
594
|
+
const tokens = estimateMessagesTokens(history.messages);
|
|
595
|
+
if (tokens <= this.options.compactionThreshold) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
logger.info({ tokens, threshold: this.options.compactionThreshold }, "Compacting history");
|
|
599
|
+
try {
|
|
600
|
+
// 分离要保留的最近消息和要压缩的旧消息
|
|
601
|
+
const keepRecent = Math.min(10, Math.floor(history.messages.length / 2));
|
|
602
|
+
const toCompact = history.messages.slice(0, -keepRecent);
|
|
603
|
+
const toKeep = history.messages.slice(-keepRecent);
|
|
604
|
+
if (toCompact.length === 0) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
// 生成摘要
|
|
608
|
+
const summary = await summarizeInStages(toCompact, {
|
|
609
|
+
provider: this.options.provider,
|
|
610
|
+
model: this.options.model,
|
|
611
|
+
previousSummary: history.summary,
|
|
612
|
+
maxChunkTokens: 4000,
|
|
613
|
+
contextWindow: this.options.contextWindow,
|
|
614
|
+
});
|
|
615
|
+
// 更新历史
|
|
616
|
+
history.summary = summary;
|
|
617
|
+
history.messages = toKeep;
|
|
618
|
+
logger.info({ compacted: toCompact.length, kept: toKeep.length, summaryLength: summary.length }, "History compacted");
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
logger.error({ error }, "Failed to compact history");
|
|
622
|
+
// 压缩失败,回退到简单裁剪
|
|
623
|
+
const pruned = pruneHistoryForContextShare({
|
|
624
|
+
messages: history.messages,
|
|
625
|
+
maxContextTokens: this.options.contextWindow,
|
|
626
|
+
maxHistoryShare: 0.5,
|
|
627
|
+
});
|
|
628
|
+
history.messages = pruned.messages;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/** 清除会话 */
|
|
632
|
+
clearSession(context) {
|
|
633
|
+
const sessionKey = this.getSessionKey(context);
|
|
634
|
+
this.options.sessionStore.delete(sessionKey);
|
|
635
|
+
logger.debug({ sessionKey }, "Session cleared");
|
|
636
|
+
}
|
|
637
|
+
/** 获取会话信息 */
|
|
638
|
+
getSessionInfo(context) {
|
|
639
|
+
const sessionKey = this.getSessionKey(context);
|
|
640
|
+
const history = this.options.sessionStore.get(sessionKey);
|
|
641
|
+
if (!history)
|
|
642
|
+
return null;
|
|
643
|
+
return {
|
|
644
|
+
messageCount: history.messages.length,
|
|
645
|
+
estimatedTokens: estimateMessagesTokens(history.messages),
|
|
646
|
+
hasSummary: !!history.summary,
|
|
647
|
+
lastUpdate: new Date(history.lastUpdate),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
/** 恢复会话历史(从 transcript 消息重建 Agent 上下文) */
|
|
651
|
+
restoreSessionFromTranscript(sessionKey, messages) {
|
|
652
|
+
// 检查是否已有会话(如果服务没有重启,可能还在内存中)
|
|
653
|
+
const existing = this.options.sessionStore.get(sessionKey);
|
|
654
|
+
if (existing && existing.messages.length > 0) {
|
|
655
|
+
logger.debug({ sessionKey, messageCount: existing.messages.length }, "Session already exists, skipping restore");
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
// 将 transcript 消息转换为 ChatMessage 格式
|
|
659
|
+
const chatMessages = messages.map((msg) => ({
|
|
660
|
+
role: msg.role,
|
|
661
|
+
content: msg.content,
|
|
662
|
+
}));
|
|
663
|
+
// 保存到 sessionStore
|
|
664
|
+
const sessionData = {
|
|
665
|
+
messages: chatMessages,
|
|
666
|
+
lastUpdate: Date.now(),
|
|
667
|
+
totalTokensUsed: 0,
|
|
668
|
+
};
|
|
669
|
+
this.options.sessionStore.set(sessionKey, sessionData);
|
|
670
|
+
logger.debug({ sessionKey, messageCount: chatMessages.length }, "Session restored from transcript");
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
/** 创建 Agent */
|
|
674
|
+
export function createAgent(config) {
|
|
675
|
+
return new Agent({
|
|
676
|
+
model: config.agent.defaultModel,
|
|
677
|
+
provider: config.agent.defaultProvider,
|
|
678
|
+
systemPrompt: config.agent.systemPrompt ?? "",
|
|
679
|
+
temperature: config.agent.temperature,
|
|
680
|
+
maxTokens: config.agent.maxTokens,
|
|
681
|
+
workingDirectory: config.agent.workingDirectory ?? process.cwd(),
|
|
682
|
+
enableFunctionCalling: config.agent.enableFunctionCalling ?? true,
|
|
683
|
+
sessionStore: createSessionStore(config.sessions),
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
//# sourceMappingURL=agent.js.map
|