
Product
Introducing Repository Access Permissions and Custom Roles
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.
@tuanhung303/opencode-acp
Advanced tools
Reduce LLM token usage by up to 50% through intelligent context pruning. Auto-supersede duplicates, manually discard/distill content, and preserve critical state.
Your AI agent wastes half its tokens re-reading old tool outputs, stale file contents, and duplicate results. ACP fixes that — it's a zero-config OpenCode plugin that automatically prunes obsolete context so your agent stays fast, cheap, and focused.
WITHOUT ACP WITH ACP
┌──────────────────────────┐ ┌──────────────────────────┐
│ read(config.ts) 3k tk │ │ │
│ edit(config.ts) 2k tk │ │ │
│ read(config.ts) 3k tk │ ───► │ read(config.ts) 3k tk │ ← latest only
│ git status 1k tk │ │ git status 1k tk │ ← latest only
│ git status 1k tk │ │ │
│ glob(**/*.ts) 4k tk │ │ glob(**/*.ts) 4k tk │
├──────────────────────────┤ ├──────────────────────────┤
│ Total: ~14k tokens │ │ Total: ~8k tokens -43% │
└──────────────────────────┘ └──────────────────────────┘
| Workload | Without ACP | With ACP | Savings |
|---|---|---|---|
| Typical Session | ~80k tokens | ~40k tokens | 50% |
| Long Session | ~150k tokens | ~75k tokens | 50% |
| File-Heavy Work | ~100k tokens | ~35k tokens | 65% |
Add to your OpenCode config:
// opencode.jsonc
{
"plugin": ["@tuanhung303/opencode-acp@latest"],
}
That's it. ACP works out of the box — no configuration needed.
git status, repeated URL fetches are automatically superseded (details)discard, distill, or replace any context block by hash (API reference)ACP works with zero config. For fine-tuning, use presets:
// .opencode/acp.jsonc
{
"strategies": {
"aggressivePruning": {
"preset": "balanced", // "compact" | "balanced" | "verbose"
},
},
}
| Preset | Description | Best For |
|---|---|---|
| compact | Maximum cleanup, all options enabled | Long sessions, token-constrained |
| balanced | Good defaults, preserves user code | Most use cases (default) |
| verbose | Minimal cleanup, preserves all | Debugging, audit trails |
→ Full configuration reference
| Document | Description |
|---|---|
| Configuration | Full config reference, all flags, protected tools |
| API Reference | context_prune tool interface, batch ops, pattern replace |
| Auto-Supersede | All 8 automatic deduplication strategies |
| Troubleshooting | Common errors and fixes |
| Architecture | Plugin internals and message flow |
| Validation Guide | 43 test scenarios |
| Changelog | Version history |
| Provider | Thinking Mode | Compatible | Notes |
|---|---|---|---|
| Anthropic | Extended thinking | ✅ | Strict validation |
| DeepSeek | DeepThink | ✅ | Similar to Anthropic |
| Kimi | K1 thinking | ✅ | Similar to Anthropic |
| OpenAI | — | ✅ | No thinking mode |
| — | ✅ | No thinking mode |
npm test → 4. PRCI/CD: PRs run lint + type check + tests automatically. Merges to main auto-publish to npm.
MIT © tuanhung303
Read this section before modifying ACP code. These are hard-won lessons from debugging production issues.
❌ WRONG:
async function executeContextToolDiscard(ctx, toolCtx, hashes) {
const { state, logger } = ctx
// Validate hashes...
if (validHashes.length === 0) {
// Early return without fetching messages
const currentParams = getCurrentParams(state, [], logger) // ← BUG: Empty array
return "No valid hashes"
}
// Only fetch messages in success path
const messages = await client.session.messages(...)
}
✅ CORRECT:
async function executeContextToolDiscard(ctx, toolCtx, hashes) {
const { client, state, logger } = ctx
// ALWAYS fetch messages first - required for thinking mode API compatibility
const messagesResponse = await client.session.messages({
path: { id: toolCtx.sessionID },
})
const messages = messagesResponse.data || messagesResponse
// ALWAYS initialize session - syncs reasoning_content
await ensureSessionInitialized(client, state, toolCtx.sessionID, logger, messages)
// Now validate hashes...
if (validHashes.length === 0) {
const currentParams = getCurrentParams(state, messages, logger) // ← Use actual messages
return "No valid hashes"
}
}
Why? Anthropic's thinking mode API requires reasoning_content on all assistant messages with tool calls. Skipping ensureSessionInitialized causes 400 errors.
ensureSessionInitializedThis function syncs reasoning_content from message parts to msg.info. Without it:
error, status code: 400, message: thinking is enabled but reasoning_content is missing
in assistant tool call message at index 2
Rule: Call ensureSessionInitialized at the START of every context_prune tool function, before any early returns.
❌ WRONG:
// Completely removing reasoning_content breaks API
state.prune.reasoningPartIds.push(partId)
// No replacement content → field removed → API error
✅ CORRECT:
// Convert discard to distill with minimal placeholder
if (reasoningHashes.length > 0) {
const minimalSummaries = reasoningHashes.map(() => "—")
await executeContextReasoningDistill(
ctx,
toolCtx,
reasoningHashes.map((h, i) => [h, minimalSummaries[i]]),
)
}
Why? The reasoning_content field must exist (even if minimal) for thinking mode providers.
Always test context_prune tool changes with:
"zzzzzz" (no-op path)The no-op path is where most bugs hide because it's less tested.
| Provider | Thinking Mode | reasoning_content Required | Notes |
|---|---|---|---|
| Anthropic | Extended thinking | ✅ Yes | Strict validation |
| DeepSeek | DeepThink | ✅ Yes | Similar to Anthropic |
| Kimi | K1 thinking | ✅ Yes | Similar to Anthropic |
| OpenAI | N/A | ❌ No | No thinking mode |
| N/A | ❌ No | No thinking mode |
Always test with Anthropic when modifying context/pruning logic—it has the strictest API validation.
User Message → Transform Hook → ensureReasoningContentSync → API Call
↑
Context tool runs HERE
If context_prune tool returns early (no-op), the transform hook may not have synced reasoning_content yet. That's why each tool function must call ensureSessionInitialized itself.
When you see reasoning_content is missing errors:
ensureSessionInitialized called in ALL code paths?context_prune({ action: "discard", targets: [["zzzzzz"]] })If you're an AI assistant reading this:
Quick Pruning Reference:
- Light pressure (<50%): No action
- Moderate (50-75%): Prune old tool outputs using
context_prune({ action: "discard", targets: [[hash]] })- High (75-90%): Anchor critical info, then batch prune non-essential items
- Critical (>90%): Aggressively discard all disposable context
Always Protect: Active todos, current file edits, user requirements
Ask the user: "Would you like me to run a context optimization check?"
FAQs
Reduce LLM token usage by up to 50% through intelligent context pruning. Auto-supersede duplicates, manually discard/distill content, and preserve critical state.
The npm package @tuanhung303/opencode-acp receives a total of 37 weekly downloads. As such, @tuanhung303/opencode-acp popularity was classified as not popular.
We found that @tuanhung303/opencode-acp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.

Product
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.