Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
c3e9008
working through it
jorgenwh Dec 11, 2025
850252a
Merge branch 'master' of github.com:Tarquinen/opencode-dynamic-contex…
jorgenwh Dec 11, 2025
f9fd11a
crazy refactor
jorgenwh Dec 11, 2025
db25bd2
couple of bug fixes
jorgenwh Dec 11, 2025
fb11f6a
new config
jorgenwh Dec 11, 2025
a57ce23
show error toast if loading config file fails
jorgenwh Dec 11, 2025
951dbcd
building prototype
jorgenwh Dec 11, 2025
90496a3
config logic
jorgenwh Dec 12, 2025
befa392
small refactor and prepare strategies
jorgenwh Dec 12, 2025
58145ba
refactor
jorgenwh Dec 12, 2025
32d782d
delete claude.md
jorgenwh Dec 12, 2025
da85205
remove old log watch script
jorgenwh Dec 12, 2025
0e7630d
deduplicate tool id logic
jorgenwh Dec 12, 2025
8c48761
refactor
jorgenwh Dec 12, 2025
1a2fc6c
wip
jorgenwh Dec 13, 2025
3143b87
deduplication and prune tool works. MUCH nicer code
jorgenwh Dec 13, 2025
b47fec3
pull master
jorgenwh Dec 13, 2025
7730dc4
readme
jorgenwh Dec 13, 2025
0914d82
reamde
jorgenwh Dec 13, 2025
7c90034
sync
jorgenwh Dec 13, 2025
8227d64
ai nudge implementation
jorgenwh Dec 13, 2025
75539fd
set onIdle and pruneThinkingBlock strategies to disabled by default
jorgenwh Dec 13, 2025
5b9493d
say onIdle is legacy
jorgenwh Dec 13, 2025
07fa3c4
workflow fix
jorgenwh Dec 13, 2025
72fcc07
Merge pull request #119 from Opencode-DCP/nudge-frequency-and-config-…
jorgenwh Dec 13, 2025
7c70245
disallow subagents to use plugin
jorgenwh Dec 13, 2025
2d4ae04
.gitignore
jorgenwh Dec 13, 2025
9f043dc
Merge pull request #120 from Opencode-DCP/disallow-subagents-to-use-p…
jorgenwh Dec 13, 2025
bd48b4e
cleanup
jorgenwh Dec 13, 2025
ebf8151
aids
jorgenwh Dec 13, 2025
9d64c42
on-idle re-implemented
jorgenwh Dec 13, 2025
32bde8d
Merge pull request #121 from Opencode-DCP/on-idle-strategy
jorgenwh Dec 13, 2025
cc6cc68
refactor: use shared buildToolIdList from utils
Tarquinen Dec 13, 2025
9b23955
Merge pull request #122 from Opencode-DCP/fix/remove-duplicate-buildT…
Tarquinen Dec 13, 2025
8a95101
Prevents compacted tools from appearing in the prunable tools list
spoons-and-mirrors Dec 13, 2025
9a45c98
Merge branch 'dev' into fix-compacted-filter
spoons-and-mirrors Dec 13, 2025
760be9d
Merge pull request #123 from spoons-and-mirrors/fix-compacted-filter
spoons-and-mirrors Dec 13, 2025
c8a5bbc
refactor: consolidate duplicate extractParameterKey function
Tarquinen Dec 13, 2025
41189c5
Merge pull request #124 from Opencode-DCP/refactor/consolidate-extrac…
Tarquinen Dec 13, 2025
583d343
enable pruneTool by default and reorder above onIdle
Tarquinen Dec 13, 2025
b24a6a7
Merge pull request #125 from Opencode-DCP/feature/enable-prune-tool-b…
Tarquinen Dec 13, 2025
17c22a9
refactor: remove dead code from Logger class
Tarquinen Dec 13, 2025
78ac18c
Merge pull request #126 from Opencode-DCP/refactor/remove-dead-logger…
Tarquinen Dec 13, 2025
188f5ee
refactor: remove unused version-checker module
Tarquinen Dec 13, 2025
3a77692
Merge pull request #127 from Opencode-DCP/refactor/remove-version-che…
Tarquinen Dec 13, 2025
acb2543
fix: skip pruned tools when counting toward nudge threshold
Tarquinen Dec 14, 2025
8b5037e
fix: await session state load before syncing tool cache
Tarquinen Dec 14, 2025
07d3a53
Merge pull request #128 from Opencode-DCP/fix/nudge-counter-after-res…
Tarquinen Dec 14, 2025
b5daf99
fix: save session state after updating token stats
Tarquinen Dec 14, 2025
84cc8e3
Merge pull request #129 from Opencode-DCP/fix/persist-token-stats-aft…
Tarquinen Dec 14, 2025
31cd1d9
refactor: consolidate message utilities and eliminate duplicate itera…
Tarquinen Dec 14, 2025
1637f1b
cleanup
Tarquinen Dec 14, 2025
3259b34
Merge pull request #130 from Opencode-DCP/refactor/consolidate-messag…
Tarquinen Dec 14, 2025
51f4826
refactor: remove unused reason parameter from buildAnalysisPrompt
Tarquinen Dec 14, 2025
394844e
Merge pull request #131 from Opencode-DCP/refactor/remove-unused-reas…
Tarquinen Dec 14, 2025
f9e47dc
fix: simplify prune tool schema for OAuth plugin compatibility
Tarquinen Dec 14, 2025
5ae736d
Merge pull request #132 from Opencode-DCP/refactor/remove-unused-reas…
Tarquinen Dec 14, 2025
968d656
1.0.0-beta.1
Tarquinen Dec 14, 2025
b323912
fix: properly reset nudge counter after prune tool is used
Tarquinen Dec 15, 2025
4cc05c6
Merge pull request #133 from Opencode-DCP/fix/nudge-counter-reset
Tarquinen Dec 15, 2025
c21a974
v1.0.0-beta.2 - Bump version
Tarquinen Dec 15, 2025
cecaff1
Merge pull request #134 from Opencode-DCP/release/v1.0.0-beta.2
Tarquinen Dec 15, 2025
f4ba0c1
fix: prevent protected tools from being pruned
Tarquinen Dec 15, 2025
e1ec087
refactor: move prune tool ID validation to prune-tool.ts
Tarquinen Dec 15, 2025
551f0d8
refactor: simplify bounds check with .some()
Tarquinen Dec 15, 2025
83e4703
Merge pull request #136 from Opencode-DCP/fix/protected-tool-pruning
Tarquinen Dec 15, 2025
ca1f35f
Add system prompt transformation hook for synthetic prompt injection
Tarquinen Dec 15, 2025
1915ee2
Merge pull request #137 from Opencode-DCP/feature/system-prompt-trans…
Tarquinen Dec 15, 2025
9565fc7
Bump version to 1.0.0-beta.3
Tarquinen Dec 15, 2025
ef3b8a1
Merge pull request #138 from Opencode-DCP/release/beta.3
Tarquinen Dec 15, 2025
ff959c3
fix: skip inserting prunable tools list when empty
Tarquinen Dec 15, 2025
dfd5270
Merge pull request #139 from Opencode-DCP/fix/empty-prunable-tools-list
Tarquinen Dec 15, 2025
a0ee808
chore: remove pruneThinkingBlocks (not yet implemented)
Tarquinen Dec 15, 2025
4b5c3b4
Merge pull request #140 from Opencode-DCP/chore/remove-prune-thinking…
Tarquinen Dec 15, 2025
e1eb62e
feat: support DCP config in OPENCODE_CONFIG_DIR
didacog Dec 11, 2025
6693dd4
Merge pull request #135 from didacog/feature/custom-config-path
Tarquinen Dec 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions .claude/settings.local.json

This file was deleted.

4 changes: 1 addition & 3 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ name: PR Checks

on:
pull_request:
branches: [main, master]
push:
branches: [main, master]
branches: [master, dev]

jobs:
validate:
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ Thumbs.db

# OpenCode
.opencode/
AGENTS.md

# Tests (local development only)
tests/
Expand Down
92 changes: 61 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,15 @@ Restart OpenCode. The plugin will automatically start optimizing your sessions.

## How Pruning Works

DCP uses two complementary techniques:
DCP uses multiple strategies to reduce context size:

**Automatic Deduplication** — Silently identifies repeated tool calls (e.g., reading the same file multiple times) and keeps only the most recent output. Runs on every request with zero LLM cost.
**Deduplication** — Identifies repeated tool calls (e.g., reading the same file multiple times) and keeps only the most recent output. Runs automatically on every request with zero LLM cost.

**AI Analysis** — Uses a language model to semantically analyze conversation context and identify tool outputs that are no longer relevant to the current task.
**On Idle Analysis** — Uses a language model to semantically analyze conversation context during idle periods and identify tool outputs that are no longer relevant.

## Context Pruning Tool
**Prune Tool** — Exposes a `prune` tool that the AI can call to manually trigger pruning when it determines context cleanup is needed.

When `strategies.onTool` is enabled, DCP exposes a `prune` tool to Opencode that the AI can call to trigger pruning on demand.

Adjust `nudge_freq` to control how aggressively the AI is prompted to prune — lower values trigger reminders sooner and more often.

## How It Works
*More strategies coming soon.*

Your session history is never modified. DCP replaces pruned outputs with a placeholder before sending requests to your LLM.

Expand All @@ -47,40 +43,74 @@ LLM providers like Anthropic and OpenAI cache prompts based on exact prefix matc

## Configuration

DCP uses its own config file (`~/.config/opencode/dcp.jsonc` or `.opencode/dcp.jsonc`), created automatically on first run.
DCP uses its own config file:

### Options
- Global: `~/.config/opencode/dcp.jsonc` (or `dcp.json`), created automatically on first run
- Custom config directory: `$OPENCODE_CONFIG_DIR/dcp.jsonc` (or `dcp.json`), if `OPENCODE_CONFIG_DIR` is set
- Project: `.opencode/dcp.jsonc` (or `dcp.json`) in your project’s `.opencode` directory

| Option | Default | Description |
|--------|---------|-------------|
| `enabled` | `true` | Enable/disable the plugin |
| `debug` | `false` | Log to `~/.config/opencode/logs/dcp/` |
| `model` | (session) | Model for analysis (e.g., `"anthropic/claude-haiku-4-5"`) |
| `showModelErrorToasts` | `true` | Show notifications on model fallback |
| `showUpdateToasts` | `true` | Show notifications when a new version is available |
| `strictModelSelection` | `false` | Only run AI analysis with session or configured model (disables fallback models) |
| `pruning_summary` | `"detailed"` | `"off"`, `"minimal"`, or `"detailed"` |
| `nudge_freq` | `10` | How often to remind AI to prune (lower = more frequent) |
| `protectedTools` | `["task", "todowrite", "todoread", "prune", "batch", "write", "edit"]` | Tools that are never pruned |
| `strategies.onIdle` | `["ai-analysis"]` | Strategies for automatic pruning |
| `strategies.onTool` | `["ai-analysis"]` | Strategies when AI calls `prune` |

**Strategies:** `"ai-analysis"` uses LLM to identify prunable outputs. Empty array disables that trigger. Deduplication runs automatically on every request.
<details>
<summary><strong>Default Configuration</strong> (click to expand)</summary>

```jsonc
{
// Enable or disable the plugin
"enabled": true,
// Enable debug logging to ~/.config/opencode/logs/dcp/
"debug": false,
// Show toast notifications when a new version is available
"showUpdateToasts": true,
// Summary display: "off", "minimal", or "detailed"
"pruningSummary": "detailed",
// Strategies for pruning tokens from chat history
"strategies": {
"onIdle": ["ai-analysis"],
"onTool": ["ai-analysis"]
},
"protectedTools": ["task", "todowrite", "todoread", "prune", "batch", "write", "edit"]
// Remove duplicate tool calls (same tool with same arguments)
"deduplication": {
"enabled": true,
// Additional tools to protect from pruning
"protectedTools": []
},
// Exposes a prune tool to your LLM to call when it determines pruning is necessary
"pruneTool": {
"enabled": true,
// Additional tools to protect from pruning
"protectedTools": [],
// Nudge the LLM to use the prune tool (every <frequency> tool results)
"nudge": {
"enabled": true,
"frequency": 10
}
},
// (Legacy) Run an LLM to analyze what tool calls are no longer relevant on idle
"onIdle": {
"enabled": false,
// Override model for analysis (format: "provider/model")
// "model": "anthropic/claude-haiku-4-5",
// Show toast notifications when model selection fails
"showModelErrorToasts": true,
// When true, fallback models are not permitted
"strictModelSelection": false,
// Additional tools to protect from pruning
"protectedTools": []
}
}
}
```

</details>

### Protected Tools

By default, these tools are always protected from pruning across all strategies:
`task`, `todowrite`, `todoread`, `prune`, `batch`, `write`, `edit`

The `protectedTools` arrays in each strategy add to this default list.

### Config Precedence

Settings are merged in order: **Defaults** → **Global** (`~/.config/opencode/dcp.jsonc`) → **Project** (`.opencode/dcp.jsonc`). Each level overrides the previous, so project settings take priority over global, which takes priority over defaults.
Settings are merged in order:
Defaults → Global (`~/.config/opencode/dcp.jsonc`) → Config Dir (`$OPENCODE_CONFIG_DIR/dcp.jsonc`) → Project (`.opencode/dcp.jsonc`).
Each level overrides the previous, so project settings take priority over config-dir and global, which take priority over defaults.

Restart OpenCode after making config changes.

Expand Down
97 changes: 29 additions & 68 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import type { Plugin } from "@opencode-ai/plugin"
import { getConfig } from "./lib/config"
import { Logger } from "./lib/logger"
import { createJanitorContext } from "./lib/core/janitor"
import { checkForUpdates } from "./lib/version-checker"
import { createPluginState } from "./lib/state"
import { installFetchWrapper } from "./lib/fetch-wrapper"
import { createPruningTool } from "./lib/pruning-tool"
import { createEventHandler, createChatParamsHandler } from "./lib/hooks"
import { createToolTracker } from "./lib/fetch-wrapper/tool-tracker"
import { loadPrompt } from "./lib/prompt"
import { createSessionState } from "./lib/state"
import { createPruneTool } from "./lib/strategies"
import { createChatMessageTransformHandler, createEventHandler } from "./lib/hooks"

const plugin: Plugin = (async (ctx) => {
const { config, migrations } = getConfig(ctx)
const config = getConfig(ctx)

if (!config.enabled) {
return {}
Expand All @@ -23,82 +20,46 @@ const plugin: Plugin = (async (ctx) => {

// Initialize core components
const logger = new Logger(config.debug)
const state = createPluginState()

const janitorCtx = createJanitorContext(
ctx.client,
state,
logger,
{
protectedTools: config.protectedTools,
model: config.model,
showModelErrorToasts: config.showModelErrorToasts ?? true,
strictModelSelection: config.strictModelSelection ?? false,
pruningSummary: config.pruning_summary,
workingDirectory: ctx.directory
}
)

// Create tool tracker for nudge injection
const toolTracker = createToolTracker()

// Install global fetch wrapper for context pruning and system message injection
installFetchWrapper(state, logger, ctx.client, config, toolTracker)
const state = createSessionState()

// Log initialization
logger.info("plugin", "DCP initialized", {
logger.info("DCP initialized", {
strategies: config.strategies,
model: config.model || "auto"
})

// Check for updates after a delay
setTimeout(() => {
checkForUpdates(ctx.client, logger, config.showUpdateToasts ?? true).catch(() => { })
}, 5000)

// Show migration toast if there were config migrations
if (migrations.length > 0) {
setTimeout(async () => {
try {
await ctx.client.tui.showToast({
body: {
title: "DCP: Config upgraded",
message: migrations.join('\n'),
variant: "info",
duration: 8000
}
})
} catch {
// Silently ignore toast errors
}
}, 7000)
}

return {
"experimental.chat.system.transform": async (_input: unknown, output: { system: string[] }) => {
const syntheticPrompt = loadPrompt("synthetic")
output.system.push(syntheticPrompt)
},
"experimental.chat.messages.transform": createChatMessageTransformHandler(
ctx.client,
state,
logger,
config
),
tool: config.strategies.pruneTool.enabled ? {
prune: createPruneTool({
client: ctx.client,
state,
logger,
config,
workingDirectory: ctx.directory
}),
} : undefined,
config: async (opencodeConfig) => {
// Add prune to primary_tools by mutating the opencode config
// This works because config is cached and passed by reference
if (config.strategies.onTool.length > 0) {
if (config.strategies.pruneTool.enabled) {
const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? []
opencodeConfig.experimental = {
...opencodeConfig.experimental,
primary_tools: [...existingPrimaryTools, "prune"],
}
logger.info("plugin", "Added 'prune' to experimental.primary_tools via config mutation")
logger.info("Added 'prune' to experimental.primary_tools via config mutation")
}
},
event: createEventHandler(ctx.client, janitorCtx, logger, config, toolTracker),
"chat.params": createChatParamsHandler(ctx.client, state, logger, toolTracker),
tool: config.strategies.onTool.length > 0 ? {
prune: createPruningTool({
client: ctx.client,
state,
logger,
config,
notificationCtx: janitorCtx.notificationCtx,
workingDirectory: ctx.directory
}, toolTracker),
} : undefined,
event: createEventHandler(ctx.client, config, state, logger, ctx.directory),
}
}) satisfies Plugin

Expand Down
Loading