How Headroom Handles Tool Outputs Differently from User Messages: SDK Source Code Deep Dive
Headroom identifies tool-result messages by their role: "tool" and structural markers, normalizes them into OpenAI-format blocks that the compression proxy can summarize, and preserves user messages unchanged unless a developer overrides the default behavior with custom hooks.
If you are building agentic applications with large language models, understanding how Headroom handles tool outputs differently from user messages is critical to preserving context window integrity. According to the chopratejas/headroom source code, the SDK treats tool results as a distinct compressible message type while shielding user messages from modification by default. This design allows large JSON payloads returned by functions to be summarized without distorting the original user intent.
How Headroom Detects Tool Messages Versus User Messages
In sdk/typescript/src/utils/format.ts, the detectFormat utility scans the message array for structural markers that separate tool traffic from user traffic. It checks for OpenAI-style tool_calls arrays, Vercel-style tool-call and tool-result parts, and—most importantly—the presence of role: "tool" together with a tool_call_id (lines 33–46).
Converting Provider Formats to OpenAI Tool-Result Blocks
Once Headroom detects a tool message, it normalizes the array into an OpenAI lingua franca before compression. In sdk/typescript/src/utils/format.ts, the toOpenAI router dispatches messages to format-specific helpers such as vercelToOpenAI and anthropicToOpenAI (lines 36–44). For a tool-result message, the helper creates a tool-result block that carries the raw JSON or string payload alongside the originating tool_call_id. This normalization ensures that the downstream proxy can treat all tool outputs as uniform text content regardless of their original provider.
Compression Proxy: Why Tool Outputs Are Summarized but User Messages Are Preserved
The actual compression happens in sdk/typescript/src/compress.ts. The compress function forwards the OpenAI-converted messages to the Headroom proxy (lines 58–62). Because the proxy receives tool results as ordinary text inside tool-result blocks, it can freely summarize or truncate large JSON payloads. In contrast, user messages retain role: "user" and are shielded by the default protective bias: the SDK does not apply the "protect-user-message" logic to role: "tool" messages. Consequently, only tool outputs are compressed while user prompts remain intact.
Preserving Tool Semantics on the Return Trip
After the proxy returns compressed OpenAI messages, sdk/typescript/src/utils/format.ts reconstructs the original provider format via fromOpenAI (lines 73–79). When it encounters a compressed tool-result block, it emits a Vercel-style tool-result part—or the equivalent Anthropic, Gemini, or other provider shape—so that downstream agent code still receives a proper tool response. This round-trip conversion guarantees that format normalization never leaks tool semantics.
Practical Example: Compressing Tool Results Without Modifying User Messages
The following TypeScript snippet demonstrates a realistic message flow that includes a user prompt, an assistant tool call, and a large tool-result payload. When you pass this array to compress, Headroom leaves the user message untouched and targets only the tool output for summarization.
import { compress, CompressionHooks } from "headroom";
const messages = [
{ role: "user", content: "Show me the latest sales numbers." },
{
role: "assistant",
content: "",
tool_calls: [
{
id: "tc_1",
type: "function",
function: { name: "fetch_sales", arguments: "{}" },
},
],
},
{
role: "tool",
content: JSON.stringify({ jan: 1200, feb: 1350, mar: 1420 }),
tool_call_id: "tc_1",
},
];
compress(messages, {
hooks: new CompressionHooks(),
tokenBudget: 500,
}).then((result) => {
console.log(result.messages);
});
When you run this code, Headroom executes four distinct steps:
detectFormatrecognizes the mixed message flow and identifies the tool-result payload.toOpenAIconverts the tool result into a standardizedtool-resultblock that includes thetool_call_id.- The Headroom proxy summarizes the large JSON payload because tool blocks are treated as ordinary compressible text.
fromOpenAIrestores the shortened payload back into a Vercel-styletool-resultpart for downstream consumption.
For a production-ready illustration, see sdk/typescript/examples/tool-calling-agent.ts, which shows an agent where tool results are automatically compressed during active conversation turns.
Customizing Tool Output Handling with CompressionHooks
While the default behavior compresses tool outputs and protects user messages, developers can override this logic through hooks. In sdk/typescript/src/hooks.ts, the extractToolCalls helper records every tool name that appears—whether from role: "tool" messages or tool-call parts—around lines 95–101. You can feed this metadata into a custom CompressionHooks instance to apply per-tool bias, skip summarization for specific functions, or run post-processing on selected payloads.
Summary
- Role detection – In
sdk/typescript/src/utils/format.ts,detectFormatidentifies tool messages viarole: "tool",tool_call_id, and provider-specific structural markers. - OpenAI normalization –
toOpenAIconverts tool results into uniformtool-resultblocks so the proxy can process them as plain text. - Selective compression –
compressinsdk/typescript/src/compress.tssubjects tool outputs to summarization while leavingrole: "user"messages unchanged. - Format restoration –
fromOpenAIrebuilds the native provider format after compression, emitting propertool-resultparts for downstream compatibility. - Hook extensibility –
extractToolCallsinsdk/typescript/src/hooks.tsexposes tool metadata for custom bias and conditional processing.
Frequently Asked Questions
Does Headroom compress user messages by default?
No. Headroom's default CompressionHooks apply a protective bias to user messages, leaving them intact during a compression run. Only tool-result payloads are treated as freely compressible text unless you explicitly customize the hook behavior.
How does Headroom detect a tool result across different LLM providers?
The detectFormat utility in sdk/typescript/src/utils/format.ts inspects the message array for provider-specific markers. These include OpenAI's tool_calls arrays, Vercel's tool-call and tool-result parts, and the role: "tool" field paired with a tool_call_id.
Can developers prevent specific tool outputs from being summarized?
Yes. You can supply custom hooks to the compress function. The extractToolCalls helper in sdk/typescript/src/hooks.ts surfaces every tool name that appears in the conversation, enabling you to write conditional logic that protects selected tool results or applies specialized post-processing.
What happens to the tool_call_id during the compression pipeline?
The tool_call_id is preserved throughout the round trip. toOpenAI embeds it into the normalized tool-result block, and fromOpenAI carries it back into the provider-native format so the link between the assistant's tool request and the tool's response remains intact.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →