mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
ef9ddac58a
- keepSharp: caller predicate pins exact-match-critical blocks (IDs, hashes, paths) as text, overriding the chars/token heuristic. Reported via info.keptSharpBlocks. Never forces imaging; throwing predicate = false. - emitRecoverable: optional recovery map (block -> original text + provenance) so a stateful caller can re-inject or re-fetch imaged content. The recover half of the fidelity contract; documented mitigation for lossy recall. - edge/Workers-safe: process.env read is typeof-guarded; @napi-rs/canvas moved to devDependencies (atlas is build-time only, runtime PNG encoder is pure-JS). - comments: trimmed ~1,640 lines of essay/derivation/changelog narration across src/ (transform.ts -73%), keeping one-line rationale; deep math points to docs/. - New exports/types in index.ts + library.ts; +12 tests (keep-sharp, recoverable). Build clean; 354/354 tests pass.
161 lines
6.2 KiB
TypeScript
161 lines
6.2 KiB
TypeScript
/**
|
|
* Tests for the recovery channel (Task #2): `emitRecoverable` + `info.recoverable`.
|
|
*
|
|
* `keepSharp` is the *keep-as-text* half of the fidelity contract — it pins a
|
|
* block before it is ever imaged. `emitRecoverable` is the *recover* half: for
|
|
* blocks pxpipe DID render to image(s), it returns a `RecoverableBlock` carrying
|
|
* the exact original text + provenance, so a stateful caller (a harness, not a
|
|
* proxy) can re-inject the bytes or re-fetch from source if the model later
|
|
* needs the imaged region verbatim. This is the documented mitigation for the
|
|
* silent-confabulation failure mode: imaged content becomes lossy-but-recoverable
|
|
* instead of lossy-and-permanent.
|
|
*
|
|
* Contract being verified:
|
|
* - Default (option off): `info.recoverable` is undefined even when imaging.
|
|
* - emitRecoverable → true: each imaged live-region block yields an entry with
|
|
* a stable `rec_` id, kind, toolUseId, byte-exact original `text`, and
|
|
* `imageCount`.
|
|
* - The id is content-derived and stable (same content → same id).
|
|
* - A kept-sharp (never-imaged) block produces NO recovery entry.
|
|
* - Recording is free when the option is off (no entries, no field).
|
|
* - The channel is reachable through the public library wrapper.
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { transformRequest } from '../src/core/transform.js';
|
|
import { transformAnthropicMessages } from '../src/core/library.js';
|
|
|
|
const enc = new TextEncoder();
|
|
const dec = new TextDecoder();
|
|
|
|
function makeReq(content: unknown[], model = 'claude-3-5-sonnet') {
|
|
return enc.encode(
|
|
JSON.stringify({
|
|
model,
|
|
system: 'x'.repeat(80_000),
|
|
messages: [{ role: 'user', content }],
|
|
}),
|
|
);
|
|
}
|
|
|
|
function parse(body: Uint8Array): any {
|
|
return JSON.parse(dec.decode(body));
|
|
}
|
|
|
|
function userBlocks(body: Uint8Array): any[] {
|
|
const req = parse(body);
|
|
const user = (req.messages ?? []).find((m: any) => m.role === 'user');
|
|
return Array.isArray(user?.content) ? user.content : [];
|
|
}
|
|
|
|
// Big enough that the profitability gate images it by default.
|
|
const BIG = 'x'.repeat(50_000);
|
|
|
|
describe('emitRecoverable recovery channel', () => {
|
|
it('emits no recovery map by default, even when content is imaged', async () => {
|
|
const { info } = await transformRequest(
|
|
makeReq([{ type: 'tool_result', tool_use_id: 'toolu_a', content: BIG }]),
|
|
{ multiCol: 1, charsPerToken: 2 },
|
|
);
|
|
// The block WAS imaged...
|
|
expect(info.toolResultImgs ?? 0).toBeGreaterThan(0);
|
|
// ...but with the option off, pxpipe keeps nothing (a proxy can't anyway).
|
|
expect(info.recoverable).toBeUndefined();
|
|
});
|
|
|
|
it('records an imaged tool_result with byte-exact original text + provenance', async () => {
|
|
const { body, info } = await transformRequest(
|
|
makeReq([{ type: 'tool_result', tool_use_id: 'toolu_a', content: BIG }]),
|
|
{ multiCol: 1, charsPerToken: 2, emitRecoverable: true },
|
|
);
|
|
|
|
// The block was imaged out of the body...
|
|
const tr = userBlocks(body).find((b) => b.type === 'tool_result');
|
|
const hasImage =
|
|
Array.isArray(tr?.content) &&
|
|
tr.content.some((b: any) => b.type === 'image');
|
|
expect(hasImage).toBe(true);
|
|
|
|
// ...and recorded for recovery, with the exact bytes preserved.
|
|
expect(Array.isArray(info.recoverable)).toBe(true);
|
|
const entry = info.recoverable!.find((r) => r.kind === 'tool_result');
|
|
expect(entry).toBeTruthy();
|
|
expect(entry!.id).toMatch(/^rec_[0-9a-f]{8}$/);
|
|
expect(entry!.toolUseId).toBe('toolu_a');
|
|
expect(entry!.text).toBe(BIG); // byte-exact, pre-compaction
|
|
expect(entry!.imageCount).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('derives a stable content id (same content → same id)', async () => {
|
|
const opts = { multiCol: 1, charsPerToken: 2, emitRecoverable: true } as const;
|
|
const a = await transformRequest(
|
|
makeReq([{ type: 'tool_result', tool_use_id: 'toolu_a', content: BIG }]),
|
|
opts,
|
|
);
|
|
const b = await transformRequest(
|
|
makeReq([{ type: 'tool_result', tool_use_id: 'toolu_a', content: BIG }]),
|
|
opts,
|
|
);
|
|
const idA = a.info.recoverable!.find((r) => r.kind === 'tool_result')!.id;
|
|
const idB = b.info.recoverable!.find((r) => r.kind === 'tool_result')!.id;
|
|
expect(idA).toBe(idB);
|
|
|
|
// Different tool_use_id → different id (provenance is part of the key).
|
|
const c = await transformRequest(
|
|
makeReq([{ type: 'tool_result', tool_use_id: 'toolu_OTHER', content: BIG }]),
|
|
opts,
|
|
);
|
|
const idC = c.info.recoverable!.find((r) => r.kind === 'tool_result')!.id;
|
|
expect(idC).not.toBe(idA);
|
|
});
|
|
|
|
it('does NOT record a block that keepSharp kept as text (never imaged)', async () => {
|
|
const { info } = await transformRequest(
|
|
makeReq([{ type: 'tool_result', tool_use_id: 'keep_me', content: BIG }]),
|
|
{
|
|
multiCol: 1,
|
|
charsPerToken: 2,
|
|
emitRecoverable: true,
|
|
keepSharp: (blk) => blk.toolUseId === 'keep_me',
|
|
},
|
|
);
|
|
// Pinned as text up-front, so there is nothing to recover.
|
|
expect(info.keptSharpBlocks ?? 0).toBeGreaterThan(0);
|
|
expect(info.toolResultImgs ?? 0).toBe(0);
|
|
expect(info.recoverable).toBeUndefined();
|
|
});
|
|
|
|
it('records only the imaged sibling when one block is kept sharp', async () => {
|
|
const { info } = await transformRequest(
|
|
makeReq([
|
|
{ type: 'tool_result', tool_use_id: 'keep_me', content: BIG },
|
|
{ type: 'tool_result', tool_use_id: 'image_me', content: BIG },
|
|
]),
|
|
{
|
|
multiCol: 1,
|
|
charsPerToken: 2,
|
|
emitRecoverable: true,
|
|
keepSharp: (blk) => blk.toolUseId === 'keep_me',
|
|
},
|
|
);
|
|
const ids = (info.recoverable ?? []).map((r) => r.toolUseId);
|
|
expect(ids).toContain('image_me');
|
|
expect(ids).not.toContain('keep_me');
|
|
});
|
|
|
|
it('is reachable through the public library wrapper', async () => {
|
|
const result = await transformAnthropicMessages({
|
|
body: makeReq(
|
|
[{ type: 'tool_result', tool_use_id: 'toolu_a', content: BIG }],
|
|
'claude-fable-5',
|
|
),
|
|
model: 'claude-fable-5',
|
|
options: { charsPerToken: 2, emitRecoverable: true },
|
|
});
|
|
expect(result.applied).toBe(true);
|
|
const entry = (result.info.recoverable ?? []).find((r) => r.kind === 'tool_result');
|
|
expect(entry).toBeTruthy();
|
|
expect(entry!.text).toBe(BIG);
|
|
});
|
|
});
|