foliko 1.1.93 → 2.0.1
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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +56 -30
- package/REFACTORING_PLAN.md +645 -0
- package/docs/architecture.md +131 -0
- package/docs/migration.md +57 -0
- package/docs/public-api.md +138 -0
- package/docs/usage.md +385 -0
- package/examples/ambient-example.js +20 -137
- package/examples/basic.js +21 -48
- package/examples/bootstrap.js +16 -74
- package/examples/mcp-example.js +6 -29
- package/examples/skill-example.js +6 -19
- package/examples/workflow.js +8 -56
- package/package.json +8 -4
- package/plugins/README.md +49 -0
- package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
- package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
- package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
- package/plugins/ambient/README.md +14 -0
- package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
- package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
- package/plugins/{ambient-agent → ambient}/index.js +2 -2
- package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
- package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
- package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
- package/plugins/core/default/bootstrap.js +224 -0
- package/plugins/core/default/config.js +222 -0
- package/plugins/core/default/index.js +58 -0
- package/plugins/core/mcp/index.js +1 -0
- package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
- package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
- package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
- package/plugins/{session-plugin.js → core/session/index.js} +9 -73
- package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
- package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
- package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
- package/plugins/{think-plugin.js → core/think/index.js} +24 -91
- package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
- package/plugins/default-plugins.js +6 -720
- package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
- package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
- package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
- package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
- package/plugins/install/README.md +9 -0
- package/plugins/{install-plugin.js → install/index.js} +3 -3
- package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
- package/plugins/{web-plugin.js → io/web/index.js} +11 -113
- package/plugins/memory/README.md +13 -0
- package/plugins/{memory-plugin.js → memory/index.js} +4 -18
- package/plugins/messaging/email/README.md +19 -0
- package/plugins/{email → messaging/email}/index.js +3 -3
- package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +4 -4
- package/plugins/{qq-plugin.js → messaging/qq/index.js} +6 -17
- package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +4 -4
- package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +16 -16
- package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
- package/plugins/{tools-plugin.js → tools/index.js} +68 -116
- package/plugins/trading/README.md +15 -0
- package/plugins/{gate-trading.js → trading/index.js} +8 -8
- package/{examples → sandbox}/test-concurrent-chat.js +2 -2
- package/{examples → sandbox}/test-long-chat.js +2 -2
- package/{examples → sandbox}/test-session-chat.js +2 -2
- package/{examples → sandbox}/test-web-plugin.js +1 -1
- package/{examples → sandbox}/test-weixin-feishu.js +2 -2
- package/src/agent/base.js +56 -0
- package/src/{core/agent-chat.js → agent/chat.js} +11 -11
- package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
- package/src/agent/index.js +111 -0
- package/src/agent/main.js +337 -0
- package/src/agent/prompt.js +78 -0
- package/src/agent/sub.js +198 -0
- package/src/agent/worker.js +104 -0
- package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
- package/{cli/src → src/cli}/commands/chat.js +25 -21
- package/{cli/src → src/cli}/index.js +1 -0
- package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
- package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
- package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
- package/src/common/errors.js +402 -0
- package/src/{utils → common}/logger.js +33 -0
- package/src/{utils/chat-queue.js → common/queue.js} +2 -2
- package/src/config/plugin-config.js +50 -0
- package/src/context/agent.js +32 -0
- package/src/context/compaction-prompts.js +170 -0
- package/src/context/compaction-utils.js +191 -0
- package/src/context/compressor.js +413 -0
- package/src/context/index.js +9 -0
- package/src/{core/context-manager.js → context/manager.js} +1 -1
- package/src/context/request.js +50 -0
- package/src/context/session.js +33 -0
- package/src/context/storage.js +30 -0
- package/src/executors/mcp-client.js +153 -0
- package/src/executors/mcp-desc.js +236 -0
- package/src/executors/mcp-executor.js +91 -956
- package/src/{core → framework}/command-registry.js +1 -1
- package/src/framework/framework.js +300 -0
- package/src/framework/index.js +18 -0
- package/src/framework/lifecycle.js +203 -0
- package/src/framework/loader.js +78 -0
- package/src/framework/registry.js +86 -0
- package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
- package/src/index.js +130 -15
- package/src/llm/index.js +26 -0
- package/src/llm/provider.js +212 -0
- package/src/llm/registry.js +11 -0
- package/src/{core/token-counter.js → llm/tokens.js} +4 -37
- package/src/{core/plugin-base.js → plugin/base.js} +10 -136
- package/src/plugin/index.js +14 -0
- package/src/plugin/loader.js +101 -0
- package/src/plugin/manager.js +484 -0
- package/src/{core → session}/branch-summary-auto.js +2 -2
- package/src/{core/chat-session.js → session/chat.js} +2 -2
- package/src/session/index.js +7 -0
- package/src/{core/session-manager.js → session/session.js} +2 -2
- package/src/session/ttl.js +92 -0
- package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
- package/src/tool/executor.js +85 -0
- package/src/tool/index.js +15 -0
- package/src/tool/registry.js +143 -0
- package/src/{core/tool-router.js → tool/router.js} +17 -124
- package/src/tool/schema.js +108 -0
- package/src/utils/data-splitter.js +1 -1
- package/src/utils/download.js +1 -1
- package/src/utils/index.js +6 -6
- package/src/utils/message-validator.js +1 -1
- package/tests/core/context-storage.test.js +46 -0
- package/tests/core/llm.test.js +54 -0
- package/tests/core/plugin.test.js +42 -0
- package/tests/core/tool.test.js +60 -0
- package/tests/setup.js +10 -0
- package/tests/smoke.test.js +58 -0
- package/vitest.config.js +9 -0
- package/cli/src/daemon.js +0 -149
- package/docs/CONTEXT_DESIGN.md +0 -1596
- package/docs/ai-sdk-optimization.md +0 -655
- package/docs/features.md +0 -120
- package/docs/qq-bot.md +0 -976
- package/docs/quick-reference.md +0 -160
- package/docs/user-manual.md +0 -1391
- package/images/geometric_shapes.jpg +0 -0
- package/images/sunset_mountain_lake.jpg +0 -0
- package/skills/poster-guide/SKILL.md +0 -792
- package/src/capabilities/index.js +0 -11
- package/src/core/agent.js +0 -808
- package/src/core/context-compressor.js +0 -959
- package/src/core/enhanced-context-compressor.js +0 -210
- package/src/core/framework.js +0 -1422
- package/src/core/index.js +0 -30
- package/src/core/plugin-manager.js +0 -961
- package/src/core/provider-registry.js +0 -159
- package/src/core/provider.js +0 -156
- package/src/core/request-context.js +0 -98
- package/src/core/subagent.js +0 -442
- package/src/core/system-prompt-builder.js +0 -120
- package/src/core/tool-executor.js +0 -202
- package/src/core/tool-registry.js +0 -517
- package/src/core/worker-agent.js +0 -192
- package/src/executors/executor-base.js +0 -58
- package/src/utils/error-boundary.js +0 -363
- package/src/utils/error.js +0 -374
- package/system.md +0 -1645
- package/website_v2/README.md +0 -57
- package/website_v2/SPEC.md +0 -1
- package/website_v2/docs/api.html +0 -128
- package/website_v2/docs/configuration.html +0 -147
- package/website_v2/docs/plugin-development.html +0 -129
- package/website_v2/docs/project-structure.html +0 -89
- package/website_v2/docs/skill-development.html +0 -85
- package/website_v2/index.html +0 -489
- package/website_v2/scripts/main.js +0 -93
- package/website_v2/styles/animations.css +0 -8
- package/website_v2/styles/docs.css +0 -83
- package/website_v2/styles/main.css +0 -417
- package/xhs_auth.json +0 -268
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
- /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/handlers.js +0 -0
- /package/plugins/{email → messaging/email}/monitor.js +0 -0
- /package/plugins/{email → messaging/email}/parser.js +0 -0
- /package/plugins/{email → messaging/email}/reply.js +0 -0
- /package/plugins/{email → messaging/email}/utils.js +0 -0
- /package/{examples → sandbox}/test-chat.js +0 -0
- /package/{examples → sandbox}/test-mcp.js +0 -0
- /package/{examples → sandbox}/test-reload.js +0 -0
- /package/{examples → sandbox}/test-telegram.js +0 -0
- /package/{examples → sandbox}/test-tg-bot.js +0 -0
- /package/{examples → sandbox}/test-tg-simple.js +0 -0
- /package/{examples → sandbox}/test-tg.js +0 -0
- /package/{examples → sandbox}/test-think.js +0 -0
- /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
- /package/{cli/src → src/cli}/commands/daemon.js +0 -0
- /package/{cli/src → src/cli}/commands/list.js +0 -0
- /package/{cli/src → src/cli}/commands/plugin.js +0 -0
- /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
- /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
- /package/{cli/src → src/cli}/utils/ansi.js +0 -0
- /package/{cli/src → src/cli}/utils/config.js +0 -0
- /package/{cli/src → src/cli}/utils/markdown.js +0 -0
- /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
- /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
- /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
- /package/src/{core → common}/constants.js +0 -0
- /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
- /package/src/{utils/event-emitter.js → common/events.js} +0 -0
- /package/src/{utils → common}/id.js +0 -0
- /package/src/{utils → common}/retry.js +0 -0
- /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
- /package/src/{core/session-entry.js → session/entry.js} +0 -0
- /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compaction utility functions
|
|
5
|
+
* 提取自 compressor.js 的纯函数工具集
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { estimateTokens } = require('../llm/tokens');
|
|
9
|
+
const { safeJsonStringify } = require('../llm/tokens');
|
|
10
|
+
const { EntryTypes, createCustomMessage, createBranchSummaryMessage, createCompactionSummaryMessage } = require('../session/entry');
|
|
11
|
+
|
|
12
|
+
const TOOL_RESULT_MAX_CHARS = 2000;
|
|
13
|
+
|
|
14
|
+
function createFileOps() {
|
|
15
|
+
return { read: new Set(), written: new Set(), edited: new Set() };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function extractFileOpsFromMessage(message, fileOps) {
|
|
19
|
+
if (message.role !== 'assistant') return;
|
|
20
|
+
if (!message.content || !Array.isArray(message.content)) return;
|
|
21
|
+
|
|
22
|
+
for (const block of message.content) {
|
|
23
|
+
if (!block || block.type !== 'toolCall') continue;
|
|
24
|
+
const args = block.arguments;
|
|
25
|
+
if (!args) continue;
|
|
26
|
+
const filePath = typeof args.path === 'string' ? args.path : undefined;
|
|
27
|
+
if (!filePath) continue;
|
|
28
|
+
switch (block.name) {
|
|
29
|
+
case 'read': fileOps.read.add(filePath); break;
|
|
30
|
+
case 'write': fileOps.written.add(filePath); break;
|
|
31
|
+
case 'edit': fileOps.edited.add(filePath); break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function computeFileLists(fileOps) {
|
|
37
|
+
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
|
38
|
+
const readOnly = [...fileOps.read].filter(f => !modified.has(f)).sort();
|
|
39
|
+
const modifiedFiles = [...modified].sort();
|
|
40
|
+
return { readFiles: readOnly, modifiedFiles };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function formatFileOperations(readFiles, modifiedFiles) {
|
|
44
|
+
const sections = [];
|
|
45
|
+
if (readFiles.length > 0) sections.push(`<read-files>\n${readFiles.join('\n')}\n</read-files>`);
|
|
46
|
+
if (modifiedFiles.length > 0) sections.push(`<modified-files>\n${modifiedFiles.join('\n')}\n</modified-files>`);
|
|
47
|
+
if (sections.length === 0) return '';
|
|
48
|
+
return `\n\n${sections.join('\n\n')}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function truncateForSummary(text, maxChars) {
|
|
52
|
+
if (text.length <= maxChars) return text;
|
|
53
|
+
const truncatedChars = text.length - maxChars;
|
|
54
|
+
return `${text.slice(0, maxChars)}\n\n[... ${truncatedChars} more characters truncated]`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function serializeConversation(messages) {
|
|
58
|
+
const parts = [];
|
|
59
|
+
for (const msg of messages) {
|
|
60
|
+
if (msg.role === 'user') {
|
|
61
|
+
const content = Array.isArray(msg.content)
|
|
62
|
+
? msg.content.filter(c => c.type === 'text').map(c => c.text).join('')
|
|
63
|
+
: msg.content;
|
|
64
|
+
if (content) parts.push(`[User]: ${content}`);
|
|
65
|
+
} else if (msg.role === 'assistant') {
|
|
66
|
+
const textParts = []; const thinkingParts = []; const toolCalls = [];
|
|
67
|
+
for (const block of msg.content || []) {
|
|
68
|
+
if (block.type === 'text') textParts.push(block.text);
|
|
69
|
+
else if (block.type === 'thinking') thinkingParts.push(block.thinking);
|
|
70
|
+
else if (block.type === 'toolCall') {
|
|
71
|
+
const argsStr = Object.entries(block.arguments || {})
|
|
72
|
+
.map(([k, v]) => `${k}=${safeJsonStringify(v)}`).join(', ');
|
|
73
|
+
toolCalls.push(`${block.name}(${argsStr})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (thinkingParts.length > 0) parts.push(`[Assistant thinking]: ${thinkingParts.join('\n')}`);
|
|
77
|
+
if (textParts.length > 0) parts.push(`[Assistant]: ${textParts.join('\n')}`);
|
|
78
|
+
if (toolCalls.length > 0) parts.push(`[Assistant tool calls]: ${toolCalls.join('; ')}`);
|
|
79
|
+
} else if (msg.role === 'toolResult') {
|
|
80
|
+
const content = Array.isArray(msg.content)
|
|
81
|
+
? msg.content.filter(c => c.type === 'text').map(c => c.text).join('')
|
|
82
|
+
: msg.content;
|
|
83
|
+
if (content) parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return parts.join('\n\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getMessageFromEntry(entry) {
|
|
90
|
+
switch (entry.type) {
|
|
91
|
+
case EntryTypes.MESSAGE:
|
|
92
|
+
if (entry.message.role === 'toolResult') return undefined;
|
|
93
|
+
return entry.message;
|
|
94
|
+
case EntryTypes.CUSTOM_MESSAGE:
|
|
95
|
+
return createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp);
|
|
96
|
+
case EntryTypes.BRANCH_SUMMARY:
|
|
97
|
+
return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp);
|
|
98
|
+
case EntryTypes.COMPACTION:
|
|
99
|
+
return createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp);
|
|
100
|
+
default:
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getMessageFromEntryForCompaction(entry) {
|
|
106
|
+
if (entry.type === EntryTypes.COMPACTION) return undefined;
|
|
107
|
+
return getMessageFromEntry(entry);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function findValidCutPoints(entries, startIndex, endIndex) {
|
|
111
|
+
const cutPoints = [];
|
|
112
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
113
|
+
const entry = entries[i];
|
|
114
|
+
if (entry.type === EntryTypes.MESSAGE) {
|
|
115
|
+
const role = entry.message.role;
|
|
116
|
+
switch (role) {
|
|
117
|
+
case 'bashExecution': case 'custom': case 'branchSummary':
|
|
118
|
+
case 'compactionSummary': case 'user': case 'assistant':
|
|
119
|
+
cutPoints.push(i); break;
|
|
120
|
+
case 'toolResult': break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (entry.type === EntryTypes.BRANCH_SUMMARY || entry.type === EntryTypes.CUSTOM_MESSAGE) {
|
|
124
|
+
cutPoints.push(i);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return cutPoints;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findTurnStartIndex(entries, entryIndex, startIndex) {
|
|
131
|
+
for (let i = entryIndex; i >= startIndex; i--) {
|
|
132
|
+
const entry = entries[i];
|
|
133
|
+
if (entry.type === EntryTypes.BRANCH_SUMMARY || entry.type === EntryTypes.CUSTOM_MESSAGE) return i;
|
|
134
|
+
if (entry.type === EntryTypes.MESSAGE && (entry.message.role === 'user' || entry.message.role === 'bashExecution')) return i;
|
|
135
|
+
}
|
|
136
|
+
return -1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function findCutPoint(entries, startIndex, endIndex, keepRecentTokens) {
|
|
140
|
+
const cutPoints = findValidCutPoints(entries, startIndex, endIndex);
|
|
141
|
+
if (cutPoints.length === 0) {
|
|
142
|
+
return { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };
|
|
143
|
+
}
|
|
144
|
+
let accumulatedTokens = 0;
|
|
145
|
+
let cutIndex = cutPoints[0];
|
|
146
|
+
|
|
147
|
+
for (let i = endIndex - 1; i >= startIndex; i--) {
|
|
148
|
+
const entry = entries[i];
|
|
149
|
+
if (entry.type !== EntryTypes.MESSAGE) continue;
|
|
150
|
+
accumulatedTokens += estimateTokens(entry.message);
|
|
151
|
+
if (accumulatedTokens >= keepRecentTokens) {
|
|
152
|
+
for (let c = 0; c < cutPoints.length; c++) {
|
|
153
|
+
if (cutPoints[c] >= i) { cutIndex = cutPoints[c]; break; }
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
while (cutIndex > startIndex) {
|
|
159
|
+
const prevEntry = entries[cutIndex - 1];
|
|
160
|
+
if (prevEntry.type === EntryTypes.COMPACTION) break;
|
|
161
|
+
if (prevEntry.type === EntryTypes.MESSAGE) break;
|
|
162
|
+
cutIndex--;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const cutEntry = entries[cutIndex];
|
|
166
|
+
const isUserMessage = cutEntry.type === EntryTypes.MESSAGE && cutEntry.message.role === 'user';
|
|
167
|
+
const turnStartIndex = isUserMessage ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);
|
|
168
|
+
|
|
169
|
+
return { firstKeptEntryIndex: cutIndex, turnStartIndex, isSplitTurn: !isUserMessage && turnStartIndex !== -1 };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function extractFileOperations(messages, entries, prevCompactionIndex) {
|
|
173
|
+
const fileOps = createFileOps();
|
|
174
|
+
if (prevCompactionIndex >= 0) {
|
|
175
|
+
const prevCompaction = entries[prevCompactionIndex];
|
|
176
|
+
if (!prevCompaction.fromHook && prevCompaction.details) {
|
|
177
|
+
const { details } = prevCompaction;
|
|
178
|
+
if (Array.isArray(details.readFiles)) { for (const f of details.readFiles) fileOps.read.add(f); }
|
|
179
|
+
if (Array.isArray(details.modifiedFiles)) { for (const f of details.modifiedFiles) fileOps.edited.add(f); }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const msg of messages) extractFileOpsFromMessage(msg, fileOps);
|
|
183
|
+
return fileOps;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
TOOL_RESULT_MAX_CHARS, createFileOps, extractFileOpsFromMessage,
|
|
188
|
+
computeFileLists, formatFileOperations, truncateForSummary,
|
|
189
|
+
serializeConversation, getMessageFromEntry, getMessageFromEntryForCompaction,
|
|
190
|
+
findValidCutPoints, findTurnStartIndex, findCutPoint, extractFileOperations,
|
|
191
|
+
};
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ContextCompressor - 上下文压缩器
|
|
5
|
+
* Ported from pi's compaction/compaction.ts with enhanced functionality
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { logger } = require('../common/logger');
|
|
9
|
+
const { estimateTokens, estimateContextTokens, buildSessionContext, TokenCounter } = require('../llm/tokens');
|
|
10
|
+
const { validateMessagesPairing } = require('../utils/message-validator');
|
|
11
|
+
const { EntryTypes } = require('../session/entry');
|
|
12
|
+
const {
|
|
13
|
+
createFileOps, extractFileOpsFromMessage, computeFileLists, formatFileOperations,
|
|
14
|
+
serializeConversation, getMessageFromEntry, getMessageFromEntryForCompaction,
|
|
15
|
+
findCutPoint, findTurnStartIndex, extractFileOperations,
|
|
16
|
+
} = require('./compaction-utils');
|
|
17
|
+
const {
|
|
18
|
+
SUMMARIZATION_SYSTEM_PROMPT, SUMMARIZATION_PROMPT, UPDATE_SUMMARIZATION_PROMPT,
|
|
19
|
+
TURN_PREFIX_SUMMARIZATION_PROMPT, BRANCH_SUMMARY_PREAMBLE, BRANCH_SUMMARY_PROMPT,
|
|
20
|
+
DEFAULT_COMPACTION_SETTINGS, MODEL_CONTEXT_LIMITS, COMPRESSION_TIMEOUT_MS,
|
|
21
|
+
} = require('./compaction-prompts');
|
|
22
|
+
|
|
23
|
+
// ─── Helper classes ───────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
class CompactionDetails {
|
|
26
|
+
constructor(readFiles = [], modifiedFiles = []) {
|
|
27
|
+
this.readFiles = readFiles;
|
|
28
|
+
this.modifiedFiles = modifiedFiles;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class Result {
|
|
33
|
+
constructor(ok, value, error) {
|
|
34
|
+
this.ok = ok; this.value = value; this.error = error;
|
|
35
|
+
}
|
|
36
|
+
static success(value) { return new Result(true, value, null); }
|
|
37
|
+
static failure(error) { return new Result(false, null, error); }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class CompactionError extends Error {
|
|
41
|
+
constructor(code, message, cause) {
|
|
42
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
43
|
+
this.name = 'CompactionError'; this.code = code;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class BranchSummaryError extends Error {
|
|
48
|
+
constructor(code, message, cause) {
|
|
49
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
50
|
+
this.name = 'BranchSummaryError'; this.code = code;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Compaction preparation ──────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
class CompactionResult {
|
|
57
|
+
constructor(summary, firstKeptEntryId, tokensBefore, details) {
|
|
58
|
+
this.summary = summary;
|
|
59
|
+
this.firstKeptEntryId = firstKeptEntryId;
|
|
60
|
+
this.tokensBefore = tokensBefore;
|
|
61
|
+
this.details = details;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function prepareCompaction(pathEntries, settings) {
|
|
66
|
+
if (pathEntries.length === 0 || pathEntries[pathEntries.length - 1].type === EntryTypes.COMPACTION) {
|
|
67
|
+
return Result.success(undefined);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let prevCompactionIndex = -1;
|
|
71
|
+
for (let i = pathEntries.length - 1; i >= 0; i--) {
|
|
72
|
+
if (pathEntries[i].type === EntryTypes.COMPACTION) { prevCompactionIndex = i; break; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let previousSummary;
|
|
76
|
+
let boundaryStart = 0;
|
|
77
|
+
if (prevCompactionIndex >= 0) {
|
|
78
|
+
const prevCompaction = pathEntries[prevCompactionIndex];
|
|
79
|
+
previousSummary = prevCompaction.summary;
|
|
80
|
+
const firstKeptEntryIndex = pathEntries.findIndex(entry => entry.id === prevCompaction.firstKeptEntryId);
|
|
81
|
+
boundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;
|
|
82
|
+
}
|
|
83
|
+
const boundaryEnd = pathEntries.length;
|
|
84
|
+
const tokensBefore = estimateContextTokens(buildSessionContext(pathEntries).messages).tokens;
|
|
85
|
+
|
|
86
|
+
const cutPoint = findCutPoint(pathEntries, boundaryStart, boundaryEnd, settings.keepRecentTokens);
|
|
87
|
+
const firstKeptEntry = pathEntries[cutPoint.firstKeptEntryIndex];
|
|
88
|
+
if (!firstKeptEntry?.id) {
|
|
89
|
+
return Result.failure(new CompactionError('invalid_session', 'First kept entry has no UUID'));
|
|
90
|
+
}
|
|
91
|
+
const firstKeptEntryId = firstKeptEntry.id;
|
|
92
|
+
|
|
93
|
+
const historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptEntryIndex;
|
|
94
|
+
const messagesToSummarize = [];
|
|
95
|
+
for (let i = boundaryStart; i < historyEnd; i++) {
|
|
96
|
+
const msg = getMessageFromEntryForCompaction(pathEntries[i]);
|
|
97
|
+
if (msg) messagesToSummarize.push(msg);
|
|
98
|
+
}
|
|
99
|
+
const turnPrefixMessages = [];
|
|
100
|
+
if (cutPoint.isSplitTurn) {
|
|
101
|
+
for (let i = cutPoint.turnStartIndex; i < cutPoint.firstKeptEntryIndex; i++) {
|
|
102
|
+
const msg = getMessageFromEntryForCompaction(pathEntries[i]);
|
|
103
|
+
if (msg) turnPrefixMessages.push(msg);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const fileOps = extractFileOperations(messagesToSummarize, pathEntries, prevCompactionIndex);
|
|
107
|
+
if (cutPoint.isSplitTurn) {
|
|
108
|
+
for (const msg of turnPrefixMessages) extractFileOpsFromMessage(msg, fileOps);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Result.success({
|
|
112
|
+
firstKeptEntryId, messagesToSummarize, turnPrefixMessages,
|
|
113
|
+
isSplitTurn: cutPoint.isSplitTurn, tokensBefore, previousSummary, fileOps, settings,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class BranchPreparation {
|
|
118
|
+
constructor(messages, fileOps, totalTokens) {
|
|
119
|
+
this.messages = messages; this.fileOps = fileOps; this.totalTokens = totalTokens;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function prepareBranchEntries(entries, tokenBudget = 0) {
|
|
124
|
+
const messages = [];
|
|
125
|
+
const fileOps = createFileOps();
|
|
126
|
+
let totalTokens = 0;
|
|
127
|
+
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (entry.type === EntryTypes.BRANCH_SUMMARY && !entry.fromHook && entry.details) {
|
|
130
|
+
const { details } = entry;
|
|
131
|
+
if (Array.isArray(details.readFiles)) { for (const f of details.readFiles) fileOps.read.add(f); }
|
|
132
|
+
if (Array.isArray(details.modifiedFiles)) { for (const f of details.modifiedFiles) fileOps.edited.add(f); }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
137
|
+
const entry = entries[i];
|
|
138
|
+
const message = getMessageFromEntry(entry);
|
|
139
|
+
if (!message) continue;
|
|
140
|
+
extractFileOpsFromMessage(message, fileOps);
|
|
141
|
+
|
|
142
|
+
const tokens = estimateTokens(message);
|
|
143
|
+
if (tokenBudget > 0 && totalTokens + tokens > tokenBudget) {
|
|
144
|
+
if (entry.type === EntryTypes.COMPACTION || entry.type === EntryTypes.BRANCH_SUMMARY) {
|
|
145
|
+
if (totalTokens < tokenBudget * 0.9) { messages.unshift(message); totalTokens += tokens; }
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
messages.unshift(message);
|
|
150
|
+
totalTokens += tokens;
|
|
151
|
+
}
|
|
152
|
+
return new BranchPreparation(messages, fileOps, totalTokens);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function collectEntriesForBranchSummary(session, oldLeafId, targetId) {
|
|
156
|
+
if (!oldLeafId) return { entries: [], commonAncestorId: null };
|
|
157
|
+
|
|
158
|
+
const oldPath = new Set(session.getBranch(oldLeafId).map(e => e.id));
|
|
159
|
+
const targetPath = session.getBranch(targetId);
|
|
160
|
+
let commonAncestorId = null;
|
|
161
|
+
for (let i = targetPath.length - 1; i >= 0; i--) {
|
|
162
|
+
if (oldPath.has(targetPath[i].id)) { commonAncestorId = targetPath[i].id; break; }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const entries = [];
|
|
166
|
+
let current = oldLeafId;
|
|
167
|
+
while (current && current !== commonAncestorId) {
|
|
168
|
+
const entry = session.getEntry(current);
|
|
169
|
+
if (!entry) break;
|
|
170
|
+
entries.push(entry);
|
|
171
|
+
current = entry.parentId;
|
|
172
|
+
}
|
|
173
|
+
entries.reverse();
|
|
174
|
+
return { entries, commonAncestorId };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── ContextCompressor ───────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
class ContextCompressor {
|
|
180
|
+
constructor(config = {}) {
|
|
181
|
+
this.config = config;
|
|
182
|
+
this.agent = config.agent;
|
|
183
|
+
this.framework = config.framework;
|
|
184
|
+
|
|
185
|
+
this.model = config.model || 'deepseek-chat';
|
|
186
|
+
this._maxContextTokens = config.maxContextTokens || this._getDefaultContextLimit();
|
|
187
|
+
this._keepRecentMessages = config.keepRecentMessages || 20;
|
|
188
|
+
this._compressionMessageThreshold = config.compressionMessageThreshold || 200;
|
|
189
|
+
this._enableSmartCompress = config.enableSmartCompress !== false;
|
|
190
|
+
this._compactionSettings = config.compactionSettings || DEFAULT_COMPACTION_SETTINGS;
|
|
191
|
+
|
|
192
|
+
this._compressionCount = 0;
|
|
193
|
+
this._compressionInProgress = false;
|
|
194
|
+
this._compressionPromise = null;
|
|
195
|
+
this._compressionTimeoutId = null;
|
|
196
|
+
this._maxToolResultSize = config.maxToolResultSize || 4000;
|
|
197
|
+
this._tokenCounter = new TokenCounter();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
_getDefaultContextLimit() {
|
|
201
|
+
const modelKey = Object.keys(MODEL_CONTEXT_LIMITS).find(k =>
|
|
202
|
+
this.model.toLowerCase().includes(k.toLowerCase())
|
|
203
|
+
);
|
|
204
|
+
return modelKey ? MODEL_CONTEXT_LIMITS[modelKey] : 40000;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
validateMessagesPairing(messages) {
|
|
208
|
+
return validateMessagesPairing(messages);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async compress(sessionId, messages, messageStore) {
|
|
212
|
+
if (this._compressionInProgress && this._compressionPromise) {
|
|
213
|
+
logger.debug('Compression already in progress, waiting...');
|
|
214
|
+
return this._compressionPromise;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (messages.length <= this._keepRecentMessages) return;
|
|
218
|
+
|
|
219
|
+
this._compressionInProgress = true;
|
|
220
|
+
this._compressionPromise = this._executeWithTimeout(sessionId, messages, messageStore).finally(() => {
|
|
221
|
+
this._compressionInProgress = false;
|
|
222
|
+
this._compressionPromise = null;
|
|
223
|
+
if (this._compressionTimeoutId) { clearTimeout(this._compressionTimeoutId); this._compressionTimeoutId = null; }
|
|
224
|
+
});
|
|
225
|
+
return this._compressionPromise;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async _executeWithTimeout(sessionId, messages, messageStore) {
|
|
229
|
+
try {
|
|
230
|
+
return await Promise.race([
|
|
231
|
+
this._doCompress(sessionId, messages, messageStore),
|
|
232
|
+
new Promise((_, reject) => {
|
|
233
|
+
this._compressionTimeoutId = setTimeout(() => {
|
|
234
|
+
this._compressionTimeoutId = null;
|
|
235
|
+
reject(new Error(`Compression timeout (${COMPRESSION_TIMEOUT_MS}ms)`));
|
|
236
|
+
}, COMPRESSION_TIMEOUT_MS);
|
|
237
|
+
}),
|
|
238
|
+
]);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
logger.warn('Compression failed:', err.message);
|
|
241
|
+
this._simpleCompress(sessionId, messages, messageStore);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
cancelCompression() {
|
|
246
|
+
if (this._compressionTimeoutId) { clearTimeout(this._compressionTimeoutId); this._compressionTimeoutId = null; }
|
|
247
|
+
this._compressionInProgress = false;
|
|
248
|
+
this._compressionPromise = null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async _doCompress(sessionId, messages, messageStore) {
|
|
252
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
253
|
+
const otherMessages = messages.filter(m => m.role !== 'system');
|
|
254
|
+
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
255
|
+
const messagesToSummarize = otherMessages.slice(0, -this._keepRecentMessages);
|
|
256
|
+
const compressedCount = messagesToSummarize.length;
|
|
257
|
+
|
|
258
|
+
let summaryContent = '';
|
|
259
|
+
if (this._enableSmartCompress && this.agent?._chatHandler?._aiClient) {
|
|
260
|
+
try {
|
|
261
|
+
const summaryText = await this._summarizeMessages(messagesToSummarize);
|
|
262
|
+
summaryContent = `[Early conversation summary]: ${summaryText || '(no content)'}`;
|
|
263
|
+
} catch (err) {
|
|
264
|
+
logger.warn('AI summary failed, using simple compression:', err.message);
|
|
265
|
+
summaryContent = `[Context compressed: ${compressedCount} early messages omitted. Kept recent ${this._keepRecentMessages} messages.]`;
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
summaryContent = `[Context compressed: ${compressedCount} early messages omitted. Kept recent ${this._keepRecentMessages} messages.]`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const summary = { role: 'assistant', content: summaryContent };
|
|
272
|
+
const combined = [...systemMessages, summary, ...recentMessages];
|
|
273
|
+
this._cleanupOrphanedToolResults(combined);
|
|
274
|
+
|
|
275
|
+
messages.length = 0;
|
|
276
|
+
messages.push(...combined);
|
|
277
|
+
|
|
278
|
+
this._compressionCount++;
|
|
279
|
+
const tokenCount = this._tokenCounter.countMessages(messages);
|
|
280
|
+
if (messageStore.compressionState) {
|
|
281
|
+
messageStore.compressionState.count++;
|
|
282
|
+
messageStore.compressionState.lastCompressedAt = Date.now();
|
|
283
|
+
messageStore.compressionState.lastTokenCount = tokenCount;
|
|
284
|
+
}
|
|
285
|
+
if (typeof messageStore.recordCompression === 'function') messageStore.recordCompression(tokenCount);
|
|
286
|
+
|
|
287
|
+
logger.info(`Context compressed (${this._compressionCount} times). Messages: ${messages.length}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
_simpleCompress(sessionId, messages, messageStore) {
|
|
291
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
292
|
+
const otherMessages = messages.filter(m => m.role !== 'system');
|
|
293
|
+
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
294
|
+
const compressedCount = otherMessages.length - this._keepRecentMessages;
|
|
295
|
+
|
|
296
|
+
const summary = { role: 'assistant', content: `[Context compressed: ${compressedCount} early messages omitted. Kept recent ${this._keepRecentMessages} messages.]` };
|
|
297
|
+
const combined = [...systemMessages, summary, ...recentMessages];
|
|
298
|
+
this._cleanupOrphanedToolResults(combined);
|
|
299
|
+
|
|
300
|
+
messages.length = 0;
|
|
301
|
+
messages.push(...combined);
|
|
302
|
+
|
|
303
|
+
this._compressionCount++;
|
|
304
|
+
if (messageStore.compressionState) {
|
|
305
|
+
messageStore.compressionState.count++;
|
|
306
|
+
messageStore.compressionState.lastCompressedAt = Date.now();
|
|
307
|
+
messageStore.compressionState.lastTokenCount = this._tokenCounter.countMessages(combined);
|
|
308
|
+
}
|
|
309
|
+
logger.info(`Context simple compressed. Messages: ${messages.length}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
_cleanupOrphanedToolResults(messages) {
|
|
313
|
+
const validToolCallIds = new Set();
|
|
314
|
+
for (const msg of messages) {
|
|
315
|
+
if (msg.role !== 'assistant') continue;
|
|
316
|
+
if (Array.isArray(msg.content)) {
|
|
317
|
+
for (const item of msg.content) {
|
|
318
|
+
if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) validToolCallIds.add(item.toolCallId);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
322
|
+
for (const tc of msg.tool_calls) { if (tc.id) validToolCallIds.add(tc.id); }
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let removedItems = 0;
|
|
327
|
+
let removedMsgs = 0;
|
|
328
|
+
for (const msg of messages) {
|
|
329
|
+
if (msg.role !== 'tool' || !Array.isArray(msg.content)) continue;
|
|
330
|
+
const originalLength = msg.content.length;
|
|
331
|
+
msg.content = msg.content.filter((item) => {
|
|
332
|
+
if (item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId && !validToolCallIds.has(item.toolCallId)) {
|
|
333
|
+
removedItems++; return false;
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
});
|
|
337
|
+
if (msg.content.length === 0 && originalLength > 0) msg._orphaned = true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
341
|
+
if (messages[i]._orphaned) { messages.splice(i, 1); removedMsgs++; }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (removedItems > 0 || removedMsgs > 0) {
|
|
345
|
+
logger.debug(`[ContextCompressor] cleanup: removed ${removedItems} orphaned tool-result items, ${removedMsgs} orphaned tool messages`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async _summarizeMessages(messages) {
|
|
350
|
+
if (!this.framework) throw new Error('Framework not available');
|
|
351
|
+
|
|
352
|
+
const msg_str = messages.map(m => {
|
|
353
|
+
let text = '';
|
|
354
|
+
if (typeof m.content === 'string') text = m.content;
|
|
355
|
+
else if (Array.isArray(m.content)) text = m.content.filter(c => c.type === 'text').map(c => c.text).join(' ');
|
|
356
|
+
if (m.role === 'tool') return '';
|
|
357
|
+
return `${m.role}: ${text}`;
|
|
358
|
+
}).filter(line => line.length > 0).join('\n');
|
|
359
|
+
|
|
360
|
+
const task = `Summarize the following conversation, keeping only meaningful information. Describe in 1000 characters or less:\n\n${msg_str}`;
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const subagent = this.framework.createSubAgent({
|
|
364
|
+
name: 'context-compressor',
|
|
365
|
+
role: 'Information extraction expert',
|
|
366
|
+
systemPrompt: 'You are a conversation summarization assistant. Extract and preserve only meaningful information. Keep output concise.',
|
|
367
|
+
maxRetries: 0,
|
|
368
|
+
disableTools: true,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const result = await subagent.chat(task);
|
|
372
|
+
if (result.success) return result.message;
|
|
373
|
+
throw new Error(result.error || 'Summary generation failed');
|
|
374
|
+
} catch (err) {
|
|
375
|
+
logger.warn('Summarize failed:', err.message);
|
|
376
|
+
throw err;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
getStats() {
|
|
381
|
+
return { compressionCount: this._compressionCount, inProgress: this._compressionInProgress };
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports = {
|
|
386
|
+
ContextCompressor,
|
|
387
|
+
CompactionDetails,
|
|
388
|
+
CompactionResult,
|
|
389
|
+
CompactionError,
|
|
390
|
+
BranchSummaryError,
|
|
391
|
+
Result,
|
|
392
|
+
DEFAULT_COMPACTION_SETTINGS,
|
|
393
|
+
createFileOps,
|
|
394
|
+
extractFileOpsFromMessage,
|
|
395
|
+
computeFileLists,
|
|
396
|
+
formatFileOperations,
|
|
397
|
+
serializeConversation,
|
|
398
|
+
findCutPoint,
|
|
399
|
+
findTurnStartIndex,
|
|
400
|
+
SUMMARIZATION_SYSTEM_PROMPT,
|
|
401
|
+
SUMMARIZATION_PROMPT,
|
|
402
|
+
UPDATE_SUMMARIZATION_PROMPT,
|
|
403
|
+
TURN_PREFIX_SUMMARIZATION_PROMPT,
|
|
404
|
+
BRANCH_SUMMARY_PREAMBLE,
|
|
405
|
+
BRANCH_SUMMARY_PROMPT,
|
|
406
|
+
extractFileOperations,
|
|
407
|
+
prepareCompaction,
|
|
408
|
+
prepareBranchEntries,
|
|
409
|
+
collectEntriesForBranchSummary,
|
|
410
|
+
getMessageFromEntry,
|
|
411
|
+
getMessageFromEntryForCompaction,
|
|
412
|
+
EntryTypes,
|
|
413
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
ContextStorage: require('./storage'),
|
|
5
|
+
RequestContext: require('./request').RequestContext,
|
|
6
|
+
SessionContext: require('./session').SessionContext,
|
|
7
|
+
AgentContext: require('./agent').AgentContext,
|
|
8
|
+
ContextCompressor: require('./compressor').ContextCompressor,
|
|
9
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { uuid } = require('../common/id');
|
|
4
|
+
|
|
5
|
+
class RequestContext {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.requestId = options.requestId || `req_${uuid()}`;
|
|
8
|
+
this.traceId = options.traceId || `trace_${uuid()}`;
|
|
9
|
+
this.parentSpanId = options.parentSpanId || null;
|
|
10
|
+
this.timeout = options.timeout || 120000;
|
|
11
|
+
this.startTime = options.startTime || Date.now();
|
|
12
|
+
this.isStream = options.isStream || false;
|
|
13
|
+
this.userId = options.userId || null;
|
|
14
|
+
this.cancelled = false;
|
|
15
|
+
this.abortController = null;
|
|
16
|
+
this.metadata = options.metadata || {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getAbortController() {
|
|
20
|
+
if (!this.abortController) {
|
|
21
|
+
this.abortController = new AbortController();
|
|
22
|
+
}
|
|
23
|
+
return this.abortController;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
isTimeout() { return Date.now() - this.startTime > this.timeout; }
|
|
27
|
+
|
|
28
|
+
cancel() {
|
|
29
|
+
this.cancelled = true;
|
|
30
|
+
if (this.abortController) this.abortController.abort();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getElapsed() { return Date.now() - this.startTime; }
|
|
34
|
+
|
|
35
|
+
toJSON() {
|
|
36
|
+
return {
|
|
37
|
+
requestId: this.requestId,
|
|
38
|
+
traceId: this.traceId,
|
|
39
|
+
parentSpanId: this.parentSpanId,
|
|
40
|
+
timeout: this.timeout,
|
|
41
|
+
startTime: this.startTime,
|
|
42
|
+
elapsed: this.getElapsed(),
|
|
43
|
+
isStream: this.isStream,
|
|
44
|
+
userId: this.userId,
|
|
45
|
+
cancelled: this.cancelled,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { RequestContext };
|