Skip to content

fix(transform): pass tool-search managed tools (defer_loading) through the tool rewrite#76

Merged
teamchong merged 3 commits into
teamchong:mainfrom
byingyang:fix/defer-loading-passthrough
Jul 19, 2026
Merged

fix(transform): pass tool-search managed tools (defer_loading) through the tool rewrite#76
teamchong merged 3 commits into
teamchong:mainfrom
byingyang:fix/defer-loading-passthrough

Conversation

@byingyang

@byingyang byingyang commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Problem

The Anthropic tool-search beta (tool_search_tool_regex_20251119 / _bm25_) lets callers mark tools with defer_loading: true: the server keeps those tools out of the model's context — billed at ~zero — until the model searches for them.

pxpipe's tool rewrite (compressTools, on by default) currently treats every entry of req.tools the same: stub the description, strip schema annotations, and render the full docs into the imaged Tool Reference. For deferred tools this materializes documentation the API was deliberately keeping free — the imaged slab carries their full docs in every request, converting a ~free deferred tool into permanently-billed image tokens. With a large tool surface (a Claude Code session with many MCP servers ships hundreds of tools, ~490k chars of docs measured), the imaged slab is dominated by documentation that should never have entered context.

Stubbing the search tool itself is also unsafe: it is schema-less and server-defined, and rewriting its description breaks the beta's contract.

Fix

Tools where defer_loading === true, and tools whose type starts with tool_search_tool, now pass through the rewrite byte-identical: no stub, no schema strip, and no entry in the imaged Tool Reference. Everything else is rewritten exactly as before.

A deferred_tools_skipped counter is added to TransformInfo and events.jsonl for observability.

Verification

  • New tests/defer-loading.test.ts (3 tests): deferred + search tools survive byte-identical while sibling tools are still rewritten; deferred docs are excluded from toolDocsChars and absent from the outgoing body outside the tool's own definition; requests without deferred tools are byte-for-byte unaffected (no-op path).
  • Full suite: 33 files / 648 tests pass; pnpm run build clean.
  • E2E: ran the patched proxy locally and drove a real Claude Code -p session through it (224 tools, no defer_loading) — transforms and responses unchanged, confirming the no-op path on today's most common traffic.

@byingyang byingyang left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 Self-review (deep): 1 CodeRabbit-missed finding(s) from a deep self-review.

Comment thread src/core/transform.ts Outdated
Tools carrying defer_loading: true belong to Anthropic's tool-search beta:
the server keeps them out of context (billed ~zero) until the model searches
for them. Rewriting them — stub description, annotation-stripped schema, full
docs rendered into the imaged Tool Reference — materializes documentation the
API was deliberately keeping free, inflating every request. A Claude Code
session with a large MCP surface ships hundreds of tools (~490k chars
observed), so an unpatched proxy can image the entire set into every request.

The tool_search_tool_regex/_bm25 server tools themselves must also pass
through untouched: they are schema-less and server-defined, and stubbing
their description breaks the beta's contract.

Deferred tools now pass through byte-identical, are excluded from the imaged
Tool Reference, and a deferred_tools_skipped counter lands in events.jsonl
for observability.
…rred tools

Self-review nit: no test exercised the below_min_chars early-return path
(all tools deferred, tiny system prompt) where transformRequest returns
the original body before req.tools = toolsRewritten ever runs. Locks in
that this path stays byte-identical.

Auto-fix-by: babysit-prs
@teamchong
teamchong force-pushed the fix/defer-loading-passthrough branch from a0bc870 to bf4215b Compare July 19, 2026 03:45
@teamchong
teamchong merged commit eaa97ff into teamchong:main Jul 19, 2026
1 check passed
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