SessionCompaction
SessionCompaction 是 OpenCode 的消息压缩和性能优化系统,用于管理长对话的上下文和内存使用。
概述
当对话历史增长到接近模型上下文限制时,SessionCompaction 会自动压缩对话历史。它通过 AI 生成对话摘要,并选择性移除不相关的工具调用输出,确保系统能够持续处理长对话。
定义位置
packages/opencode/src/session/compaction.ts
主要常量
| 常量名 | 值 | 说明 |
|---|---|---|
PRUNE_MINIMUM | 20,000 | 最少要剪裁的 token 数量 |
PRUNE_PROTECT | 40,000 | 保护的最近工具调用 token 数量 |
OUTPUT_TOKEN_MAX | 32,000 | 最大输出 token 数量 |
主要函数
isOverflow(input)
检查当前 token 使用是否超过模型上下文。
参数:
input.tokens: 当前使用的 token 统计input.model: 模型配置
返回值: boolean - 是否溢出
逻辑:
- 检查
config.compaction.auto是否启用 - 如果模型上下文限制为 0,返回 false(无限制)
- 计算总 token 数量:
input + cache.read + output - 计算可用上下文空间
- 判断是否溢出
位置: packages/opencode/src/session/compaction.ts:30
process(input)
执行 AI 驱动的压缩,生成对话摘要。
参数:
input.parentID: 父消息 IDinput.messages: 需要压缩的消息列表input.sessionID: 会话 IDinput.abort: 中止信号input.auto: 是否自动压缩
返回值: "continue" | "stop"
工作流程:
- 获取或创建 "compaction" agent
- 创建带
summary: true标记的 assistant 消息 - 允许插件通过
experimental.session.compacting钩子自定义 - 生成默认提示词或使用插件提供的提示词
- 调用 LLM 生成对话摘要
- 如果是自动压缩,创建合成 "continue" 消息
- 发布
session.compacted事件
位置: packages/opencode/src/session/compaction.ts:92
create(input)
创建压缩任务/部分。
参数:
input.sessionID: 会话 IDinput.agent: Agent 名称input.model: 模型信息input.auto: 是否自动压缩
返回值: 创建的 Part
位置: packages/opencode/src/session/compaction.ts:195
prune(input)
移除旧的、不相关的工具调用输出以节省空间。
参数:
input.sessionID: 会话 ID
工作流程:
- 检查
config.compaction.prune是否启用 - 从后向前遍历消息,直到找到 40k+ token 的工具调用
- 跳过受保护的工具(如 "skill")
- 只有能回收至少 20k token 时才执行剪裁
- 标记工具部分的
time.compacted时间戳
剪裁策略:
- 保护最近 2 轮对话(至少一个 user + assistant 循环)
- 遇到已压缩的消息时停止
- 跳过受保护工具的输出
位置: packages/opencode/src/session/compaction.ts:49
事件
| 事件名 | 类型 | 说明 |
|---|---|---|
session.compacted | { sessionID: string } | 压缩完成 |
TypeScript 类型定义
export namespace SessionCompaction {
export const PRUNE_MINIMUM = 20_000
export const PRUNE_PROTECT = 40_000
export async function isOverflow(input: {
tokens: MessageV2.Assistant["tokens"]
model: Provider.Model
}): Promise<boolean>
export async function process(input: {
parentID: string
messages: MessageV2.WithParts[]
sessionID: string
abort: AbortSignal
auto: boolean
}): Promise<"continue" | "stop">
export const create = fn(...)
export async function prune(input: {
sessionID: string
}): Promise<void>
}
工作流程
触发机制
SessionProcessor 监听 LLM token 使用
↓
检测到 isOverflow() 为 true
↓
检查 config.compaction.auto 是否启用
↓
创建 CompactionPart
↓
主循环检测到 CompactionPart
↓
调用 process() 生成摘要
↓
发布 session.compacted 事件
压缩流程
process()
├─ 获取 "compaction" agent
├─ 创建 assistant 消息 (summary: true)
├─ 调用插件钩子 experimental.session.compacting
├─ 生成或获取压缩提示词
├─ 调用 LLM 生成摘要
├─ (可选) 创建合成 "continue" 消息
└─ 发布 session.compacted 事件
消息过滤
MessageV2.filterCompacted(stream)
过滤消息,只包含在最后一次成功压缩之后的消息。
位置: packages/opencode/src/session/message-v2.ts
逻辑:
- 跟踪已完成的压缩点
- 在完成的摘要后的用户消息处停止
- 返回反向的消息列表(最新的在前)
配置
{
compaction: {
auto: boolean, // 启用自动压缩(默认:true)
prune: boolean, // 启用工具输出剪裁(默认:true)
}
}
插件集成
插件可以通过 experimental.session.compacting 钩子自定义压缩行为:
{
experimental: {
session: {
compacting: {
context: string[], // 附加到默认提示词的上下文
prompt: string, // 覆盖整个压缩提示词
}
}
}
}
示例插件钩子:
Plugin.hook("experimental.session.compacting", async (ctx) => {
return {
context: ["Focus on code changes", "Include file paths"],
prompt: undefined, // 使用默认提示词
}
})
性能优化策略
1. 选择性剪裁
- 保护最近的 40k token 的工具调用
- 只有能回收至少 20k token 时才执行剪裁
- 防止破坏最近的上下文
2. 精确的 Token 计算
分别追踪:
tokens.input- 输入 tokenstokens.output- 输出 tokenstokens.reasoning- 推理 tokenstokens.cache.read- 读取的缓存 tokenstokens.cache.write- 写入的缓存 tokens
3. 内存效率
- 压缩点充当对话历史的"检查点"
- 较旧的消息从 LLM 上下文中排除,但保留在存储中
- 摘要消息作为 AI 的压缩上下文
4. 受保护的工具
某些工具的输出不会被剪裁:
const PRUNE_PROTECTED_TOOLS = ["skill"]
典型使用场景
1. 自动压缩触发
// SessionProcessor 检测到溢出
if (await SessionCompaction.isOverflow({ tokens, model })) {
// 创建压缩任务
await SessionCompaction.create({
sessionID: session.id,
agent: "default",
model: { providerID, modelID },
auto: true,
})
// 主循环会检测到并执行压缩
}
2. 手动创建压缩任务
// 手动触发压缩
await SessionCompaction.create({
sessionID: session.id,
agent: "custom",
model: { providerID, modelID },
auto: false, // 不自动创建 continue 消息
})
3. 手动剪裁工具输出
// 即使未溢出,也可以手动剪裁
await SessionCompaction.prune({ sessionID: session.id })
4. 检查是否需要压缩
const model = await Provider.getModel(providerID, modelID)
const messages = await Session.messages({ sessionID })
const tokens = calculateTokens(messages)
if (await SessionCompaction.isOverflow({ tokens, model })) {
console.log("需要压缩对话历史")
}
对象关系
架构概览
相关文档
变更历史
| 版本 | 变更内容 | 日期 |
|---|---|---|
| v1 | 初始 SessionCompaction 架构 | - |
| v1.1 | 添加插件支持 | - |
| v1.2 | 改进剪裁策略 | - |