opc-agent 4.0.44 → 4.1.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/.github/ISSUE_TEMPLATE/bug_report.md +20 -20
- package/.github/ISSUE_TEMPLATE/feature_request.md +14 -14
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -13
- package/CHANGELOG.md +48 -48
- package/CONTRIBUTING.md +36 -36
- package/README.zh-CN.md +497 -497
- package/dist/channels/wechat.js +6 -6
- package/dist/cli.js +2 -2
- package/dist/core/runtime.js +18 -0
- package/dist/deploy/index.js +56 -56
- package/dist/providers/index.js +39 -13
- package/dist/studio/server.js +211 -20
- package/dist/studio-ui/index.html +279 -24
- package/dist/ui/components.js +105 -105
- package/examples/README.md +22 -22
- package/examples/basic-agent.ts +90 -90
- package/examples/brain-integration.ts +71 -71
- package/examples/multi-channel.ts +74 -74
- package/fix-sidebar.mjs +188 -188
- package/install.ps1 +154 -154
- package/install.sh +164 -164
- package/package.json +1 -1
- package/scripts/install.ps1 +31 -31
- package/scripts/install.sh +40 -40
- package/serve-studio.js +13 -13
- package/serve-test.js +25 -25
- package/src/channels/dingtalk.ts +46 -46
- package/src/channels/email.ts +351 -351
- package/src/channels/feishu.ts +349 -349
- package/src/channels/googlechat.ts +42 -42
- package/src/channels/imessage.ts +31 -31
- package/src/channels/irc.ts +82 -82
- package/src/channels/line.ts +32 -32
- package/src/channels/matrix.ts +33 -33
- package/src/channels/mattermost.ts +57 -57
- package/src/channels/msteams.ts +32 -32
- package/src/channels/nostr.ts +32 -32
- package/src/channels/qq.ts +33 -33
- package/src/channels/signal.ts +32 -32
- package/src/channels/sms.ts +33 -33
- package/src/channels/telegram.ts +616 -616
- package/src/channels/twitch.ts +65 -65
- package/src/channels/voice-call.ts +100 -100
- package/src/channels/websocket.ts +399 -399
- package/src/channels/wechat.ts +329 -329
- package/src/channels/whatsapp.ts +32 -32
- package/src/cli/chat.ts +99 -99
- package/src/cli/setup.ts +314 -314
- package/src/cli.ts +2 -2
- package/src/core/agent.ts +476 -476
- package/src/core/api-server.ts +277 -277
- package/src/core/audio.ts +98 -98
- package/src/core/collaboration.ts +275 -275
- package/src/core/context-discovery.ts +85 -85
- package/src/core/context-refs.ts +140 -140
- package/src/core/gateway.ts +106 -106
- package/src/core/heartbeat.ts +51 -51
- package/src/core/hooks.ts +105 -105
- package/src/core/ide-bridge.ts +133 -133
- package/src/core/node-network.ts +86 -86
- package/src/core/profiles.ts +122 -122
- package/src/core/runtime.ts +18 -0
- package/src/core/scheduler.ts +187 -187
- package/src/core/session-manager.ts +137 -137
- package/src/core/subagent.ts +98 -98
- package/src/core/vision.ts +180 -180
- package/src/core/workflow-graph.ts +365 -365
- package/src/daemon.ts +96 -96
- package/src/deploy/index.ts +255 -255
- package/src/doctor.ts +156 -156
- package/src/eval/index.ts +211 -211
- package/src/eval/suites/basic.json +16 -16
- package/src/eval/suites/memory.json +12 -12
- package/src/eval/suites/safety.json +14 -14
- package/src/hub/brain-seed.ts +54 -54
- package/src/hub/client.ts +60 -60
- package/src/mcp/servers/calculator-mcp.ts +65 -65
- package/src/mcp/servers/crypto-mcp.ts +73 -73
- package/src/mcp/servers/database-mcp.ts +72 -72
- package/src/mcp/servers/datetime-mcp.ts +69 -69
- package/src/mcp/servers/filesystem.ts +66 -66
- package/src/mcp/servers/github-mcp.ts +58 -58
- package/src/mcp/servers/index.ts +63 -63
- package/src/mcp/servers/json-mcp.ts +102 -102
- package/src/mcp/servers/memory-mcp.ts +56 -56
- package/src/mcp/servers/regex-mcp.ts +53 -53
- package/src/mcp/servers/web-mcp.ts +49 -49
- package/src/memory/context-compressor.ts +189 -189
- package/src/memory/seed-loader.ts +212 -212
- package/src/memory/user-profiler.ts +215 -215
- package/src/plugins/content-filter.ts +23 -23
- package/src/plugins/logger.ts +18 -18
- package/src/plugins/rate-limiter.ts +38 -38
- package/src/protocols/a2a/client.ts +132 -132
- package/src/protocols/a2a/index.ts +8 -8
- package/src/protocols/a2a/server.ts +333 -333
- package/src/protocols/a2a/types.ts +88 -88
- package/src/protocols/a2a/utils.ts +50 -50
- package/src/protocols/agui/client.ts +83 -83
- package/src/protocols/agui/index.ts +4 -4
- package/src/protocols/agui/server.ts +218 -218
- package/src/protocols/agui/types.ts +153 -153
- package/src/protocols/index.ts +2 -2
- package/src/protocols/mcp/agent-tools.ts +134 -134
- package/src/protocols/mcp/index.ts +8 -8
- package/src/protocols/mcp/server.ts +262 -262
- package/src/protocols/mcp/types.ts +69 -69
- package/src/providers/index.ts +632 -608
- package/src/publish/index.ts +376 -376
- package/src/scheduler/cron-engine.ts +191 -191
- package/src/scheduler/index.ts +2 -2
- package/src/schema/oad.ts +217 -217
- package/src/security/approval.ts +131 -131
- package/src/security/approvals.ts +143 -143
- package/src/security/elevated.ts +105 -105
- package/src/security/guardrails.ts +248 -248
- package/src/security/index.ts +9 -9
- package/src/security/keys.ts +87 -87
- package/src/security/secrets.ts +129 -129
- package/src/skills/builtin/index.ts +408 -408
- package/src/skills/marketplace.ts +113 -113
- package/src/skills/types.ts +42 -42
- package/src/studio/server.ts +209 -22
- package/src/studio/templates-data.ts +178 -178
- package/src/studio-ui/index.html +279 -24
- package/src/telemetry/index.ts +324 -324
- package/src/tools/builtin/browser.ts +299 -299
- package/src/tools/builtin/datetime.ts +41 -41
- package/src/tools/builtin/file.ts +107 -107
- package/src/tools/builtin/home-assistant.ts +116 -116
- package/src/tools/builtin/rl-tools.ts +243 -243
- package/src/tools/builtin/shell.ts +43 -43
- package/src/tools/builtin/vision.ts +64 -64
- package/src/tools/builtin/web-search.ts +126 -126
- package/src/tools/builtin/web.ts +35 -35
- package/src/tools/document-processor.ts +213 -213
- package/src/tools/image-generator.ts +150 -150
- package/src/tools/integrations/calendar.ts +73 -73
- package/src/tools/integrations/code-exec.ts +39 -39
- package/src/tools/integrations/csv-analyzer.ts +92 -92
- package/src/tools/integrations/database.ts +44 -44
- package/src/tools/integrations/email-send.ts +76 -76
- package/src/tools/integrations/git-tool.ts +42 -42
- package/src/tools/integrations/github-tool.ts +76 -76
- package/src/tools/integrations/image-gen.ts +56 -56
- package/src/tools/integrations/index.ts +92 -92
- package/src/tools/integrations/jira.ts +83 -83
- package/src/tools/integrations/notion.ts +71 -71
- package/src/tools/integrations/npm-tool.ts +48 -48
- package/src/tools/integrations/pdf-reader.ts +58 -58
- package/src/tools/integrations/slack.ts +65 -65
- package/src/tools/integrations/summarizer.ts +49 -49
- package/src/tools/integrations/translator.ts +48 -48
- package/src/tools/integrations/trello.ts +60 -60
- package/src/tools/integrations/vector-search.ts +42 -42
- package/src/tools/integrations/web-scraper.ts +47 -47
- package/src/tools/integrations/web-search.ts +58 -58
- package/src/tools/integrations/webhook.ts +38 -38
- package/src/tools/mcp-client.ts +131 -131
- package/src/tools/web-scraper.ts +179 -179
- package/src/tools/web-search.ts +180 -180
- package/src/ui/components.ts +127 -127
- package/srv-out.txt +1 -1
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/tech-support/README.md +43 -43
- package/templates/tech-support/oad.yaml +45 -45
- package/test-agent/Dockerfile +9 -9
- package/test-agent/README.md +50 -50
- package/test-agent/agent.yaml +23 -23
- package/test-agent/docker-compose.yml +11 -11
- package/test-agent/oad.yaml +31 -31
- package/test-agent/package-lock.json +1492 -1492
- package/test-agent/package.json +17 -17
- package/test-agent/src/index.ts +24 -24
- package/test-agent/src/skills/echo.ts +15 -15
- package/test-agent/tsconfig.json +24 -24
- package/test-full.js +43 -43
- package/test-sidebar.js +22 -22
- package/test-studio3.js +75 -75
- package/test-studio4.js +41 -41
- package/tests/a2a-protocol.test.ts +285 -285
- package/tests/agui-protocol.test.ts +246 -246
- package/tests/api-server.test.ts +148 -148
- package/tests/approvals.test.ts +89 -89
- package/tests/audio.test.ts +40 -40
- package/tests/brain-seed-extended.test.ts +490 -490
- package/tests/brain-seed.test.ts +239 -239
- package/tests/browser.test.ts +179 -179
- package/tests/channels/discord.test.ts +79 -79
- package/tests/channels/email.test.ts +148 -148
- package/tests/channels/feishu.test.ts +123 -123
- package/tests/channels/telegram.test.ts +129 -129
- package/tests/channels/websocket.test.ts +53 -53
- package/tests/channels/wechat.test.ts +170 -170
- package/tests/channels-extra.test.ts +45 -45
- package/tests/chat-cli.test.ts +160 -160
- package/tests/cli.test.ts +46 -46
- package/tests/context-compressor.test.ts +172 -172
- package/tests/context-refs.test.ts +121 -121
- package/tests/cron-engine.test.ts +101 -101
- package/tests/daemon.test.ts +135 -135
- package/tests/deepbrain-wire.test.ts +234 -234
- package/tests/deploy-and-dag.test.ts +196 -196
- package/tests/doctor.test.ts +38 -38
- package/tests/document-processor.test.ts +69 -69
- package/tests/e2e-nocode.test.ts +442 -442
- package/tests/elevated.test.ts +69 -69
- package/tests/eval.test.ts +173 -173
- package/tests/gateway.test.ts +63 -63
- package/tests/guardrails.test.ts +177 -177
- package/tests/home-assistant.test.ts +40 -40
- package/tests/hooks.test.ts +79 -79
- package/tests/ide-bridge.test.ts +38 -38
- package/tests/image-generator.test.ts +84 -84
- package/tests/init-role.test.ts +124 -124
- package/tests/integrations.test.ts +249 -249
- package/tests/mcp-client.test.ts +92 -92
- package/tests/mcp-server.test.ts +178 -178
- package/tests/mcp-servers.test.ts +260 -260
- package/tests/node-network.test.ts +74 -74
- package/tests/plugin-a2a-enhanced.test.ts +230 -230
- package/tests/profiles.test.ts +61 -61
- package/tests/publish.test.ts +231 -231
- package/tests/rl-tools.test.ts +93 -93
- package/tests/sandbox-manager.test.ts +46 -46
- package/tests/scheduler.test.ts +200 -200
- package/tests/secrets.test.ts +107 -107
- package/tests/security-enhanced.test.ts +233 -233
- package/tests/settings-api.test.ts +148 -148
- package/tests/setup.test.ts +73 -73
- package/tests/subagent.test.ts +193 -193
- package/tests/telegram-discord.test.ts +60 -60
- package/tests/telemetry.test.ts +186 -186
- package/tests/user-profiler.test.ts +169 -169
- package/tests/v090-features.test.ts +254 -254
- package/tests/vision.test.ts +61 -61
- package/tests/voice-call.test.ts +47 -47
- package/tests/voice-enhanced.test.ts +169 -169
- package/tests/voice-interaction.test.ts +38 -38
- package/tests/web-search.test.ts +155 -155
- package/tests/workflow-graph.test.ts +279 -279
- package/tutorial/customer-service-agent/README.md +612 -612
- package/tutorial/customer-service-agent/SOUL.md +26 -26
- package/tutorial/customer-service-agent/agent.yaml +63 -63
- package/tutorial/customer-service-agent/package.json +19 -19
- package/tutorial/customer-service-agent/src/index.ts +69 -69
- package/tutorial/customer-service-agent/src/skills/faq.ts +27 -27
- package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -22
- package/tutorial/customer-service-agent/tsconfig.json +14 -14
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
import type { Message } from '../core/types';
|
|
2
|
-
|
|
3
|
-
export interface CompressorConfig {
|
|
4
|
-
maxTokens: number;
|
|
5
|
-
compressThreshold: number;
|
|
6
|
-
preserveRecent: number;
|
|
7
|
-
brain?: any;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface CompressResult {
|
|
11
|
-
messages: Message[];
|
|
12
|
-
learnedCount: number;
|
|
13
|
-
savedTokens: number;
|
|
14
|
-
summary: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const DEFAULT_CONFIG: CompressorConfig = {
|
|
18
|
-
maxTokens: 8000,
|
|
19
|
-
compressThreshold: 0.8,
|
|
20
|
-
preserveRecent: 10,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Context compression with optional DeepBrain memory offloading.
|
|
25
|
-
*/
|
|
26
|
-
export class ContextCompressor {
|
|
27
|
-
private config: CompressorConfig;
|
|
28
|
-
|
|
29
|
-
constructor(config: Partial<CompressorConfig> = {}) {
|
|
30
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Estimate token count using language-aware heuristic.
|
|
35
|
-
* English: ~1 token per 4 chars. Chinese: ~1 token per 2 chars.
|
|
36
|
-
*/
|
|
37
|
-
estimateTokens(text: string): number {
|
|
38
|
-
let tokens = 0;
|
|
39
|
-
for (const char of text) {
|
|
40
|
-
// CJK Unicode range detection
|
|
41
|
-
const code = char.codePointAt(0) ?? 0;
|
|
42
|
-
if (
|
|
43
|
-
(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified
|
|
44
|
-
(code >= 0x3400 && code <= 0x4dbf) || // CJK Extension A
|
|
45
|
-
(code >= 0x3000 && code <= 0x303f) // CJK Punctuation
|
|
46
|
-
) {
|
|
47
|
-
tokens += 0.5; // 1 token per 2 chars
|
|
48
|
-
} else {
|
|
49
|
-
tokens += 0.25; // 1 token per 4 chars
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return Math.ceil(tokens);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private estimateMessagesTokens(messages: Message[]): number {
|
|
56
|
-
return messages.reduce((sum, m) => sum + this.estimateTokens(m.content), 0);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Extract key insights from messages for brain storage.
|
|
61
|
-
*/
|
|
62
|
-
private extractInsights(messages: Message[]): Array<{ content: string; type: string }> {
|
|
63
|
-
const insights: Array<{ content: string; type: string }> = [];
|
|
64
|
-
for (const msg of messages) {
|
|
65
|
-
const c = msg.content;
|
|
66
|
-
// Decisions
|
|
67
|
-
if (/\b(decided|decision|choose|chose|will use|going with|let's go|确定|决定)\b/i.test(c)) {
|
|
68
|
-
insights.push({ content: c.slice(0, 500), type: 'decision' });
|
|
69
|
-
}
|
|
70
|
-
// Facts / definitions
|
|
71
|
-
else if (/\b(is defined as|means|equals|refers to|是指|定义)\b/i.test(c)) {
|
|
72
|
-
insights.push({ content: c.slice(0, 500), type: 'fact' });
|
|
73
|
-
}
|
|
74
|
-
// Preferences
|
|
75
|
-
else if (/\b(prefer|like|want|don't like|不喜欢|喜欢|偏好)\b/i.test(c)) {
|
|
76
|
-
insights.push({ content: c.slice(0, 500), type: 'preference' });
|
|
77
|
-
}
|
|
78
|
-
// Code snippets
|
|
79
|
-
else if (/```[\s\S]{20,}```/.test(c)) {
|
|
80
|
-
insights.push({ content: c.slice(0, 800), type: 'code' });
|
|
81
|
-
}
|
|
82
|
-
// Long assistant messages likely contain useful info
|
|
83
|
-
else if (msg.role === 'assistant' && c.length > 200) {
|
|
84
|
-
insights.push({ content: c.slice(0, 500), type: 'knowledge' });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return insights;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Generate a simple summary from messages (no-brain fallback).
|
|
92
|
-
*/
|
|
93
|
-
private summarize(messages: Message[]): string {
|
|
94
|
-
const topics = new Set<string>();
|
|
95
|
-
const keyLines: string[] = [];
|
|
96
|
-
|
|
97
|
-
for (const msg of messages) {
|
|
98
|
-
// Extract first meaningful sentence
|
|
99
|
-
const firstLine = msg.content.split(/[.\n!?。!?]/)[0]?.trim();
|
|
100
|
-
if (firstLine && firstLine.length > 10 && firstLine.length < 200) {
|
|
101
|
-
if (keyLines.length < 5) keyLines.push(`[${msg.role}] ${firstLine}`);
|
|
102
|
-
}
|
|
103
|
-
// Extract topic words (capitalized words, Chinese phrases)
|
|
104
|
-
const words = msg.content.match(/[A-Z][a-z]{2,}/g) ?? [];
|
|
105
|
-
words.forEach(w => topics.add(w));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const topicStr = [...topics].slice(0, 10).join(', ');
|
|
109
|
-
const linesStr = keyLines.join('; ');
|
|
110
|
-
return `Topics: ${topicStr || 'general discussion'}. Key points: ${linesStr || 'varied conversation'}`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Compress messages when token count exceeds threshold.
|
|
115
|
-
*/
|
|
116
|
-
async compress(messages: Message[], config?: Partial<CompressorConfig>): Promise<CompressResult> {
|
|
117
|
-
const cfg = { ...this.config, ...config };
|
|
118
|
-
const totalTokens = this.estimateMessagesTokens(messages);
|
|
119
|
-
const threshold = cfg.maxTokens * cfg.compressThreshold;
|
|
120
|
-
|
|
121
|
-
// Under threshold — return as-is
|
|
122
|
-
if (totalTokens <= threshold) {
|
|
123
|
-
return {
|
|
124
|
-
messages: [...messages],
|
|
125
|
-
learnedCount: 0,
|
|
126
|
-
savedTokens: 0,
|
|
127
|
-
summary: '',
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const recentCount = Math.min(cfg.preserveRecent, messages.length);
|
|
132
|
-
const splitIdx = messages.length - recentCount;
|
|
133
|
-
const oldMessages = messages.slice(0, splitIdx);
|
|
134
|
-
const recentMessages = messages.slice(splitIdx);
|
|
135
|
-
|
|
136
|
-
if (oldMessages.length === 0) {
|
|
137
|
-
return { messages: [...messages], learnedCount: 0, savedTokens: 0, summary: '' };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const oldTokens = this.estimateMessagesTokens(oldMessages);
|
|
141
|
-
let learnedCount = 0;
|
|
142
|
-
let summary: string;
|
|
143
|
-
|
|
144
|
-
if (cfg.brain) {
|
|
145
|
-
// Extract and learn insights
|
|
146
|
-
const insights = this.extractInsights(oldMessages);
|
|
147
|
-
for (const insight of insights) {
|
|
148
|
-
try {
|
|
149
|
-
await cfg.brain.learn(insight.content, { insight_type: insight.type });
|
|
150
|
-
learnedCount++;
|
|
151
|
-
} catch { /* non-critical */ }
|
|
152
|
-
}
|
|
153
|
-
summary = `${oldMessages.length} messages compressed. Extracted ${learnedCount} insights (${insights.map(i => i.type).filter((v, i, a) => a.indexOf(v) === i).join(', ')}).`;
|
|
154
|
-
} else {
|
|
155
|
-
summary = this.summarize(oldMessages);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const compressionMessage: Message = {
|
|
159
|
-
id: `compressed-${Date.now()}`,
|
|
160
|
-
role: 'system',
|
|
161
|
-
content: `[Context compressed: ${oldMessages.length} messages → ${summary}${cfg.brain ? ' Details stored in Brain, use recall() to retrieve.' : ''}]`,
|
|
162
|
-
timestamp: Date.now(),
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
messages: [compressionMessage, ...recentMessages],
|
|
167
|
-
learnedCount,
|
|
168
|
-
savedTokens: oldTokens - this.estimateTokens(compressionMessage.content),
|
|
169
|
-
summary,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Restore context from brain for a given query.
|
|
175
|
-
*/
|
|
176
|
-
async restore(query: string, brain: any): Promise<string[]> {
|
|
177
|
-
if (!brain?.recall) return [];
|
|
178
|
-
try {
|
|
179
|
-
const results = await brain.recall(query);
|
|
180
|
-
if (Array.isArray(results)) {
|
|
181
|
-
return results.map((r: any) => typeof r === 'string' ? r : r.content ?? JSON.stringify(r));
|
|
182
|
-
}
|
|
183
|
-
if (typeof results === 'string') return [results];
|
|
184
|
-
return [];
|
|
185
|
-
} catch {
|
|
186
|
-
return [];
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
1
|
+
import type { Message } from '../core/types';
|
|
2
|
+
|
|
3
|
+
export interface CompressorConfig {
|
|
4
|
+
maxTokens: number;
|
|
5
|
+
compressThreshold: number;
|
|
6
|
+
preserveRecent: number;
|
|
7
|
+
brain?: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CompressResult {
|
|
11
|
+
messages: Message[];
|
|
12
|
+
learnedCount: number;
|
|
13
|
+
savedTokens: number;
|
|
14
|
+
summary: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_CONFIG: CompressorConfig = {
|
|
18
|
+
maxTokens: 8000,
|
|
19
|
+
compressThreshold: 0.8,
|
|
20
|
+
preserveRecent: 10,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Context compression with optional DeepBrain memory offloading.
|
|
25
|
+
*/
|
|
26
|
+
export class ContextCompressor {
|
|
27
|
+
private config: CompressorConfig;
|
|
28
|
+
|
|
29
|
+
constructor(config: Partial<CompressorConfig> = {}) {
|
|
30
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Estimate token count using language-aware heuristic.
|
|
35
|
+
* English: ~1 token per 4 chars. Chinese: ~1 token per 2 chars.
|
|
36
|
+
*/
|
|
37
|
+
estimateTokens(text: string): number {
|
|
38
|
+
let tokens = 0;
|
|
39
|
+
for (const char of text) {
|
|
40
|
+
// CJK Unicode range detection
|
|
41
|
+
const code = char.codePointAt(0) ?? 0;
|
|
42
|
+
if (
|
|
43
|
+
(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified
|
|
44
|
+
(code >= 0x3400 && code <= 0x4dbf) || // CJK Extension A
|
|
45
|
+
(code >= 0x3000 && code <= 0x303f) // CJK Punctuation
|
|
46
|
+
) {
|
|
47
|
+
tokens += 0.5; // 1 token per 2 chars
|
|
48
|
+
} else {
|
|
49
|
+
tokens += 0.25; // 1 token per 4 chars
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Math.ceil(tokens);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private estimateMessagesTokens(messages: Message[]): number {
|
|
56
|
+
return messages.reduce((sum, m) => sum + this.estimateTokens(m.content), 0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract key insights from messages for brain storage.
|
|
61
|
+
*/
|
|
62
|
+
private extractInsights(messages: Message[]): Array<{ content: string; type: string }> {
|
|
63
|
+
const insights: Array<{ content: string; type: string }> = [];
|
|
64
|
+
for (const msg of messages) {
|
|
65
|
+
const c = msg.content;
|
|
66
|
+
// Decisions
|
|
67
|
+
if (/\b(decided|decision|choose|chose|will use|going with|let's go|确定|决定)\b/i.test(c)) {
|
|
68
|
+
insights.push({ content: c.slice(0, 500), type: 'decision' });
|
|
69
|
+
}
|
|
70
|
+
// Facts / definitions
|
|
71
|
+
else if (/\b(is defined as|means|equals|refers to|是指|定义)\b/i.test(c)) {
|
|
72
|
+
insights.push({ content: c.slice(0, 500), type: 'fact' });
|
|
73
|
+
}
|
|
74
|
+
// Preferences
|
|
75
|
+
else if (/\b(prefer|like|want|don't like|不喜欢|喜欢|偏好)\b/i.test(c)) {
|
|
76
|
+
insights.push({ content: c.slice(0, 500), type: 'preference' });
|
|
77
|
+
}
|
|
78
|
+
// Code snippets
|
|
79
|
+
else if (/```[\s\S]{20,}```/.test(c)) {
|
|
80
|
+
insights.push({ content: c.slice(0, 800), type: 'code' });
|
|
81
|
+
}
|
|
82
|
+
// Long assistant messages likely contain useful info
|
|
83
|
+
else if (msg.role === 'assistant' && c.length > 200) {
|
|
84
|
+
insights.push({ content: c.slice(0, 500), type: 'knowledge' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return insights;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate a simple summary from messages (no-brain fallback).
|
|
92
|
+
*/
|
|
93
|
+
private summarize(messages: Message[]): string {
|
|
94
|
+
const topics = new Set<string>();
|
|
95
|
+
const keyLines: string[] = [];
|
|
96
|
+
|
|
97
|
+
for (const msg of messages) {
|
|
98
|
+
// Extract first meaningful sentence
|
|
99
|
+
const firstLine = msg.content.split(/[.\n!?。!?]/)[0]?.trim();
|
|
100
|
+
if (firstLine && firstLine.length > 10 && firstLine.length < 200) {
|
|
101
|
+
if (keyLines.length < 5) keyLines.push(`[${msg.role}] ${firstLine}`);
|
|
102
|
+
}
|
|
103
|
+
// Extract topic words (capitalized words, Chinese phrases)
|
|
104
|
+
const words = msg.content.match(/[A-Z][a-z]{2,}/g) ?? [];
|
|
105
|
+
words.forEach(w => topics.add(w));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const topicStr = [...topics].slice(0, 10).join(', ');
|
|
109
|
+
const linesStr = keyLines.join('; ');
|
|
110
|
+
return `Topics: ${topicStr || 'general discussion'}. Key points: ${linesStr || 'varied conversation'}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Compress messages when token count exceeds threshold.
|
|
115
|
+
*/
|
|
116
|
+
async compress(messages: Message[], config?: Partial<CompressorConfig>): Promise<CompressResult> {
|
|
117
|
+
const cfg = { ...this.config, ...config };
|
|
118
|
+
const totalTokens = this.estimateMessagesTokens(messages);
|
|
119
|
+
const threshold = cfg.maxTokens * cfg.compressThreshold;
|
|
120
|
+
|
|
121
|
+
// Under threshold — return as-is
|
|
122
|
+
if (totalTokens <= threshold) {
|
|
123
|
+
return {
|
|
124
|
+
messages: [...messages],
|
|
125
|
+
learnedCount: 0,
|
|
126
|
+
savedTokens: 0,
|
|
127
|
+
summary: '',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const recentCount = Math.min(cfg.preserveRecent, messages.length);
|
|
132
|
+
const splitIdx = messages.length - recentCount;
|
|
133
|
+
const oldMessages = messages.slice(0, splitIdx);
|
|
134
|
+
const recentMessages = messages.slice(splitIdx);
|
|
135
|
+
|
|
136
|
+
if (oldMessages.length === 0) {
|
|
137
|
+
return { messages: [...messages], learnedCount: 0, savedTokens: 0, summary: '' };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const oldTokens = this.estimateMessagesTokens(oldMessages);
|
|
141
|
+
let learnedCount = 0;
|
|
142
|
+
let summary: string;
|
|
143
|
+
|
|
144
|
+
if (cfg.brain) {
|
|
145
|
+
// Extract and learn insights
|
|
146
|
+
const insights = this.extractInsights(oldMessages);
|
|
147
|
+
for (const insight of insights) {
|
|
148
|
+
try {
|
|
149
|
+
await cfg.brain.learn(insight.content, { insight_type: insight.type });
|
|
150
|
+
learnedCount++;
|
|
151
|
+
} catch { /* non-critical */ }
|
|
152
|
+
}
|
|
153
|
+
summary = `${oldMessages.length} messages compressed. Extracted ${learnedCount} insights (${insights.map(i => i.type).filter((v, i, a) => a.indexOf(v) === i).join(', ')}).`;
|
|
154
|
+
} else {
|
|
155
|
+
summary = this.summarize(oldMessages);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const compressionMessage: Message = {
|
|
159
|
+
id: `compressed-${Date.now()}`,
|
|
160
|
+
role: 'system',
|
|
161
|
+
content: `[Context compressed: ${oldMessages.length} messages → ${summary}${cfg.brain ? ' Details stored in Brain, use recall() to retrieve.' : ''}]`,
|
|
162
|
+
timestamp: Date.now(),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
messages: [compressionMessage, ...recentMessages],
|
|
167
|
+
learnedCount,
|
|
168
|
+
savedTokens: oldTokens - this.estimateTokens(compressionMessage.content),
|
|
169
|
+
summary,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Restore context from brain for a given query.
|
|
175
|
+
*/
|
|
176
|
+
async restore(query: string, brain: any): Promise<string[]> {
|
|
177
|
+
if (!brain?.recall) return [];
|
|
178
|
+
try {
|
|
179
|
+
const results = await brain.recall(query);
|
|
180
|
+
if (Array.isArray(results)) {
|
|
181
|
+
return results.map((r: any) => typeof r === 'string' ? r : r.content ?? JSON.stringify(r));
|
|
182
|
+
}
|
|
183
|
+
if (typeof results === 'string') return [results];
|
|
184
|
+
return [];
|
|
185
|
+
} catch {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|