/** * Request-body transformer. Extracts the static system prompt + tool definitions, * renders them as PNG image blocks, and rewrites the body to reference those images — * saving 65-73% input tokens while preserving reasoning quality. */ import type { ImageBlock, Message, MessagesRequest } from './types.js'; import { LINES_PER_IMAGE, maxCharsPerImage, type RenderStyle } from './render.js'; import type { GptHistoryOptions } from './openai-history.js'; import { type VisionPricing } from './vision-cost.js'; /** Per-block descriptor passed to `TransformOptions.keepSharp`. */ export interface KeepSharpBlock { /** Which live-region path is asking. `` blocks are never offered: * they always stay text, so the predicate has nothing to decide. */ readonly kind: 'tool_result' | 'tool_result_part'; /** The block's text exactly as the caller produced it (pre-render, pre-compaction). */ readonly text: string; /** `tool_use_id` of the owning tool_result, when applicable. */ readonly toolUseId?: string; } /** A block pxpipe rendered to image(s), returned in `TransformInfo.recoverable` * when the caller sets `emitRecoverable`. Lets a stateful harness restore * byte-exact content if the model needs the imaged region verbatim. */ export interface RecoverableBlock { /** `rec_` + 8 hex SHA-256 over kind + toolUseId + original text. */ readonly id: string; readonly kind: 'tool_result' | 'tool_result_part'; readonly toolUseId?: string; /** Original text before compaction/reflow/paging — the bytes to restore. */ readonly text: string; readonly imageCount: number; } export interface TransformOptions { /** Resolved model id. Its profile supplies font, columns, height, and history defaults. */ model?: string; /** Master switch — false makes this a no-op pass-through. */ compress?: boolean; /** Move tool descriptions into the same image (and stub the originals). */ compressTools?: boolean; /** Compress large tool_result text content across all user messages. */ compressToolResults?: boolean; /** Don't compress if total compressible chars below this. */ minCompressChars?: number; /** Per-block threshold for compressToolResults (chars). */ minToolResultChars?: number; /** Soft-wrap width in monospace cells. */ cols?: number; /** Hard upper bound on images per tool_result; source text truncated with a paging * marker above this to stay under Anthropic's 100-image/request cap. Default 10. */ maxImagesPerToolResult?: number; /** Ceiling on total decoded image bytes in one request, caller images * included. The provider's hard limit is a count, not a size, so the image * cap alone lets a long session assemble a request that is legal by count and * fails by weight: production traffic degrades sharply past roughly 20 MiB * with 500s, 502s, empty 200s and stalls (#157). Groups are admitted whole, * and a group that does not fit keeps its source text. */ maxImageBytes?: number; /** Chars-per-token assumption for `isCompressionProfitable()`. Default 4. */ charsPerToken?: number; /** Multi-turn amortization horizon for the history-collapse gate. N≥2 evaluates as * if N future turns share the prefix (worst-case-warm-image vs best-case-warm-text). * Default 1 (per-turn cold gate). See docs/HISTORY_CACHE_MODEL.md. */ historyAmortizationHorizon?: number; /** Tokens the un-rewritten path would have cache-hit on. Adds a one-time burn * penalty `priorWarmTokens × (CC − CR)` to the image side so the gate accounts * for invalidating a warm text cache. Default 0 (cold-start). ≤0 clamped to 0. */ priorWarmTokens?: number; /** Symmetric counterpart: tokens the image path would have cache-hit on. Adds the * same burn formula to the TEXT side, preventing the gate from flipping out of * image mode when the image prefix is already warm. Default 0. ≤0 clamped to 0. */ priorWarmImageTokens?: number; /** GPT only: collapse the OLD closed-tool-call conversation prefix into history * image(s), keeping the recent tail as text. Independent of the static slab. * Default on. See src/core/openai-history.ts. */ collapseHistory?: boolean; /** GPT only: history-collapse tuning overrides (keepTail / collapseChunk / …). */ gptHistory?: Partial; /** Re-pack image-bound text into a ↵-delimited stream to fill `cols` (~29%→75-80% * glyph-fill). ON by default (98.95% char accuracy at L1 OCR eval, +1pp vs baseline). * Hard newlines become visible ↵ glyphs — tell the model via system prompt. */ reflow?: boolean; /** Caller fidelity hint: return `true` for a block that must stay as text (IDs, * hashes, file paths — content where mis-OCR would be silent and wrong). Only * consulted on per-block live-region paths (reminders, tool_results). A throwing * or non-boolean return is treated as `false`. */ keepSharp?: (block: KeepSharpBlock) => boolean; /** When true, populate `TransformInfo.recoverable` with original text + provenance * for every block rendered to images. Off by default (entries inflate `info`; * only a stateful harness can use them). */ emitRecoverable?: boolean; } /** * Subscription OAuth requests are classified as first-party traffic only when * one of these exact identities remains the first, separate top-level system * block. Never render one into an image or concatenate other text into its * block: the endpoint answers an unclassified request with an opaque * `429 rate_limit_error: "Error"` even when the account has quota left (#149). * * Which identity ships depends on the entrypoint, not the product. Claude Code * 2.1.220 picks one of three: * * if (vertex) return CLI; * if (nonInteractive) return appendSystemPrompt ? CLI_WITHIN_SDK : AGENT_SDK; * return CLI; * * so the terminal sends the CLI line, `claude -p` / the VS Code extension / * Claude Desktop's `stream-json` mode send the Agent SDK line, and adding * `--append-system-prompt` to any of those switches to the CLI-within-SDK line. */ export declare const CLAUDE_CODE_OAUTH_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude."; export declare const CLAUDE_CODE_WITHIN_SDK_OAUTH_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK."; export declare const CLAUDE_AGENT_SDK_OAUTH_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK."; /** Empirical cpt for the system-slab path (Opus 4.7 tokenizer, N=391, observed 1.91). * Slab-specific because reminders/tool_results have unknown shape; those use CHARS_PER_TOKEN. */ export declare const SLAB_CHARS_PER_TOKEN = 2; /** Empirical cpt for the history-collapse path (same Opus 4.7 telemetry as SLAB_CHARS_PER_TOKEN). * History is even denser (tool_use JSON dominates), so 2.0 is doubly conservative. */ export declare const HISTORY_CHARS_PER_TOKEN = 2; /** Chars-per-token for the `pxpipe export` *reporting* estimate (factsheet & savings %). * Less conservative than the gate's CHARS_PER_TOKEN=4: reporting wants an accurate * figure (~3.7 for source/prose text), not a safe-side under-estimate. Single source * of truth — src/core/export.ts imports this rather than redefining it. */ export declare const REPORT_CHARS_PER_TOKEN = 3.7; /** Gate-only conservatism: a 10% upward bias on the estimated image cost, * keeping the gate on the safe (pass-through) side near break-even. This is NOT * part of any provider's documented cost — those live in `visionTokens` * (vision-cost.ts); this margin only tunes the gate, and it is deliberately the * same margin for every model family on this path. The OpenAI/Responses gate * deliberately applies NO margin: it reproduces the renderer's exact page split * and compares against an exact o200k baseline, so it has no estimation error * to absorb (see `evalOpenAIGate`). */ export declare const GATE_MARGIN = 1.1; /** Everything the gate needs to price a hypothetical render: page geometry plus * the serving model's image-cost regime (`vision`/`visionTier`, satisfied by a * `GptModelProfile`). Defaults to the dense Anthropic page. */ interface GateGeometry { cols: number; maxHeightPx: number; maxChars: number; style: RenderStyle; pricing: VisionPricing; } /** Re-exported from render.ts, which owns the cell geometry these derive from. * Kept exported here because the eval harnesses and gpt paths import them from * transform. Single implementation, so the page-capacity math can't fork. */ export { LINES_PER_IMAGE, maxCharsPerImage }; /** Lossless pre-render whitespace compactor (each `\n` costs ≥1 visual row): * 1. Strip trailing whitespace per line (preserves leading indent). * 2. Collapse 3+ consecutive newlines to 2. Typically saves 10-25% rows on * markdown/tool-doc slabs, enough to flip borderline gates to profitable. */ export declare function compactSlabWhitespace(text: string): string; /** Decompose the break-even gate into components for telemetry. Returns the * imageTokens, textTokens, and symmetric burn terms the gate uses internally, * or `null` for empty/non-finite input. */ export declare function evalCompressionProfitability(text: string, cols: number, imageCountCap?: number | undefined, charsPerToken?: number, priorWarmTokens?: number, priorWarmImageTokens?: number, shrinkWidth?: boolean, geometry?: GateGeometry): { imageTokens: number; textTokens: number; burnImageSide: number; burnTextSide: number; profitable: boolean; } | null; export declare function isCompressionProfitable(text: string, cols?: number, imageCountCap?: number, charsPerToken?: number, priorWarmTokens?: number, priorWarmImageTokens?: number, shrinkWidth?: boolean, maxCharsPerImage?: number, geometry?: GateGeometry): boolean; /** * Horizon-aware variant of `isCompressionProfitable` for history-collapse. * * Evaluates expected lifetime cost over N turns: worst-case-warm for image * (cache_create turn 1, cache_read turns 2..N) vs best-case-warm for text * (cache_read all N). Gate condition: I×(CC + CR×(N-1)) < T×CR×N. * Examples: N=5 → I < 0.30×T; N=10 → I < 0.47×T. * Falls back to cold per-turn gate when `horizon <= 1`. See docs/HISTORY_CACHE_MODEL.md. */ export declare function isCompressionProfitableAmortized(text: string, cols: number, imageCountCap: number | undefined, charsPerToken: number, horizon: number, priorWarmTokens?: number, priorWarmImageTokens?: number, shrinkWidth?: boolean, maxCharsPerImage?: number, geometry?: GateGeometry): boolean; /** Logical bucket for per-gate-call char attribution. Used by the rolling-cpt * regression to derive per-bucket marginal cpt from production telemetry. */ export type BucketName = 'static_slab' | 'reminder' | 'tool_result_json' | 'tool_result_log' | 'tool_result_prose' | 'history'; /** Pre-compaction TEXT char totals per bucket. Absent when no bucket fired. */ export type BucketChars = Partial>; /** Parsed contents of Claude Code's + git status blocks. All optional — * fields are only populated if the corresponding line is present. */ export interface EnvFields { /** Working directory at the time `claude` was launched. */ cwd?: string; isGitRepo?: boolean; /** Current git branch, parsed from or a "Branch:" line. */ gitBranch?: string; platform?: string; osVersion?: string; /** "Today's date" as Claude Code reported it (YYYY-MM-DD). */ today?: string; } export interface TransformInfo { compressed: boolean; reason?: string; /** Exact UTF-8 byte length of the final serialized provider request. */ serializedRequestBytes?: number; /** Result of the profile-level serialized request guard. */ sizeLimitOutcome?: 'within_limit' | 'rejected'; origChars: number; /** Total source chars image-encoded this request (static slab + reminders + tool_results). * Unlike `origChars` (static slab + tool docs only), reflects what `imageCount` replaced. */ compressedChars: number; imageCount: number; imageBytes: number; /** Σ width×height across all rendered images. Pairs with upstream token count for * empirical px/token regression: `tokens ≈ α·outgoingTextChars + β·imagePixels`. */ imagePixels?: number; /** Provider-estimated vision tokens the rendered images cost as input. */ imageTokens?: number; /** Provider-specific text-token estimate of the content pxpipe imaged/stripped — * the would-have-paid "as plain text" baseline. Compared against imageTokens * for the per-request saving. See src/core/openai-savings.ts. */ baselineImagedTokens?: number; /** Provider-specific estimate of native tokens added solely by pxpipe (pointers, exact-token * sheets, and framing). Removed from the unproxied counterfactual. */ nativeInjectedTokens?: number; /** Total TEXT chars in the outgoing body (system + messages, excluding image base64). * Denominator for empirical chars-per-token regression on cold-miss events. */ outgoingTextChars?: number; /** User-pinned instructions relocated to the request tail, and the chars that * cost. Both absent when nothing is pinned. Uncached by construction (the * block lands after every breakpoint), so these chars are paid every turn. */ pinChars?: number; /** Pin folding threw and was skipped. The body still goes out unpinned. */ pinError?: string; /** Claude Code's volatile per-turn billing line, stripped from the body. * The proxy forwards it as a real HTTP header on the upstream request. * Any body position at-or-after the last cache_control marker renders as * user-attributed text; any earlier position busts the cached prefix. */ billingLine?: string; /** OpenAI Responses only: local o200k decomposition of the ORIGINAL request * before pxpipe rewrites it. No provider count_tokens call. Categories are * mutually exclusive text-token estimates; imageParts counts native images. */ responsesComposition?: { instructions: number; systemDeveloper: number; userAssistant: number; functionCalls: number; functionOutputs: number; reasoningEncrypted: number; compactionOpaque: number; toolsJson: number; other: number; totalLocal: number; imageParts: number; /** Responses native-tool-state classification and realized image share. */ completedFunctionPairs?: number; recentNativeFunctionPairs?: number; oldFunctionPairs?: number; openFunctionCalls?: number; orphanFunctionOutputs?: number; malformedFunctionItems?: number; imageableFunctionCalls?: number; imageableFunctionOutputs?: number; collapsedFunctionPairs?: number; collapsedFunctionCalls?: number; collapsedFunctionOutputs?: number; /** Item `type` values that acted as a hard barrier in the Responses * planner, with occurrence counts (`local_shell_call:12`). Every barrier * forces a page break, so a frequent type here is directly responsible * for under-filled images. Diagnostic only — never affects routing. */ barrierTypes?: string[]; }; /** Length of the static (cacheable) slab rendered into the image. */ staticChars: number; /** Length of the dynamic (per-turn) slab kept as plain text. */ dynamicChars: number; dynamicBlockCount: number; /** Tag-shaped blocks in the static slab not in DYNAMIC_BLOCK_TAGS. * Canary: a new per-turn Claude Code tag would appear here before cache rate collapses. */ unknownStaticTags?: string[]; /** Static-slab tags whose content changed within a session — proven dynamic, * busting the image cache each turn. The real alert signal. */ churningStaticTags?: string[]; env?: EnvFields; /** sha8 of static slab + tool docs (what goes in the image). Repeats across turns → cache hits. */ systemSha8?: string; /** sha8 of first user message text (first 4 KiB). Rough thread/session id. */ firstUserSha8?: string; /** Raw bytes of the first rendered image. Dashboard preview only; NOT persisted to JSONL. */ firstImagePng?: Uint8Array; firstImageWidth?: number; firstImageHeight?: number; /** All rendered PNGs this request. Dashboard only; NOT persisted to JSONL. */ imagePngs?: Uint8Array[]; imageDims?: Array<{ width: number; height: number; }>; /** Legacy shared source text for one render group. Dashboard-only; not persisted. */ imageSourceText?: string; /** Source text parallel to imagePngs/imageDims. One entry per PNG; a multi-page * render may repeat its section source. Dashboard-only; not persisted. */ imageSourceTexts?: Array; toolResultImgs?: number; /** Image blocks the CLIENT already sent (screenshots, pasted images, prior * tool_result images). They count against the provider's hard image cap just * like ours do, so every pxpipe imaging path must price them in — a request * whose own images already fill the cap must not get a single one from us. * Counted once, before any rewrite. See {@link imageHeadroom}. */ nativeImages?: number; /** Imaging steps skipped because the cap was exhausted (telemetry for tuning). */ imageBudgetSkips?: number; /** Decoded bytes of the CLIENT's own image blocks, counted once before any * rewrite. They occupy the same weight budget ours do, and they are never * removed to make room: a caller's screenshot outranks our compression. */ nativeImageBytes?: number; /** Imaging groups skipped because the byte budget, not the count cap, was * exhausted. Distinct from {@link imageBudgetSkips} because the two have * different fixes: one wants fewer pages, the other wants smaller ones. */ imageByteSkips?: number; /** Set when the request landed within 10% of the byte budget. Nothing was * dropped, but the next turn of the same session probably will be. */ imageBytesNearLimit?: boolean; /** Image blocks actually present in the outgoing body — ours AND the client's. * This is the only number the provider counts. It is <= imageCount + nativeImages * because the history collapse can absorb messages that already carried images. */ wireImages?: number; /** Chars of tool docs moved to the system-text Tool Reference (not imaged). */ toolDocsChars?: number; /** Codepoints missing from the atlas (rendered as blank cells). Telemetry for atlas tuning. */ droppedChars?: number; /** Top dropped codepoints by frequency (`U+HHHH` → count), at most 20 entries. */ droppedCodepointsTop?: Record; /** Why blocks passed through without compression. Only present when count > 0. */ passthroughReasons?: { below_threshold?: number; not_profitable?: number; kept_sharp?: number; image_budget?: number; }; /** Slab gate diagnostics — imageTokens, textTokens, burn terms, and verdict. * Lets hosts measure flap-prevention efficacy and tune amortization horizon. */ gateEval?: { readonly site: 'slab'; readonly imageTokens: number; readonly textTokens: number; /** `priorWarmTokens × (CC − CR)` added to image side. */ readonly burnImageSide: number; /** `priorWarmImageTokens × (CC − CR)` added to text side (anti-flapping anchor). */ readonly burnTextSide: number; readonly profitable: boolean; }; /** Pre-compaction TEXT char totals per gate-call bucket. Rolling-cpt regression denominator. */ bucketChars?: BucketChars; /** Chars fed into the history-image renderer. Folded into `bucketChars.history` too. */ historyTextChars?: number; /** Blocks pinned as text by the caller's `keepSharp` predicate this request. */ keptSharpBlocks?: number; /** Imaged live-region blocks with original text + provenance, when `emitRecoverable`. */ recoverable?: RecoverableBlock[]; truncatedToolResults?: number; omittedChars?: number; /** History-collapse: messages collapsed into the synthetic prepended user message. */ collapsedTurns?: number; collapsedChars?: number; /** History-collapse images. Also folded into `info.imageCount`. */ collapsedImages?: number; /** sha8 of concatenated history-image base64. Stable across the collapse window → * proves Anthropic's prompt cache can `cache_read` (0.1×) instead of `cache_create`. * A changing hash means cache-key drift is back. Only set when collapse produced images. */ historyImageSha?: string; /** Freeze-grid step the history collapse actually used, in messages. Rises when the * adaptive packer merges chunks to fit the image budget; must never fall within a * session (a finer re-cut re-keys every chunk). */ historyFreezeStep?: number; /** The collapse re-cut the grid for page fill instead of cache freeze — only set when * the session's upstream cache was provably dead (idle past TTL, or after a reject). */ historyPackFill?: boolean; /** The image budget could not hold the whole closed prefix; the tail stayed live text. */ historyBudgetTrimmed?: boolean; /** sha8 of the ACTUAL cacheable prefix sent this turn (tools + system + * message blocks through the imaged history/slab boundary; the live tail is * excluded). Read-only measurement. A change turn-over-turn within a session * ⇒ pxpipe serialized different prefix bytes (we busted our own cache, * pxpipe-side); STABLE while cache_create spikes / cache_read collapses ⇒ the * prefix was evicted upstream. Decisive attribution signal (see #11). */ cachePrefixSha8?: string; /** Approx size (chars) of that cached prefix — pairs with cachePrefixSha8 so a * bust reads as growth (size up) vs pure invalidation (size unchanged). */ cachePrefixBytes?: number; /** Per-layer digests of that same pinned prefix, in wire order: tool * definitions, system blocks, and the imaged head (messages up to and * including the history/slab boundary). Exactly one of these moving names * the cache-bust culprit; the aggregate cachePrefixSha8 alone cannot. */ cachePrefixToolsSha8?: string; cachePrefixSystemSha8?: string; cachePrefixHeadSha8?: string; /** Digest of the span Anthropic actually caches: everything up to and * including the LAST cache_control marker. After a collapse this is a strict * subset of the boundary-scoped prefix — the newest freeze chunk re-renders * every turn by design and sits after the marker — so THIS is the digest that * must stay stable turn over turn, and the boundary one is context. */ cachePrefixMarkedSha8?: string; cachePrefixMarkedBytes?: number; /** Where that last marker sits, as `m.b`. A marker that * roams between turns re-cuts the cached span and busts it on its own. */ cachePrefixMarkerPos?: string; /** Why the history collapse didn't run (or did). Diagnostic only. */ historyReason?: 'no_history' | 'prefix_too_short' | 'no_closed_prefix' | 'below_min_chars' | 'below_min_tokens' | 'not_profitable' | 'too_many_images' /** Rendered, then not applied: the collapse group did not fit the decoded * image-byte budget, so the original text stands. Distinct from * `too_many_images`, which is the provider's count cap. */ | 'image_bytes' | 'render_empty' | 'over_budget' | 'collapsed'; /** Token count of the pre-compression body from /v1/messages/count_tokens (free). * Absent when probe failed — event excluded from savings rollup. */ baselineTokens?: number; /** Token count of the pre-compression body truncated at the last cache_control marker. * Absent when the original body has no cache_control markers (cacheable=0 exactly). */ baselineCacheableTokens?: number; /** 'ok': both probes resolved. 'partial': full-body resolved but cacheable-prefix * didn't (exclude from rollup — cacheable=0 fallback is dishonest). 'failed': no * baseline. undefined: no probe attempted. */ baselineProbeStatus?: 'ok' | 'partial' | 'failed'; } /** sha256[0..8] hex via Web Crypto (works in Node 18+ and Workers). 32-bit collision-safe. */ export declare function sha8(text: string): Promise; /** First user message text, capped at 4 KiB (stable thread id; hashing large pastes is wasteful). */ export declare function firstUserText(req: MessagesRequest): string; /** * True when the first message carries a `` block — the * envelope Claude Code uses to inject CLAUDE.md project instructions. Such a * message must never be rendered to pixels: imaged rules read as description * rather than instruction, so the model stops obeying them mid-session. */ export declare function firstMessageHasSystemReminder(messages: Message[] | undefined): boolean; /** Parse structured fields from the dynamic slab for telemetry. Read-only. */ export declare function extractEnvFields(dynamicText: string): EnvFields; /** Visual row count after soft-wrap at `cols`. * * Only hard `\n` starts a new row. The reflow ↵ sentinel is an inline glyph * (see wrapLines in render.ts: "never forces a row break"), so packing many * original newlines into one soft-wrapped stream must NOT inflate the row * count. Treating ↵ as a break overstated image pages ~6× on reflowed * history and flipped profitable collapses to not_profitable. */ export declare function countVisualRows(text: string, cols: number): number; /** Estimate how many images `text` will render to at the given column width. * Counts soft-wrapped visual rows, which is what render.ts actually budgets * against. Exported for tests + the paging gate. * */ export declare function estimateImageCount(textOrLen: string | number, cols: number, maxCharsPerImage?: number, maxLinesPerColumn?: number): number; /** Classify content so we can pick a truncation strategy. Cheap heuristics on * the first ~4 KiB. Returns: * - `'structured'`: JSON/YAML/diff markers at the top. Truncate tail. * - `'log'`: ≥30% of lines start with a log level or timestamp. Truncate middle. * - `'other'`: prose, file dumps, etc. Truncate middle. * Exported for tests. */ export declare function classifyContent(text: string): 'structured' | 'log' | 'other'; /** Truncate `text` so it renders to roughly `maxImages` images at the given * `cols`. Picks head/tail split based on `classifyContent`. Budget measured * in visual rows (what render.ts actually slices on). Returns the truncated * text (with paging marker embedded) and the count of chars omitted. If * `text` already fits, returns unchanged with `omittedChars: 0`. Exported * for tests. */ export declare function truncateForBudget(text: string, maxImages: number, cols: number, maxCharsPerImage?: number, linesPerImage?: number): { text: string; omittedChars: number; truncated: boolean; }; /** * Render text → Anthropic image blocks for the proxy. The width-selection rule below * is mirrored exactly by * the public SDK primitive `renderTextToImages` (library.ts), so the proxy and the * `pxpipe export` CLI emit byte-identical PNGs for the same text. Exported so * export-proxy-align.test.ts can pin that invariant against the real proxy code. */ export declare function textToImageBlocks(text: string, cols: number, /** Shrink canvas to the longest wrapped line. Default `true`. */ shrinkWidth?: boolean, style?: RenderStyle, maxHeightPx?: number): Promise<{ blocks: ImageBlock[]; /** Raw PNG bytes parallel to `blocks` (avoids re-decoding base64 for dashboard). */ pngs: Uint8Array[]; /** Pixel dimensions parallel to `pngs`. */ dims: Array<{ width: number; height: number; }>; droppedChars: number; droppedCodepoints: Map; /** Σ width×height — caller accumulates into `info.imagePixels` for px/token regression. */ pixels: number; }>; /** * Image blocks this request may still add before the provider's hard cap. * * The cap counts EVERY image on the wire: the client's own (`nativeImages`) and * ours (`imageCount`). Pricing only ours is how a request with 103 client images * still got imaged further and came back 400 — the cap is a wire property, not a * pxpipe property. Never negative; callers treat 0 as "keep it as text". */ export declare function imageHeadroom(info: TransformInfo): number; /** Bytes still available for image content, caller images already deducted. * * Separate from {@link imageHeadroom} because the two limits fail differently. * The count cap is the provider's documented limit and rejects with a clear * error. The byte budget is empirical: past roughly 20 MiB production requests * start failing as 500s, 502s, empty 200s and stalls, which read as flakiness * rather than as "too big". */ export declare function imageByteHeadroom(info: TransformInfo, limit: number): number; /** Decoded bytes of the caller's own images, at both nesting levels. Runs BEFORE * any rewrite, for the same reason {@link countNativeImages} does. */ export declare function countNativeImageBytes(messages: readonly Message[] | undefined): number; /** Count image blocks already present in the caller's messages. Runs BEFORE any * rewrite, so it sees the client's images only — ours do not exist yet. */ export declare function countNativeImages(messages: readonly Message[] | undefined): number; /** * Rewrite a Messages API request body. Returns the new body (still JSON * bytes) plus diagnostic info. On any error, returns the original bytes * unchanged. */ export declare function transformRequest(body: Uint8Array, opts?: TransformOptions): Promise<{ body: Uint8Array; info: TransformInfo; }>; //# sourceMappingURL=transform.d.ts.map