fix(render): reduce dense page size

This commit is contained in:
Steven Chong
2026-05-26 13:15:54 -04:00
parent 63a7810b52
commit 36ac1faa96
4 changed files with 39 additions and 78 deletions
-53
View File
@@ -113,47 +113,6 @@ export interface HistoryCollapseInfo {
droppedCodepoints: Map<number, number>;
}
const MAX_VERBATIM_GIT_STATUS_CHARS = 8000;
function toolResultTextContent(block: ToolResultBlock): string {
const inner = block.content;
if (typeof inner === 'string') return inner;
if (!Array.isArray(inner)) return '';
const parts: string[] = [];
for (const sub of inner) {
if (sub && (sub as TextBlock).type === 'text') parts.push((sub as TextBlock).text);
}
return parts.join('\n');
}
function isGitStatusShortOutput(text: string): boolean {
if (!text || text.length > MAX_VERBATIM_GIT_STATUS_CHARS) return false;
const lines = text.split(/\r?\n/).filter((line) => line.length > 0);
if (lines.length === 0) return false;
return lines.every((line) => /^(?:[ MADRCUT?!]{2}|[?!]{2}) .+/.test(line));
}
function collectVerbatimGitStatusOutputs(
messages: Message[],
upToExclusive: number,
): string[] {
const out: string[] = [];
let used = 0;
const limit = Math.min(upToExclusive, messages.length);
for (let i = 0; i < limit; i++) {
const msg = messages[i]!;
if (!Array.isArray(msg.content)) continue;
for (const blk of msg.content) {
if (!blk || (blk as { type?: string }).type !== 'tool_result') continue;
const text = toolResultTextContent(blk as ToolResultBlock);
if (!isGitStatusShortOutput(text)) continue;
if (used + text.length > MAX_VERBATIM_GIT_STATUS_CHARS) return out;
out.push(text);
used += text.length;
}
}
return out;
}
/**
* Compute the largest index `i*` in `[0..messages.length - keepTail - 1]`
@@ -409,21 +368,9 @@ export async function collapseHistory(
}
}
// Build the synthetic user message.
const verbatimGitStatusOutputs = collectVerbatimGitStatusOutputs(messages, collapseLen);
const verbatimBlocks: TextBlock[] = verbatimGitStatusOutputs.length > 0
? [{
type: 'text',
text: [
'[Exact git status --short output preserved as text. Use this block as the source of truth over the rendered history image.]',
...verbatimGitStatusOutputs.map((out, i) => `--- git status --short #${i + 1} ---\n${out}`),
'[End exact git status --short output.]',
].join('\n\n'),
}]
: [];
const syntheticContent: ContentBlock[] = [
{ type: 'text', text: '[Earlier in this conversation:]' },
...imageBlocks,
...verbatimBlocks,
{ type: 'text', text: '[End of earlier context.]' },
];
const syntheticUser: Message = {
+1 -1
View File
@@ -43,7 +43,7 @@ export const READABLE_CHARS_PER_IMAGE = 50000;
* and collapsed history). The 50k canvas-max page is token-efficient but too
* dense for OCR on lockfiles/JSON/code. Keep those blocks paged into smaller
* images so the model can read them reliably. */
export const DENSE_CONTENT_CHARS_PER_IMAGE = 6000;
export const DENSE_CONTENT_CHARS_PER_IMAGE = 5000;
export const DENSE_CONTENT_COLS = 180;
export const DENSE_RENDER_STYLE: RenderStyle = { cellWBonus: 2, cellHBonus: 2, aa: true };
/** Default columns per row. 1568 px / 5 px-per-cell = 313 cells. We render
+15 -24
View File
@@ -24,6 +24,7 @@ import {
HISTORY_DEFAULTS,
} from '../src/core/history.js';
import { transformRequest, isCompressionProfitable } from '../src/core/transform.js';
import { DENSE_CONTENT_CHARS_PER_IMAGE } from '../src/core/render.js';
import type { Message } from '../src/core/types.js';
// A tiny helper so test fixtures are readable.
@@ -312,23 +313,12 @@ describe('collapseHistory', () => {
expect(out[2]).toBe(msgs[11]);
});
it('preserves git status --short tool output as exact text alongside history images', async () => {
const status = [
'M package-lock.json',
'M package.json',
'M packages/capnweb-typecheck/DESIGN.md',
'M packages/capnweb-typecheck/src/index.ts',
' M packages/capnweb-typecheck/src/plugin.ts',
'M tsdown.config.ts',
'?? handoff.md',
'?? packages/capnweb-typecheck/src/transform/',
].join('\n');
const msgs: Message[] = [
usr('run git status'),
asst([{ type: 'tool_use', id: 'bash-1', name: 'bash', input: { command: 'git status --short' } }]),
usr([{ type: 'tool_result', tool_use_id: 'bash-1', content: status }]),
asst('noted'),
];
it('splits dense collapsed history into readable image pages without plaintext side channels', async () => {
const body = Array.from(
{ length: 180 },
(_, i) => `line ${i}: ${'x'.repeat(80)}`,
).join('\n');
const msgs: Message[] = [usr(body), asst(body), usr(body)];
const { messages: out, info } = await collapseHistory(msgs, () => true, {
keepTail: 0,
@@ -337,14 +327,15 @@ describe('collapseHistory', () => {
});
expect(info.reason).toBe(undefined);
const content = out[0]!.content as Array<Record<string, unknown>>;
expect(content.some((c) => c.type === 'image')).toBe(true);
const exact = content.find(
(c) => c.type === 'text' && typeof c.text === 'string' && c.text.includes('source of truth'),
expect(info.collapsedImages).toBeGreaterThanOrEqual(
Math.ceil(info.collapsedChars / DENSE_CONTENT_CHARS_PER_IMAGE),
);
expect(exact).toBeTruthy();
expect((exact!.text as string)).toContain(status);
for (const line of status.split('\n')) expect((exact!.text as string)).toContain(line);
const content = out[0]!.content as Array<Record<string, unknown>>;
expect(content.filter((c) => c.type === 'text')).toEqual([
{ type: 'text', text: '[Earlier in this conversation:]' },
{ type: 'text', text: '[End of earlier context.]' },
]);
expect(content.filter((c) => c.type === 'image')).toHaveLength(info.collapsedImages);
});
it('preserves a tool_use sequence that straddles the live-tail boundary', async () => {
+23
View File
@@ -92,6 +92,29 @@ describe('dense readable render profile', () => {
expect(imgs[0]!.width).toBeGreaterThan(1000);
expect(imgs[0]!.width).toBeLessThanOrEqual(1568);
});
it('pages diff-shaped tool output at the dense readable budget', async () => {
const diffish = Array.from(
{ length: 160 },
(_, i) => `${i % 2 === 0 ? '+' : '-'}const value${i} = ${i}; // ${'changed '.repeat(8)}`,
).join('\n');
expect(diffish.length).toBeGreaterThan(DENSE_CONTENT_CHARS_PER_IMAGE * 2);
const imgs = await renderTextToPngsWithCharLimit(
diffish,
DENSE_CONTENT_COLS,
DENSE_CONTENT_CHARS_PER_IMAGE,
DENSE_RENDER_STYLE,
);
expect(imgs.length).toBeGreaterThanOrEqual(
Math.ceil(diffish.length / DENSE_CONTENT_CHARS_PER_IMAGE),
);
for (const img of imgs) {
expect(img.width).toBeLessThanOrEqual(1568);
expect(img.height).toBeLessThanOrEqual(1568);
}
});
});
describe('classifyContent', () => {