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,173 @@
|
|
|
1
|
+
import { getEntries } from './message.js';
|
|
2
|
+
import { getFileSnapshots } from './snapshot.js';
|
|
3
|
+
/**
|
|
4
|
+
* Drop tool messages whose id cannot be paired with an assistant tool_call,
|
|
5
|
+
* and inject synthetic tool results for orphan tool_calls (assistant 发出了
|
|
6
|
+
* tool_call 但无对应 tool result——服务重启中断场景)。
|
|
7
|
+
*
|
|
8
|
+
* OpenAI Chat 协议要求每个 role=tool 消息必须有 tool_call_id,且必须匹配
|
|
9
|
+
* 前面某条 assistant 消息的 tool_calls[].id。部分输出不规范的 provider
|
|
10
|
+
* 会产生空 id 的 tool_call/tool_result 碎片;这些碎片发回 provider 会触发
|
|
11
|
+
* "invalid tool_call_id"。这里在重建上下文时丢弃:
|
|
12
|
+
* - assistant.toolCalls 里 id 为空的项(连带丢弃其后无法配对的 tool result)
|
|
13
|
+
* - role=tool 但 toolCallId 为空、或找不到对应 assistant tool_call 的消息
|
|
14
|
+
* 正常(完整配对)的数据不受影响。
|
|
15
|
+
*
|
|
16
|
+
* 新增:assistant 有有效 tool_call 但后续无匹配 tool result(服务重启中断)时,
|
|
17
|
+
* 注入合成 tool result 告知模型工具被中断,而非丢弃 tool_call 让模型
|
|
18
|
+
* 盲目重试可能已产生副作用的非幂等工具。
|
|
19
|
+
*/
|
|
20
|
+
function sanitizeToolPairs(messages) {
|
|
21
|
+
// 收集所有有效的 assistant tool_call id(非空)
|
|
22
|
+
const validCallIds = new Set();
|
|
23
|
+
for (const m of messages) {
|
|
24
|
+
if (m.role === 'assistant' && m.toolCalls) {
|
|
25
|
+
for (const tc of m.toolCalls) {
|
|
26
|
+
if (tc.id)
|
|
27
|
+
validCallIds.add(tc.id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// 收集已有 tool result 配对的 tool_call id(用于检测孤儿 tool_call)
|
|
32
|
+
const answeredCallIds = new Set();
|
|
33
|
+
for (const m of messages) {
|
|
34
|
+
if (m.role === 'tool' && m.toolCallId && validCallIds.has(m.toolCallId)) {
|
|
35
|
+
answeredCallIds.add(m.toolCallId);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const result = [];
|
|
39
|
+
for (const m of messages) {
|
|
40
|
+
if (m.role === 'assistant' && m.toolCalls) {
|
|
41
|
+
// 丢弃 id 为空的 tool_call 项;若删空了则整个 toolCalls 移除
|
|
42
|
+
const filtered = m.toolCalls.filter((tc) => tc.id);
|
|
43
|
+
if (filtered.length === 0) {
|
|
44
|
+
result.push({ role: 'assistant', content: m.content });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
result.push({ ...m, toolCalls: filtered });
|
|
48
|
+
// 为无 result 配对的 tool_call 注入合成 result(服务中断场景)
|
|
49
|
+
for (const tc of filtered) {
|
|
50
|
+
if (!answeredCallIds.has(tc.id)) {
|
|
51
|
+
result.push({
|
|
52
|
+
role: 'tool',
|
|
53
|
+
toolCallId: tc.id,
|
|
54
|
+
content: JSON.stringify({
|
|
55
|
+
error: 'Tool execution was interrupted by server restart',
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (m.role === 'tool') {
|
|
64
|
+
// toolCallId 为空、或找不到对应 assistant tool_call:丢弃整条 tool 消息
|
|
65
|
+
if (!m.toolCallId || !validCallIds.has(m.toolCallId)) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
result.push(m);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/** Convert a stored Message to a protocol-level ChatMessage for the LLM. */
|
|
74
|
+
function messageToChatMessage(msg) {
|
|
75
|
+
const textParts = msg.content
|
|
76
|
+
.filter((p) => p._tag === 'text' || p._tag === 'thinking')
|
|
77
|
+
.map((p) => (p._tag === 'thinking' ? `<think>${p.text}</think>` : p.text))
|
|
78
|
+
.join('');
|
|
79
|
+
const toolCalls = msg.content
|
|
80
|
+
.filter((p) => p._tag === 'tool_call')
|
|
81
|
+
.map((p) => ({
|
|
82
|
+
id: p.id,
|
|
83
|
+
name: p.tool,
|
|
84
|
+
arguments: JSON.stringify(p.input),
|
|
85
|
+
}));
|
|
86
|
+
const toolResultPart = msg.content.find((p) => p._tag === 'tool_result');
|
|
87
|
+
const imageParts = msg.content.filter((p) => p._tag === 'image');
|
|
88
|
+
// 含图片:构建多模态 content 数组(text 在前,image 在后)
|
|
89
|
+
if (imageParts.length > 0) {
|
|
90
|
+
const parts = [];
|
|
91
|
+
if (textParts)
|
|
92
|
+
parts.push({ type: 'text', text: textParts });
|
|
93
|
+
for (const img of imageParts) {
|
|
94
|
+
if (img._tag !== 'image')
|
|
95
|
+
continue;
|
|
96
|
+
parts.push({ type: 'image', mediaType: img.mediaType, data: img.data });
|
|
97
|
+
}
|
|
98
|
+
const multimodal = { role: msg.role, content: parts };
|
|
99
|
+
if (toolCalls.length > 0)
|
|
100
|
+
multimodal.toolCalls = toolCalls;
|
|
101
|
+
return multimodal;
|
|
102
|
+
}
|
|
103
|
+
// 无图片:保持原纯字符串逻辑(零回归)
|
|
104
|
+
const chat = {
|
|
105
|
+
role: msg.role,
|
|
106
|
+
content: textParts || (toolResultPart ? JSON.stringify(toolResultPart.output) : ''),
|
|
107
|
+
};
|
|
108
|
+
if (toolCalls.length > 0) {
|
|
109
|
+
chat.toolCalls = toolCalls;
|
|
110
|
+
}
|
|
111
|
+
if (toolResultPart && toolResultPart._tag === 'tool_result') {
|
|
112
|
+
chat.toolCallId = toolResultPart.id;
|
|
113
|
+
chat.content = JSON.stringify(toolResultPart.output);
|
|
114
|
+
}
|
|
115
|
+
return chat;
|
|
116
|
+
}
|
|
117
|
+
/** Convert all session entries (messages + special) to ChatMessage[] for the LLM. */
|
|
118
|
+
function entriesToChatMessages(entries, snapshots) {
|
|
119
|
+
const messages = [];
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (!('_tag' in entry)) {
|
|
122
|
+
messages.push(messageToChatMessage(entry));
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
switch (entry._tag) {
|
|
126
|
+
case 'compaction':
|
|
127
|
+
case 'squash':
|
|
128
|
+
messages.push({ role: 'system', content: `[Compacted History]\n${entry.summary}` });
|
|
129
|
+
break;
|
|
130
|
+
case 'branch_summary':
|
|
131
|
+
messages.push({ role: 'system', content: `[Branch Context]\n${entry.summary}` });
|
|
132
|
+
break;
|
|
133
|
+
case 'steering':
|
|
134
|
+
messages.push({ role: 'system', content: entry.content });
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return injectSnapshots(sanitizeToolPairs(messages), snapshots);
|
|
139
|
+
}
|
|
140
|
+
/** Inject file snapshot block after the first message (preserves cache prefix). */
|
|
141
|
+
function injectSnapshots(messages, snapshots) {
|
|
142
|
+
if (snapshots.length === 0)
|
|
143
|
+
return messages;
|
|
144
|
+
// Keep only the highest-version snapshot per filePath (avoid stale duplicates).
|
|
145
|
+
const latestByPath = new Map();
|
|
146
|
+
for (const s of snapshots) {
|
|
147
|
+
const prev = latestByPath.get(s.filePath);
|
|
148
|
+
if (!prev || s.version > prev.version)
|
|
149
|
+
latestByPath.set(s.filePath, s);
|
|
150
|
+
}
|
|
151
|
+
const block = [...latestByPath.values()]
|
|
152
|
+
.map((s) => `[Cached File: ${s.filePath}]\n\`\`\`\n${s.content}\n\`\`\``)
|
|
153
|
+
.join('\n\n');
|
|
154
|
+
const snapshotMessage = {
|
|
155
|
+
role: 'system',
|
|
156
|
+
content: `[Active File Snapshots — DO NOT re-read these files]\n${block}`,
|
|
157
|
+
};
|
|
158
|
+
if (messages.length === 0)
|
|
159
|
+
return [snapshotMessage];
|
|
160
|
+
const first = messages[0];
|
|
161
|
+
if (!first)
|
|
162
|
+
return [snapshotMessage];
|
|
163
|
+
return [first, snapshotMessage, ...messages.slice(1)];
|
|
164
|
+
}
|
|
165
|
+
/** Get the full session context: all entries + file snapshots. */
|
|
166
|
+
async function getSessionContext(handle, sessionId) {
|
|
167
|
+
const [entries, snapshots] = await Promise.all([
|
|
168
|
+
getEntries(handle, sessionId),
|
|
169
|
+
getFileSnapshots(handle, sessionId),
|
|
170
|
+
]);
|
|
171
|
+
return { entries, snapshots };
|
|
172
|
+
}
|
|
173
|
+
export { entriesToChatMessages, getSessionContext, injectSnapshots, messageToChatMessage };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { archiveOriginalEntries, getArchive, getArchiveOriginalEntries, parseArchiveReference, resolveArchiveReference, searchArchives, } from './archive.js';
|
|
2
|
+
export { forkSession, getBranches, getTree } from './branch.js';
|
|
3
|
+
export { buildCompactionPrompt, compactSession, extractHotFiles, findSafeCutPoint, } from './compaction.js';
|
|
4
|
+
export { entriesToChatMessages, getSessionContext, injectSnapshots, messageToChatMessage, } from './context.js';
|
|
5
|
+
export { appendMessage, deleteEntriesByIds, deleteMessagesAfter, getEntries, getMessageCount, getMessages, insertEntry, } from './message.js';
|
|
6
|
+
export { createSession, deleteSession, getLLMSegments, getSession, listSessions, migrateLegacyDetails, saveLLMSegments, segmentFingerprint, touchSession, updateSessionTitle, } from './session.js';
|
|
7
|
+
export { checkFileSnapshot, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot, } from './snapshot.js';
|
|
8
|
+
export { squashRecent } from './squash.js';
|
|
9
|
+
export { estimateMessageTokens, estimateTokens } from './token.js';
|
|
10
|
+
export type { ArchiveRef, BranchSummaryEntry, CompactionArchive, CompactionConfig, CompactionEntry, CompactionResult, FileSnapshot, HotFile, MessageInput, SessionEntry, SessionTreeNode, SquashConfig, SquashEntry, SteeringEntry, Summarizer, } from './types.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Session layer: conversation persistence, branching, compaction, and context reconstruction.
|
|
2
|
+
export { archiveOriginalEntries, getArchive, getArchiveOriginalEntries, parseArchiveReference, resolveArchiveReference, searchArchives, } from './archive.js';
|
|
3
|
+
export { forkSession, getBranches, getTree } from './branch.js';
|
|
4
|
+
export { buildCompactionPrompt, compactSession, extractHotFiles, findSafeCutPoint, } from './compaction.js';
|
|
5
|
+
export { entriesToChatMessages, getSessionContext, injectSnapshots, messageToChatMessage, } from './context.js';
|
|
6
|
+
export { appendMessage, deleteEntriesByIds, deleteMessagesAfter, getEntries, getMessageCount, getMessages, insertEntry, } from './message.js';
|
|
7
|
+
export { createSession, deleteSession, getLLMSegments, getSession, listSessions, migrateLegacyDetails, saveLLMSegments, segmentFingerprint, touchSession, updateSessionTitle, } from './session.js';
|
|
8
|
+
export { checkFileSnapshot, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot, } from './snapshot.js';
|
|
9
|
+
export { squashRecent } from './squash.js';
|
|
10
|
+
export { estimateMessageTokens, estimateTokens } from './token.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import { sessionEntries } from '../db/schema.js';
|
|
3
|
+
import type { Message } from '../shared/types/message.js';
|
|
4
|
+
import type { MessageInput, SessionEntry } from './types.js';
|
|
5
|
+
/** Append a message to a session. Returns the stored Message with generated id/timestamp. */
|
|
6
|
+
declare function appendMessage(handle: DB, sessionId: string, input: MessageInput): Promise<Message>;
|
|
7
|
+
/** Get messages for a session (tag='message' only), ordered chronologically. */
|
|
8
|
+
declare function getMessages(handle: DB, sessionId: string, opts?: {
|
|
9
|
+
limit?: number;
|
|
10
|
+
offset?: number;
|
|
11
|
+
}): Promise<Message[]>;
|
|
12
|
+
/** Count messages in a session. */
|
|
13
|
+
declare function getMessageCount(handle: DB, sessionId: string): Promise<number>;
|
|
14
|
+
/** Delete all messages after the given 0-based index (keeps 0..index inclusive). */
|
|
15
|
+
declare function deleteMessagesAfter(handle: DB, sessionId: string, messageIndex: number): Promise<void>;
|
|
16
|
+
/** Low-level: get ALL entries (messages + special) in chronological order. */
|
|
17
|
+
declare function getEntries(handle: DB, sessionId: string): Promise<SessionEntry[]>;
|
|
18
|
+
/** Low-level: delete entries by id. */
|
|
19
|
+
declare function deleteEntriesByIds(handle: DB, ids: string[]): Promise<void>;
|
|
20
|
+
/** Low-level: insert a raw entry row (for compaction/squash/branch_summary/steering). */
|
|
21
|
+
declare function insertEntry(handle: DB, values: typeof sessionEntries.$inferInsert): Promise<typeof sessionEntries.$inferSelect>;
|
|
22
|
+
export { appendMessage, deleteEntriesByIds, deleteMessagesAfter, getEntries, getMessageCount, getMessages, insertEntry, };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { and, asc, count, eq, inArray } from 'drizzle-orm';
|
|
2
|
+
import { sessionEntries } from '../db/schema.js';
|
|
3
|
+
import { generateId } from '../shared/index.js';
|
|
4
|
+
import { touchSession } from './session.js';
|
|
5
|
+
import { estimateMessageTokens } from './token.js';
|
|
6
|
+
/** Convert a Date-like DB value to epoch milliseconds. */
|
|
7
|
+
function toEpochMs(value) {
|
|
8
|
+
if (value instanceof Date)
|
|
9
|
+
return value.getTime();
|
|
10
|
+
if (typeof value === 'number')
|
|
11
|
+
return value;
|
|
12
|
+
return new Date(value).getTime();
|
|
13
|
+
}
|
|
14
|
+
/** Convert a message-tagged row to a Message. */
|
|
15
|
+
function rowToMessage(row) {
|
|
16
|
+
return {
|
|
17
|
+
id: row.id,
|
|
18
|
+
sessionId: row.sessionId,
|
|
19
|
+
role: row.role,
|
|
20
|
+
content: row.content,
|
|
21
|
+
tokenCount: row.tokenCount ?? 0,
|
|
22
|
+
createdAt: toEpochMs(row.createdAt),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Convert any row to a SessionEntry (dispatch on tag). */
|
|
26
|
+
function rowToEntry(row) {
|
|
27
|
+
if (row.tag === 'message') {
|
|
28
|
+
return rowToMessage(row);
|
|
29
|
+
}
|
|
30
|
+
const content = row.content;
|
|
31
|
+
const createdAt = toEpochMs(row.createdAt);
|
|
32
|
+
switch (row.tag) {
|
|
33
|
+
case 'compaction':
|
|
34
|
+
return {
|
|
35
|
+
_tag: 'compaction',
|
|
36
|
+
id: row.id,
|
|
37
|
+
sessionId: row.sessionId,
|
|
38
|
+
summary: content.summary,
|
|
39
|
+
originalEntryIds: content.originalEntryIds,
|
|
40
|
+
archiveId: content.archiveId,
|
|
41
|
+
tokenCount: row.tokenCount ?? 0,
|
|
42
|
+
createdAt,
|
|
43
|
+
};
|
|
44
|
+
case 'squash':
|
|
45
|
+
return {
|
|
46
|
+
_tag: 'squash',
|
|
47
|
+
id: row.id,
|
|
48
|
+
sessionId: row.sessionId,
|
|
49
|
+
summary: content.summary,
|
|
50
|
+
squashedEntryIds: content.squashedEntryIds,
|
|
51
|
+
archiveId: content.archiveId,
|
|
52
|
+
tokenCount: row.tokenCount ?? 0,
|
|
53
|
+
createdAt,
|
|
54
|
+
};
|
|
55
|
+
case 'branch_summary':
|
|
56
|
+
return {
|
|
57
|
+
_tag: 'branch_summary',
|
|
58
|
+
id: row.id,
|
|
59
|
+
sessionId: row.sessionId,
|
|
60
|
+
summary: content.summary,
|
|
61
|
+
sourceSessionId: content.sourceSessionId,
|
|
62
|
+
createdAt,
|
|
63
|
+
};
|
|
64
|
+
case 'steering':
|
|
65
|
+
return {
|
|
66
|
+
_tag: 'steering',
|
|
67
|
+
id: row.id,
|
|
68
|
+
sessionId: row.sessionId,
|
|
69
|
+
content: content.text,
|
|
70
|
+
createdAt,
|
|
71
|
+
};
|
|
72
|
+
default:
|
|
73
|
+
// Fallback: treat unknown tags as messages
|
|
74
|
+
return rowToMessage(row);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Append a message to a session. Returns the stored Message with generated id/timestamp. */
|
|
78
|
+
async function appendMessage(handle, sessionId, input) {
|
|
79
|
+
const tokenCount = input.tokenCount ?? estimateMessageTokens(input.content);
|
|
80
|
+
const [row] = await handle.db
|
|
81
|
+
.insert(sessionEntries)
|
|
82
|
+
.values({
|
|
83
|
+
id: generateId(),
|
|
84
|
+
sessionId,
|
|
85
|
+
tag: 'message',
|
|
86
|
+
role: input.role,
|
|
87
|
+
content: input.content,
|
|
88
|
+
tokenCount,
|
|
89
|
+
})
|
|
90
|
+
.returning();
|
|
91
|
+
await touchSession(handle, sessionId);
|
|
92
|
+
if (!row)
|
|
93
|
+
throw new Error('Failed to insert message');
|
|
94
|
+
return rowToMessage(row);
|
|
95
|
+
}
|
|
96
|
+
/** Get messages for a session (tag='message' only), ordered chronologically. */
|
|
97
|
+
async function getMessages(handle, sessionId, opts) {
|
|
98
|
+
const rows = await handle.db
|
|
99
|
+
.select()
|
|
100
|
+
.from(sessionEntries)
|
|
101
|
+
.where(and(eq(sessionEntries.sessionId, sessionId), eq(sessionEntries.tag, 'message')))
|
|
102
|
+
.orderBy(asc(sessionEntries.createdAt))
|
|
103
|
+
.limit(opts?.limit ?? 100_000)
|
|
104
|
+
.offset(opts?.offset ?? 0);
|
|
105
|
+
return rows.map(rowToMessage);
|
|
106
|
+
}
|
|
107
|
+
/** Count messages in a session. */
|
|
108
|
+
async function getMessageCount(handle, sessionId) {
|
|
109
|
+
const [result] = await handle.db
|
|
110
|
+
.select({ value: count() })
|
|
111
|
+
.from(sessionEntries)
|
|
112
|
+
.where(and(eq(sessionEntries.sessionId, sessionId), eq(sessionEntries.tag, 'message')));
|
|
113
|
+
return result?.value ?? 0;
|
|
114
|
+
}
|
|
115
|
+
/** Delete all messages after the given 0-based index (keeps 0..index inclusive). */
|
|
116
|
+
async function deleteMessagesAfter(handle, sessionId, messageIndex) {
|
|
117
|
+
const messages = await getMessages(handle, sessionId);
|
|
118
|
+
const toDelete = messages.slice(messageIndex + 1);
|
|
119
|
+
if (toDelete.length > 0) {
|
|
120
|
+
await handle.db.delete(sessionEntries).where(inArray(sessionEntries.id, toDelete.map((m) => m.id)));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Low-level: get ALL entries (messages + special) in chronological order. */
|
|
124
|
+
async function getEntries(handle, sessionId) {
|
|
125
|
+
const rows = await handle.db
|
|
126
|
+
.select()
|
|
127
|
+
.from(sessionEntries)
|
|
128
|
+
.where(eq(sessionEntries.sessionId, sessionId))
|
|
129
|
+
.orderBy(asc(sessionEntries.createdAt));
|
|
130
|
+
return rows.map(rowToEntry);
|
|
131
|
+
}
|
|
132
|
+
/** Low-level: delete entries by id. */
|
|
133
|
+
async function deleteEntriesByIds(handle, ids) {
|
|
134
|
+
if (ids.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
await handle.db.delete(sessionEntries).where(inArray(sessionEntries.id, ids));
|
|
137
|
+
}
|
|
138
|
+
/** Low-level: insert a raw entry row (for compaction/squash/branch_summary/steering). */
|
|
139
|
+
async function insertEntry(handle, values) {
|
|
140
|
+
const [row] = await handle.db.insert(sessionEntries).values(values).returning();
|
|
141
|
+
if (!row)
|
|
142
|
+
throw new Error('Failed to insert entry');
|
|
143
|
+
return row;
|
|
144
|
+
}
|
|
145
|
+
export { appendMessage, deleteEntriesByIds, deleteMessagesAfter, getEntries, getMessageCount, getMessages, insertEntry, };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import { sessions } from '../db/schema.js';
|
|
3
|
+
import type { LLMSegment } from '../shared/types/agent.js';
|
|
4
|
+
import type { ChatTool } from '../shared/types/llm.js';
|
|
5
|
+
import type { LastRun, Session } from '../shared/types/message.js';
|
|
6
|
+
/** Convert a DB row (with Date timestamps) to the shared Session type (with number timestamps). */
|
|
7
|
+
export declare function rowToSession(row: typeof sessions.$inferSelect): Session;
|
|
8
|
+
/** Create a new root session. */
|
|
9
|
+
declare function createSession(handle: DB, title: string, projectId?: string, agentType?: string): Promise<Session>;
|
|
10
|
+
/** Get a session by id, or null if not found. */
|
|
11
|
+
declare function getSession(handle: DB, id: string): Promise<Session | null>;
|
|
12
|
+
/** List all sessions. */
|
|
13
|
+
declare function listSessions(handle: DB): Promise<Session[]>;
|
|
14
|
+
/** Delete a session (cascades to entries, archives, snapshots via FK). */
|
|
15
|
+
declare function deleteSession(handle: DB, id: string): Promise<void>;
|
|
16
|
+
/** Update a session's title. */
|
|
17
|
+
declare function updateSessionTitle(handle: DB, id: string, title: string): Promise<void>;
|
|
18
|
+
/** Bump updatedAt to now (used after appending messages). */
|
|
19
|
+
declare function touchSession(handle: DB, id: string): Promise<void>;
|
|
20
|
+
/** 规格化工具集并计算前缀指纹。tools 顺序不影响指纹(按 name 排序)。 */
|
|
21
|
+
export declare function segmentFingerprint(systemPrompt: string, tools: ChatTool[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* 将旧 metadata.llmDetails 迁移为单个 legacy segment。
|
|
24
|
+
* - 无 llmDetails 或已有 segments → 原样返回。
|
|
25
|
+
* - 否则取首条的 systemPrompt/tools 作为段首快照,所有旧 detail 转为 calls,
|
|
26
|
+
* responseText 从 responseChunks 的 text 块拼接提取。
|
|
27
|
+
* 幂等:迁移后 llmDetails 字段被移除,不会重复迁移。
|
|
28
|
+
*/
|
|
29
|
+
export declare function migrateLegacyDetails(meta: Record<string, unknown>): Record<string, unknown>;
|
|
30
|
+
/** 读取会话 metadata.segments(读取时顺带迁移旧 llmDetails)。 */
|
|
31
|
+
export declare function getLLMSegments(handle: DB, id: string): Promise<LLMSegment[]>;
|
|
32
|
+
/** 全量替换会话 metadata.segments(每轮 loop 结束写入;段数据轻量)。 */
|
|
33
|
+
export declare function saveLLMSegments(handle: DB, id: string, segments: LLMSegment[]): Promise<void>;
|
|
34
|
+
/** 更新会话 metadata.lastRun(agent run 开始/结束时写入;重启后检测中断用)。 */
|
|
35
|
+
declare function updateSessionLastRun(handle: DB, id: string, lastRun: LastRun): Promise<void>;
|
|
36
|
+
declare function listSessionsByProject(handle: DB, projectId: string): Promise<Session[]>;
|
|
37
|
+
export { createSession, deleteSession, getSession, listSessions, listSessionsByProject, touchSession, updateSessionLastRun, updateSessionTitle, };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { sessions } from '../db/schema.js';
|
|
3
|
+
import { generateId } from '../shared/index.js';
|
|
4
|
+
/** Convert a DB row (with Date timestamps) to the shared Session type (with number timestamps). */
|
|
5
|
+
export function rowToSession(row) {
|
|
6
|
+
const created = row.createdAt instanceof Date ? row.createdAt.getTime() : new Date(row.createdAt).getTime();
|
|
7
|
+
const updated = row.updatedAt instanceof Date ? row.updatedAt.getTime() : new Date(row.updatedAt).getTime();
|
|
8
|
+
return {
|
|
9
|
+
id: row.id,
|
|
10
|
+
title: row.title,
|
|
11
|
+
parentId: row.parentId,
|
|
12
|
+
projectId: row.projectId,
|
|
13
|
+
branchPoint: row.branchPoint,
|
|
14
|
+
metadata: (row.metadata ?? {}),
|
|
15
|
+
agentType: row.agentType ?? null,
|
|
16
|
+
worktreePath: row.worktreePath ?? null,
|
|
17
|
+
createdAt: created,
|
|
18
|
+
updatedAt: updated,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Create a new root session. */
|
|
22
|
+
async function createSession(handle, title, projectId, agentType) {
|
|
23
|
+
const [row] = await handle.db
|
|
24
|
+
.insert(sessions)
|
|
25
|
+
.values({ title, projectId: projectId ?? null, agentType: agentType ?? null })
|
|
26
|
+
.returning();
|
|
27
|
+
if (!row)
|
|
28
|
+
throw new Error('Failed to insert session');
|
|
29
|
+
return rowToSession(row);
|
|
30
|
+
}
|
|
31
|
+
/** Get a session by id, or null if not found. */
|
|
32
|
+
async function getSession(handle, id) {
|
|
33
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
34
|
+
return row ? rowToSession(row) : null;
|
|
35
|
+
}
|
|
36
|
+
/** List all sessions. */
|
|
37
|
+
async function listSessions(handle) {
|
|
38
|
+
const rows = await handle.db.select().from(sessions);
|
|
39
|
+
return rows.map(rowToSession);
|
|
40
|
+
}
|
|
41
|
+
/** Delete a session (cascades to entries, archives, snapshots via FK). */
|
|
42
|
+
async function deleteSession(handle, id) {
|
|
43
|
+
await handle.db.delete(sessions).where(eq(sessions.id, id));
|
|
44
|
+
}
|
|
45
|
+
/** Update a session's title. */
|
|
46
|
+
async function updateSessionTitle(handle, id, title) {
|
|
47
|
+
await handle.db.update(sessions).set({ title, updatedAt: new Date() }).where(eq(sessions.id, id));
|
|
48
|
+
}
|
|
49
|
+
/** Bump updatedAt to now (used after appending messages). */
|
|
50
|
+
async function touchSession(handle, id) {
|
|
51
|
+
await handle.db.update(sessions).set({ updatedAt: new Date() }).where(eq(sessions.id, id));
|
|
52
|
+
}
|
|
53
|
+
/** 规格化工具集并计算前缀指纹。tools 顺序不影响指纹(按 name 排序)。 */
|
|
54
|
+
export function segmentFingerprint(systemPrompt, tools) {
|
|
55
|
+
const norm = JSON.stringify({
|
|
56
|
+
systemPrompt,
|
|
57
|
+
tools: [...tools]
|
|
58
|
+
.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters }))
|
|
59
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
|
|
60
|
+
});
|
|
61
|
+
let h = 5381;
|
|
62
|
+
for (let i = 0; i < norm.length; i++)
|
|
63
|
+
h = ((h << 5) + h + norm.charCodeAt(i)) | 0;
|
|
64
|
+
return (h >>> 0).toString(16);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 将旧 metadata.llmDetails 迁移为单个 legacy segment。
|
|
68
|
+
* - 无 llmDetails 或已有 segments → 原样返回。
|
|
69
|
+
* - 否则取首条的 systemPrompt/tools 作为段首快照,所有旧 detail 转为 calls,
|
|
70
|
+
* responseText 从 responseChunks 的 text 块拼接提取。
|
|
71
|
+
* 幂等:迁移后 llmDetails 字段被移除,不会重复迁移。
|
|
72
|
+
*/
|
|
73
|
+
export function migrateLegacyDetails(meta) {
|
|
74
|
+
if (meta.segments !== undefined)
|
|
75
|
+
return meta;
|
|
76
|
+
const legacy = meta.llmDetails;
|
|
77
|
+
if (!Array.isArray(legacy) || legacy.length === 0)
|
|
78
|
+
return meta;
|
|
79
|
+
const first = legacy[0];
|
|
80
|
+
const segment = {
|
|
81
|
+
id: generateId(),
|
|
82
|
+
fingerprint: segmentFingerprint(first.systemPrompt, first.tools ?? []),
|
|
83
|
+
provider: first.provider,
|
|
84
|
+
model: first.model,
|
|
85
|
+
systemPrompt: first.systemPrompt,
|
|
86
|
+
tools: first.tools ?? [],
|
|
87
|
+
startedAt: first.timestamp,
|
|
88
|
+
trigger: 'initial',
|
|
89
|
+
...(first.contextWindow !== undefined ? { contextWindow: first.contextWindow } : {}),
|
|
90
|
+
calls: legacy.map((d) => {
|
|
91
|
+
const detail = d;
|
|
92
|
+
return {
|
|
93
|
+
id: detail.id,
|
|
94
|
+
timestamp: detail.timestamp,
|
|
95
|
+
usage: detail.usage,
|
|
96
|
+
latency: detail.latency,
|
|
97
|
+
cost: detail.cost,
|
|
98
|
+
...(detail.thinking ? { thinking: detail.thinking } : {}),
|
|
99
|
+
responseText: (detail.responseChunks ?? [])
|
|
100
|
+
.map((c) => (c._tag === 'text' && typeof c.text === 'string' ? c.text : ''))
|
|
101
|
+
.join(''),
|
|
102
|
+
};
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
const { llmDetails: _omit, ...rest } = meta;
|
|
106
|
+
return { ...rest, segments: [segment] };
|
|
107
|
+
}
|
|
108
|
+
/** 读取会话 metadata.segments(读取时顺带迁移旧 llmDetails)。 */
|
|
109
|
+
export async function getLLMSegments(handle, id) {
|
|
110
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
111
|
+
if (!row)
|
|
112
|
+
return [];
|
|
113
|
+
const meta = migrateLegacyDetails((row.metadata ?? {}));
|
|
114
|
+
return meta.segments ?? [];
|
|
115
|
+
}
|
|
116
|
+
/** 全量替换会话 metadata.segments(每轮 loop 结束写入;段数据轻量)。 */
|
|
117
|
+
export async function saveLLMSegments(handle, id, segments) {
|
|
118
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
119
|
+
if (!row)
|
|
120
|
+
return;
|
|
121
|
+
const meta = migrateLegacyDetails((row.metadata ?? {}));
|
|
122
|
+
const { segments: _omit, ...rest } = meta;
|
|
123
|
+
const next = { ...rest, segments };
|
|
124
|
+
await handle.db
|
|
125
|
+
.update(sessions)
|
|
126
|
+
.set({ metadata: next, updatedAt: new Date() })
|
|
127
|
+
.where(eq(sessions.id, id));
|
|
128
|
+
}
|
|
129
|
+
/** 更新会话 metadata.lastRun(agent run 开始/结束时写入;重启后检测中断用)。 */
|
|
130
|
+
async function updateSessionLastRun(handle, id, lastRun) {
|
|
131
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
132
|
+
if (!row)
|
|
133
|
+
return;
|
|
134
|
+
const meta = (row.metadata ?? {});
|
|
135
|
+
await handle.db
|
|
136
|
+
.update(sessions)
|
|
137
|
+
.set({ metadata: { ...meta, lastRun }, updatedAt: new Date() })
|
|
138
|
+
.where(eq(sessions.id, id));
|
|
139
|
+
}
|
|
140
|
+
async function listSessionsByProject(handle, projectId) {
|
|
141
|
+
const rows = await handle.db.select().from(sessions).where(eq(sessions.projectId, projectId));
|
|
142
|
+
return rows.map(rowToSession);
|
|
143
|
+
}
|
|
144
|
+
export { createSession, deleteSession, getSession, listSessions, listSessionsByProject, touchSession, updateSessionLastRun, updateSessionTitle, };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Message } from '../shared/types/message.js';
|
|
2
|
+
export interface ShakeConfig {
|
|
3
|
+
/** 保护最近 N token 的上下文不被 shake。 */
|
|
4
|
+
protectTokens: number;
|
|
5
|
+
/** 总节省 token < minSavings 时不 shake(preview 路径用)。 */
|
|
6
|
+
minSavings: number;
|
|
7
|
+
/** fenced/XML block 的最小 token 阈值。 */
|
|
8
|
+
fenceMinTokens: number;
|
|
9
|
+
/** 受保护的工具名列表(其 tool_result 不被 shake)。 */
|
|
10
|
+
protectedTools: string[];
|
|
11
|
+
}
|
|
12
|
+
/** Auto-shake 默认配置:保护活跃尾部,保守阈值。 */
|
|
13
|
+
export declare const DEFAULT_SHAKE_CONFIG: ShakeConfig;
|
|
14
|
+
export type ShakeRegion = {
|
|
15
|
+
kind: 'toolResult';
|
|
16
|
+
id: string;
|
|
17
|
+
messageId: string;
|
|
18
|
+
messageIndex: number;
|
|
19
|
+
partIndex: number;
|
|
20
|
+
tokens: number;
|
|
21
|
+
originalText: string;
|
|
22
|
+
label: string;
|
|
23
|
+
/** tool_call_id(tool_result part.id),前端用于跨消息合并后匹配渲染块。 */
|
|
24
|
+
toolCallId: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'block';
|
|
27
|
+
id: string;
|
|
28
|
+
messageId: string;
|
|
29
|
+
messageIndex: number;
|
|
30
|
+
partIndex: number;
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
tokens: number;
|
|
34
|
+
originalText: string;
|
|
35
|
+
label: string;
|
|
36
|
+
};
|
|
37
|
+
/** API 返回给前端的区域视图。 */
|
|
38
|
+
export type ShakeRegionView = {
|
|
39
|
+
id: string;
|
|
40
|
+
kind: 'toolResult' | 'block';
|
|
41
|
+
messageId: string;
|
|
42
|
+
messageIndex: number;
|
|
43
|
+
partIndex: number;
|
|
44
|
+
tokens: number;
|
|
45
|
+
label: string;
|
|
46
|
+
preview: string;
|
|
47
|
+
placeholder: string;
|
|
48
|
+
isAfterProtectWindow: boolean;
|
|
49
|
+
/** tool_result 的 tool_call_id(仅 toolResult 类别),前端跨消息合并后匹配渲染块。 */
|
|
50
|
+
toolCallId?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* 定位 fenced 代码块和顶层 XML 元素 span。返回字符偏移 [start, end) 数组,
|
|
54
|
+
* 覆盖完整块(含围栏/标签行,不含尾换行)。围栏内抑制 XML 检测。
|
|
55
|
+
* 未闭合围栏/标签不产生 range(保守策略)。
|
|
56
|
+
*/
|
|
57
|
+
export declare function scanTextForBlockRanges(text: string): Array<{
|
|
58
|
+
start: number;
|
|
59
|
+
end: number;
|
|
60
|
+
}>;
|
|
61
|
+
/** 收集可 shake 的区域。纯函数,不修改输入。 */
|
|
62
|
+
export declare function collectShakeRegions(messages: Message[], config: ShakeConfig): ShakeRegion[];
|
|
63
|
+
/** 原位替换选中区域。返回新数组,不修改原数组。 */
|
|
64
|
+
export declare function applyShakeRegions(messages: Message[], regions: ShakeRegion[]): Message[];
|
|
65
|
+
/** 区域转 API 视图。protectWindow 窗口内的标记 isAfterProtectWindow=false。 */
|
|
66
|
+
export declare function toRegionViews(regions: ShakeRegion[], config: ShakeConfig, messages: Message[]): ShakeRegionView[];
|