OpenCode-DCP 动态上下文裁剪
适用版本:v3.1.12+
仓库:Opencode-DCP/opencode-dynamic-context-pruning
许可证:AGPL-3.0-or-later
1. 概述
DCP(Dynamic Context Pruning,动态上下文裁剪)是 OpenCode 专用插件,通过智能管理对话上下文来优化 token 使用量。它在不修改原始会话历史的前提下,用占位符替换已裁剪的内容,再发送给 LLM。
1.1 核心价值
| 问题 | DCP 方案 |
|---|---|
| 长对话 token 溢出 | 自动压缩已完成的讨论段落 |
| 重复工具调用浪费 token | 去重策略,仅保留最新输出 |
| 错误输出占用上下文 | 自动清理过期的错误输入 |
| 关键信息被误删 | 多层保护机制(工具/标签/文件/用户消息) |
1.2 技术栈
- 语言:TypeScript (85.3%) + Python (11.1%)
- npm 包名:
@tarquinen/opencode-dcp - 集成方式:OpenCode 插件系统(
@opencode-ai/pluginAPI)
2. 架构设计
2.1 整体架构
graph TB
subgraph OpenCode
A[用户消息] --> B[OpenCode 会话管理器]
B --> C{DCP 插件}
end
subgraph DCP 核心引擎
C --> D[Compress 压缩工具]
C --> E[Deduplication 去重策略]
C --> F[PurgeErrors 错误清理策略]
C --> G[Nudge 提醒系统]
end
subgraph 数据流
D --> H[压缩摘要<br/>Block Summary]
E --> I[去重后的<br/>工具输出]
F --> J[清理后的<br/>错误记录]
end
H --> K[优化后的 Context]
I --> K
J --> K
K --> L[LLM API 请求]
style C fill:#f96,stroke:#333,stroke-width:2px
style D fill:#69f,stroke:#333
style E fill:#69f,stroke:#333
style F fill:#69f,stroke:#333
style G fill:#fc6,stroke:#333
2.2 工作流程
sequenceDiagram
participant U as 用户
participant OC as OpenCode
participant DCP as DCP 插件
participant LLM as LLM API
U->>OC: 发送消息
OC->>DCP: tool.execute.before hook
Note over DCP: 检查上下文大小
alt 上下文 < minContextLimit
DCP-->>OC: 不干预(静默通过)
else 上下文 ≥ minContextLimit 且 < maxContextLimit
DCP->>OC: 注入轻量提醒(iteration/turn nudge)
else 上下文 ≥ maxContextLimit
DCP->>OC: 注入强压缩提示(context-limit nudge)
end
OC->>LLM: 发送请求(含 DCP 修改后的上下文)
LLM-->>OC: 响应
alt LLM 调用 compress 工具
LLM->>DCP: compress(startId, endId, summary)
DCP->>DCP: 替换消息范围为摘要
DCP->>DCP: 执行去重 + 错误清理
DCP-->>LLM: 压缩完成
end
OC-->>U: 返回响应
3. 核心机制
3.1 Compress 压缩
Compress 是暴露给模型的主动工具,模型根据任务完成状态自主决定何时压缩、压缩哪些内容。
两种压缩模式
graph LR
subgraph "Range 模式(默认·稳定)"
R1[消息 1] --> R2[消息 2]
R2 --> R3[消息 3]
R3 --> R4[消息 4]
R1 -.->|整段压缩| RS[Block Summary]
R2 -.->|整段压缩| RS
R3 -.->|整段压缩| RS
R4 -.->|整段压缩| RS
end
subgraph "Message 模式(实验性)"
M1[消息 1] -.->|独立压缩| MS1[Summary 1]
M2[消息 2] -->|保留| M2
M3[消息 3] -.->|独立压缩| MS3[Summary 3]
M4[消息 4] -->|保留| M4
end
| 特性 | Range 模式 | Message 模式 |
|---|---|---|
| 粒度 | 粗粒度(连续跨度) | 细粒度(逐条消息) |
| 嵌套支持 | ✅ 旧摘要可嵌套进新摘要 | ❌ 无嵌套机制 |
| 碎片化 | 低 | 高 |
| Token 效率 | 高 | 中 |
| 精确控制 | 一般 | 精确(外科手术式) |
| 稳定性 | 稳定版 | 实验性 |
嵌套压缩(Range 模式独有)
graph TB
subgraph "第一轮压缩"
A1[msg 1-10] --> B1["Block b1<br/>(摘要 A)"]
end
subgraph "第二轮压缩"
B1 --> C1["Block b2<br/>(摘要 B,内含 b1 占位符)"]
A2[msg 11-20] --> C1
end
subgraph "展开后"
C1 --> D1["b2 摘要展开时<br/>b1 被替换为完整内容<br/>→ 信息层级保留"]
end
3.2 Deduplication 去重
识别重复的工具调用(相同工具 + 相同参数),仅保留最新一次的输出。
- 触发时机:compress 工具运行时一起执行
- 对 Prompt Cache 的影响:仅在压缩时才改变,不会频繁打断缓存
3.3 PurgeErrors 错误清理
清理报错的工具调用,在可配置的轮次后(默认 4 轮)移除其输入内容。
- 错误消息本身保留(保持调试上下文)
- 仅移除可能很大的输入数据(如文件内容)
- 触发时机:同样在 compress 工具运行时执行
3.4 Nudge 提醒系统
DCP 通过分级提醒机制引导模型适时压缩:
graph LR
subgraph "上下文大小区间"
Z1["0 ~ minContextLimit"] -->|无提醒| N0[静默]
Z2["minContextLimit ~ maxContextLimit"] -->|轻量提醒| N1["turn-nudge<br/>iteration-nudge"]
Z3["≥ maxContextLimit"] -->|强压缩| N2["context-limit-nudge"]
end
style Z1 fill:#9f9
style Z2 fill:#ff9
style Z3 fill:#f99
| 提醒类型 | 触发条件 | 行为 |
|---|---|---|
| turn-nudge | 上下文 ≥ min 且最近 N 轮有工具调用 | 每轮末尾温和提醒 |
| iteration-nudge | 上下文 ≥ min 且距用户消息超过 N 条 | 自上次用户消息后周期性提醒 |
| context-limit-nudge | 上下文 ≥ max | 按 nudgeFrequency 频率注入强制压缩指令 |
4. 保护机制
DCP 提供多层保护,确保关键信息不被裁剪:
graph LR
subgraph "保护层级"
P1["🛡️ 全局裁剪保护<br/>(不被删除/去重/清理)"]
P2["🛡️ Compress 摘要保护<br/>(输出附加到摘要)"]
P3["🛡️ Protect Tags<br/>(标签内文本原文保留)"]
P4["🛡️ 用户消息保护<br/>(用户消息不压缩)"]
P5["🛡️ 文件模式保护<br/>(匹配的文件操作不裁剪)"]
P6["🛡️ 轮次保护<br/>(最近 N 轮不裁剪)"]
end
P1 --> T1["task, skill, todowrite, todoread,<br/>compress, batch, plan_enter,<br/>plan_exit, write, edit"]
P2 --> T2["task, skill, todowrite, todoread"]
P3 --> T3["<protect>...</protect> 标签"]
P4 --> T4["protectUserMessages: true"]
P5 --> T5["protectedFilePatterns: ['**/*.config.ts']"]
P6 --> T6["turnProtection: { turns: N }"]
默认保护工具列表:
全局裁剪保护(不被删除/去重/清理):
task, skill, todowrite, todoread, compress, batch, plan_enter, plan_exit, write, edit
Compress 摘要附加保护(输出原文写入压缩摘要):
task, skill, todowrite, todoread
可通过各级
protectedTools配置数组追加自定义工具,支持 glob 通配符(如"mcp_*")。
5. 推荐配置
以下是针对大上下文窗口模型(如 Claude 200K)优化的推荐配置,核心策略是前期放养、后期密集压缩、关键信息不丢。
配置文件路径:~/.config/opencode/dcp.jsonc
{
"$schema": "https://raw.githubusercontent.com/Opencode-DCP/opencode-dynamic-context-pruning/master/dcp.schema.json",
// 精简通知,减少对话噪声
"pruneNotification": "minimal",
// 启用轮次保护(默认保护最近 4 轮)
"turnProtection": {
"enabled": true
},
"compress": {
// 下限阈值:180K 以下完全不催压缩
"minContextLimit": 180000,
// 上限阈值:模型窗口的 30%
"maxContextLimit": "30%",
// 超上限后每 3 次请求提醒一次(更积极)
"nudgeFrequency": 3,
// 用户消息后第 10 条开始迭代提醒
"iterationNudgeThreshold": 10,
// 压缩时保留用户消息原文
"protectUserMessages": true,
// 启用 <protect> 标签保护
"protectTags": true
}
}
graph LR
subgraph "上下文生命周期"
A["0 ~ 180K tokens"] -->|完全自由| B["DCP 静默<br/>不干预"]
B --> C["≥ 180K tokens"]
C -->|所有 nudge 同时激活| D["turn-nudge<br/>iteration-nudge<br/>context-limit-nudge"]
D -->|模型执行压缩| E["上下文回落"]
E -->|循环| A
end
style A fill:#9f9
style B fill:#9f9
style C fill:#f99
style D fill:#fc6
style E fill:#69f
策略总结:“放养到 180K,然后密集催压”
- 前期充分利用大窗口模型的上下文容量
- 接近上限时通过高频 nudge(每 3 次 fetch)和早期迭代提醒(第 10 条)快速压缩
- 用户消息和
<protect>标签内容始终不丢,保证关键信息完整
注意:
minContextLimit(180K)是所有 nudge 的硬性门槛。当maxContextLimit30% ≈ 60K 低于minContextLimit时,实际效果是 180K 以下一切静默,超过 180K 后所有 nudge(含强压缩)同时激活。
6. 配置参考
6.1 配置文件优先级
graph LR
G["全局配置<br/>~/.config/opencode/dcp.jsonc"] --> C["自定义目录<br/>$OPENCODE_CONFIG_DIR/dcp.jsonc"]
C --> P["项目级配置<br/>.opencode/dcp.jsonc"]
P --> F["最终生效配置"]
style P fill:#f96
style F fill:#6f9
后者覆盖前者,项目级配置最优先。修改后需重启 OpenCode。
6.2 顶层配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean | true | 启用/禁用 DCP 插件 |
autoUpdate | boolean | true | 自动更新到 npm 最新版 |
debug | boolean | false | 调试日志(输出到 ~/.config/opencode/logs/dcp/) |
pruneNotification | enum | "detailed" | 裁剪通知级别:off / minimal / detailed |
pruneNotificationType | enum | "chat" | 通知位置:chat(对话内)/ toast(系统通知) |
protectedFilePatterns | string[] | [] | 文件保护 glob 模式(如 '**/*.config.ts') |
6.3 commands 配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
commands.enabled | boolean | true | 启用 /dcp 斜杠命令 |
commands.protectedTools | string[] | [] | /dcp sweep 时额外保护的工具(支持 glob) |
6.4 manualMode 配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
manualMode.enabled | boolean | false | 手动模式(禁用自主上下文管理) |
manualMode.automaticStrategies | boolean | true | 手动模式下仍运行去重/错误清理 |
6.5 turnProtection 配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
turnProtection.enabled | boolean | false | 启用轮次保护 |
turnProtection.turns | number | 4 | 保护最近 N 轮 |
6.6 experimental 配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
experimental.allowSubAgents | boolean | false | 允许 DCP 在子代理会话中运行 |
experimental.customPrompts | boolean | false | 启用用户可编辑的 prompt 覆盖 |
6.7 compress 配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
compress.mode | enum | "range" | 压缩模式:range(范围)/ message(逐条) |
compress.permission | enum | "allow" | 权限:allow(自动)/ ask(询问)/ deny(禁用) |
compress.showCompression | boolean | false | 在通知中显示压缩摘要内容 |
compress.summaryBuffer | boolean | true | 活跃摘要 token 扩展有效 maxContextLimit |
compress.maxContextLimit | number | string | 100000 | 上限阈值(支持数字或 "X%" 百分比) |
compress.minContextLimit | number | string | 50000 | 下限阈值(支持数字或 "X%" 百分比) |
compress.modelMaxLimits | object | — | 按模型覆盖 maxContextLimit |
compress.modelMinLimits | object | — | 按模型覆盖 minContextLimit |
compress.nudgeFrequency | number | 5 | 超过上限时 nudge 频率(1=每次,5=每5次) |
compress.iterationNudgeThreshold | number | 15 | 用户消息后第 N 条开始迭代提醒 |
compress.nudgeForce | enum | "soft" | 提醒强度:strong / soft |
compress.protectedTools | string[] | [] | 输出附加到压缩摘要的工具(支持 glob) |
compress.protectTags | boolean | false | 保留 <protect> 标签内文本 |
compress.protectUserMessages | boolean | false | 压缩时保留用户消息原文 |
6.8 strategies 配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
strategies.deduplication.enabled | boolean | true | 启用去重策略 |
strategies.deduplication.protectedTools | string[] | [] | 排除去重的工具(支持 glob) |
strategies.purgeErrors.enabled | boolean | true | 启用错误清理策略 |
strategies.purgeErrors.turns | number | 4 | 错误输入保留的轮次 |
strategies.purgeErrors.protectedTools | string[] | [] | 排除错误清理的工具(支持 glob) |
7. 使用指南
7.1 斜杠命令
| 命令 | 功能 |
|---|---|
/dcp | 显示可用命令列表 |
/dcp context | 当前会话 token 使用分布(按类别) |
/dcp stats | 所有会话累计裁剪统计 |
/dcp sweep [N] | 裁剪最近用户消息后的工具输出(可选数量 N) |
/dcp manual [on|off] | 切换手动模式 |
/dcp compress [focus] | 手动触发一次压缩(可指定聚焦内容) |
/dcp decompress <n> | 还原指定 ID 的压缩块 |
/dcp recompress <n> | 重新压缩已还原的块 |
7.2 Prompt 覆盖
DCP 暴露 6 个可编辑的 prompt(需先启用 experimental.customPrompts: true):
| Prompt 文件 | 用途 |
|---|---|
system | DCP 系统指令 |
compress-range | Range 模式压缩指令 |
compress-message | Message 模式压缩指令 |
context-limit-nudge | 超上限强压缩提示 |
turn-nudge | 轮次结束提醒 |
iteration-nudge | 迭代过程提醒 |
- 默认文件位置:
~/.config/opencode/dcp-prompts/defaults/ - 覆盖方式:在 overrides 目录下创建同名文件
- 重置方式:删除 overrides 目录下的对应文件
8. 附录:Prompt Cache 影响
graph LR
subgraph "无 DCP"
A1["请求 1: ABCDEF"] --> C1["缓存前缀 ABCDEF ✅"]
A2["请求 2: ABCDEFGH"] --> C2["命中缓存 ABCDEF ✅<br/>仅计算 GH"]
end
subgraph "有 DCP(裁剪后)"
B1["请求 1: ABCDEF"] --> D1["缓存前缀 ABCDEF ✅"]
B2["请求 2: A[摘要]FGH"] --> D2["缓存失效 ❌<br/>前缀改变"]
end
裁剪改变消息内容,导致缓存前缀失效(命中率 ~85% vs 无 DCP ~90%),但换来总 token 数减少、过期上下文导致的幻觉降低、可用对话长度延长。按请求计费(如 GitHub Copilot)或缓存与非缓存 token 同价(如 Cerebras)的场景不受影响。