import { describe, expect, it } from 'vitest'; import { renderChunkToPng, renderTextToPngs, expandTabsInLine, minifyForRender, } from '../src/core/render.js'; import { encodeGrayPng, bytesToBase64 } from '../src/core/png.js'; import { transformRequest, isCompressionProfitable, maxCharsPerImage } from '../src/core/transform.js'; import { atlasRank, ATLAS_CELL_H, ATLAS_CELL_W, ATLAS_PIXELS, ATLAS_WIDE_FLAGS, ATLAS_NUM_GLYPHS, } from '../src/core/atlas.js'; describe('png encoder', () => { it('produces a valid PNG signature', async () => { const pixels = new Uint8Array(4 * 4).fill(128); // 4×4 mid-gray const png = await encodeGrayPng(pixels, 4, 4); expect(png.slice(0, 8)).toEqual( new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), ); // Last chunk should be IEND const tail = png.slice(-12); expect(String.fromCharCode(tail[4]!, tail[5]!, tail[6]!, tail[7]!)).toBe('IEND'); }); it('round-trips bytesToBase64 ↔ atob', () => { const original = new Uint8Array([0, 1, 2, 3, 254, 255]); const b64 = bytesToBase64(original); const decoded = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)); expect(decoded).toEqual(original); }); }); describe('renderer', () => { it('renders a one-line string to a single PNG', async () => { const img = await renderChunkToPng('Hello, world!'); expect(img.png.slice(0, 8)).toEqual( new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), ); expect(img.height).toBeLessThanOrEqual(1568); expect(img.width).toBeGreaterThan(0); }); it('splits very long input into multiple PNGs', async () => { const huge = ('lorem ipsum dolor sit amet '.repeat(20) + '\n').repeat(500); const imgs = await renderTextToPngs(huge); expect(imgs.length).toBeGreaterThan(1); for (const img of imgs) expect(img.height).toBeLessThanOrEqual(1568); }); // ---- Unicode coverage tests (Unifont atlas) ------------------------------- // These confirm the sparse-codepoint + wide-glyph machinery works end-to-end. // None of them assert specific PNG bytes (the byte-deterministic guarantee // is covered by the 'renders identical input...' test below); they assert // the *contract*: known glyphs render without dropping, missing glyphs // increment droppedChars, and wide chars advance two cells. it('renders a Chinese codepoint without dropping (CJK Unified)', async () => { const img = await renderChunkToPng('中文'); // U+4E2D U+6587 expect(img.droppedChars).toBe(0); expect(img.charsRendered).toBe(2); expect(img.width).toBeGreaterThan(0); }); it('renders Cyrillic without dropping', async () => { const img = await renderChunkToPng('Привет мир'); // 10 codepoints incl. space expect(img.droppedChars).toBe(0); expect(img.charsRendered).toBe(10); }); it('renders Greek, Hebrew, Arabic, box-drawing, and math symbols', async () => { // One glyph from each profile range that the atlas claims to cover. // (The renderer is left-to-right only; Hebrew/Arabic will appear in // source order, not bidi-correct order — that's a documented limitation // of this slice, not a test failure.) const sample = 'α β π — → ∑ ∫ √ ─ │ ┌ ┐'; const img = await renderChunkToPng(sample); expect(img.droppedChars).toBe(0); }); it('treats codepoints outside the atlas as dropped (e.g. emoji)', async () => { // 😀 is U+1F600 — Supplementary Plane, not in BMP. Even `full-bmp` profile // wouldn't cover it. Renderer must advance by 1 cell and bump the counter, // not crash on the surrogate pair. const img = await renderChunkToPng('hi 😀 world'); expect(img.droppedChars).toBe(1); // charsRendered counts codepoints, NOT UTF-16 units — the emoji is one // codepoint even though it occupies two UTF-16 units. expect(img.charsRendered).toBe(10); // 'hi ' (3) + 😀 (1) + ' world' (6) = 10 }); it('CJK characters advance two cells; mixed lines wrap correctly', async () => { // 100 cols, mixed Latin + CJK. 30 Latin chars + 40 CJK chars = 30 + 80 = // 110 visual columns → must wrap to 2 lines. const latin30 = 'abcdefghijklmnopqrstuvwxyz0123'; const cjk40 = '中'.repeat(40); const img = await renderChunkToPng(latin30 + cjk40, 100); // First line fills 30 + 35*2 = 100 cols (35 CJK chars). // Second line holds the remaining 5 CJK chars. // Image height: 2 lines × CELL_H + 2*PAD_Y. PAD_Y is 4 px (matches // render.ts's const). CELL_H comes from the atlas so this stays correct // across font-size changes. expect(img.charsRendered).toBe(latin30.length + 40); expect(img.droppedChars).toBe(0); const expectedHeight = 2 * 4 /* PAD_Y */ + 2 * ATLAS_CELL_H; expect(img.height).toBe(expectedHeight); }); it('does NOT split a wide glyph across the column boundary', async () => { // 99 Latin + 1 CJK at cols=100: the CJK would land at col 99 (1 col left) // and needs 2. Wrap math must move it to a new line, leaving col 99 blank // on the first line. const line = 'a'.repeat(99) + '中'; const img = await renderChunkToPng(line, 100); expect(img.charsRendered).toBe(100); expect(img.droppedChars).toBe(0); // Two lines: first has 99 'a', second has the '中'. const expectedHeight = 2 * 4 /* PAD_Y */ + 2 * ATLAS_CELL_H; expect(img.height).toBe(expectedHeight); }); // --- Atlas profile coverage: 6 blocks added per #27 + #28 ----------------- // These confirm the codepoints the drop-histogram surfaced as 95% of // production drops are now in the atlas. Each `atlasRank` returns ≥ 0 // for a representative glyph from each block. it('atlas covers Dingbats (✓ ✗ ❌)', () => { expect(atlasRank('✓'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('✗'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('❌'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); }); it('atlas covers Miscellaneous Symbols (⚠ ★)', () => { expect(atlasRank('⚠'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('★'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); }); it('atlas covers Letterlike Symbols (ℝ ℕ ℤ ℚ ℂ)', () => { expect(atlasRank('ℝ'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('ℕ'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('ℤ'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('ℚ'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('ℂ'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); }); it('atlas covers Block Elements (█ ░ ▒)', () => { expect(atlasRank('█'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('░'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('▒'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); }); it('atlas covers Geometric Shapes (▲ ▼ ► ◄ ●)', () => { expect(atlasRank('▲'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('▼'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('►'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('◄'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('●'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); }); it('atlas covers Miscellaneous Technical (⌈ ⌉ ⌊ ⌋)', () => { expect(atlasRank('⌈'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('⌉'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('⌊'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('⌋'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); }); it('atlas covers Enclosed Alphanumerics (ⓘ ① ② ⑩)', () => { expect(atlasRank('ⓘ'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('①'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('②'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('⑩'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); }); it('atlas pixel storage is bit-packed (1 bit per pixel)', () => { // Sanity check on the storage format. Total pixels across all glyphs = // numGlyphs × cellH × (cellW or 2*cellW) depending on each glyph's // wide-flag. The packed byte buffer must be ceil(totalBits / 8) bytes // — within 1 byte of the theoretical minimum. let totalBits = 0; for (let r = 0; r < ATLAS_NUM_GLYPHS; r++) { const srcW = ATLAS_WIDE_FLAGS[r] === 1 ? 2 * ATLAS_CELL_W : ATLAS_CELL_W; totalBits += srcW * ATLAS_CELL_H; } const expectedBytes = Math.ceil(totalBits / 8); expect(ATLAS_PIXELS.byteLength).toBe(expectedBytes); // Hard guarantee: the 8-bit format would have used totalBits bytes, so // bit-packed is exactly 8× smaller (modulo the rounding to whole bytes). expect(ATLAS_PIXELS.byteLength).toBeLessThanOrEqual(totalBits / 8 + 1); }); it('atlas covers Hangul Syllables (한 글 안 녕) — full-bmp profile only', () => { // The default profile is now `full-bmp`, which ships ~11k Hangul // Syllables (U+AC00..U+D7AF). The `practical` profile drops these // for Workers free-tier deployments; if someone regenerates the atlas // with ATLAS_PROFILE=practical, these expectations will (correctly) // fail — that's the signal to update the test alongside the deploy. expect(atlasRank('한'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('글'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('안'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); expect(atlasRank('녕'.codePointAt(0)!)).toBeGreaterThanOrEqual(0); }); it('rendering a mix of newly-covered glyphs produces droppedChars: 0', async () => { // The histogram script found these were the most common drops in real // traffic. After the full-bmp default plus #29 cell-height fix, all // render cleanly — Hangul `한 글` included. const sample = '✓ ⚠ ℝ █ ▲ ⌈ ⌉ ⓘ ✗ ► ▼ ● ░ ★ ℕ ① 한 글 中 文'; const img = await renderChunkToPng(sample); expect(img.droppedChars).toBe(0); expect(img.droppedCodepoints.size).toBe(0); }); it('droppedCodepoints map is populated correctly when drops occur', async () => { // 😀 is supplementary-plane (not in atlas regardless of profile). The // codepoint should appear in the map with count 1; charsRendered counts // it as a single codepoint. const img = await renderChunkToPng('hi 😀 there'); expect(img.droppedChars).toBe(1); expect(img.droppedCodepoints.size).toBe(1); expect(img.droppedCodepoints.get(0x1f600)).toBe(1); }); it('droppedCodepoints tallies repeat drops correctly', async () => { // Three occurrences of the same dropped codepoint → count 3. const img = await renderChunkToPng('😀😀😀'); expect(img.droppedChars).toBe(3); expect(img.droppedCodepoints.size).toBe(1); expect(img.droppedCodepoints.get(0x1f600)).toBe(3); }); // --- Whitespace minify (HANDOFF R1) --------------------------------------- // Conservative whitespace cleanup before tab-expand + wrap. Strip trailing // whitespace per line; collapse 4+ \n runs down to 3 \n (max 2 blank lines). // Mid-line spaces and leading indent are NEVER touched (alignment + structure // are preserved). it('minifyForRender: strips trailing spaces', () => { expect(minifyForRender('foo \n')).toBe('foo\n'); }); it('minifyForRender: strips trailing tab + space mix', () => { expect(minifyForRender('foo\t \n')).toBe('foo\n'); }); it('minifyForRender: collapses 5 newlines to 3 (= 2 blank lines)', () => { expect(minifyForRender('foo\n\n\n\n\nbar')).toBe('foo\n\n\nbar'); }); it('minifyForRender: preserves 2 newlines (= 1 blank line)', () => { expect(minifyForRender('foo\n\nbar')).toBe('foo\n\nbar'); }); it('minifyForRender: preserves 3 newlines (= 2 blank lines, the cap)', () => { expect(minifyForRender('foo\n\n\nbar')).toBe('foo\n\n\nbar'); }); it('minifyForRender: NEVER collapses mid-line spaces (alignment preserved)', () => { expect(minifyForRender('a b c')).toBe('a b c'); }); it('minifyForRender: NEVER strips leading whitespace (indent preserved)', () => { expect(minifyForRender(' foo')).toBe(' foo'); }); it('minifyForRender: real-world mix of trailing whitespace + blank runs', () => { // Stack-trace shaped: lines with trailing spaces + 5-line blank gaps. const input = 'Error: x failed \n\tat foo() \n\n\n\n\n\tat bar()\n'; const expected = 'Error: x failed\n\tat foo()\n\n\n\tat bar()\n'; expect(minifyForRender(input)).toBe(expected); }); it('minify pipeline integration: trailing whitespace + blank runs → shorter image', async () => { // Same content, with-vs-without whitespace bloat. The "bloated" version // has trailing spaces and 6-line blank gaps; the "clean" version has // neither. Both render to single-PNG output for the test; we measure // the height delta and confirm the bloated→minified reduction is real. const cleanLines = ['line one', 'line two', '', '', 'line three', 'line four']; const bloatedLines = [ 'line one ', // trailing whitespace 'line two ', '', '', '', '', '', '', '', 'line three ', 'line four ', ]; const cleanImg = await renderChunkToPng(cleanLines.join('\n')); const bloatedImg = await renderChunkToPng(bloatedLines.join('\n')); // After minify, both should render to the same final shape. expect(bloatedImg.height).toBe(cleanImg.height); expect(bloatedImg.droppedChars).toBe(0); expect(cleanImg.droppedChars).toBe(0); }); // --- Tab expansion (production bug fix) ----------------------------------- // Real telemetry on 2026-05-19 showed 5,339 of 5,358 drops (99.6%) were // U+0009 TAB. Tabs are control codepoints, not glyphs — they expand to // a visible `→` (U+2192) at the tab boundary + padding spaces to the next // 4-stop. The visible arrow preserves "this was an indent" structure for // the OCR'd model; silent spaces would lose that signal. it('expandTabsInLine: basic — a\\tb → a→<2sp>b (col 1 → col 4, span=3)', () => { expect(expandTabsInLine('a\tb')).toBe('a→ b'); }); it('expandTabsInLine: leading tab — \\tx → →<3sp>x (col 0 → col 4, span=4)', () => { expect(expandTabsInLine('\tx')).toBe('→ x'); }); it('expandTabsInLine: ab\\tc → ab→<1sp>c (col 2 → col 4, span=2)', () => { expect(expandTabsInLine('ab\tc')).toBe('ab→ c'); }); it('expandTabsInLine: abc\\tx → abc→x (col 3 → col 4, span=1, no padding)', () => { // NOTE: the team-lead brief showed `abc→ x` here, but the brief's own // formula `tabWidth - (col % tabWidth)` gives span=1 at col=3 — single // arrow, zero padding. Implementing per the formula (consistent across // all other cases); flagging the brief example as a typo. expect(expandTabsInLine('abc\tx')).toBe('abc→x'); }); it('expandTabsInLine: no tabs → unchanged (fast path)', () => { expect(expandTabsInLine('a\nb')).toBe('a\nb'); expect(expandTabsInLine('hello world')).toBe('hello world'); }); it('expandTabsInLine: tab after CJK uses visual width (中 = 2 cols)', () => { // 中 at cols 0-1, tab at col 2 → span = 4 - 2 = 2 (arrow + 1 space). expect(expandTabsInLine('中\tx')).toBe('中→ x'); }); it('renders tab-containing text with droppedChars: 0 (was dropping pre-fix)', async () => { const img = await renderChunkToPng('a\tb'); expect(img.droppedChars).toBe(0); expect(img.droppedCodepoints.size).toBe(0); }); it('renders leading tab with droppedChars: 0', async () => { const img = await renderChunkToPng('\tx'); expect(img.droppedChars).toBe(0); }); it('full pipeline: foo\\n\\tbar renders to two lines with visible → in the indent', async () => { // Brief's specific E2E ask. `foo\n\tbar` is two logical lines: // line 0: "foo" // line 1: "\tbar" → expands to "→ bar" // Both lines render cleanly with no drops; arrow U+2192 is in the Arrows // block (covered by every profile). const img = await renderChunkToPng('foo\n\tbar'); expect(img.droppedChars).toBe(0); expect(img.droppedCodepoints.size).toBe(0); // Two visible lines = 2 cell-rows of pixels (height check). const expectedHeight = 2 * 4 /* PAD_Y */ + 2 * ATLAS_CELL_H; expect(img.height).toBe(expectedHeight); // Sanity: charsRendered counts input codepoints (4 + 1 + 4 = 9 chars // including the embedded `\n`). The arrow + padding spaces aren't in // the input — they're created post-`\n`-split — so `charsRendered` // still reflects the original input length. expect(img.charsRendered).toBe('foo\n\tbar'.length); }); it('multiple tabs land on their respective tab stops', async () => { // `a\tbb\tc`: // 'a' → col 0..1 // '\t' → col 1, fills to col 4 (3 spaces) // 'bb' → col 4..6 // '\t' → col 6, fills to col 8 (2 spaces) // 'c' → col 8..9 // Net: 'a' + 3 spaces + 'bb' + 2 spaces + 'c' — all visible glyphs in // the atlas, zero drops. const img = await renderChunkToPng('a\tbb\tc'); expect(img.droppedChars).toBe(0); }); it('tab after CJK char respects East Asian Wide column count', async () => { // 中 is 2 visual cols. So tab after 中 fills col 2 → col 4 (2 spaces). const img = await renderChunkToPng('中\tx'); expect(img.droppedChars).toBe(0); }); it('tab at the start of multiple lines resets column tracking per line', async () => { // Each line independently treats tab as expanding from col 0 (4 spaces). const img = await renderChunkToPng('\ta\n\tb\n\tc'); expect(img.droppedChars).toBe(0); }); it('a long string with embedded tabs produces zero drops', async () => { // Stress test for the production failure mode (tabs in tool_result-like // text dumps with thousands of indented lines). const line = 'fn\tname\tlocation\n'.repeat(500); const img = await renderChunkToPng(line); expect(img.droppedChars).toBe(0); // Codepoint 0x0009 must NOT appear in any drop tally. expect(img.droppedCodepoints.has(0x09)).toBe(false); }); }); describe('transform', () => { it('is a no-op when below min-chars', async () => { const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'You are helpful.', }); const bytes = new TextEncoder().encode(req); const { body, info } = await transformRequest(bytes, { minCompressChars: 100 }); expect(info.compressed).toBe(false); expect(body).toBe(bytes); // returns same reference }); it('compresses large system fields into image blocks', async () => { const bigSystem = 'You are a helpful assistant. '.repeat(1100); // ~31.9k chars, well past 2-image break-even (20k) const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: bigSystem, }); const bytes = new TextEncoder().encode(req); const { body, info } = await transformRequest(bytes); expect(info.compressed).toBe(true); expect(info.imageCount).toBeGreaterThanOrEqual(1); const out = JSON.parse(new TextDecoder().decode(body)); // Default placement is 'user' — images go into the first user message, // not the system field (Anthropic rejects image blocks in `system`). const userContent = out.messages[0].content as any[]; expect(Array.isArray(userContent)).toBe(true); const imageBlocks = userContent.filter((b: any) => b.type === 'image'); expect(imageBlocks.length).toBe(info.imageCount); expect(imageBlocks[0].source.media_type).toBe('image/png'); // And the system field must NOT contain image blocks (would 400). if (Array.isArray(out.system)) { for (const b of out.system) expect(b.type).not.toBe('image'); } }); it('folds tool docs into the same image and stubs originals', async () => { const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'short', tools: [ { name: 'BigTool', // Long enough to push the combined slab past the 2-image break-even // (20k chars). 'A very long tool description. ' = 30 chars × 1100 = 33k. description: 'A very long tool description. '.repeat(1100), input_schema: { type: 'object', properties: { x: { type: 'string' } } }, }, ], }); const bytes = new TextEncoder().encode(req); const { body, info } = await transformRequest(bytes); expect(info.compressed).toBe(true); const out = JSON.parse(new TextDecoder().decode(body)); expect(out.tools[0].description).toContain('See image'); expect(out.tools[0].name).toBe('BigTool'); }); it('preserves input_schema structure (properties / required / enum) when compressing', async () => { // Production 400s were traced to the proxy replacing input_schema with a // bare `{ type: 'object' }`, which Anthropic's tool-use validator rejected // when the model tried to actually invoke a tool. The fix preserves the // schema SHELL (type, properties keys, required, enum, items) and only // strips long-form `description` / `title` / `$schema` / `default` / // `examples`. The image still carries the original schema for the model. const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'x'.repeat(40000), // force compression tools: [ { name: 'Read', description: 'Read a file from disk', input_schema: { type: 'object', description: 'Reads a file', // should be stripped $schema: 'http://json-schema.org/draft-07/schema#', // stripped properties: { file_path: { type: 'string', description: 'Absolute path to the file', // stripped }, mode: { type: 'string', enum: ['read', 'binary'], // preserved description: 'Read mode', // stripped default: 'read', // stripped }, }, required: ['file_path'], // preserved verbatim }, }, { name: 'Bash', description: 'Run a bash command', input_schema: { type: 'object', properties: { command: { type: 'string', description: 'cmd' }, env: { type: 'object', description: 'env vars', // stripped properties: { // nested properties — descriptions stripped, structure kept PATH: { type: 'string', description: 'path var' }, }, }, files: { type: 'array', items: { type: 'object', properties: { path: { type: 'string', description: 'path' }, }, required: ['path'], }, }, }, required: ['command'], }, }, ], }); const bytes = new TextEncoder().encode(req); const { body, info } = await transformRequest(bytes); expect(info.compressed).toBe(true); expect(info.reason).toBeUndefined(); // no advisory for these valid schemas const out = JSON.parse(new TextDecoder().decode(body)); // Tool 0 (Read): properties + required preserved; descriptions stripped; // enum preserved. const read = out.tools[0]; expect(read.input_schema.type).toBe('object'); expect(read.input_schema.description).toBeUndefined(); expect(read.input_schema.$schema).toBeUndefined(); expect(read.input_schema.required).toEqual(['file_path']); expect(Object.keys(read.input_schema.properties)).toEqual(['file_path', 'mode']); expect(read.input_schema.properties.file_path.type).toBe('string'); expect(read.input_schema.properties.file_path.description).toBeUndefined(); expect(read.input_schema.properties.mode.enum).toEqual(['read', 'binary']); expect(read.input_schema.properties.mode.default).toBeUndefined(); // Tool 1 (Bash): nested object + array-of-object both keep their structure. const bash = out.tools[1]; expect(bash.input_schema.required).toEqual(['command']); expect(bash.input_schema.properties.env.type).toBe('object'); expect(bash.input_schema.properties.env.description).toBeUndefined(); expect(bash.input_schema.properties.env.properties.PATH.type).toBe('string'); expect(bash.input_schema.properties.env.properties.PATH.description).toBeUndefined(); expect(bash.input_schema.properties.files.type).toBe('array'); expect(bash.input_schema.properties.files.items.type).toBe('object'); expect(bash.input_schema.properties.files.items.required).toEqual(['path']); expect(bash.input_schema.properties.files.items.properties.path.description).toBeUndefined(); }); it('tool-doc JSON is rendered compact (no whitespace) so cols=100 fill stays dense', async () => { // Regression for the 40%-fill / -69% dashboard reduction crisis: pretty // schemas (indent=2) put each key on its own line, wasting ~70% of cols // at typical widths. Compact form is unambiguous and reads fluently // on one wrapped row. The static slab+tool-docs are rendered into the // image, but we can verify the source serialization by computing what // the renderer would see for a known schema. const schema = { type: 'object', properties: { file_path: { type: 'string' }, mode: { type: 'string', enum: ['read', 'binary'] }, }, required: ['file_path'], }; const pretty = JSON.stringify(schema, null, 2); const compact = JSON.stringify(schema); // Compact must be strictly shorter and have far fewer newlines. expect(compact.length).toBeLessThan(pretty.length); expect(compact.split('\n').length).toBe(1); expect(pretty.split('\n').length).toBeGreaterThan(8); // Compact must roundtrip back to the same value (no information loss). expect(JSON.parse(compact)).toEqual(schema); }); it('flags schemas without properties via info.reason', async () => { // Some tools legitimately ship a bare `{type:'object'}` schema. We fall // back to the legacy stub but tag info.reason so we can spot them in the // events.jsonl when triaging future 400s. const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'x'.repeat(40000), tools: [ { name: 'NoSchema', description: 'd', input_schema: { type: 'object' } }, ], }); const { body, info } = await transformRequest(new TextEncoder().encode(req)); expect(info.reason).toBe('schema_no_properties'); const out = JSON.parse(new TextDecoder().decode(body)); expect(out.tools[0].input_schema).toEqual({ type: 'object' }); }); it('leaves input_schema untouched when the original is missing', async () => { // If the tool ships without an input_schema, we should NOT invent one. const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'x'.repeat(40000), tools: [{ name: 'Bare', description: 'd' }], }); const { body } = await transformRequest(new TextEncoder().encode(req)); const out = JSON.parse(new TextDecoder().decode(body)); expect('input_schema' in out.tools[0]).toBe(false); }); // Snapshot-style tests against real-world Claude Code tool schemas. // These exercise the full preservation contract: type / properties / // required / enum / items / oneOf / anyOf / allOf / $ref / numeric & // string constraints / format. Each case asserts the exact post-strip // shape so a regression in stripSchemaDescriptions surfaces immediately. describe('real-world tool-schema preservation', () => { async function rewriteOne(toolSchema: unknown): Promise { const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'x'.repeat(40000), tools: [{ name: 'T', description: 'd', input_schema: toolSchema }], }); const { body } = await transformRequest(new TextEncoder().encode(req)); const out = JSON.parse(new TextDecoder().decode(body)); return out.tools[0].input_schema; } it("Read (file_path + optional offset/limit) round-trips correctly", async () => { const got = await rewriteOne({ type: 'object', properties: { file_path: { type: 'string', description: 'The absolute path to the file to read', }, offset: { type: 'integer', description: 'Line number to start at', minimum: 0, maximum: 9007199254740991, }, limit: { type: 'integer', description: 'Number of lines', exclusiveMinimum: 0, }, }, required: ['file_path'], additionalProperties: false, }); expect(got).toEqual({ type: 'object', properties: { file_path: { type: 'string' }, offset: { type: 'integer', minimum: 0, maximum: 9007199254740991 }, limit: { type: 'integer', exclusiveMinimum: 0 }, }, required: ['file_path'], additionalProperties: false, }); }); it('Bash (command + optional timeout + boolean run_in_background) round-trips', async () => { const got = await rewriteOne({ type: 'object', properties: { command: { type: 'string', description: 'The command to execute' }, timeout: { type: 'number', description: 'Optional timeout in ms (max 600000)', maximum: 600000, }, run_in_background: { type: 'boolean', description: 'Run async, do not wait', default: false, }, }, required: ['command'], }); expect(got).toEqual({ type: 'object', properties: { command: { type: 'string' }, timeout: { type: 'number', maximum: 600000 }, run_in_background: { type: 'boolean' }, }, required: ['command'], }); }); it('Edit (file_path + old_string + new_string + replace_all) round-trips', async () => { const got = await rewriteOne({ type: 'object', properties: { file_path: { type: 'string', description: 'absolute path' }, old_string: { type: 'string', description: 'text to replace' }, new_string: { type: 'string', description: 'replacement' }, replace_all: { type: 'boolean', description: 'Replace every occurrence', default: false, }, }, required: ['file_path', 'old_string', 'new_string'], }); expect(got).toEqual({ type: 'object', properties: { file_path: { type: 'string' }, old_string: { type: 'string' }, new_string: { type: 'string' }, replace_all: { type: 'boolean' }, }, required: ['file_path', 'old_string', 'new_string'], }); }); it('preserves enum constraints (Status-style tool)', async () => { const got = await rewriteOne({ type: 'object', properties: { status: { type: 'string', description: 'Job status', enum: ['pending', 'in_progress', 'completed', 'failed'], }, }, required: ['status'], }); expect(got).toEqual({ type: 'object', properties: { status: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'failed'] }, }, required: ['status'], }); }); it("preserves oneOf/anyOf/allOf composition variants", async () => { const got = await rewriteOne({ type: 'object', properties: { identifier: { description: 'either an id or a name', oneOf: [ { type: 'string', description: 'name lookup', minLength: 1 }, { type: 'integer', description: 'numeric id', minimum: 1 }, ], }, filter: { anyOf: [ { type: 'string', description: 'plain text' }, { type: 'null' }, ], }, combo: { allOf: [ { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] }, { type: 'object', properties: { b: { type: 'number' } } }, ], }, }, required: ['identifier'], }); expect(got).toEqual({ type: 'object', properties: { identifier: { oneOf: [ { type: 'string', minLength: 1 }, { type: 'integer', minimum: 1 }, ], }, filter: { anyOf: [{ type: 'string' }, { type: 'null' }], }, combo: { allOf: [ { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] }, { type: 'object', properties: { b: { type: 'number' } } }, ], }, }, required: ['identifier'], }); }); it('preserves $ref + $defs', async () => { const got = await rewriteOne({ type: 'object', $defs: { Loc: { type: 'object', description: 'A 2D location', properties: { lat: { type: 'number', description: 'latitude' }, lng: { type: 'number', description: 'longitude' }, }, required: ['lat', 'lng'], }, }, properties: { here: { $ref: '#/$defs/Loc' }, there: { $ref: '#/$defs/Loc' }, }, required: ['here'], }); expect(got).toEqual({ type: 'object', $defs: { Loc: { type: 'object', properties: { lat: { type: 'number' }, lng: { type: 'number' } }, required: ['lat', 'lng'], }, }, properties: { here: { $ref: '#/$defs/Loc' }, there: { $ref: '#/$defs/Loc' }, }, required: ['here'], }); }); it('preserves short `format` tokens and strips long ones', async () => { const got = await rewriteOne({ type: 'object', properties: { when: { type: 'string', format: 'date-time' }, // 9 chars, kept who: { type: 'string', format: 'uri' }, // 3 chars, kept freeform: { type: 'string', // 40-char "format" — almost certainly a description in disguise. format: 'a-very-long-format-string-that-is-prose', }, }, }); expect(got).toEqual({ type: 'object', properties: { when: { type: 'string', format: 'date-time' }, who: { type: 'string', format: 'uri' }, freeform: { type: 'string' }, // long format stripped }, }); }); it('preserves pattern + numeric/length constraints + uniqueItems', async () => { const got = await rewriteOne({ type: 'object', properties: { email: { type: 'string', description: 'email address', pattern: '^[^@]+@[^@]+$', minLength: 3, maxLength: 254, }, tags: { type: 'array', description: 'list of tags', uniqueItems: true, minItems: 0, maxItems: 10, items: { type: 'string', minLength: 1 }, }, }, }); expect(got).toEqual({ type: 'object', properties: { email: { type: 'string', pattern: '^[^@]+@[^@]+$', minLength: 3, maxLength: 254, }, tags: { type: 'array', uniqueItems: true, minItems: 0, maxItems: 10, items: { type: 'string', minLength: 1 }, }, }, }); }); it('handles boolean additionalProperties (true/false)', async () => { const got = await rewriteOne({ type: 'object', properties: { a: { type: 'string' } }, additionalProperties: false, }); expect(got).toEqual({ type: 'object', properties: { a: { type: 'string' } }, additionalProperties: false, }); const got2 = await rewriteOne({ type: 'object', properties: { a: { type: 'string' } }, additionalProperties: true, }); expect(got2).toEqual({ type: 'object', properties: { a: { type: 'string' } }, additionalProperties: true, }); }); it('recognises oneOf-rooted schemas as structured (no schema_no_properties flag)', async () => { // A tool whose root schema is a union has no top-level `properties` but // IS structurally valid — it must NOT be flagged as no-structure. const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'x'.repeat(40000), tools: [ { name: 'UnionTool', description: 'd', input_schema: { oneOf: [ { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }, { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] }, ], }, }, ], }); const { body, info } = await transformRequest(new TextEncoder().encode(req)); expect(info.reason).toBeUndefined(); const out = JSON.parse(new TextDecoder().decode(body)); expect(out.tools[0].input_schema).toEqual({ oneOf: [ { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }, { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] }, ], }); }); it('leaves nodes deeper than the recursion cap untouched (no corruption)', async () => { // Build a schema 25 levels deep. The cap is 20; everything beyond it // must pass through verbatim — we'd rather ship a slightly bigger // schema than corrupt one. type Nest = { type: string; properties?: Record; description?: string }; const deep: Nest = { type: 'string', description: 'leaf' }; let cur: Nest = deep; for (let i = 0; i < 25; i++) { cur = { type: 'object', description: `level ${i}`, properties: { next: cur } }; } const got = (await rewriteOne(cur)) as Record; // Walk down and confirm we reach the original deep node intact. let node: Record = got; for (let i = 0; i < 20; i++) { const props = node.properties as Record; node = props.next as Record; } // We've now descended 20 levels (depth cap). The next 5 levels were // beyond the cap and should still carry their descriptions verbatim. let seenDescriptionBelowCap = false; while (node && typeof node === 'object') { if (typeof node.description === 'string') seenDescriptionBelowCap = true; node = (node.properties as Record | undefined)?.next as Record< string, unknown >; if (!node) break; } expect(seenDescriptionBelowCap).toBe(true); }); }); it('strips x-anthropic-billing-header line and keeps it as text', async () => { const sysText = 'x-anthropic-billing-header: cch=abc123\n' + 'real prompt text. '.repeat(2500); const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: sysText, }); const bytes = new TextEncoder().encode(req); const { body, info } = await transformRequest(bytes); expect(info.compressed).toBe(true); const out = JSON.parse(new TextDecoder().decode(body)); const textBlocks = out.system.filter((b: any) => b.type === 'text'); expect(textBlocks.some((b: any) => b.text.includes('x-anthropic-billing-header'))).toBe(true); }); it('keeps as text outside the image so cache_control stays stable', async () => { // Dense slab (long single line) so the row-aware break-even gate // greenlights compression. Same total chars as the old short-line // fixture but profitable: 1 image @ 2500 < 52800/4 = 13200 text. const staticSlab = 'claude.md ground truth. '.repeat(2200); const envBlock = "\nWorking directory: /tmp/parityproj\nIs directory a git repo: Yes\nPlatform: darwin\nToday's date: 2026-05-18\n"; const sys = staticSlab + '\n' + envBlock; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const { body: outBytes, info } = await transformRequest(body); expect(info.compressed).toBe(true); expect(info.dynamicBlockCount).toBe(1); expect(info.dynamicChars).toBeGreaterThan(0); expect(info.staticChars).toBeGreaterThan(info.dynamicChars); const out = JSON.parse(new TextDecoder().decode(outBytes)); // With placement='user' (the default), images live in the first user // message and the dynamic block is kept as text in the system // field — so cache_control on the image is unaffected by env drift. const userContent = out.messages[0].content as any[]; const sysBlocks = (Array.isArray(out.system) ? out.system : []) as any[]; const hasImage = userContent.some((b: any) => b.type === 'image'); expect(hasImage).toBe(true); // must show up as text somewhere outside the image — the dynamic // tail. With 'user' placement that's the system field. const allText = [...sysBlocks, ...userContent] .filter((b: any) => b.type === 'text') .map((b: any) => b.text) .join('\n'); expect(allText).toContain(''); expect(allText).toContain('Working directory: /tmp/parityproj'); // The static slab must NOT appear in any text block — it lives in the // image now. for (const b of [...sysBlocks, ...userContent]) { if (b.type === 'text') expect(b.text).not.toContain('claude.md ground truth.'); } }); it('puts cache_control on the image only, never on the dynamic tail', async () => { const sys = 'x'.repeat(40000) + '\nWorking directory: /tmp/x\n\n' + '\n[ ] do thing\n'; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const { body: outBytes, info } = await transformRequest(body); expect(info.dynamicBlockCount).toBe(2); const out = JSON.parse(new TextDecoder().decode(outBytes)); // cache_control must land on exactly one image block — anywhere in the // request (system field OR user message), never on a text block. const sysBlocks = (Array.isArray(out.system) ? out.system : []) as any[]; const userContent = (out.messages[0].content ?? []) as any[]; const cached = [...sysBlocks, ...userContent].filter((b: any) => b.cache_control); expect(cached.length).toBe(1); expect(cached[0].type).toBe('image'); }); it('extracts env fields (cwd, platform, today, isGitRepo, branch) into info.env', async () => { const sys = 'claude.md\n'.repeat(400) + "\n" + 'Working directory: /Users/me/code/pixelpipe\n' + 'Is directory a git repo: Yes\n' + 'Platform: darwin\n' + 'OS Version: Darwin 25.0.0\n' + "Today's date: 2026-05-18\n" + '\n' + '\nOn branch main\nnothing to commit\n'; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const { info } = await transformRequest(body); expect(info.env).toBeDefined(); expect(info.env!.cwd).toBe('/Users/me/code/pixelpipe'); expect(info.env!.isGitRepo).toBe(true); expect(info.env!.platform).toBe('darwin'); expect(info.env!.osVersion).toBe('Darwin 25.0.0'); expect(info.env!.today).toBe('2026-05-18'); expect(info.env!.gitBranch).toBe('main'); }); it('leaves info.env undefined when there is no block', async () => { const sys = 'claude.md\n'.repeat(400); const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const { info } = await transformRequest(body); expect(info.env).toBeUndefined(); }); it('computes stable systemSha8 across turns when the static slab is identical', async () => { const staticSlab = 'claude.md\n'.repeat(400); const t1 = staticSlab + "\nWorking directory: /a\nToday's date: 2026-05-18\n"; const t2 = staticSlab + "\nWorking directory: /a\nToday's date: 2026-05-19\n"; const mk = (sys: string) => new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const a = await transformRequest(mk(t1)); const b = await transformRequest(mk(t2)); expect(a.info.systemSha8).toBeDefined(); expect(b.info.systemSha8).toBeDefined(); // Static slab is identical, dynamic block changed → systemSha8 must NOT // change (the whole point is that the cached payload is stable). expect(a.info.systemSha8).toBe(b.info.systemSha8); }); it('computes firstUserSha8 from the first user message', async () => { const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [ { role: 'user', content: 'continue from HANDOFF?' }, { role: 'assistant', content: 'sure' }, { role: 'user', content: 'a totally different message' }, ], system: 'claude.md\n'.repeat(400), }), ); const { info } = await transformRequest(body); expect(info.firstUserSha8).toBeDefined(); expect(info.firstUserSha8).toMatch(/^[0-9a-f]{8}$/); }); it('renders identical input to byte-identical output (determinism = cacheability)', async () => { // The whole token-savings story collapses if the renderer is non- // deterministic, because identical system prompts on consecutive turns // would produce different image bytes → 0% cache hit. Guard rail. const sys = 'x'.repeat(40000); const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const a = await transformRequest(body); const b = await transformRequest( new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ), ); // Compare image PNG bytes only — the request envelope wraps the same // bytes but JSON ordering is deterministic too, so the whole body should // match. Default placement is 'user', so the images live in the first // user message. const ua = (JSON.parse(new TextDecoder().decode(a.body)).messages[0].content ?? []) as any[]; const ub = (JSON.parse(new TextDecoder().decode(b.body)).messages[0].content ?? []) as any[]; const imgsA = ua.filter((x: any) => x.type === 'image').map((x: any) => x.source.data); const imgsB = ub.filter((x: any) => x.type === 'image').map((x: any) => x.source.data); expect(imgsA.length).toBeGreaterThan(0); expect(imgsA).toEqual(imgsB); expect(a.info.systemSha8).toBe(b.info.systemSha8); }); it('flags unknown tag-shaped blocks in the static slab (canary for new dynamic tags)', async () => { const sys = 'claude.md\n'.repeat(400) + '\nfoo.ts\nbar.ts\n\n' + "\nWorking directory: /tmp\n"; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const { info } = await transformRequest(body); expect(info.unknownStaticTags).toBeDefined(); expect(info.unknownStaticTags).toContain('recent_files'); // is known, must NOT appear here. expect(info.unknownStaticTags).not.toContain('env'); }); it('does not flag as an unknown tag (it lives in KNOWN_STATIC_TAGS)', async () => { const sys = 'claude.md\n'.repeat(400) + '\nstring\nnumber\n\n' + '\nWorking directory: /tmp\n'; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const { info } = await transformRequest(body); // is known-static; it should NOT show up as an unknown tag. expect(info.unknownStaticTags).toBeUndefined(); }); it('omits unknownStaticTags when the static slab has no tag-shaped blocks', async () => { const sys = 'claude.md\n'.repeat(400) + '\nWorking directory: /tmp\n'; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const { info } = await transformRequest(body); expect(info.unknownStaticTags).toBeUndefined(); }); it('passes through when the system prompt is only dynamic blocks', async () => { const sys = '\nWorking directory: /tmp\n'; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: sys, }), ); const { body: outBytes, info } = await transformRequest(body, { minCompressChars: 100 }); // Static slab is empty → below_min_chars → no-op pass-through. expect(info.compressed).toBe(false); expect(info.reason).toMatch(/below_min_chars/); const out = JSON.parse(new TextDecoder().decode(outBytes)); expect(out.system).toBe(sys); }); it("uses ttl='1h' on the image cache_control (Anthropic ordering rule)", async () => { // Without ttl='1h' on our cache_control, Claude Code's own ttl='1h' // breakpoint on later user-message content triggers 400: "ttl='1h' must // not come after ttl='5m'" because our default 5m would land first. const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [{ role: 'user', content: 'hi' }], system: 'x'.repeat(40000), }), ); const { body: outBytes } = await transformRequest(body); const out = JSON.parse(new TextDecoder().decode(outBytes)); const blocks = [ ...((Array.isArray(out.system) ? out.system : []) as any[]), ...((out.messages?.[0]?.content ?? []) as any[]), ]; const cached = blocks.filter((b: any) => b.cache_control); expect(cached.length).toBe(1); expect(cached[0].cache_control.ttl).toBe('1h'); }); it('compresses long blocks in the first user message', async () => { // 'a long policy note. ' = 20 chars. 600× = 12k chars + reminder tags // — past the 10k minReminderChars threshold AND past 1-image break-even. const reminder = '\n' + 'a long policy note. '.repeat(600) + '\n'; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [ { role: 'user', content: [ { type: 'text', text: 'real user prompt' }, { type: 'text', text: reminder }, ], }, ], system: 'x'.repeat(40000), }), ); const { body: outBytes, info } = await transformRequest(body); expect(info.reminderImgs).toBeGreaterThanOrEqual(1); const out = JSON.parse(new TextDecoder().decode(outBytes)); const content = out.messages[0].content as any[]; // Reminder text must NOT appear as a text block anymore. for (const b of content) { if (b.type === 'text') expect(b.text).not.toContain(''); } // But the user's actual prompt must still be there. const userTexts = content.filter((b: any) => b.type === 'text').map((b: any) => b.text); expect(userTexts.some((t: string) => t.includes('real user prompt'))).toBe(true); // Reminder images carry NO cache_control (only the system+tools image // does — Anthropic caps at 4 breakpoints). const reminderImageBlocks = content.filter( (b: any) => b.type === 'image' && !b.cache_control, ); expect(reminderImageBlocks.length).toBeGreaterThanOrEqual(info.reminderImgs ?? 0); }); it('leaves short blocks alone (below minReminderChars)', async () => { const shortReminder = '\nshort note\n'; const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [ { role: 'user', content: [{ type: 'text', text: shortReminder }], }, ], system: 'x'.repeat(40000), }), ); const { body: outBytes, info } = await transformRequest(body); expect(info.reminderImgs ?? 0).toBe(0); const out = JSON.parse(new TextDecoder().decode(outBytes)); const allText = (out.messages[0].content as any[]) .filter((b: any) => b.type === 'text') .map((b: any) => b.text) .join('\n'); expect(allText).toContain(''); }); it('compresses large tool_result text content across user messages', async () => { // 'output line. ' = 13 chars × 1000 = 13k chars — past minToolResultChars // (10k) AND past 1-image break-even (also 10k). const bigResult = 'output line. '.repeat(1000); const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [ { role: 'user', content: [ { type: 'tool_result', tool_use_id: 'toolu_x', content: bigResult, }, ], }, ], system: 'x'.repeat(40000), }), ); const { body: outBytes, info } = await transformRequest(body); expect(info.toolResultImgs).toBeGreaterThanOrEqual(1); const out = JSON.parse(new TextDecoder().decode(outBytes)); // Find the tool_result block and confirm its content is now image blocks. const tr = (out.messages[0].content as any[]).find((b: any) => b.type === 'tool_result'); expect(tr).toBeDefined(); expect(Array.isArray(tr.content)).toBe(true); const imgInner = (tr.content as any[]).filter((b: any) => b.type === 'image'); expect(imgInner.length).toBeGreaterThanOrEqual(1); // No cache_control on tool_result images. for (const b of imgInner) expect(b.cache_control).toBeUndefined(); }); it('leaves is_error tool_results untouched (Anthropic forbids images there)', async () => { const bigResult = 'error trace. '.repeat(1000); // 13k chars — past 10k break-even const body = new TextEncoder().encode( JSON.stringify({ model: 'claude', messages: [ { role: 'user', content: [ { type: 'tool_result', tool_use_id: 'toolu_x', content: bigResult, is_error: true, }, ], }, ], system: 'x'.repeat(40000), }), ); const { body: outBytes, info } = await transformRequest(body); expect(info.toolResultImgs ?? 0).toBe(0); const out = JSON.parse(new TextDecoder().decode(outBytes)); const tr = (out.messages[0].content as any[]).find((b: any) => b.type === 'tool_result'); expect(tr).toBeDefined(); expect(tr.is_error).toBe(true); expect(typeof tr.content).toBe('string'); }); // --- dropped_codepoints_top telemetry -------------------------------------- // Records the top-20 dropped codepoints on each request. Lets the operator // see which Unicode blocks to add to the atlas profile without having to // capture & inspect the request body. it('populates droppedCodepointsTop when drops occur, sorted by count', async () => { // System slab forces compression. The slab contains drops for two distinct // supplementary-plane codepoints at different rates so we can verify the // sort order. const cpA = String.fromCodePoint(0x1f600); // 😀 const cpB = String.fromCodePoint(0x1f604); // 😄 const cpC = String.fromCodePoint(0x1f60a); // 😊 const sys = 'x'.repeat(40000) + // bulk to force compression '\n' + cpA.repeat(10) + // 10 drops of U+1F600 '\n' + cpB.repeat(3) + // 3 drops of U+1F604 '\n' + cpC.repeat(1); // 1 drop of U+1F60A const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: sys, }); const { info } = await transformRequest(new TextEncoder().encode(req)); expect(info.compressed).toBe(true); expect(info.droppedChars).toBeGreaterThanOrEqual(14); expect(info.droppedCodepointsTop).toBeDefined(); const top = info.droppedCodepointsTop!; expect(top['U+1F600']).toBe(10); expect(top['U+1F604']).toBe(3); expect(top['U+1F60A']).toBe(1); // Ensure key format is the expected U+HHHH uppercase with no surprises. for (const k of Object.keys(top)) { expect(k).toMatch(/^U\+[0-9A-F]{4,}$/); } // Sorted by count desc: iteration of object keys preserves insertion order // in V8/JSC, so the first key is the highest-count drop. const keys = Object.keys(top); expect(keys[0]).toBe('U+1F600'); }); it('omits droppedCodepointsTop entirely when no drops occur', async () => { // Pure ASCII; nothing the practical-profile atlas wouldn't cover. const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'x'.repeat(40000), }); const { info } = await transformRequest(new TextEncoder().encode(req)); expect(info.compressed).toBe(true); expect(info.droppedChars ?? 0).toBe(0); expect(info.droppedCodepointsTop).toBeUndefined(); }); it('caps droppedCodepointsTop at 20 entries', async () => { // 25 distinct supplementary-plane codepoints, each appearing N times so // we can verify the cap drops the smallest counts. let payload = 'x'.repeat(40000) + '\n'; for (let i = 0; i < 25; i++) { // U+1F300..U+1F318 — 25 distinct codepoints, each occurring (25 - i) times // so U+1F300 occurs 25 times, U+1F318 occurs 1 time. payload += String.fromCodePoint(0x1f300 + i).repeat(25 - i); } const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: payload, }); const { info } = await transformRequest(new TextEncoder().encode(req)); expect(info.droppedCodepointsTop).toBeDefined(); const top = info.droppedCodepointsTop!; expect(Object.keys(top).length).toBe(20); // The 5 smallest-count codepoints (last in the input) must be dropped // from the top-20. for (let i = 20; i < 25; i++) { const hex = (0x1f300 + i).toString(16).toUpperCase().padStart(4, '0'); expect(top[`U+${hex}`]).toBeUndefined(); } // The top entry is the most-frequent. expect(top['U+1F300']).toBe(25); }); // --- Per-block break-even gate (URGENT slice, supersedes prior threshold tests) --- // history-researcher's round-3 analysis measured Anthropic's real per-image // cost at ~2,500 tokens. At the current renderer config (14,100 chars/image) // the break-even point is 10,000 chars per image. Blocks shorter than that // cost MORE as images than as text. The fix: gate every per-block image // encoding on `isCompressionProfitable()` which checks // ceil(textLen / 14100) * 2500 < textLen / 4 // Tests below confirm the function math AND that the gates correctly skip // net-loss compressions in the full pipeline. it('isCompressionProfitable: false at 5000 chars (1 image @ 2500 > 1250 text)', () => { expect(isCompressionProfitable(5000)).toBe(false); }); it('isCompressionProfitable: false at 10000 chars (1 image @ 2500 == 2500 text — strict <)', () => { expect(isCompressionProfitable(10000)).toBe(false); }); it('isCompressionProfitable: true at 10001 chars (tiny win past break-even)', () => { expect(isCompressionProfitable(10001)).toBe(true); }); it('isCompressionProfitable: true at 14000 chars (clear single-image win)', () => { expect(isCompressionProfitable(14000)).toBe(true); }); it('isCompressionProfitable: false at 20000 chars (2 images @ 5000 == 5000 text — strict <)', () => { // Hits 2-image break-even exactly. Strict < returns false for safety — // we'd rather skip a tied trade than risk a net loss on a budgeting wobble. expect(isCompressionProfitable(20000)).toBe(false); }); it('isCompressionProfitable: true at 40000 chars (3 images, clear win)', () => { expect(isCompressionProfitable(40000)).toBe(true); }); // --- Adaptive break-even: CHARS_PER_IMAGE derived from atlas cell, not hardcoded --- // Brief: when font-rater swaps to a smaller cell (e.g. Cozette 4×7), more chars // pack into one image, so the N-image break-even thresholds shift. Tests below // verify both the regression case (current Unifont 5×11) AND that the formula // responds to `cols` (which scales chars/image linearly the same way a smaller // cell-H would). it('maxCharsPerImage: matches the historic 14,100 constant at the shipping config', () => { // Unifont 5×11, cols=100 → floor((1568−8)/11) × 100 = 141 × 100 = 14,100. // If this ever drifts, every break-even test downstream needs re-pinning. expect(maxCharsPerImage(100)).toBe(14_100); }); it('maxCharsPerImage: scales linearly with cols (same atlas)', () => { expect(maxCharsPerImage(50)).toBe(7_050); expect(maxCharsPerImage(200)).toBe(28_200); }); it('isCompressionProfitable: doubling cols halves the 2-image break-even threshold', () => { // At cols=100, CHARS_PER_IMAGE=14,100. 20,000 chars needs 2 images (cost // 5000 tokens) vs 5000 text-tokens → tied, strict `<` returns false. expect(isCompressionProfitable(20_000, 100)).toBe(false); // At cols=200, CHARS_PER_IMAGE=28,200. 20,000 chars fits in 1 image // (cost 2500 tokens) vs 5000 text-tokens → clear win. expect(isCompressionProfitable(20_000, 200)).toBe(true); }); it('isCompressionProfitable: tiny-cols config raises the break-even threshold', () => { // Simulated narrow render: cols=20 → CHARS_PER_IMAGE=2820. A 10,001-char // block needs ceil(10001/2820)=4 images (10,000 tokens) vs 2500 text → // huge net loss. At cols=100 the same block was profitable. expect(isCompressionProfitable(10_001, 100)).toBe(true); expect(isCompressionProfitable(10_001, 20)).toBe(false); }); it('isCompressionProfitable(string): row-aware → newline-heavy sparse content (~5500 chars/img) rejected as net-loss', () => { // Regression for the -69% dashboard bug: the number-arg form estimates // by chars/charsPerImage which assumes uniform line-fill. That assumes // 14100 chars/image but renderTextToPngs actually packs ~141 visual // rows/image — newline-heavy code/logs hit row cap WAY before char cap. // // 50000 chars of `x.md\n` is 5000 short lines → 5000 rows / 141 = 36 // images. 36 * 2500 = 90000 image tokens vs 50000/4 = 12500 text tokens. // Massive net loss. Number-arg form would incorrectly accept (50000 chars // / 14100 chars-per-img = 4 imgs → 10000 < 12500 → "profitable"). const sparse = 'x.md\n'.repeat(10_000); expect(isCompressionProfitable(sparse, 100)).toBe(false); expect(isCompressionProfitable(sparse.length, 100)).toBe(true); // back-compat: looser estimate }); it('isCompressionProfitable(string): row-aware → dense single-line content packs full-width and profits', () => { // Same 50000 chars but as ONE line wraps to 100-char rows → 500 rows / 141 // = 4 images. 4 * 2500 = 10000 image tokens vs 50000/4 = 12500 text → // profitable. Both forms agree on dense content. const dense = 'x'.repeat(50_000); expect(isCompressionProfitable(dense, 100)).toBe(true); expect(isCompressionProfitable(dense.length, 100)).toBe(true); }); it('isCompressionProfitable(string, cols, cap): truncation cap lets 500KB log become profitable', () => { // For tool_result paging — actual image cost is bounded by maxImagesPerToolResult // while the SAVED text is the full pre-truncation length. Without cap we'd // reject (50k rows = 355 images), with cap=10 we accept (10*2500=25000 vs // 500000/4=125000 text → win by 100k). const lines: string[] = []; for (let i = 0; i < 10_000; i++) lines.push(`log entry ${i} payload`); const log = lines.join('\n'); expect(isCompressionProfitable(log, 100)).toBe(false); // 10k rows × 2500 way over expect(isCompressionProfitable(log, 100, 10)).toBe(true); // capped, profits }); it('isCompressionProfitable: smaller-cell atlas (Cozette-shape) would let 16k blocks become 1-image wins (cols proxy)', () => { // True smaller-cell test would need to mock ATLAS_CELL_H. We use cols as // a proxy since CHARS_PER_IMAGE = cols × floor((1568−8)/cell_H) — doubling // cols at fixed cell_H is mathematically the same as halving cell_H at // fixed cols. A Cozette 4×7 cell at cols=100 yields floor(1560/7)×100 = // 22,200 chars/image, ~57% more than today. Equivalent: cols=157 at the // current cell. A 16,000-char block needs 2 images today (2-image break- // even fails); at the equivalent Cozette-shape config it fits in 1. expect(isCompressionProfitable(16_000, 100)).toBe(false); // 2 imgs @ 5000 vs 4000 text expect(isCompressionProfitable(16_000, 157)).toBe(true); // 1 img @ 2500 vs 4000 text }); it('break-even gate: 7000-char tool_result stays as text (below break-even)', async () => { // Above the old 5000 minToolResultChars cutoff but still net-loss to // image (image=2500 > text=7000/4=1750). The fast-path threshold (now // 10000) catches it first; if someone overrides --min-tool-result-chars // to 5000 the break-even check is the gate. Test it via override. const longResult = 'x'.repeat(7000); const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [ { role: 'user', content: [ { type: 'tool_result', tool_use_id: 'toolu_x', content: longResult }, ], }, ], system: 'x'.repeat(40000), }); // Override the fast-path threshold to 5000 so the break-even check is // the only thing that can reject the block. Confirms the real gate. const { body: outBytes, info } = await transformRequest( new TextEncoder().encode(req), { minToolResultChars: 5000 }, ); expect(info.compressed).toBe(true); expect(info.toolResultImgs ?? 0).toBe(0); expect(info.passthroughReasons?.not_profitable ?? 0).toBeGreaterThanOrEqual(1); // tool_result content should still be the original 7000-char string. const out = JSON.parse(new TextDecoder().decode(outBytes)); const tr = (out.messages[0].content as Array<{ type: string; content: unknown }>).find( (b) => b.type === 'tool_result', ); expect(typeof tr!.content).toBe('string'); expect((tr!.content as string).length).toBe(7000); }); it('break-even gate: 13000-char tool_result still images (clear win at 1 image)', async () => { // 13000 chars > 10000 break-even → image saves tokens. const longResult = 'x'.repeat(13000); const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [ { role: 'user', content: [ { type: 'tool_result', tool_use_id: 'toolu_x', content: longResult }, ], }, ], system: 'x'.repeat(40000), }); const { body: outBytes, info } = await transformRequest(new TextEncoder().encode(req)); expect(info.compressed).toBe(true); expect((info.toolResultImgs ?? 0)).toBeGreaterThan(0); const out = JSON.parse(new TextDecoder().decode(outBytes)); const tr = (out.messages[0].content as Array<{ type: string; content: unknown }>).find( (b) => b.type === 'tool_result', ); expect(Array.isArray(tr!.content)).toBe(true); }); it('break-even gate: 8000-char reminder stays as text (below break-even)', async () => { // Below minReminderChars threshold (10000) → marked below_threshold. const reminder = '' + 'x'.repeat(8000) + ''; const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [ { role: 'user', content: [{ type: 'text', text: reminder }] }, ], system: 'x'.repeat(40000), }); const { info } = await transformRequest(new TextEncoder().encode(req)); expect(info.compressed).toBe(true); expect(info.reminderImgs ?? 0).toBe(0); expect(info.passthroughReasons?.below_threshold ?? 0).toBeGreaterThanOrEqual(1); }); it('break-even gate: 12000-char reminder images (above threshold and profitable)', async () => { const reminder = '' + 'x'.repeat(12000) + ''; const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [ { role: 'user', content: [{ type: 'text', text: reminder }] }, ], system: 'x'.repeat(40000), }); const { info } = await transformRequest(new TextEncoder().encode(req)); expect(info.compressed).toBe(true); expect((info.reminderImgs ?? 0)).toBeGreaterThan(0); }); it('break-even gate: passthroughReasons omitted when no passthrough happened', async () => { // 40k slab, no per-block reminders or tool_results. Only the static slab // gets imaged; nothing's gated by the per-block check. const req = JSON.stringify({ model: 'claude-3-5-sonnet', messages: [{ role: 'user', content: 'hi' }], system: 'x'.repeat(40000), }); const { info } = await transformRequest(new TextEncoder().encode(req)); expect(info.compressed).toBe(true); expect(info.passthroughReasons).toBeUndefined(); }); });