Library API
Embed distil in your own agent — Python or TypeScript, in-process, no proxy and no daemon.
Python
from distil import compress_messages, expand_handle
result = compress_messages(messages) # OpenAI/Anthropic-style dicts
print(f"{result.saved_pct:.1f}% smaller")
response = client.messages.create(model=..., messages=result.messages)
original = expand_handle(result.handles[0]) # byte-exact, any time, any process
Tool results get the reversible digest; user and system text get lossless
transforms only; the model's own turns are never rewritten. The input list is
never mutated, and unchanged messages come back by identity. Pass
verbatim=True for lossless-only with no handles.
Named compress_messages/expand_handle rather
than compress/expand because distil.compress and
distil.expand are modules. Python binds a submodule onto its parent on
import, so a top-level export with those names would resolve to the function in a fresh
interpreter and to the module in any program that had touched the submodule. A
test enumerates submodules and fails on any future collision.
TypeScript
import { compress } from "distil-llm";
const r = compress(messages); // never mutates the input
console.log(`${r.savedPct.toFixed(1)}% smaller`);
And as Vercel AI SDK middleware:
import { wrapLanguageModel } from "ai";
import { distilMiddleware } from "distil-llm";
const model = wrapLanguageModel({ model: myModel, middleware: distilMiddleware() });
The middleware implements transformParams only. Compression happens on
the way in; wrapping the response would mean rewriting the model's own output.
Tool results are handled in both the v5 (output.value) and v4
(result) shapes, so an SDK bump cannot silently stop compressing the
largest thing in an agent's context.
The tier boundary — and why it is where it is
| Path | Tier | Certificate |
|---|---|---|
distil wrap | digest (reversible) | ✅ covered |
| proxy / gateway | digest (reversible) | ✅ covered |
| MCP server | digest (reversible) | ✅ covered |
| in-process library | lossless only | n/a — nothing is elided |
The in-process libraries are lossless-tier on purpose. The digest mints
restore handles whose originals must live in one store shared with the proxy and
the MCP server — otherwise expand fails to resolve a handle the model can
see. It is also the tier the decision-equivalence certificate measures, so a second
implementation of it would be a second thing to certify, and nobody would notice when
the two drifted.
The TypeScript port is held byte-identical
A JS implementation that merely compressed similarly would hand you a guarantee that does not describe the code you are running. So the port is checked against the Python engine over a shared corpus, and CI fails on any divergence.
Where byte-identity is not achievable the port declines rather than emitting
output the certificate does not cover — Python renders an integral float as
1.0 and JS renders it 1; JS objects hoist integer-like keys.
Both are detected on the source and the text is left byte-exact. Tests assert the
declines and assert that safe cases still compress, so conformance cannot be
achieved by declining everything.
Framework hooks
| Framework | Entry point |
|---|---|
| LangChain | distil.integrations.langchain.compress_messages |
| LangGraph | distil.integrations.langgraph.pre_model_hook() |
| LiteLLM | distil.integrations.litellm.compress(kwargs) |
| Agno | distil.integrations.agno.compressed_model(model) |
| Strands | distil.integrations.strands.compressing_hook() |
| Vercel AI SDK | distilMiddleware() (npm) |
Every one is duck-typed and never imports its framework. That is what keeps distil a zero-dependency install, and it means a framework release cannot break the integration.
Runnable examples
Each of these runs as-is: python_library.py · js_library.ts · js_ai_sdk_middleware.ts · python_agno.py · python_strands.py