c0de-agent 0.0.0 → 1.1.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 +21 -0
- package/README.md +101 -0
- package/dist/cli/commands/acp.d.ts +19 -0
- package/dist/cli/commands/acp.js +50 -0
- package/dist/cli/commands/chat.d.ts +13 -0
- package/dist/cli/commands/chat.js +26 -0
- package/dist/cli/commands/config.d.ts +17 -0
- package/dist/cli/commands/config.js +77 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +14 -0
- package/dist/cli/commands/plugin.d.ts +11 -0
- package/dist/cli/commands/plugin.js +31 -0
- package/dist/cli/commands/serve.d.ts +18 -0
- package/dist/cli/commands/serve.js +35 -0
- package/dist/cli/commands/update.d.ts +24 -0
- package/dist/cli/commands/update.js +40 -0
- package/dist/cli/deps.d.ts +30 -0
- package/dist/cli/deps.js +70 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +155 -0
- package/dist/cli/modes/acp.d.ts +18 -0
- package/dist/cli/modes/acp.js +57 -0
- package/dist/cli/modes/print.d.ts +16 -0
- package/dist/cli/modes/print.js +30 -0
- package/dist/cli/parser.d.ts +19 -0
- package/dist/cli/parser.js +28 -0
- package/dist/cli/utils/output.d.ts +11 -0
- package/dist/cli/utils/output.js +23 -0
- package/dist/cli/utils/prompt.d.ts +12 -0
- package/dist/cli/utils/prompt.js +18 -0
- package/dist/core/agent.d.ts +12 -0
- package/dist/core/agent.js +100 -0
- package/dist/core/agents/builtin.d.ts +6 -0
- package/dist/core/agents/builtin.js +76 -0
- package/dist/core/agents/discovery.d.ts +6 -0
- package/dist/core/agents/discovery.js +96 -0
- package/dist/core/agents/index.d.ts +6 -0
- package/dist/core/agents/index.js +4 -0
- package/dist/core/agents/parallel.d.ts +13 -0
- package/dist/core/agents/parallel.js +37 -0
- package/dist/core/agents/registry.d.ts +4 -0
- package/dist/core/agents/registry.js +23 -0
- package/dist/core/agents/types.d.ts +37 -0
- package/dist/core/agents/types.js +1 -0
- package/dist/core/compact.d.ts +11 -0
- package/dist/core/compact.js +31 -0
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.js +85 -0
- package/dist/core/context.d.ts +23 -0
- package/dist/core/context.js +93 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +11 -0
- package/dist/core/loop.d.ts +35 -0
- package/dist/core/loop.js +915 -0
- package/dist/core/metrics.d.ts +27 -0
- package/dist/core/metrics.js +94 -0
- package/dist/core/prompt-registry.d.ts +21 -0
- package/dist/core/prompt-registry.js +200 -0
- package/dist/core/prompt.d.ts +11 -0
- package/dist/core/prompt.js +13 -0
- package/dist/core/secret.d.ts +12 -0
- package/dist/core/secret.js +51 -0
- package/dist/core/slash.d.ts +15 -0
- package/dist/core/slash.js +113 -0
- package/dist/core/steering.d.ts +8 -0
- package/dist/core/steering.js +15 -0
- package/dist/core/title.d.ts +42 -0
- package/dist/core/title.js +116 -0
- package/dist/core/tool-exec.d.ts +20 -0
- package/dist/core/tool-exec.js +64 -0
- package/dist/core/types.d.ts +97 -0
- package/dist/core/types.js +1 -0
- package/dist/core/worktree.d.ts +24 -0
- package/dist/core/worktree.js +70 -0
- package/dist/dap/index.d.ts +6 -0
- package/dist/dap/index.js +4 -0
- package/dist/dap/protocol.d.ts +40 -0
- package/dist/dap/protocol.js +132 -0
- package/dist/dap/session.d.ts +23 -0
- package/dist/dap/session.js +130 -0
- package/dist/dap/transport.d.ts +8 -0
- package/dist/dap/transport.js +30 -0
- package/dist/dap/types.d.ts +50 -0
- package/dist/dap/types.js +2 -0
- package/dist/db/client.d.ts +22 -0
- package/dist/db/client.js +39 -0
- package/dist/db/index.d.ts +6 -0
- package/dist/db/index.js +4 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +15 -0
- package/dist/db/schema.d.ts +965 -0
- package/dist/db/schema.js +112 -0
- package/dist/db/types.d.ts +11 -0
- package/dist/db/types.js +1 -0
- package/dist/llm/index.d.ts +13 -0
- package/dist/llm/index.js +8 -0
- package/dist/llm/protocols/openai-compat.d.ts +139 -0
- package/dist/llm/protocols/openai-compat.js +214 -0
- package/dist/llm/protocols/utils/index.d.ts +2 -0
- package/dist/llm/protocols/utils/index.js +2 -0
- package/dist/llm/protocols/utils/lifecycle.d.ts +23 -0
- package/dist/llm/protocols/utils/lifecycle.js +46 -0
- package/dist/llm/protocols/utils/tool-stream.d.ts +46 -0
- package/dist/llm/protocols/utils/tool-stream.js +94 -0
- package/dist/llm/provider-error.d.ts +5 -0
- package/dist/llm/provider-error.js +33 -0
- package/dist/llm/provider.d.ts +32 -0
- package/dist/llm/provider.js +195 -0
- package/dist/llm/registry.d.ts +44 -0
- package/dist/llm/registry.js +107 -0
- package/dist/llm/retry.d.ts +37 -0
- package/dist/llm/retry.js +82 -0
- package/dist/llm/routing.d.ts +36 -0
- package/dist/llm/routing.js +53 -0
- package/dist/llm/schema/errors.d.ts +88 -0
- package/dist/llm/schema/errors.js +33 -0
- package/dist/llm/schema/events.d.ts +135 -0
- package/dist/llm/schema/events.js +35 -0
- package/dist/llm/schema/ids.d.ts +22 -0
- package/dist/llm/schema/ids.js +3 -0
- package/dist/llm/schema/index.d.ts +5 -0
- package/dist/llm/schema/index.js +5 -0
- package/dist/llm/schema/messages.d.ts +97 -0
- package/dist/llm/schema/messages.js +18 -0
- package/dist/llm/schema/options.d.ts +54 -0
- package/dist/llm/schema/options.js +54 -0
- package/dist/llm/token.d.ts +2 -0
- package/dist/llm/token.js +13 -0
- package/dist/llm/transport.d.ts +31 -0
- package/dist/llm/transport.js +165 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/plugins/builtin.d.ts +6 -0
- package/dist/plugins/builtin.js +54 -0
- package/dist/plugins/hooks.d.ts +3 -0
- package/dist/plugins/hooks.js +79 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/init.d.ts +23 -0
- package/dist/plugins/init.js +30 -0
- package/dist/plugins/lifecycle.d.ts +6 -0
- package/dist/plugins/lifecycle.js +74 -0
- package/dist/plugins/loader.d.ts +16 -0
- package/dist/plugins/loader.js +69 -0
- package/dist/plugins/logger.d.ts +4 -0
- package/dist/plugins/logger.js +31 -0
- package/dist/plugins/registry.d.ts +7 -0
- package/dist/plugins/registry.js +24 -0
- package/dist/plugins/types.d.ts +113 -0
- package/dist/plugins/types.js +1 -0
- package/dist/project/detect.d.ts +4 -0
- package/dist/project/detect.js +131 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +2 -0
- package/dist/project/project.d.ts +23 -0
- package/dist/project/project.js +87 -0
- package/dist/project/resolve.d.ts +8 -0
- package/dist/project/resolve.js +81 -0
- package/dist/server/agent-manager.d.ts +34 -0
- package/dist/server/agent-manager.js +62 -0
- package/dist/server/app.d.ts +5 -0
- package/dist/server/app.js +78 -0
- package/dist/server/context.d.ts +20 -0
- package/dist/server/context.js +49 -0
- package/dist/server/dev.d.ts +14 -0
- package/dist/server/dev.js +148 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +7 -0
- package/dist/server/middleware/auth.d.ts +15 -0
- package/dist/server/middleware/auth.js +29 -0
- package/dist/server/middleware/cors.d.ts +16 -0
- package/dist/server/middleware/cors.js +44 -0
- package/dist/server/middleware/error.d.ts +15 -0
- package/dist/server/middleware/error.js +11 -0
- package/dist/server/permission/interactive.d.ts +24 -0
- package/dist/server/permission/interactive.js +56 -0
- package/dist/server/permission/store.d.ts +32 -0
- package/dist/server/permission/store.js +56 -0
- package/dist/server/routes/agent.d.ts +4 -0
- package/dist/server/routes/agent.js +19 -0
- package/dist/server/routes/catalog.d.ts +42 -0
- package/dist/server/routes/catalog.js +194 -0
- package/dist/server/routes/chat.d.ts +8 -0
- package/dist/server/routes/chat.js +318 -0
- package/dist/server/routes/commands.d.ts +5 -0
- package/dist/server/routes/commands.js +17 -0
- package/dist/server/routes/config.d.ts +4 -0
- package/dist/server/routes/config.js +27 -0
- package/dist/server/routes/files.d.ts +4 -0
- package/dist/server/routes/files.js +183 -0
- package/dist/server/routes/filesystem.d.ts +29 -0
- package/dist/server/routes/filesystem.js +108 -0
- package/dist/server/routes/health.d.ts +3 -0
- package/dist/server/routes/health.js +13 -0
- package/dist/server/routes/permissions.d.ts +5 -0
- package/dist/server/routes/permissions.js +23 -0
- package/dist/server/routes/project.d.ts +4 -0
- package/dist/server/routes/project.js +51 -0
- package/dist/server/routes/provider.d.ts +16 -0
- package/dist/server/routes/provider.js +66 -0
- package/dist/server/routes/session.d.ts +4 -0
- package/dist/server/routes/session.js +204 -0
- package/dist/server/routes/terminal.d.ts +5 -0
- package/dist/server/routes/terminal.js +66 -0
- package/dist/server/routes/tool.d.ts +4 -0
- package/dist/server/routes/tool.js +28 -0
- package/dist/server/routes/update.d.ts +14 -0
- package/dist/server/routes/update.js +49 -0
- package/dist/server/server.d.ts +56 -0
- package/dist/server/server.js +269 -0
- package/dist/server/terminal/pty-manager.d.ts +53 -0
- package/dist/server/terminal/pty-manager.js +160 -0
- package/dist/server/types.d.ts +76 -0
- package/dist/server/types.js +2 -0
- package/dist/server/util/safe-path.d.ts +7 -0
- package/dist/server/util/safe-path.js +7 -0
- package/dist/session/archive.d.ts +15 -0
- package/dist/session/archive.js +129 -0
- package/dist/session/branch.d.ts +9 -0
- package/dist/session/branch.js +63 -0
- package/dist/session/compaction.d.ts +33 -0
- package/dist/session/compaction.js +261 -0
- package/dist/session/context.d.ts +16 -0
- package/dist/session/context.js +173 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.js +10 -0
- package/dist/session/message.d.ts +22 -0
- package/dist/session/message.js +145 -0
- package/dist/session/session.d.ts +37 -0
- package/dist/session/session.js +144 -0
- package/dist/session/shake.d.ts +66 -0
- package/dist/session/shake.js +304 -0
- package/dist/session/snapshot.d.ts +11 -0
- package/dist/session/snapshot.js +60 -0
- package/dist/session/squash.d.ts +9 -0
- package/dist/session/squash.js +88 -0
- package/dist/session/token.d.ts +10 -0
- package/dist/session/token.js +33 -0
- package/dist/session/types.d.ts +122 -0
- package/dist/session/types.js +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/types/agent.d.ts +229 -0
- package/dist/shared/types/agent.js +1 -0
- package/dist/shared/types/base.d.ts +32 -0
- package/dist/shared/types/base.js +1 -0
- package/dist/shared/types/config.d.ts +114 -0
- package/dist/shared/types/config.js +1 -0
- package/dist/shared/types/index.d.ts +6 -0
- package/dist/shared/types/index.js +1 -0
- package/dist/shared/types/llm.d.ts +123 -0
- package/dist/shared/types/llm.js +1 -0
- package/dist/shared/types/message.d.ts +71 -0
- package/dist/shared/types/message.js +1 -0
- package/dist/shared/types/tool.d.ts +137 -0
- package/dist/shared/types/tool.js +1 -0
- package/dist/shared/utils/path.d.ts +15 -0
- package/dist/shared/utils/path.js +23 -0
- package/dist/shared/utils.d.ts +5 -0
- package/dist/shared/utils.js +10 -0
- package/dist/tools/builtin/bash.d.ts +12 -0
- package/dist/tools/builtin/bash.js +121 -0
- package/dist/tools/builtin/dap.d.ts +23 -0
- package/dist/tools/builtin/dap.js +231 -0
- package/dist/tools/builtin/edit.d.ts +12 -0
- package/dist/tools/builtin/edit.js +151 -0
- package/dist/tools/builtin/glob.d.ts +11 -0
- package/dist/tools/builtin/glob.js +120 -0
- package/dist/tools/builtin/grep.d.ts +6 -0
- package/dist/tools/builtin/grep.js +141 -0
- package/dist/tools/builtin/read.d.ts +6 -0
- package/dist/tools/builtin/read.js +73 -0
- package/dist/tools/builtin/resolvers.d.ts +12 -0
- package/dist/tools/builtin/resolvers.js +79 -0
- package/dist/tools/builtin/task.d.ts +9 -0
- package/dist/tools/builtin/task.js +100 -0
- package/dist/tools/builtin/write.d.ts +6 -0
- package/dist/tools/builtin/write.js +36 -0
- package/dist/tools/builtin/yield.d.ts +9 -0
- package/dist/tools/builtin/yield.js +40 -0
- package/dist/tools/executor.d.ts +9 -0
- package/dist/tools/executor.js +54 -0
- package/dist/tools/hashline/index.d.ts +2 -0
- package/dist/tools/hashline/index.js +3 -0
- package/dist/tools/hashline/patch.d.ts +48 -0
- package/dist/tools/hashline/patch.js +201 -0
- package/dist/tools/index.d.ts +30 -0
- package/dist/tools/index.js +55 -0
- package/dist/tools/permission.d.ts +13 -0
- package/dist/tools/permission.js +39 -0
- package/dist/tools/registry.d.ts +15 -0
- package/dist/tools/registry.js +54 -0
- package/dist/tools/resolver.d.ts +15 -0
- package/dist/tools/resolver.js +47 -0
- package/dist/tools/truncate.d.ts +8 -0
- package/dist/tools/truncate.js +50 -0
- package/dist/tools/types.d.ts +101 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/validate.d.ts +8 -0
- package/dist/tools/validate.js +130 -0
- package/dist/tools/websearch/fetch.d.ts +11 -0
- package/dist/tools/websearch/fetch.js +21 -0
- package/dist/tools/websearch/index.d.ts +19 -0
- package/dist/tools/websearch/index.js +78 -0
- package/dist/tools/websearch/providers/brave.d.ts +5 -0
- package/dist/tools/websearch/providers/brave.js +53 -0
- package/dist/tools/websearch/providers/duckduckgo.d.ts +3 -0
- package/dist/tools/websearch/providers/duckduckgo.js +75 -0
- package/dist/tools/websearch/providers/tavily.d.ts +11 -0
- package/dist/tools/websearch/providers/tavily.js +89 -0
- package/dist/tools/websearch/types.d.ts +51 -0
- package/dist/tools/websearch/types.js +18 -0
- package/dist/tools/websearch/websearch.d.ts +10 -0
- package/dist/tools/websearch/websearch.js +48 -0
- package/dist/update/index.d.ts +10 -0
- package/dist/update/index.js +6 -0
- package/dist/update/ipc.d.ts +15 -0
- package/dist/update/ipc.js +41 -0
- package/dist/update/scheduler.d.ts +34 -0
- package/dist/update/scheduler.js +57 -0
- package/dist/update/snapshot.d.ts +39 -0
- package/dist/update/snapshot.js +99 -0
- package/dist/update/updater.d.ts +48 -0
- package/dist/update/updater.js +83 -0
- package/dist/update/version.d.ts +18 -0
- package/dist/update/version.js +49 -0
- package/drizzle/0000_magical_nightshade.sql +55 -0
- package/drizzle/0001_chemical_doctor_faustus.sql +13 -0
- package/drizzle/0002_amusing_micromacro.sql +12 -0
- package/drizzle/0003_regular_purifiers.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +448 -0
- package/drizzle/meta/0001_snapshot.json +539 -0
- package/drizzle/meta/0002_snapshot.json +633 -0
- package/drizzle/meta/0003_snapshot.json +645 -0
- package/drizzle/meta/_journal.json +34 -0
- package/package.json +108 -8
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { estimateTokens } from './token.js';
|
|
2
|
+
/** Rough token cost of a placeholder line; used only for the savings gate. */
|
|
3
|
+
const PLACEHOLDER_TOKEN_ESTIMATE = 16;
|
|
4
|
+
/** 识别顶层 XML 元素(小写 tag,保守策略)。 */
|
|
5
|
+
const OPENING_XML = /^<([a-z_-]+)(?:\s+[^>]*)?>$/;
|
|
6
|
+
const CLOSING_XML = /^<\/([a-z_-]+)>$/;
|
|
7
|
+
/** Auto-shake 默认配置:保护活跃尾部,保守阈值。 */
|
|
8
|
+
export const DEFAULT_SHAKE_CONFIG = {
|
|
9
|
+
protectTokens: 16_000,
|
|
10
|
+
minSavings: 4_000,
|
|
11
|
+
fenceMinTokens: 400,
|
|
12
|
+
protectedTools: [],
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* 定位 fenced 代码块和顶层 XML 元素 span。返回字符偏移 [start, end) 数组,
|
|
16
|
+
* 覆盖完整块(含围栏/标签行,不含尾换行)。围栏内抑制 XML 检测。
|
|
17
|
+
* 未闭合围栏/标签不产生 range(保守策略)。
|
|
18
|
+
*/
|
|
19
|
+
export function scanTextForBlockRanges(text) {
|
|
20
|
+
const ranges = [];
|
|
21
|
+
let inFence = false;
|
|
22
|
+
let fenceStart = -1;
|
|
23
|
+
const tagStack = [];
|
|
24
|
+
let xmlStart = -1;
|
|
25
|
+
let lineStart = 0;
|
|
26
|
+
for (let i = 0; i <= text.length; i++) {
|
|
27
|
+
if (i !== text.length && text[i] !== '\n')
|
|
28
|
+
continue;
|
|
29
|
+
const line = text.slice(lineStart, i);
|
|
30
|
+
const lineEnd = i;
|
|
31
|
+
const trimmedStart = line.trimStart();
|
|
32
|
+
const isFenceLine = trimmedStart.startsWith('```') || trimmedStart.startsWith('~~~');
|
|
33
|
+
if (isFenceLine) {
|
|
34
|
+
if (!inFence) {
|
|
35
|
+
inFence = true;
|
|
36
|
+
fenceStart = lineStart;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
inFence = false;
|
|
40
|
+
ranges.push({ start: fenceStart, end: lineEnd });
|
|
41
|
+
fenceStart = -1;
|
|
42
|
+
}
|
|
43
|
+
lineStart = i + 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!inFence) {
|
|
47
|
+
// Only recognize top-level XML (no leading whitespace)
|
|
48
|
+
if (line.length === trimmedStart.length) {
|
|
49
|
+
const openingMatch = OPENING_XML.exec(trimmedStart);
|
|
50
|
+
if (openingMatch) {
|
|
51
|
+
const tagName = openingMatch[1];
|
|
52
|
+
if (tagName) {
|
|
53
|
+
if (tagStack.length === 0)
|
|
54
|
+
xmlStart = lineStart;
|
|
55
|
+
tagStack.push(tagName);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const closingMatch = CLOSING_XML.exec(trimmedStart);
|
|
60
|
+
if (closingMatch &&
|
|
61
|
+
tagStack.length > 0 &&
|
|
62
|
+
tagStack[tagStack.length - 1] === closingMatch[1]) {
|
|
63
|
+
tagStack.pop();
|
|
64
|
+
if (tagStack.length === 0 && xmlStart >= 0) {
|
|
65
|
+
ranges.push({ start: xmlStart, end: lineEnd });
|
|
66
|
+
xmlStart = -1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
lineStart = i + 1;
|
|
73
|
+
}
|
|
74
|
+
return mergeRanges(ranges);
|
|
75
|
+
}
|
|
76
|
+
/** 按 start 升序,丢弃与已保留范围重叠的(嵌套取最外层)。 */
|
|
77
|
+
function mergeRanges(ranges) {
|
|
78
|
+
if (ranges.length <= 1)
|
|
79
|
+
return ranges;
|
|
80
|
+
const sorted = [...ranges].sort((a, b) => a.start - b.start);
|
|
81
|
+
const kept = [];
|
|
82
|
+
let lastEnd = -1;
|
|
83
|
+
for (const range of sorted) {
|
|
84
|
+
if (range.start < lastEnd)
|
|
85
|
+
continue;
|
|
86
|
+
kept.push(range);
|
|
87
|
+
lastEnd = range.end;
|
|
88
|
+
}
|
|
89
|
+
return kept;
|
|
90
|
+
}
|
|
91
|
+
/** 单条消息的 token 估算(优先用缓存的 tokenCount)。 */
|
|
92
|
+
function messageTokens(m) {
|
|
93
|
+
if (m.tokenCount > 0)
|
|
94
|
+
return m.tokenCount;
|
|
95
|
+
let total = 0;
|
|
96
|
+
for (const part of m.content) {
|
|
97
|
+
switch (part._tag) {
|
|
98
|
+
case 'text':
|
|
99
|
+
case 'thinking':
|
|
100
|
+
case 'steering':
|
|
101
|
+
total += estimateTokens(part.text);
|
|
102
|
+
break;
|
|
103
|
+
case 'tool_call':
|
|
104
|
+
total += estimateTokens(JSON.stringify(part.input));
|
|
105
|
+
break;
|
|
106
|
+
case 'tool_result':
|
|
107
|
+
total += estimateTokens(JSON.stringify(part.output));
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return total;
|
|
112
|
+
}
|
|
113
|
+
/** tool_result part 的输出文本。 */
|
|
114
|
+
function toolResultText(output) {
|
|
115
|
+
if (output._tag === 'success')
|
|
116
|
+
return output.output;
|
|
117
|
+
if (output._tag === 'error')
|
|
118
|
+
return output.error;
|
|
119
|
+
if (output._tag === 'truncated')
|
|
120
|
+
return output.output;
|
|
121
|
+
return '';
|
|
122
|
+
}
|
|
123
|
+
/** 收集可 shake 的区域。纯函数,不修改输入。 */
|
|
124
|
+
export function collectShakeRegions(messages, config) {
|
|
125
|
+
const n = messages.length;
|
|
126
|
+
if (n === 0)
|
|
127
|
+
return [];
|
|
128
|
+
// accumulatedAfter[i] = i 之后所有 message 的 token 总和
|
|
129
|
+
const accumulatedAfter = new Array(n);
|
|
130
|
+
let acc = 0;
|
|
131
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
132
|
+
accumulatedAfter[i] = acc;
|
|
133
|
+
const m = messages[i];
|
|
134
|
+
if (m)
|
|
135
|
+
acc += messageTokens(m);
|
|
136
|
+
}
|
|
137
|
+
const regions = [];
|
|
138
|
+
for (let i = 0; i < n; i++) {
|
|
139
|
+
const msg = messages[i];
|
|
140
|
+
if (!msg)
|
|
141
|
+
continue;
|
|
142
|
+
const afterTokens = accumulatedAfter[i] ?? 0;
|
|
143
|
+
const isAfterProtectWindow = afterTokens >= config.protectTokens;
|
|
144
|
+
if (!isAfterProtectWindow)
|
|
145
|
+
continue;
|
|
146
|
+
for (let partIndex = 0; partIndex < msg.content.length; partIndex++) {
|
|
147
|
+
const part = msg.content[partIndex];
|
|
148
|
+
if (!part)
|
|
149
|
+
continue;
|
|
150
|
+
// tool_result 区域
|
|
151
|
+
if (part._tag === 'tool_result') {
|
|
152
|
+
// 已 shaken 跳过
|
|
153
|
+
if ('shakenAt' in part.output && part.output.shakenAt)
|
|
154
|
+
continue;
|
|
155
|
+
// protectedTools 跳过
|
|
156
|
+
if (config.protectedTools.includes(part.tool))
|
|
157
|
+
continue;
|
|
158
|
+
const text = toolResultText(part.output);
|
|
159
|
+
if (text.length === 0)
|
|
160
|
+
continue;
|
|
161
|
+
const tokens = estimateTokens(text);
|
|
162
|
+
if (tokens < config.fenceMinTokens)
|
|
163
|
+
continue;
|
|
164
|
+
regions.push({
|
|
165
|
+
kind: 'toolResult',
|
|
166
|
+
id: `${msg.id}:toolResult:${partIndex}`,
|
|
167
|
+
messageId: msg.id,
|
|
168
|
+
messageIndex: i,
|
|
169
|
+
partIndex,
|
|
170
|
+
tokens,
|
|
171
|
+
originalText: text,
|
|
172
|
+
label: part.tool,
|
|
173
|
+
toolCallId: part.id,
|
|
174
|
+
});
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
// text/thinking block 区域
|
|
178
|
+
if (part._tag === 'text' || part._tag === 'thinking') {
|
|
179
|
+
for (const range of scanTextForBlockRanges(part.text)) {
|
|
180
|
+
const slice = part.text.slice(range.start, range.end);
|
|
181
|
+
if (slice.length === 0)
|
|
182
|
+
continue;
|
|
183
|
+
const tokens = estimateTokens(slice);
|
|
184
|
+
if (tokens < config.fenceMinTokens)
|
|
185
|
+
continue;
|
|
186
|
+
regions.push({
|
|
187
|
+
kind: 'block',
|
|
188
|
+
id: `${msg.id}:block:${partIndex}:${range.start}`,
|
|
189
|
+
messageId: msg.id,
|
|
190
|
+
messageIndex: i,
|
|
191
|
+
partIndex,
|
|
192
|
+
start: range.start,
|
|
193
|
+
end: range.end,
|
|
194
|
+
tokens,
|
|
195
|
+
originalText: slice,
|
|
196
|
+
label: msg.role,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// minSavings 门控
|
|
203
|
+
let savings = 0;
|
|
204
|
+
for (const region of regions)
|
|
205
|
+
savings += Math.max(0, region.tokens - PLACEHOLDER_TOKEN_ESTIMATE);
|
|
206
|
+
if (savings < config.minSavings)
|
|
207
|
+
return [];
|
|
208
|
+
return regions;
|
|
209
|
+
}
|
|
210
|
+
/** 为区域生成占位符文本。 */
|
|
211
|
+
function placeholderFor(region) {
|
|
212
|
+
if (region.kind === 'toolResult') {
|
|
213
|
+
return `[shaken: ${region.label}, ${region.tokens} tokens]`;
|
|
214
|
+
}
|
|
215
|
+
return '[shaken]';
|
|
216
|
+
}
|
|
217
|
+
/** 原位替换选中区域。返回新数组,不修改原数组。 */
|
|
218
|
+
export function applyShakeRegions(messages, regions) {
|
|
219
|
+
if (regions.length === 0)
|
|
220
|
+
return messages;
|
|
221
|
+
// 按 messageId 分组
|
|
222
|
+
const byMessage = new Map();
|
|
223
|
+
for (const region of regions) {
|
|
224
|
+
const list = byMessage.get(region.messageId) ?? [];
|
|
225
|
+
list.push(region);
|
|
226
|
+
byMessage.set(region.messageId, list);
|
|
227
|
+
}
|
|
228
|
+
return messages.map((msg) => {
|
|
229
|
+
const msgRegions = byMessage.get(msg.id);
|
|
230
|
+
if (!msgRegions)
|
|
231
|
+
return msg;
|
|
232
|
+
// 深拷贝 content(保证不修改原对象)
|
|
233
|
+
const newContent = structuredClone(msg.content);
|
|
234
|
+
const now = Date.now();
|
|
235
|
+
// toolResult 区域:替换 output 文本
|
|
236
|
+
for (const region of msgRegions) {
|
|
237
|
+
if (region.kind !== 'toolResult')
|
|
238
|
+
continue;
|
|
239
|
+
const part = newContent[region.partIndex];
|
|
240
|
+
if (!part)
|
|
241
|
+
continue;
|
|
242
|
+
if (part._tag !== 'tool_result')
|
|
243
|
+
continue;
|
|
244
|
+
const placeholder = placeholderFor(region);
|
|
245
|
+
if (part.output._tag === 'success') {
|
|
246
|
+
part.output = { ...part.output, output: placeholder, shakenAt: now };
|
|
247
|
+
}
|
|
248
|
+
else if (part.output._tag === 'error') {
|
|
249
|
+
part.output = { ...part.output, error: placeholder, shakenAt: now };
|
|
250
|
+
}
|
|
251
|
+
else if (part.output._tag === 'truncated') {
|
|
252
|
+
part.output = { ...part.output, output: placeholder, shakenAt: now };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// block 区域:按 partIndex 分组,同一 text 内按 start 降序 splice
|
|
256
|
+
const blockByPart = new Map();
|
|
257
|
+
for (const region of msgRegions) {
|
|
258
|
+
if (region.kind !== 'block')
|
|
259
|
+
continue;
|
|
260
|
+
const list = blockByPart.get(region.partIndex) ?? [];
|
|
261
|
+
list.push(region);
|
|
262
|
+
blockByPart.set(region.partIndex, list);
|
|
263
|
+
}
|
|
264
|
+
for (const [partIndex, blockRegions] of blockByPart) {
|
|
265
|
+
const part = newContent[partIndex];
|
|
266
|
+
if (!part || (part._tag !== 'text' && part._tag !== 'thinking'))
|
|
267
|
+
continue;
|
|
268
|
+
const sorted = [...blockRegions].sort((a, b) => b.start - a.start);
|
|
269
|
+
let text = part.text;
|
|
270
|
+
for (const br of sorted) {
|
|
271
|
+
text = text.slice(0, br.start) + placeholderFor(br) + text.slice(br.end);
|
|
272
|
+
}
|
|
273
|
+
part.text = text;
|
|
274
|
+
}
|
|
275
|
+
return { ...msg, content: newContent };
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/** 区域转 API 视图。protectWindow 窗口内的标记 isAfterProtectWindow=false。 */
|
|
279
|
+
export function toRegionViews(regions, config, messages) {
|
|
280
|
+
const n = messages.length;
|
|
281
|
+
const accumulatedAfter = new Array(n);
|
|
282
|
+
let acc = 0;
|
|
283
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
284
|
+
accumulatedAfter[i] = acc;
|
|
285
|
+
const m = messages[i];
|
|
286
|
+
if (m)
|
|
287
|
+
acc += messageTokens(m);
|
|
288
|
+
}
|
|
289
|
+
return regions.map((region) => ({
|
|
290
|
+
id: region.id,
|
|
291
|
+
kind: region.kind,
|
|
292
|
+
messageId: region.messageId,
|
|
293
|
+
messageIndex: region.messageIndex,
|
|
294
|
+
partIndex: region.partIndex,
|
|
295
|
+
tokens: region.tokens,
|
|
296
|
+
label: region.label,
|
|
297
|
+
preview: region.originalText.slice(0, 200),
|
|
298
|
+
placeholder: region.kind === 'toolResult'
|
|
299
|
+
? `[shaken: ${region.label}, ${region.tokens} tokens]`
|
|
300
|
+
: '[shaken]',
|
|
301
|
+
isAfterProtectWindow: (accumulatedAfter[region.messageIndex] ?? 0) >= config.protectTokens,
|
|
302
|
+
...(region.kind === 'toolResult' ? { toolCallId: region.toolCallId } : {}),
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import type { FileSnapshot } from './types.js';
|
|
3
|
+
/** Create or update a file snapshot. Always creates a new version. Returns the snapshot id. */
|
|
4
|
+
declare function upsertFileSnapshot(handle: DB, sessionId: string, filePath: string, content: string): Promise<string>;
|
|
5
|
+
/** Get all snapshots for a session. */
|
|
6
|
+
declare function getFileSnapshots(handle: DB, sessionId: string): Promise<FileSnapshot[]>;
|
|
7
|
+
/** Get the latest snapshot for a specific file, or null. */
|
|
8
|
+
declare function getLatestFileSnapshot(handle: DB, sessionId: string, filePath: string): Promise<FileSnapshot | null>;
|
|
9
|
+
/** Quick check: return cached file content if a snapshot exists, else null. */
|
|
10
|
+
declare function checkFileSnapshot(handle: DB, sessionId: string, filePath: string): Promise<string | null>;
|
|
11
|
+
export { checkFileSnapshot, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { and, desc, eq } from 'drizzle-orm';
|
|
3
|
+
import { fileSnapshots } from '../db/schema.js';
|
|
4
|
+
import { estimateTokens } from './token.js';
|
|
5
|
+
function rowToSnapshot(row) {
|
|
6
|
+
const createdAt = row.createdAt instanceof Date ? row.createdAt.getTime() : new Date(row.createdAt).getTime();
|
|
7
|
+
return {
|
|
8
|
+
id: row.id,
|
|
9
|
+
sessionId: row.sessionId,
|
|
10
|
+
filePath: row.filePath,
|
|
11
|
+
content: row.content,
|
|
12
|
+
contentHash: row.contentHash,
|
|
13
|
+
tokenCount: row.tokenCount ?? 0,
|
|
14
|
+
version: row.version ?? 1,
|
|
15
|
+
createdAt,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Create or update a file snapshot. Always creates a new version. Returns the snapshot id. */
|
|
19
|
+
async function upsertFileSnapshot(handle, sessionId, filePath, content) {
|
|
20
|
+
const contentHash = createHash('sha256').update(content).digest('hex');
|
|
21
|
+
const tokenCount = estimateTokens(content);
|
|
22
|
+
const [existing] = await handle.db
|
|
23
|
+
.select()
|
|
24
|
+
.from(fileSnapshots)
|
|
25
|
+
.where(and(eq(fileSnapshots.sessionId, sessionId), eq(fileSnapshots.filePath, filePath)))
|
|
26
|
+
.orderBy(desc(fileSnapshots.version))
|
|
27
|
+
.limit(1);
|
|
28
|
+
const version = (existing?.version ?? 0) + 1;
|
|
29
|
+
const [row] = await handle.db
|
|
30
|
+
.insert(fileSnapshots)
|
|
31
|
+
.values({ sessionId, filePath, content, contentHash, tokenCount, version })
|
|
32
|
+
.returning();
|
|
33
|
+
if (!row)
|
|
34
|
+
throw new Error('Failed to insert file snapshot');
|
|
35
|
+
return row.id;
|
|
36
|
+
}
|
|
37
|
+
/** Get all snapshots for a session. */
|
|
38
|
+
async function getFileSnapshots(handle, sessionId) {
|
|
39
|
+
const rows = await handle.db
|
|
40
|
+
.select()
|
|
41
|
+
.from(fileSnapshots)
|
|
42
|
+
.where(eq(fileSnapshots.sessionId, sessionId));
|
|
43
|
+
return rows.map(rowToSnapshot);
|
|
44
|
+
}
|
|
45
|
+
/** Get the latest snapshot for a specific file, or null. */
|
|
46
|
+
async function getLatestFileSnapshot(handle, sessionId, filePath) {
|
|
47
|
+
const [row] = await handle.db
|
|
48
|
+
.select()
|
|
49
|
+
.from(fileSnapshots)
|
|
50
|
+
.where(and(eq(fileSnapshots.sessionId, sessionId), eq(fileSnapshots.filePath, filePath)))
|
|
51
|
+
.orderBy(desc(fileSnapshots.version))
|
|
52
|
+
.limit(1);
|
|
53
|
+
return row ? rowToSnapshot(row) : null;
|
|
54
|
+
}
|
|
55
|
+
/** Quick check: return cached file content if a snapshot exists, else null. */
|
|
56
|
+
async function checkFileSnapshot(handle, sessionId, filePath) {
|
|
57
|
+
const snapshot = await getLatestFileSnapshot(handle, sessionId, filePath);
|
|
58
|
+
return snapshot?.content ?? null;
|
|
59
|
+
}
|
|
60
|
+
export { checkFileSnapshot, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import type { CompactionResult, SquashConfig, Summarizer } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Squash the most recent `count` messages into a summary, keeping `keepRecent` verbatim.
|
|
5
|
+
* Similar to `git rebase -i + squash`: compresses recent interactions while preserving
|
|
6
|
+
* the conversation prefix and a small tail for cache stability.
|
|
7
|
+
*/
|
|
8
|
+
declare function squashRecent(handle: DB, sessionId: string, count: number, summarizer: Summarizer, config?: Partial<SquashConfig>): Promise<CompactionResult>;
|
|
9
|
+
export { squashRecent };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { generateId } from '../shared/index.js';
|
|
2
|
+
import { archiveOriginalEntries } from './archive.js';
|
|
3
|
+
import { extractHotFiles } from './compaction.js';
|
|
4
|
+
import { deleteEntriesByIds, getMessages, insertEntry } from './message.js';
|
|
5
|
+
import { upsertFileSnapshot } from './snapshot.js';
|
|
6
|
+
import { estimateTokens } from './token.js';
|
|
7
|
+
const DEFAULT_SQUASH_CONFIG = {
|
|
8
|
+
keepRecent: 2,
|
|
9
|
+
preserveFileSnapshots: true,
|
|
10
|
+
archiveOriginal: true,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Squash the most recent `count` messages into a summary, keeping `keepRecent` verbatim.
|
|
14
|
+
* Similar to `git rebase -i + squash`: compresses recent interactions while preserving
|
|
15
|
+
* the conversation prefix and a small tail for cache stability.
|
|
16
|
+
*/
|
|
17
|
+
async function squashRecent(handle, sessionId, count, summarizer, config) {
|
|
18
|
+
const cfg = { ...DEFAULT_SQUASH_CONFIG, ...config };
|
|
19
|
+
const messages = await getMessages(handle, sessionId);
|
|
20
|
+
if (messages.length < count + cfg.keepRecent) {
|
|
21
|
+
return { compacted: false, reason: 'too_few_messages' };
|
|
22
|
+
}
|
|
23
|
+
const tailStart = messages.length - cfg.keepRecent;
|
|
24
|
+
const squashStart = messages.length - count;
|
|
25
|
+
const toSquash = messages.slice(squashStart, tailStart);
|
|
26
|
+
const keepTail = messages.slice(tailStart);
|
|
27
|
+
if (toSquash.length === 0) {
|
|
28
|
+
return { compacted: false, reason: 'nothing_to_compact' };
|
|
29
|
+
}
|
|
30
|
+
const history = toSquash
|
|
31
|
+
.map((m) => `[${m.role}] ${m.content.map((p) => (p._tag === 'text' ? p.text : JSON.stringify(p))).join(' ')}`)
|
|
32
|
+
.join('\n');
|
|
33
|
+
const prompt = `将以下最近的交互压缩为简洁摘要,保留关键决策和上下文,丢弃冗余细节。
|
|
34
|
+
重点保留:修改了哪些文件、做了什么决策、当前进度、下一步计划。
|
|
35
|
+
|
|
36
|
+
## 最近操作
|
|
37
|
+
[简要描述做了什么]
|
|
38
|
+
|
|
39
|
+
## 修改的文件
|
|
40
|
+
- path: 变更描述
|
|
41
|
+
|
|
42
|
+
## 关键决策
|
|
43
|
+
[做出的重要决策]
|
|
44
|
+
|
|
45
|
+
## 当前状态
|
|
46
|
+
[进度和下一步]
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
交互历史:
|
|
50
|
+
${history}`;
|
|
51
|
+
const summary = await summarizer(prompt);
|
|
52
|
+
const squashEntryId = generateId();
|
|
53
|
+
const archiveId = cfg.archiveOriginal
|
|
54
|
+
? await archiveOriginalEntries(handle, sessionId, toSquash, 'squash', summary, squashEntryId)
|
|
55
|
+
: generateId();
|
|
56
|
+
const fileSnapshotIds = [];
|
|
57
|
+
if (cfg.preserveFileSnapshots) {
|
|
58
|
+
const hotFiles = extractHotFiles(toSquash);
|
|
59
|
+
for (const file of hotFiles) {
|
|
60
|
+
const id = await upsertFileSnapshot(handle, sessionId, file.path, file.content);
|
|
61
|
+
fileSnapshotIds.push(id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await deleteEntriesByIds(handle, toSquash.map((m) => m.id));
|
|
65
|
+
await insertEntry(handle, {
|
|
66
|
+
id: squashEntryId,
|
|
67
|
+
sessionId,
|
|
68
|
+
tag: 'squash',
|
|
69
|
+
content: {
|
|
70
|
+
summary,
|
|
71
|
+
squashedEntryIds: toSquash.map((m) => m.id),
|
|
72
|
+
archiveId,
|
|
73
|
+
},
|
|
74
|
+
tokenCount: estimateTokens(summary),
|
|
75
|
+
// Position at the first squashed message's timestamp so it sorts between
|
|
76
|
+
// the prefix and the kept tail under createdAt-ascending order.
|
|
77
|
+
createdAt: toSquash[0] ? new Date(toSquash[0].createdAt) : new Date(),
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
compacted: true,
|
|
81
|
+
summary,
|
|
82
|
+
archiveId,
|
|
83
|
+
fileSnapshots: fileSnapshotIds,
|
|
84
|
+
compactedCount: toSquash.length,
|
|
85
|
+
keptCount: keepTail.length,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export { squashRecent };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MessageContent } from '../shared/types/message.js';
|
|
2
|
+
/**
|
|
3
|
+
* CJK-aware token estimate.
|
|
4
|
+
* Chinese/CJK characters ≈ 2 tokens each (denser encoding).
|
|
5
|
+
* Other characters ≈ 4 chars/token (standard heuristic).
|
|
6
|
+
*/
|
|
7
|
+
declare const estimateTokens: (text: string) => number;
|
|
8
|
+
/** Sum token estimates across all parts of a message's content array. */
|
|
9
|
+
declare const estimateMessageTokens: (content: MessageContent[]) => number;
|
|
10
|
+
export { estimateMessageTokens, estimateTokens };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CJK-aware token estimate.
|
|
3
|
+
* Chinese/CJK characters ≈ 2 tokens each (denser encoding).
|
|
4
|
+
* Other characters ≈ 4 chars/token (standard heuristic).
|
|
5
|
+
*/
|
|
6
|
+
const estimateTokens = (text) => {
|
|
7
|
+
if (text.length === 0)
|
|
8
|
+
return 0;
|
|
9
|
+
const cjkCount = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g)?.length ?? 0;
|
|
10
|
+
const otherCount = text.length - cjkCount;
|
|
11
|
+
return Math.ceil(cjkCount * 2 + otherCount / 4);
|
|
12
|
+
};
|
|
13
|
+
/** Sum token estimates across all parts of a message's content array. */
|
|
14
|
+
const estimateMessageTokens = (content) => {
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (const part of content) {
|
|
17
|
+
switch (part._tag) {
|
|
18
|
+
case 'text':
|
|
19
|
+
case 'thinking':
|
|
20
|
+
case 'steering':
|
|
21
|
+
total += estimateTokens(part.text);
|
|
22
|
+
break;
|
|
23
|
+
case 'tool_call':
|
|
24
|
+
total += estimateTokens(JSON.stringify(part.input));
|
|
25
|
+
break;
|
|
26
|
+
case 'tool_result':
|
|
27
|
+
total += estimateTokens(JSON.stringify(part.output));
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return total;
|
|
32
|
+
};
|
|
33
|
+
export { estimateMessageTokens, estimateTokens };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { MessageRole } from '../shared/types/base.js';
|
|
2
|
+
import type { ChatMessage } from '../shared/types/llm.js';
|
|
3
|
+
import type { Message, MessageContent, Session, SessionMetadata } from '../shared/types/message.js';
|
|
4
|
+
export type { MessageRole } from '../shared/types/base.js';
|
|
5
|
+
export type { ChatMessage, Message, MessageContent, Session, SessionMetadata };
|
|
6
|
+
/** A compaction summary entry — replaces compacted messages with a summary. */
|
|
7
|
+
type CompactionEntry = {
|
|
8
|
+
_tag: 'compaction';
|
|
9
|
+
id: string;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
summary: string;
|
|
12
|
+
originalEntryIds: string[];
|
|
13
|
+
archiveId: string;
|
|
14
|
+
tokenCount: number;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
};
|
|
17
|
+
/** A squash entry — compresses recent interactions into a summary. */
|
|
18
|
+
type SquashEntry = {
|
|
19
|
+
_tag: 'squash';
|
|
20
|
+
id: string;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
summary: string;
|
|
23
|
+
squashedEntryIds: string[];
|
|
24
|
+
archiveId: string;
|
|
25
|
+
tokenCount: number;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
};
|
|
28
|
+
/** Records that a session was forked from another. */
|
|
29
|
+
type BranchSummaryEntry = {
|
|
30
|
+
_tag: 'branch_summary';
|
|
31
|
+
id: string;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
summary: string;
|
|
34
|
+
sourceSessionId: string;
|
|
35
|
+
createdAt: number;
|
|
36
|
+
};
|
|
37
|
+
/** A steering instruction injected mid-conversation. */
|
|
38
|
+
type SteeringEntry = {
|
|
39
|
+
_tag: 'steering';
|
|
40
|
+
id: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
content: string;
|
|
43
|
+
createdAt: number;
|
|
44
|
+
};
|
|
45
|
+
/** All session entries, ordered chronologically. Narrow via `'_tag' in entry`. */
|
|
46
|
+
type SessionEntry = Message | CompactionEntry | SquashEntry | BranchSummaryEntry | SteeringEntry;
|
|
47
|
+
/** Input for `appendMessage` — role + content; id/timestamps auto-generated. */
|
|
48
|
+
type MessageInput = {
|
|
49
|
+
role: MessageRole;
|
|
50
|
+
content: MessageContent[];
|
|
51
|
+
tokenCount?: number;
|
|
52
|
+
};
|
|
53
|
+
/** A hot file detected from tool-call history. */
|
|
54
|
+
type HotFile = {
|
|
55
|
+
path: string;
|
|
56
|
+
content: string;
|
|
57
|
+
tokenCount: number;
|
|
58
|
+
accessCount: number;
|
|
59
|
+
};
|
|
60
|
+
/** Configuration for compaction. */
|
|
61
|
+
type CompactionConfig = {
|
|
62
|
+
/** Token budget for the recent-message keep window. Messages are retained
|
|
63
|
+
* from the newest backward until their cumulative token count exceeds this
|
|
64
|
+
* value; everything older is summarized. Drives compaction by tokens, not
|
|
65
|
+
* by a fixed message count. */
|
|
66
|
+
keepRecentTokens: number;
|
|
67
|
+
preserveSnapshots: boolean;
|
|
68
|
+
};
|
|
69
|
+
/** Result of a compaction or squash operation. */
|
|
70
|
+
type CompactionResult = {
|
|
71
|
+
compacted: true;
|
|
72
|
+
summary: string;
|
|
73
|
+
archiveId: string;
|
|
74
|
+
fileSnapshots: string[];
|
|
75
|
+
compactedCount: number;
|
|
76
|
+
keptCount: number;
|
|
77
|
+
} | {
|
|
78
|
+
compacted: false;
|
|
79
|
+
reason: 'too_few_messages' | 'nothing_to_compact';
|
|
80
|
+
};
|
|
81
|
+
/** Configuration for squash. */
|
|
82
|
+
type SquashConfig = {
|
|
83
|
+
keepRecent: number;
|
|
84
|
+
preserveFileSnapshots: boolean;
|
|
85
|
+
archiveOriginal: boolean;
|
|
86
|
+
};
|
|
87
|
+
/** Injected summarizer — the session layer does NOT import the LLM package. */
|
|
88
|
+
type Summarizer = (prompt: string) => Promise<string>;
|
|
89
|
+
/** Parsed `@[archive:<id>]` / `@[squash:<n>]` reference. */
|
|
90
|
+
type ArchiveRef = {
|
|
91
|
+
type: 'archive' | 'squash';
|
|
92
|
+
id: string;
|
|
93
|
+
};
|
|
94
|
+
/** A node in the session tree. */
|
|
95
|
+
type SessionTreeNode = {
|
|
96
|
+
session: Session;
|
|
97
|
+
children: SessionTreeNode[];
|
|
98
|
+
};
|
|
99
|
+
/** A decoded compaction archive row. */
|
|
100
|
+
type CompactionArchive = {
|
|
101
|
+
id: string;
|
|
102
|
+
sessionId: string;
|
|
103
|
+
compactionId: string;
|
|
104
|
+
archiveType: 'compaction' | 'squash' | 'shake';
|
|
105
|
+
originalEntries: SessionEntry[];
|
|
106
|
+
summary: string;
|
|
107
|
+
tokenCount: number;
|
|
108
|
+
searchableText: string;
|
|
109
|
+
createdAt: number;
|
|
110
|
+
};
|
|
111
|
+
/** A decoded file snapshot row. */
|
|
112
|
+
type FileSnapshot = {
|
|
113
|
+
id: string;
|
|
114
|
+
sessionId: string;
|
|
115
|
+
filePath: string;
|
|
116
|
+
content: string;
|
|
117
|
+
contentHash: string;
|
|
118
|
+
tokenCount: number;
|
|
119
|
+
version: number;
|
|
120
|
+
createdAt: number;
|
|
121
|
+
};
|
|
122
|
+
export type { ArchiveRef, BranchSummaryEntry, CompactionArchive, CompactionConfig, CompactionEntry, CompactionResult, FileSnapshot, HotFile, MessageInput, SessionEntry, SessionTreeNode, SquashConfig, SquashEntry, SteeringEntry, Summarizer, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generateId, now } from './utils.js';
|