mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
47b5e412b3
Collapse old conversation history into rendered PNG sections so the model reads a compact image instead of re-billed text, while preserving prompt caching and tool-call behavior. Measures real vs compressed token/cost. Core: - GPT history collapse (openai-history.ts): append-only, o200k token-length sectioning. Sections seal only at a tool-closed boundary (open call-id set empty), so a function_call and its function_call_output never split across the collapse cut. Fixes the OpenAI 400 "No tool call found for function call output with call_id ..." that hit long Responses-API sessions. - Anthropic cache contract (history.ts): append-only per-chunk rendering; cache_control markers are preserved/moved, never added; chunk boundaries align with caller marker seams for byte-stable prefix caching. - GPT image budget (openai.ts): detail:'original' for gpt-5.x, flagship vision-multiplier fix, patch cap; schema-strip preserves real descriptions. - Savings accounting (openai-savings.ts): cached_tokens + vision-token basis. Model scope (applicability.ts): - Default imaged scope = claude-fable-5 + gpt-5.6. - gpt-5.5 and claude-opus-4-8 stay opt-in: same pipeline, but they degrade reading dense imaged history (gist drift), so silently imaging them by default is wrong. Promotion is gated on an OCR/recall threshold. Dashboard: GPT + Anthropic rendering, per-family model toggles, persisted metrics, thumbnail-expired session UI, reflow/newline handling. Tests: cache-alignment (GPT + Anthropic), history sectioning + tool-boundary invariants, savings, dashboard, sessions/restart-restore. 452 passing.
69 lines
3.0 KiB
TypeScript
69 lines
3.0 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
computeBaselineInputEff,
|
|
computeActualInputEff,
|
|
CACHE_CREATE_RATE,
|
|
CACHE_READ_RATE,
|
|
} from '../src/core/baseline.js';
|
|
|
|
/**
|
|
* The text counterfactual must be warmth-aware. The old warmth-free version
|
|
* always priced the cacheable prefix at the cheap read rate, which fabricated a
|
|
* "free read" on cold/TTL-expiry turns — text would actually re-create the
|
|
* prefix there, same as the imaged path. That phantom read showed up as a
|
|
* dashboard loss on growth/cold turns even when imaging genuinely won.
|
|
*/
|
|
describe('computeBaselineInputEff (warmth-aware)', () => {
|
|
const inp = 1000;
|
|
const cc = 0;
|
|
const cr = 0;
|
|
|
|
it('credits nothing (returns actual) when the probe could not split the prefix', () => {
|
|
const actual = computeActualInputEff(inp, cc, cr);
|
|
expect(computeBaselineInputEff(5000, 0, inp, cc, cr, true, 0)).toBe(actual);
|
|
expect(computeBaselineInputEff(5000, -1, inp, cc, cr, false, 0)).toBe(actual);
|
|
});
|
|
|
|
it('returns 0 for a non-positive baseline', () => {
|
|
expect(computeBaselineInputEff(0, 100, inp, cc, cr)).toBe(0);
|
|
expect(computeBaselineInputEff(-10, 100, inp, cc, cr)).toBe(0);
|
|
});
|
|
|
|
it('cold turn re-creates the whole cacheable prefix at the create rate', () => {
|
|
// baseline=5000, cacheable=4000, coldTail=1000. No warm cache for text.
|
|
const got = computeBaselineInputEff(5000, 4000, inp, cc, cr, false, 0);
|
|
expect(got).toBe(4000 * CACHE_CREATE_RATE + 1000 * 1.0);
|
|
});
|
|
|
|
it('defaults to the cold (warmth-free) path when warm is omitted', () => {
|
|
const explicit = computeBaselineInputEff(5000, 4000, inp, cc, cr, false, 0);
|
|
const defaulted = computeBaselineInputEff(5000, 4000, inp, cc, cr);
|
|
expect(defaulted).toBe(explicit);
|
|
});
|
|
|
|
it('warm turn reads the prefix it already had cached at the read rate', () => {
|
|
// Same prefix size as last turn: fully reused, nothing grown.
|
|
const got = computeBaselineInputEff(5000, 4000, inp, cc, cr, true, 4000);
|
|
expect(got).toBe(4000 * CACHE_READ_RATE + 1000 * 1.0);
|
|
});
|
|
|
|
it('warm growth turn reads the reused prefix and creates only the growth', () => {
|
|
// prev cached 3000, prefix grew to 4000: reused=3000, grown=1000.
|
|
const got = computeBaselineInputEff(5000, 4000, inp, cc, cr, true, 3000);
|
|
expect(got).toBe(3000 * CACHE_READ_RATE + 1000 * CACHE_CREATE_RATE + 1000 * 1.0);
|
|
});
|
|
|
|
it('caps reused at the current cacheable when the prefix shrank', () => {
|
|
// prev cached 9000 but prefix is now 4000: reused=4000, grown=0.
|
|
const got = computeBaselineInputEff(5000, 4000, inp, cc, cr, true, 9000);
|
|
expect(got).toBe(4000 * CACHE_READ_RATE + 1000 * 1.0);
|
|
});
|
|
|
|
it('never prices a cold turn cheaper than a warm turn for the same prefix', () => {
|
|
// The regression guard: cold must cost MORE than warm (no phantom free read).
|
|
const cold = computeBaselineInputEff(5000, 4000, inp, cc, cr, false, 0);
|
|
const warm = computeBaselineInputEff(5000, 4000, inp, cc, cr, true, 4000);
|
|
expect(cold).toBeGreaterThan(warm);
|
|
});
|
|
});
|