Skip to content

feat(opencode): compress and recover tool outputs - #2819

Open
javargasm wants to merge 6 commits into
rtk-ai:developfrom
javargasm:feat/opencode-output-compression
Open

feat(opencode): compress and recover tool outputs#2819
javargasm wants to merge 6 commits into
rtk-ai:developfrom
javargasm:feat/opencode-output-compression

Conversation

@javargasm

@javargasm javargasm commented Jul 4, 2026

Copy link
Copy Markdown

Summary

  • Extend RTK token savings to OpenCode built-in, plugin, and MCP tool outputs through tool.execute.after.
  • Keep compressed output recoverable through OpenCode's existing outputPath or the new bounded rtk_retrieve tool.
  • Preserve command rewriting through tool.execute.before, including permission-gated rewrites returned with exit code 3.

Changes

Output compression

  • Built-in strategies remain scoped to OpenCode tools: read, grep, glob, task, and webfetch.
  • Unknown, plugin, and MCP tools use the generic truncate-middle fallback.
  • The complete payload, including header, recovery footer, and MCP separators, must save more than 10% before it replaces the original.
  • Trigger and target budgets are separate, terminal control sequences are removed, Unicode boundaries are preserved, and results expose structured metadata.rtk metrics.
  • rtk-minimal only strips unambiguous full-line comments and now handles extensionless files such as Dockerfile.

Reversible recovery

  • Existing OpenCode metadata.outputPath values remain visible and unchanged.
  • Otherwise, outputs up to 1 MiB are cached for 24 hours under a 16 MiB total cap with mode-0600 files.
  • rtk_retrieve supports bounded offset/limit reads and case-insensitive substring search.
  • Cache admission uses an owner-aware cross-process lock, atomic writes, symlink rejection, mutation rollback, and stale temporary cleanup.
  • Referenced cache entries are never evicted to admit newer output. Raw MCP output over 1 MiB is left for OpenCode's own durable truncation path.

OpenCode runtime alignment

  • Compression stays active when the RTK binary is unavailable; only command rewriting is disabled.
  • Built-in outputs mutate the same object returned to the AI SDK.
  • Raw MCP text and textual resources are compressed in their original slots, preserving media/resource ordering and recovery IDs.
  • Hook failures fail open so successful tool execution is never turned into a plugin failure.
  • Provider-executed tools remain outside the local tool.execute.after path by OpenCode design.

Tests and documentation

  • Add hooks/opencode/rtk.test.ts with 21 Bun tests covering budgets, recovery, cache security, MCP ordering/resources, Unicode, ANSI output, comment stripping, rewrite exit codes, and Never Block behavior.
  • Add a pinned Bun CI job for the plugin suite.
  • Update hooks/opencode/README.md with the runtime flow, recovery model, configuration, and test command.

Configuration

Variable Default Purpose
RTK_TRIGGER_TOKENS 3000 Approximate output size required before compression
RTK_TARGET_TOKENS 2500 Approximate complete compressed-result budget
RTK_MAX_OUTPUT_CHARS 32000 Hard ceiling for an applied compressed result
RTK_TOKEN_THRESHOLD 3000 Backward-compatible trigger fallback
RTK_CACHE_DIR OS temp Optional trusted cache location
RTK_DEBUG unset Enable diagnostic logging in the platform temp directory

Test plan

  • bun test hooks/opencode/rtk.test.ts - 21 passed
  • Installed plugin bundle compiled with Bun
  • Prettier check passed for plugin, tests, docs, and workflow
  • Workflow YAML parsed successfully
  • cargo fmt --all -- --check
  • cargo clippy --all-targets - no warnings
  • cargo test --all - 2410 passed, 8 ignored
  • test_opencode_plugin_install_and_update - passed
  • Runtime harness against @opencode-ai/plugin and OpenCode 1.17.18
  • Live OpenCode tool verification: read compressed approximately 7509 to 2500 tokens and rtk_retrieve recovered omitted content

Runtime verification

  • Installed and repository plugin SHA-256 values matched exactly.
  • git status --short rewrote to rtk git status --short.
  • A 67,522-character result compressed to 10,000 characters with 85.2% estimated savings.
  • Mixed MCP output retained text,image,text ordering and an intact recovery ID.
  • Cache files were created with mode 0600, and search retrieval recovered the original unique content.

Review scope

This PR now changes 4 files with 1,260 additions and 23 deletions. The update intentionally remains in the existing PR because the embedded plugin, its behavioral/security tests, documentation, and CI gate form one deployable unit; splitting them would leave an untested or lossy intermediate state.

Suggested review order:

  1. hooks/opencode/rtk.test.ts - behavioral contract
  2. hooks/opencode/rtk.ts - implementation
  3. hooks/opencode/README.md - user-facing contract
  4. .github/workflows/ci.yml - focused test gate

Commits

  1. feat(opencode): compress heavy tool outputs in the plugin
  2. refactor(opencode): clean up plugin structure and readability
  3. refactor(opencode): scope HEAVY_TOOLS to OpenCode built-ins only
  4. feat(opencode): make output compression recoverable

Notes for reviewers

  • No Rust production code changed. The plugin remains embedded through include_str! in src/hooks/init.rs.
  • The implementation was reviewed adversarially for risk, reliability, resilience, and readability; all merge-blocking findings were resolved before push.

The OpenCode plugin was a thin delegator that only rewrote bash/shell
commands via `rtk rewrite`. But OpenCode's built-in tools (read, grep,
glob, task, webfetch) and MCP tools (codegraph, context7, engram,
graphify, chrome-devtools, notebooks) never pass through `rtk rewrite`
— their output reaches the LLM untouched.

Add a `tool.execute.after` hook that compresses those outputs client-side:

- Per-tool strategy registry (HEAVY_TOOLS); unlisted tools fall back to
  truncate-middle so every heavy output is covered.
- Strategies: truncate-middle, truncate-tail, json-compact, and
  rtk-minimal (strips comments, preserves OpenCode's `N:` line prefixes,
  mirroring `rtk read --level minimal`).
- Conservative guards mirroring the Rust core's never_worse: only
  compress above RTK_TOKEN_THRESHOLD, only apply when it saves >10%.
- MCP sink handling: mutate output.content[] (rebuilt into output.output
  after the hook since OpenCode 458ec7b37); skip mixed/binary content so
  image and resource attachments are never dropped.
- RTK_DEBUG=1 opt-in logging to /tmp/rtk-plugin.log; no I/O by default.

Configurable via RTK_TOKEN_THRESHOLD, RTK_MAX_OUTPUT_CHARS, RTK_DEBUG.
Document both responsibilities in hooks/opencode/README.md.
@CLAassistant

CLAassistant commented Jul 4, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

javargasm added 3 commits July 9, 2026 08:42
No behavior change — output is byte-identical to the previous version
(verified with a differential harness over 52 cases: text/JSON/source
inputs across every strategy and boundary condition).

- Extract magic numbers into named tunables (CHARS_PER_TOKEN, HEAD_RATIO,
  MIN_SAVINGS_PCT, JSON_* limits) instead of literals scattered inline.
- Replace the per-key comment wall in HEAVY_TOOLS with a compact table and
  a single strategy legend.
- Build the comment-style lookup from an extension→style map instead of a
  long switch; C_STYLE/HASH_STYLE are now shared constants.
- Factor the tool-result plumbing into extractText()/writeSink() so the
  two hooks read top-to-bottom without inline sink juggling.
- Share truncationMarker() and tokensFromChars() between strategies.
- Fold debug logging into logDebug() (timestamp added there) and gate the
  hot path on sink presence.

Net -37 lines (433 → 396), same embedded-plugin install test coverage.
The registry hard-coded strategies for MCP servers and plugins (codegraph,
context7, graphify, chrome-devtools, notebooks, engram) that most users
don't run. The plugin ships to everyone via `rtk init`, so baking in those
tool ids added noise for the common case.

Keep explicit strategies only for OpenCode's built-in tools (read, grep,
glob, task, webfetch), which are present in every install. All other tools
— MCP servers, third-party plugins, anything unknown — now flow through
DEFAULT_STRATEGY (truncate-middle), which already handles arbitrary output
safely. No tool loses compression; the non-built-ins just use the generic
path instead of a hard-coded one.

Update hooks/opencode/README.md to match.
Keep compression active without the RTK binary, preserve MCP content ordering, and expose bounded recovery through rtk_retrieve.

Add atomic cache handling, final-payload budgets, structured metrics, focused Bun coverage, CI validation, and updated documentation.
@javargasm javargasm changed the title feat(opencode): compress heavy tool outputs in the plugin feat(opencode): compress and recover tool outputs Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants