Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • SessionCompaction

SessionCompaction

SessionCompaction 是 OpenCode 的消息压缩和性能优化系统,用于管理长对话的上下文和内存使用。

概述

当对话历史增长到接近模型上下文限制时,SessionCompaction 会自动压缩对话历史。它通过 AI 生成对话摘要,并选择性移除不相关的工具调用输出,确保系统能够持续处理长对话。

定义位置

packages/opencode/src/session/compaction.ts

主要常量

常量名值说明
PRUNE_MINIMUM20,000最少要剪裁的 token 数量
PRUNE_PROTECT40,000保护的最近工具调用 token 数量
OUTPUT_TOKEN_MAX32,000最大输出 token 数量

主要函数

isOverflow(input)

检查当前 token 使用是否超过模型上下文。

参数:

  • input.tokens: 当前使用的 token 统计
  • input.model: 模型配置

返回值: boolean - 是否溢出

逻辑:

  1. 检查 config.compaction.auto 是否启用
  2. 如果模型上下文限制为 0,返回 false(无限制)
  3. 计算总 token 数量:input + cache.read + output
  4. 计算可用上下文空间
  5. 判断是否溢出

位置: packages/opencode/src/session/compaction.ts:30

process(input)

执行 AI 驱动的压缩,生成对话摘要。

参数:

  • input.parentID: 父消息 ID
  • input.messages: 需要压缩的消息列表
  • input.sessionID: 会话 ID
  • input.abort: 中止信号
  • input.auto: 是否自动压缩

返回值: "continue" | "stop"

工作流程:

  1. 获取或创建 "compaction" agent
  2. 创建带 summary: true 标记的 assistant 消息
  3. 允许插件通过 experimental.session.compacting 钩子自定义
  4. 生成默认提示词或使用插件提供的提示词
  5. 调用 LLM 生成对话摘要
  6. 如果是自动压缩,创建合成 "continue" 消息
  7. 发布 session.compacted 事件

位置: packages/opencode/src/session/compaction.ts:92

create(input)

创建压缩任务/部分。

参数:

  • input.sessionID: 会话 ID
  • input.agent: Agent 名称
  • input.model: 模型信息
  • input.auto: 是否自动压缩

返回值: 创建的 Part

位置: packages/opencode/src/session/compaction.ts:195

prune(input)

移除旧的、不相关的工具调用输出以节省空间。

参数:

  • input.sessionID: 会话 ID

工作流程:

  1. 检查 config.compaction.prune 是否启用
  2. 从后向前遍历消息,直到找到 40k+ token 的工具调用
  3. 跳过受保护的工具(如 "skill")
  4. 只有能回收至少 20k token 时才执行剪裁
  5. 标记工具部分的 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

逻辑:

  1. 跟踪已完成的压缩点
  2. 在完成的摘要后的用户消息处停止
  3. 返回反向的消息列表(最新的在前)

配置

{
  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 - 输入 tokens
  • tokens.output - 输出 tokens
  • tokens.reasoning - 推理 tokens
  • tokens.cache.read - 读取的缓存 tokens
  • tokens.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("需要压缩对话历史")
}

对象关系

架构概览

相关文档

  • Session - 会话对象
  • Message - 消息对象
  • Part - 消息部分
  • Snapshot - 快照系统
  • Agent - Agent 配置

变更历史

版本变更内容日期
v1初始 SessionCompaction 架构-
v1.1添加插件支持-
v1.2改进剪裁策略-