374 Commits

Author SHA1 Message Date
Steven 7e48713b4e feat: make Gemini 3.6 Flash a default pxpipe reader (#134)
Add native Google AI Studio transformation, measured token accounting, dashboard support, and a production 312×728 profile for Gemini 3.6 Flash.

Gemini uses 26% fewer vision tokens than Fable 5 per full page and matches or exceeds Fable across the production-profile quality suite.
2026-07-21 16:41:40 -04:00
dhruvraajeev 41f2ed1d39 fix(render): glyph surgery so Spleen 5x8 K no longer reads as H (#127)
* fix(render): glyph surgery so Spleen 5x8 K no longer reads as H

The stock Spleen K was H with one crossbar pixel removed (Hamming 1), the
worst confusable pair in the atlas. Because the API downscales images and the
model infers rather than scans, that difference vanishes and K is silently
misread as H - with no signal of uncertainty.

gen-atlas.ts now repaints K with a diagonal-legged bitmap: Hamming 8 from H,
and >=6 from every other ASCII glyph. Scoped to the narrow Spleen 5x8 primary
cell, so the JetBrains-Mono and CJK-fallback atlases are untouched. Zero
token-cost change - the glyph occupies the same 5x8 box.

Guarded by a regression test asserting no alphanumeric pair sits below
Hamming 2. Reproduce with: npx tsx eval/glyph-matrix/demo-glyph.mts

Closes the "optional glyph surgery" item in the 2026-07-01 legibility audit.
2026-07-21 00:31:24 -04:00
Diego Rodrigues de Sa e Souza 5e324fba09 docs: add OmniGlyph to a Derived projects section (#89) 2026-07-20 22:31:28 -04:00
Divyesh 4a5113ac15 docs(readme): add Related projects section linking pxpipe-windows (#108)
* docs(readme): add Related projects section linking pxpipe-windows

Per the offer in #88 — adds a Related projects entry pointing to the Windows
fork. Docs-only, single link line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(readme): clarify community project support

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Steven Chong <25894545+teamchong@users.noreply.github.com>
2026-07-20 22:23:12 -04:00
Shubham Srivastava fbe2c251b8 fix(responses-bridge): encode assistant text as output_text, not input_text (#129)
When a Claude Code /v1/messages request is bridged to a GPT model on the
Responses path (bridgedGptMessages), prior assistant text turns were lowered
to content parts of type input_text. The OpenAI Responses API requires
assistant-role message content to be output_text; input_text under
role:assistant is rejected with 400, breaking every multi-turn session to a
Responses-routed GPT model after the first assistant reply. The bridge's read
side already maps assistant text to/from output_text — only the write side was
wrong. Thread the role into inputParts and pick the text type accordingly;
user/system stay input_text.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 22:14:18 -04:00
Ben Younes b754d95295 fix: capture transactional factsheet anchors (#78) 2026-07-19 00:00:08 -04:00
Brad Yinger eaa97ffe9c fix(transform): pass tool-search managed tools (defer_loading) through the tool rewrite (#76)
* fix(transform): pass tool-search managed tools through the tool rewrite

Tools carrying defer_loading: true belong to Anthropic's tool-search beta:
the server keeps them out of context (billed ~zero) until the model searches
for them. Rewriting them — stub description, annotation-stripped schema, full
docs rendered into the imaged Tool Reference — materializes documentation the
API was deliberately keeping free, inflating every request. A Claude Code
session with a large MCP surface ships hundreds of tools (~490k chars
observed), so an unpatched proxy can image the entire set into every request.

The tool_search_tool_regex/_bm25 server tools themselves must also pass
through untouched: they are schema-less and server-defined, and stubbing
their description breaks the beta's contract.

Deferred tools now pass through byte-identical, are excluded from the imaged
Tool Reference, and a deferred_tools_skipped counter lands in events.jsonl
for observability.

* test(defer-loading): cover below-min-chars early return with all-deferred tools

Self-review nit: no test exercised the below_min_chars early-return path
(all tools deferred, tiny system prompt) where transformRequest returns
the original body before req.tools = toolsRewritten ever runs. Locks in
that this path stays byte-identical.

Auto-fix-by: babysit-prs

* test(defer-loading): focus coverage on passthrough behavior
2026-07-18 23:47:57 -04:00
Xyra Sinclair abc0194219 fix(proxy): never route an sk-ant bearer to the OpenAI upstream (#49)
Prevent Anthropic bearer tokens from being forwarded to OpenAI when routing /v1/models. Add tests for Anthropic, OpenAI, and x-api-key routing.
2026-07-18 23:29:02 -04:00
Monish Ananda b6c6c0358f fix: preserve $schema and draft-07 tuple items in schema stripper (#45)
fix: preserve draft-07 schema semantics

Keep $schema dialect declarations and recurse through tuple-form items
so valid draft-07 tool schemas aren't rejected as 2020-12.
2026-07-18 23:18:59 -04:00
Steven a4b40f5edb fix(openai): correct rendered-context framing for annotation-only tool imaging (#123)
Align Chat and Responses framing with annotation-only tool imaging, preserving native tool definitions as authoritative. Includes output-level regressions adapted from #19.

Co-authored-by: Danil Silantyev <danilsilantyevwork@gmail.com>
2026-07-18 22:58:56 -04:00
Steven c260eae9d8 fix(dashboard): compare per-request cost on the same requests (#93) (#124)
The dashboard labeled the average cost of small passthrough requests as what the larger imaged requests would have cost without pxpipe. Compare each paid imaged request with its own cache-aware text counterfactual instead, and explain the same-request calculation in the math drawer.

Fixes #93
2026-07-18 22:54:01 -04:00
Danil Silantyev 69c3a51829 fix(prompting): discourage guessing exact strings from imaged text (#32)
FINDINGS.md documents the main failure mode of imaged text: the reader (Opus
especially) confabulates a plausible exact value instead of abstaining. Add
abstention guidance to the rendered framing so the model defers exact identifiers
to the factsheet / source rather than guess.

- the imaged slab banner (imageInstructionHeader) and the history-transcript
  intro (HISTORY_SYNTHETIC_INTRO) now say: for exact identifiers, paths, hashes,
  version strings, and numbers, use the adjacent exact-value factsheet; if a value
  was only in an image and not in the factsheet, do not guess — say it is not safe
  to quote and re-read the source.
- no geometry, model-allowlist, or gate change; the exact factsheet still rides
  after the image blocks unchanged.
- test asserts the phrasing appears in info.imageSourceText for an applied
  transform and in the history intro, and NOT in a non-applied (compress=false)
  transform.

Full suite green (633).
2026-07-18 20:02:27 -04:00
Danil Silantyev 028f6a7cd8 fix(vision): patch-based Anthropic image-token model + doc sync (#27)
Anthropic bills images by a 28-px patch grid (⌈w/28⌉×⌈h/28⌉ after tier downscale), not (w·h)/750 — the old fit overcharged large images ~4–5%.

- new src/core/anthropic-vision.ts: exact patch + downscale model matching
  Anthropic's reference (incl. extreme aspect ratios); wired into the unified
  visionTokensForModel router.
- gate counts real 28-px patches; 10% bias renamed ANTHROPIC_GATE_MARGIN, gate-only.
- removed ANTHROPIC_PIXELS_PER_TOKEN / IMAGE_COST_SAFETY_MARGIN; cols 313 → 312
  (slab = 1568 px cap).
- docs (README / RENDER_SIZING / TRANSFORM_INFO / audit) synced to shipped
  geometry (1568×728, 312 cols) and the patch billing model.
2026-07-18 19:55:30 -04:00
Steven 2398337da4 fix(dashboard): collapse image model scope into one opt-in row (#116) (#120)
Three full-width rows of model chips were always visible, but only
Fable 5 is enabled by default. Now it's one collapsed line; expanding
shows chips + PXPIPE_MODELS CSV, a Fable-5-only warning, and a routing
help modal (/model claude-<model> to switch).

Fixes #116
2026-07-18 13:18:13 -04:00
Steven 8c628b7fc6 fix(logs): tag compression skip reasons as savings:skip(...) (#114) (#115) (#119)
Two issues (#114, #115) reported that pxpipe was "causing" 429 unsupported_model and 529 overloaded errors. It wasn't, but our log output was actively misleading people into believing it was, and this change fixes that.
2026-07-18 12:47:40 -04:00
Steven 4bc30d3c63 fix(dashboard): price one-hour cache writes (#118) 2026-07-18 11:58:56 -04:00
Steven Chong 64b62851da refactor(proxy): simplify provider routing 2026-07-18 10:21:33 -04:00
Steven Chong b02d3b5c42 fix(proxy): preserve existing Fable transform behavior 2026-07-17 22:55:31 -04:00
Steven Chong 733046e9c7 docs: normalize benchmark table labels 2026-07-17 22:33:20 -04:00
Steven Chong f3fb2a8a21 eval: complete Kimi K3 quality suite 2026-07-17 20:02:32 -04:00
Steven Chong efef8cf0d8 eval: complete Sol and Grok quality runs 2026-07-17 18:29:21 -04:00
teamchong 4c78363757 feat(proxy): add scoped Cloudflare model routing 2026-07-17 13:01:43 -04:00
teamchong 9af5a42092 docs: remove stale provider reference 2026-07-17 12:57:42 -04:00
Ben Younes 11e572286a fix(transform): keep appended system blocks live (#66) (#75)
Keep non-cache-controlled appended system text in the Anthropic system field when the cacheable system slab is rendered to images.

Generated by Ora Studio
Vibe coded by ousamabenyounes
2026-07-16 17:27:46 -04:00
Michael Coelho 6dca4adc7b fix(render): escape atlas-missing glyphs as [U+HEX] instead of dropping them (#96) (#96) 2026-07-16 17:22:41 -04:00
teamchong 2ae5f2c88e docs: note Windows community-supported policy 2026-07-16 17:15:18 -04:00
Makori Brian 16960b8230 fix(tests): Windows compatibility for docs-integrity, export-git e2e, and slow-machine timeouts (#52)
- docs-integrity: normalize walker paths to forward slashes so keys match on Windows
- export-git-e2e: run tsx's JS entry via node instead of the .bin/.cmd shim (same pattern as #63)
- vitest: 30s testTimeout for CPU-bound PNG-render tests on slower machines

(build.mjs portion of the original branch dropped — superseded by #63)
2026-07-16 17:14:27 -04:00
fabinhousmark c59502383b Fix silent build failure on Windows (spawnSync pnpm) (#63)
scripts/build.mjs spawned `pnpm exec tsc` with `spawnSync('pnpm', ...,
{ shell: false })`. On Windows the launcher is `pnpm.cmd`, which spawnSync
cannot run without a shell: it returns `status: null` (ENOENT), so the
`process.exit(tsc.status ?? 1)` line exited the build with code 1 and no
output — `pnpm run build` failed silently.

Run tsc's JS entry with the current Node binary instead
(`node <resolved typescript/bin/tsc>`), mirroring the existing --version
smoke check. This avoids the .cmd spawn problem (and the shell:true DEP0190
arg-escaping warning) entirely, is cross-platform, and removes the build's
assumption that pnpm is the caller. Also surface spawn errors explicitly
instead of collapsing them into a bare exit 1.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 16:58:17 -04:00
Shubham Srivastava 7da9b26363 fix(dashboard): escape model id in hx-vals to prevent injection/XSS (#73)
The model id is interpolated into the single-quoted hx-vals attribute. A
crafted id (reachable as an `active` model from any /v1/messages request on
the unauthenticated localhost dashboard) could break out of the JSON or the
attribute itself. Build valid JSON via JSON.stringify, then escapeHtml the
whole attribute value; the browser decodes the entities before htmx reads it,
so well-formed ids are unaffected. Closes #58.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:32:38 -04:00
Shubham Srivastava 50ca4725df fix(proxy): stop persisting 4xx request bodies by default (privacy) (#74)
Request bodies hold full prompts and any secrets in context. On 4xx (429s are
routine) they were gzipped to ~/.pxpipe/events.jsonl / 4xx-bodies/ with no
retention limit. Gate the capture behind captureErrorReqBody (env
PXPIPE_DEBUG_CAPTURE_4XX=1), default off, with a one-time startup warning. The
upstream errorBody (no user content) and the sha8 hash still land. Closes #69.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:30:09 -04:00
teamchong e33106b51e docs(readme): clarify that export writes a fresh pxpipe-export-XXXXXX/ folder (#79 follow-up) 2026-07-16 13:26:54 -04:00
Ben Younes f83a1900a0 docs: document offline export workflow (#79) 2026-07-16 13:25:07 -04:00
Steven b22c037bed fix: pass native typed tools through untouched (#43) (#113)
Server-side tools (e.g. advisor_20260301, web_search_20250305) were
getting a description injected and a heading in the imaged Tool
Reference, causing Anthropic to 400 with "Extra inputs are not
permitted". Guard the rewrite map so any tool with a non-"custom"
type passes through byte-identical, mirroring the OpenAI-path
isFunctionTool guard.

Fixes #43
2026-07-16 13:16:06 -04:00
teamchong f024730261 fix(transform): drop scope:"global" from relocated cache_control markers
Claude Code marks its system prompt with cache_control
{type:"ephemeral", ttl:"1h", scope:"global"}. When pxpipe relocates
that marker onto a rendered image, the new position is never a valid
global prefix (slab pages 1..N-1 and other injected blocks carry no
marker), so Anthropic rejects the whole request with 400
invalid_request_error — making pxpipe unusable in front of Claude Code.

Demote relocated markers to plain ephemeral by stripping the scope key
(type/ttl preserved) at all four relocation sites: the slab->history
anchor move, the static-system marker, and the reminder / tool_result
image renders. Markers without scope pass through untouched, so
byte-stability of existing traffic is unaffected. pxpipe still never
adds markers of its own.

Closes #95
2026-07-16 13:00:46 -04:00
teamchong 1e64cb7622 fix(node): handle 'error' in waitForDrain to avoid unhandled 'error' crash
The rewrite dropped the implicit 'error' listener that events.once()
used to attach. If the ServerResponse emits 'error' while awaiting
drain, Node raises an unhandled 'error' event and crashes the process.
Attach a third once-listener and detach all three on whichever fires
first.

Found during maintainer review of #91.
2026-07-16 12:36:30 -04:00
zannensk 4bef78283b fix(node): stop ServerResponse listener leak in waitForDrain
waitForDrain raced Promise.race([once(out,'drain'), once(out,'close')]).
The losing once() listener was never removed, and events.once() also
attaches an implicit 'error' listener, so every backpressure cycle on a
long streamed response leaked one 'close' + one 'error' listener on the
same ServerResponse. Over long SSE streams this triggers
MaxListenersExceededWarning, unbounded heap growth, and eventually a
silent OOM exit of the proxy (observed as 'Connection closed mid-response'
on the client with no shutdown log).

Manage the two listeners manually and remove both on whichever fires
first. Behaviour is unchanged: drain resolves, close rejects with
'client response closed'.
2026-07-16 12:36:30 -04:00
Khalid Shaikh 0f080af1a0 fix(anthropic): preserve Claude Code OAuth identity 2026-07-16 12:12:18 -04:00
Steven Chong 76f4fd2282 feat: add Sol Messages bridge and history compression 2026-07-15 15:15:58 -04:00
teamchong af41ce6e41 chore(release): 0.9.0
Mainly ships the subagent env-relocation fix: the relocated environment
block was landing in the last user message every turn, steering Agent
subagent calls to fable instead of haiku. Diff-based static/volatile env
split (splitEnvByVolatility) fixes it. Also includes Sol opt-in default
removal, per-model render profiles, and dashboard total scoping already
in the Unreleased log.
v0.9.0
2026-07-14 09:33:20 -04:00
teamchong 2f75ab97ca docs(readme): drop savings paragraph implying GPT/Codex is a default path
GPT/Codex is opt-in-only (see Model scope bullet); this paragraph
described its savings behavior alongside Claude Code's without ever
saying so.
2026-07-14 09:33:20 -04:00
teamchong 9bca945550 fix: diff-based static/volatile env split replaces blanket relocation
The relocated '# Environment' block put model-identity/catalog lines in
the LAST user message every turn, steering subagent picks to fable
instead of haiku (LinkedIn report). splitEnvByVolatility now learns per
project (claudeMdSha) which env entries are byte-stable and promotes
them into the imaged slab; churned/new entries keep the live-tail
relocation, with the identity regex retained as a second layer.

Invariants (pinned by tests):
- first-ever sightings stay volatile: git state never bakes into a
  fresh session's image (48.8% cold-create fix intact)
- the slab never re-renders mid-session: static side frozen byte-exact;
  a churned promoted entry re-emits fresh text on the tail (supersedes
  its stale slab copy) and demotes next session via sticky churn history
- no user message -> env stays in system, unchanged

Telemetry: envStaticChars / envVolatileKeys.
2026-07-13 15:46:07 -04:00
Steven Chong 6d06268744 fix(dashboard): filter totals by enabled models 2026-07-13 15:40:41 -04:00
Steven Chong 208c644f9c fix: remove sparse IDS image blocks 2026-07-12 19:23:43 -04:00
teamchong 455ab636a2 eval(patch-probe): billing grid + OCR phase-sweep findings
Probe 1: both fable-5 and sonnet-5 bill vision on a 28x28 patch grid
(image_tokens = 3 + ceil(W/28)*ceil(H/28)); snap rows≡6 (mod 7),
cols≡4 (mod 28) to avoid stranding paid patch area.

Probe 2: patch-boundary straddling does NOT affect OCR accuracy
(cols: 4.47% vs 4.60%; rows: z=-0.45, 7-offset paired sweep with
line fixed effects). Real misread drivers: high-entropy runs
(bimodal derailment on base64 blobs), 5x8 confusables (w/W, 8/0),
line wraps. Harnesses: count-tokens-sweep, accuracy-phase-probe
(line-DP-aligned scoring), rescore-sweep (offline paired analysis).
2026-07-12 18:59:31 -04:00
Ousama Ben Younes 44b558ff94 test: allow slower render e2e cases 2026-07-12 10:51:04 -04:00
teamchong aef3b774fc eval(guards): 3-arm IDS/factsheet ablation on Fable read path
A (IDS rows + factsheet) 14/24, B (factsheet only) 14/24,
C (no guards) 8/24. Factsheet delivers the recall lift; IDS rows
show no measurable effect. Evidence for dropping the guards
paragraph from MODEL_RENDER_PROFILES.md.
2026-07-12 09:30:25 -04:00
teamchong 2c4cde1581 docs(render): clarify Sol geometry history 2026-07-12 09:26:06 -04:00
Steven Chong 7b7d6efa42 docs(chart): match README image width 2026-07-11 18:22:46 -04:00
Steven Chong b0cc909616 docs: record Sol and Grok quality limits 2026-07-11 18:22:46 -04:00
Steven Chong 8b61fb2d64 test(eval): add Responses quality matrix 2026-07-11 18:22:46 -04:00