Skip to content

Compress tool output to reduce token usage#5106

Open
meganrogge wants to merge 3 commits intomainfrom
merogge/tool-output-compression
Open

Compress tool output to reduce token usage#5106
meganrogge wants to merge 3 commits intomainfrom
merogge/tool-output-compression

Conversation

@meganrogge
Copy link
Copy Markdown
Contributor

@meganrogge meganrogge commented May 4, 2026

Adds a post-processing compression layer for tool results, inspired by ztk. When the model invokes a tool, we now have a chance to filter the result text before it reaches the model — same information, fewer tokens.

What

  • New IToolResultCompressor service in src/extension/tools/common/toolResultCompressor.ts with a per-tool filter registry. Filters are pure functions over text parts. The service preserves stderr and exit codes, skips outputs under 80 bytes, and drops any filter that throws (ztk's "never make it worse" rule).
  • Three initial filters for run_in_terminal in src/extension/tools/node/compressors/terminalOutputCompressor.ts:
    • git diff / git show — collapses unchanged context, drops index/similarity index headers, omits lockfile and snapshot diff bodies, preserves +/-/@@ lines verbatim.
    • ls -l / ls -la — keeps just file names (with / for dirs), drops perm/owner/size columns and total N.
    • npm install / yarn / pnpm install — drops progress bars, npm warn deprecated, funding/audit nag lines.
  • Wired into ToolsService.invokeTool as a single post-process step right after vscode.lm.invokeTool resolves. Works for any tool — including the core run_in_terminal — without core changes.
  • New setting github.copilot.chat.tools.compressOutput.enabled, off by default.
  • New telemetry event toolResultCompressed reporting { toolName, filters, beforeBytes, afterBytes } so we can quantify savings.

Why

The terminal tool routinely dumps tens of thousands of raw tokens (a single git diff HEAD~5 can be 90KB+). Most of that is metadata, unchanged context, lockfile churn, or progress noise. Compressing this client-side before it hits the model is a cheap, predictable token win that doesn't change tool semantics.

Tests

12 unit tests in src/extension/tools/node/test/terminalOutputCompressor.spec.ts covering command parsing, each filter's match logic, and the actual compression behavior. All passing.

Scope notes

  • Lives entirely in copilot-chat — no core API changes.
  • Default off; opt-in via setting (and later, experiment).
  • Easy to extend: add a new IToolResultFilter and register it. Future filters: test-runner output, read_file skeleton mode, grep_search per-file match cap, session-level dedup cache.
Screenshot 2026-05-04 at 8 53 52 PM

Copilot AI review requested due to automatic review settings May 4, 2026 23:37
@meganrogge meganrogge self-assigned this May 4, 2026
@meganrogge meganrogge added this to the 1.120.0 milestone May 4, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an opt-in post-processing layer that compresses tool results (initially focused on run_in_terminal) before they are forwarded to the language model, aiming to reduce token usage while preserving tool semantics where possible.

Changes:

  • Added a new IToolResultCompressor service with a filter registry and telemetry reporting of compression savings.
  • Implemented initial terminal output compressors for git diff/show, ls -l/-la, and npm/yarn/pnpm install, plus unit tests for the terminal filters.
  • Wired compression into ToolsService.invokeTool and exposed an (off-by-default) setting to enable it.
Show a summary per file
File Description
src/platform/configuration/common/configurationService.ts Adds a new config key for enabling tool result compression.
src/extension/tools/vscode-node/toolsService.ts Integrates compression into tool invocation and registers terminal compressors.
src/extension/tools/common/toolResultCompressor.ts Introduces the compressor service, filter interfaces, and compression + telemetry logic.
src/extension/tools/node/compressors/terminalOutputCompressor.ts Adds initial compression filters for common terminal commands.
src/extension/tools/node/test/terminalOutputCompressor.spec.ts Adds unit tests covering terminal filter matching and transformations.
src/extension/extension/vscode-node/services.ts Registers the new compressor service in the node DI container.
package.nls.json Adds localized setting description text.
package.json Contributes the new setting to VS Code configuration.

Copilot's findings

  • Files reviewed: 8/8 changed files
  • Comments generated: 5

Comment thread src/extension/tools/node/compressors/terminalOutputCompressor.ts Outdated
Comment thread src/extension/tools/common/toolResultCompressor.ts
Comment thread src/extension/tools/common/toolResultCompressor.ts
Comment thread src/extension/tools/common/toolResultCompressor.ts
Comment thread src/extension/tools/common/toolResultCompressor.ts
- gitDiffFilter: actually accumulate context-line runs and collapse them

- toolResultCompressor: disable failing filters for the rest of a pass; warn once

- toolResultCompressor: preserve LanguageModelTextPart2 audience when rewriting

- toolResultCompressor: rename byte/bytes -> char/chars in telemetry + docs

- Add unit tests for ToolResultCompressorService.maybeCompress
roblourens
roblourens previously approved these changes May 5, 2026
git --no-pager diff was being parsed as sub=--no-pager and missing the gitDiffFilter. Now long flags after the head are skipped.
@unveroleone
Copy link
Copy Markdown

Nice work! Maybe @roblourens or @meganrogge can look at my PR?

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.

5 participants