pxpipe

package module
v0.4.19 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 34 Imported by: 0

README

pxpipe-go

Go port of the core of pxpipe: a token-saving transform for the Anthropic Messages API that renders bulky context (system prompt, tool docs, old history, large tool results) into dense PNG pages, cutting input tokens by reading them through the vision channel.

This port provides a Go library and a CLI. Embed the library into your own Go server as an http.Handler reverse proxy or as pure functions over request bodies. It covers the Anthropic Messages surface and the OpenAI Chat Completions / Responses surfaces (the GPT path, including the o200k-exact profitability gate and GPT history imaging). The CLI provides process-scoped MITM launchers for Claude Code, OpenCode, and Codex plus a standalone reverse and forward proxy with terminal request telemetry. The dashboard, offline export CLI, and Gemini/Google surface remain out of scope.

Install

Install the pxpipe CLI globally with the pinned release:

go install github.com/evan-choi/pxpipe-go/cmd/pxpipe@v0.4.19

Use @latest to install the newest tagged release:

go install github.com/evan-choi/pxpipe-go/cmd/pxpipe@latest

Go installs the binary into go env GOBIN when set, or $(go env GOPATH)/bin otherwise. Add that directory to PATH; for the default location:

export PATH="$(go env GOPATH)/bin:$PATH"
pxpipe --version

Use the CLI

Run an officially supported CLI directly and pass all following arguments to the child unchanged:

pxpipe claude --model opus
pxpipe Claude.app
PXPIPE_MODELS=gpt-5.6-sol pxpipe opencode --model openai/gpt-5.6-sol
PXPIPE_MODELS=gpt-5.6-sol pxpipe codex --model gpt-5.6-sol

The Claude profile transforms HTTP requests whose paths end in /messages and preserves ANTHROPIC_BASE_URL HTTP(S) overrides. The OpenCode profile covers paths ending in /messages, /chat/completions, or /responses and disables OpenCode's experimental WebSocket transport. The Codex profile transforms paths ending in /responses; Codex must use a provider configured with supports_websockets=false because WebSocket request bodies cannot be transformed. These routes are independent of the provider domain and preserve the original scheme, authority, path, and query after transformation, so custom base URLs and localhost intermediaries work without config-file discovery. Anthropic base URLs normally omit /v1; Claude appends /v1/messages to the configured base URL.

On macOS, Claude.app is matched case-insensitively and located in ~/Applications or /Applications. pxpipe starts its bundle executable with a process-scoped ANTHROPIC_UNIX_SOCKET, NODE_EXTRA_CA_CERTS, and SSL_CERT_FILE. The socket uses TLS with a certificate signed by pxpipe's local CA; pxpipe does not install the CA in the system trust store. SSL_CERT_FILE keeps that CA available when Claude Desktop starts its bundled Claude Code. Fully quit an existing Claude process before launching it so the new process inherits the socket environment.

Any other executable uses a protocol-neutral profile that transforms paths ending in /messages, /chat/completions, or /responses:

pxpipe another-cli --its-child-flag

If the executable is literally named serve or help, disambiguate it with pxpipe -- <executable> [args...].

The CLI starts process-scoped listeners on kernel-assigned 127.0.0.1 ports, inherits the child's terminal streams and exit status, and does not install its CA into the system trust store. Only the child receives the proxy and CA environment. Requests matching the profile's Messages, Chat Completions, or Responses routes are transformed; other paths and hosts retain their original destination. The named profiles can use arbitrary provider domains, so they decrypt the child's HTTPS connections to inspect the request path; non-matching requests are forwarded unchanged to their original destination. When ANTHROPIC_UNIX_SOCKET is already set, pxpipe opens a process-scoped Unix socket for Claude Code, transforms matching HTTP requests, and forwards them over the original socket. The original socket path is never exposed to the child.

When the child exits, pxpipe writes the estimated input token usage without pxpipe and the actual usage with pxpipe to stderr, including the percentage change. If the provider does not report enough data, it prints token usage unavailable.

Command startup and process-scoped CA/proxy injection have been smoke-tested against the installed Claude Code, OpenCode, and Codex releases. Local Codex path routing, transformation, and upstream restoration are integration-tested; live OpenCode and Codex inference still need provider end-to-end validation.

Standalone server

Run a loopback reverse/forward proxy on port 47821, or select another port with -p/--port:

pxpipe serve
pxpipe serve --port 8080

Startup presents ready-to-run Claude and Codex commands. In a terminal, click the [copy] button at the right of either line to copy the full command through OSC 52. Point clients at the displayed address, for example:

ANTHROPIC_BASE_URL=http://localhost:47821 claude
NO_PROXY= no_proxy= HTTPS_PROXY=http://localhost:47821 https_proxy=http://localhost:47821 HTTP_PROXY=http://localhost:47821 http_proxy=http://localhost:47821 CODEX_CA_CERTIFICATE='/path/to/pxpipe/mitm-ca.pem' codex

The displayed Codex command supplies the generated CA and uses serve as a process-scoped forward proxy. It does not override model_provider or its base URL, so ChatGPT login, OpenAI API keys, custom provider credentials, and localhost intermediaries retain their original destination and authorization. Both uppercase and lowercase proxy variables are set by the generated command, and NO_PROXY/no_proxy are cleared so localhost providers are intercepted. The active Codex provider must set supports_websockets=false because WebSocket request bodies cannot be transformed.

The Bubble Tea terminal view shows the 20 most recent requests with status, endpoint, model, whether context was sent as text or images, cache hits, and token estimates. Anthropic Sent and cache values use provider-reported usage with cache reads weighted at 0.1x and cache creation at 1.25x. For transformed requests, As text and Saved/lost are local estimates based on transform diagnostics; OpenAI token columns are shown as - until OpenAI response usage parsing is available. Redirected/non-terminal output emits one row per request instead of redrawing the table. The server shuts down gracefully on interrupt or SIGTERM.

Use as an embedded reverse proxy

package main

import (
    "log"
    "net/http"
    "net/url"

    pxpipe "github.com/evan-choi/pxpipe-go"
)

func main() {
    anthropic, _ := url.Parse("https://api.anthropic.com")
    openAI, _ := url.Parse("https://api.openai.com")
    h := pxpipe.NewHandler(pxpipe.HandlerOptions{
        AnthropicUpstream: anthropic,
        OpenAIUpstream:    openAI,
        OnResult: func(r *http.Request, res *pxpipe.TransformResult) {
            log.Printf("%s applied=%v reason=%s images=%d",
                r.URL.Path, res.Applied, res.Reason, res.Info.ImageCount)
        },
    })
    log.Fatal(http.ListenAndServe("127.0.0.1:47821", h))
}

Then point Claude Code at it:

ANTHROPIC_BASE_URL=http://127.0.0.1:47821 claude

Canonical /v1/messages requests go to AnthropicUpstream; canonical /v1/chat/completions, /v1/responses, and /v1/responses/* requests go to OpenAIUpstream. /v1/models uses the request's auth style to choose between them. Both upstreams have the public API defaults shown above.

Provider-prefixed paths (/anthropic/*, /openai/*, /google-ai-studio/*, /compat/*) keep their full path and go to AnthropicUpstream, which lets an API gateway route them. Supported POST bodies are still transformed by wire shape; count_tokens, unknown routes, and all responses (including SSE) pass through unchanged.

Set APIKey or AuthToken/AuthTokenFunc for Anthropic credentials and OpenAIAPIKey for OpenAI. Direct OpenAI requests never receive x-api-key or anthropic-* headers.

The handler defaults to a 5-minute response-header timeout, a 2-minute stream idle timeout, and a 1-minute identical-request hold. Set UpstreamHeadersTimeout, UpstreamIdleTimeout, or DuplicateHold to a duration pointer; a pointer to zero disables that guard. TransformFunc can return live transform options per request and takes precedence over the static Transform value.

A turn containing only @pxpipe pin or @pxpipe unpin is answered locally in the caller's Anthropic Messages, Chat Completions, or Responses wire format. Set x-pxpipe-bypass: 1 to forward it instead.

Custom routes

Set ProtocolOf to map your own paths to wire protocols; return pxpipe.ProtocolNone to pass a request through, or fall back to pxpipe.DefaultProtocolOf for the built-in rules:

h := pxpipe.NewHandler(pxpipe.HandlerOptions{
    AnthropicUpstream: anthropic,
    OpenAIUpstream:    openAI,
    ProtocolOf: func(path string) pxpipe.Protocol {
        switch path {
        case "/api/llm/claude":
            return pxpipe.ProtocolAnthropicMessages
        case "/api/llm/gpt/chat":
            return pxpipe.ProtocolOpenAIChat
        case "/api/llm/gpt/responses":
            return pxpipe.ProtocolOpenAIResponses
        }
        return pxpipe.DefaultProtocolOf(path)
    },
    RewritePath: func(path string, _ pxpipe.Protocol) string {
        switch path {
        case "/api/llm/claude":
            return "/v1/messages"
        case "/api/llm/gpt/chat":
            return "/v1/chat/completions"
        case "/api/llm/gpt/responses":
            return "/v1/responses"
        }
        return path
    },
})

ProtocolOf chooses the request-body transform. RewritePath chooses the outbound API path, which also controls direct Anthropic/OpenAI routing. UpstreamFor can select an HTTP(S) upstream per request and protocol; a non-nil result takes precedence over AnthropicUpstream and OpenAIUpstream. OnResponseComplete runs after a response body reaches a clean EOF and includes Anthropic JSON/SSE token usage when present.

Use as a pure transform

res := pxpipe.TransformAnthropicMessages(pxpipe.TransformInput{
    Body:  bodyBytes,          // the JSON request body
    Model: "claude-fable-5",  // resolved model id (gates applicability)
})
if res.Applied {
    forward(res.Body)
} else {
    forward(bodyBytes) // res.Reason explains why (below_min_chars, …)
}

For the OpenAI surfaces:

body, info := pxpipe.TransformOpenAIChatCompletions(bodyBytes, nil)
body, info  = pxpipe.TransformOpenAIResponses(bodyBytes, nil)

Per-model GPT render/pricing profiles (gpt-5.x, o-series, Grok, …) resolve via pxpipe.ResolveGptProfile; unknown models can be declared without a code change through the PXPIPE_GPT_PROFILES env JSON, exactly as upstream.

Or render arbitrary text with the same model-specific geometry and style:

out, _ := pxpipe.RenderTextToImages(text, pxpipe.RenderOptions{
    Model: "gpt-5.6-sol",
    Reflow: true,
})
for _, page := range out.Pages { os.WriteFile("page.png", page.PNG, 0o644) }

Model scope

Same semantics as upstream pxpipe: the built-in allowlist is claude-fable-5 plus gemini-3.6-flash; GPT models are opt-in. Override with the PXPIPE_MODELS env CSV (e.g. PXPIPE_MODELS=claude-fable-5,gpt-5.6-sol) or at runtime via pxpipe.SetAllowedModelBases. Unlisted models pass through untransformed — that is the escape hatch for byte-exact work.

The standalone pxpipe serve command accepts every valid Anthropic and OpenAI model when PXPIPE_MODELS is unset or blank. Set PXPIPE_MODELS to restrict that server to an explicit model list.

Fidelity

The port is verified against golden fixtures generated only by the pinned TypeScript reference implementation in pxpipe/ (testdata/, tooling in tools/):

  • 17 render cases pass pixel-exact (PNG bytes differ only by deflate implementation; decoded pixels are identical).
  • 9 Anthropic transform cases match on every text output, hash, gate verdict, page count, and page dimensions; image blocks are compared by decoded pixels.
  • 15 OpenAI (Chat + Responses) cases match on every text output, hash, gate verdict (including exact o200k token counts), history-collapse plan, page count, and page dimensions; the embedded o200k tokenizer reproduces gpt-tokenizer counts exactly.
  • GPT model-profile resolution (27 ids incl. env overrides and misresolution guards) matches the TS table verbatim.
  • TS UTF-16 string-length semantics are reproduced for every char gate and telemetry counter; JSON object key order is preserved through parse → imaged-text serialization so rendered tool schemas match the reference byte-for-byte.

Known deviations:

  • PNG byte streams (and therefore imageBytes, historyImageSha, cachePrefixSha8) differ from TS — deflate output is implementation specific. They remain deterministic per input, which is what prompt-cache stability requires.
  • Keys added by the transform to objects it did not create serialize after the object's original keys in sorted order (TS appends in insertion order).
  • No Gemini/Google surface, Messages↔OpenAI bridges, or savings measurement.

Performance

Rendered pages are cached by exact render inputs. The cache retains up to 64 MiB by default. Set PXPIPE_RENDER_CACHE_BYTES to another byte limit, or set it to 0 to disable the cache.

Measured natively on an Apple M1 Pro running macOS 26.5.2, with Bun 1.3.14 and Go 1.26.5. Go used the machine-default GOMAXPROCS=10 and no PGO profile. The comparison uses pxpipe@c5fc2a8 and pxpipe-go@c3e0e50 (2026-08-11).

Values are medians of five runs. Each pxpipe run performs two warmups and three timed iterations; its table value is the median per-run mean. Each pxpipe-go run uses a one-second benchmark window. Actual latency varies with CPU architecture, available cores, and request content.

benchmark pxpipe time/op pxpipe-go time/op speedup
TransformBigClaudeCode 52.20 ms 0.72 ms 72.6×
RenderDensePage 12.50 ms 0.14 ms 91.7×
TransformOpenAIChat 52.40 ms 0.28 ms 190.5×
TransformOpenAIResponses 132.50 ms 0.47 ms 282.2×

Peak RSS was measured over the full four-benchmark suite in a fresh process for each run:

implementation median peak RSS relative to pxpipe
pxpipe 405.00 MiB baseline
pxpipe-go 107.70 MiB 73.4% lower

Go's -benchmem output reports the following allocation volume per operation:

benchmark B/op allocs/op
TransformBigClaudeCode 1,165,232 1,813
RenderDensePage 2,736 25
TransformOpenAIChat 633,320 993
TransformOpenAIResponses 1,069,497 1,043

Raw GC counts are not compared because V8 and Go use different collectors and event semantics. Peak RSS is the cross-runtime memory metric; B/op and allocs/op identify allocation pressure within the Go implementation.

After installing dependencies, reproduce the latency and allocation comparison from the repository root on Apple Silicon macOS:

for benchmark_run in 1 2 3 4 5; do
  (cd pxpipe && bun run ../tools/bench-ts.ts)
done

GOTOOLCHAIN=go1.26.5 go test -pgo=off -run '^$' \
  -bench '^(BenchmarkTransformBigClaudeCode|BenchmarkRenderDensePage|BenchmarkTransformOpenAIChat|BenchmarkTransformOpenAIResponses)$' \
  -benchtime=1s -benchmem -count=5 .

Reproduce peak RSS with macOS /usr/bin/time -l by running each full suite in a fresh process five times and taking the median maximum resident set size value (reported in bytes). Build the Go benchmark binary first so the measurement excludes the compiler and go command:

for benchmark_run in 1 2 3 4 5; do
  (cd pxpipe &&
    /usr/bin/time -l bun run ../tools/bench-ts.ts >/dev/null)
done

GOTOOLCHAIN=go1.26.5 go test -pgo=off \
  -c -o /tmp/pxpipe-go-bench.test .
for benchmark_run in 1 2 3 4 5; do
  /usr/bin/time -l /tmp/pxpipe-go-bench.test -test.run '^$' \
    -test.bench '^(BenchmarkTransformBigClaudeCode|BenchmarkRenderDensePage|BenchmarkTransformOpenAIChat|BenchmarkTransformOpenAIResponses)$' \
    -test.benchtime=1s -test.count=1 >/dev/null
done

Current macOS hot-cache profiles put the cache-prefix hit path at 0.6% of CPU; mutex contention remains negligible. The optimized high-cardinality Responses path fell from 12.04 ms/op to 7.02 ms/op; level-6 PNG compression accounts for 58.0% of CPU and uncached o200k counting for 7.2%. Framebuffers and encoders are pooled.

A Docker Desktop Alpine ARM64 A/B run found GOGC=400 with GOMEMLIMIT=192MiB 5.6% faster by parallel geomean than the default GC settings, with about 219 MiB peak cgroup memory. Treat this as a deployment candidate only and remeasure it on the production EKS instance family.

For production validation, run the native Linux harness inside an Alpine pod on the same EKS instance family and architecture as production. It rejects macOS, non-Alpine environments, architecture mismatches, Docker Desktop, LinuxKit, and WSL. Set BENCH_GOMAXPROCS to the pod's integer CPU limit:

BENCH_GOMAXPROCS=4 tools/bench-linux.sh /results/current
BENCH_GOMAXPROCS=4 BENCH_PGO=1 tools/bench-linux.sh /results/current-pgo

Run the first command from clean baseline and candidate checkouts, then compare their sequential.txt and parallel.txt files with benchstat. PGO profiles are workload- and architecture-specific; do not ship a profile trained on a developer machine or an emulated CPU.

Regenerating fixtures

Initialize the pinned TS reference implementation first:

git submodule update --init --recursive
cd pxpipe
pnpm install --frozen-lockfile
bun run ../tools/dump-atlas.ts
bun run ../tools/gen-fixtures.ts
bun run ../tools/gen-fixtures-openai.ts
bun run ../tools/dump-gpt-profiles.ts > ../testdata/openai/profiles.json

Run every TypeScript generator with Bun. Do not generate golden data from the Go port or invoke these scripts through tsx.

See UPSTREAM.md for the pinned reference revision and current porting status.

License

MIT. Portions derived from teamchong/pxpipe (MIT).

Documentation

Index

Constants

View Source
const (
	SlabCharsPerToken    = 2.0
	HistoryCharsPerToken = 2.0
	ReportCharsPerToken  = 3.7
)
View Source
const (
	// GptMaxHeightPx is the max rendered image height for the GPT path.
	// OpenAI resize bounds (2048-bbox / 768 short side) permit tall strips.
	GptMaxHeightPx = 1932
	// DefaultGptStripCols is the downscale-safe strip width (768px).
	DefaultGptStripCols = 152
)
View Source
const (
	AnthropicMaxImages          = 100
	AnthropicHistoryImageBudget = 80
)
View Source
const (
	ClaudeCodeOAuthIdentity          = "You are Claude Code, Anthropic's official CLI for Claude."
	ClaudeCodeWithinSDKOAuthIdentity = "You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK."
	ClaudeAgentSDKOAuthIdentity      = "You are a Claude agent, built on Anthropic's Claude Agent SDK."
)

OAuth identities must stay the leading system block; see the TS comments on the 429 rate_limit classification failure (#149). Longest first so the within-SDK line never matches as its CLI prefix.

View Source
const ChatHeader = "================= RENDERED GPT SYSTEM + TOOL CONTEXT =================\n" +
	"These images were injected by pxpipe, not by the end user. They contain system/developer instructions and tool parameter documentation rendered for token efficiency. Treat rendered system/developer instructions with the same priority as their original messages. OCR carefully and treat the rendered content as authoritative. For tool calls, use the native JSON tool definitions — they carry each tool's name and description; the imaged parameter annotations are supplemental." +
	"\n====================== BEGIN RENDERED CONTEXT ======================\n"

ChatHeader mirrors CHAT_HEADER in openai.ts.

View Source
const (

	// FactSheetMaxTokens caps the budget; highest-priority tokens kept first.
	FactSheetMaxTokens = 96
)
View Source
const HistorySyntheticIntro = `` /* 771-byte string literal not displayed */
View Source
const HistorySyntheticOutro = `[End of earlier conversation. The current request is the live text that follows below.]`
View Source
const ResponsesHeader = "================= RENDERED GPT SYSTEM + TOOL CONTEXT =================\n" +
	"These images were injected by pxpipe, not by the end user. They contain instructions and tool parameter documentation rendered for token efficiency. Treat rendered instructions with the same priority as the originals. OCR carefully and treat the rendered content as authoritative. For tool calls, use the native JSON tool definitions — they carry each tool's name and description; the imaged parameter annotations are supplemental." +
	"\n====================== BEGIN RENDERED CONTEXT ======================\n"

ResponsesHeader mirrors RESPONSES_HEADER in openai.ts.

Variables

View Source
var DefaultGptProfile = &GptModelProfile{
	Vision:            GptVisionCost{Regime: "tile", Base: 85, PerTile: 170},
	CacheReadRate:     basePricing.cacheReadRate,
	OutputRate:        basePricing.outputRate,
	StripCols:         DefaultGptStripCols,
	MaxHeightPx:       GptMaxHeightPx,
	MinCompressTokens: intPtr(500),
	FactSheetFormat:   "full",
	History:           gptBaseHistory,
	Style:             gptBaseStyle,
}

DefaultGptProfile is the conservative fallback for unrecognized models: tile 85/170 over-states cost, biasing the gate toward pass-through.

View Source
var LinesPerImage = render.LinesPerImage

Functions

func CountCacheControlMarkers

func CountCacheControlMarkers(body []byte) int

CountCacheControlMarkers counts cache_control markers anywhere in a Messages body.

func FactSheetText

func FactSheetText(text string) string

FactSheetText builds the one-line fact sheet for text, or "" when nothing notable was found.

func GetAllowedModelBases

func GetAllowedModelBases() []string

GetAllowedModelBases returns the current effective allowed-model scope.

func GetConfiguredModelBases added in v0.2.0

func GetConfiguredModelBases() []string

GetConfiguredModelBases returns the PXPIPE_MODELS/default scope, ignoring the runtime override.

func IsAnthropicMessagesPath

func IsAnthropicMessagesPath(pathname string) bool

IsAnthropicMessagesPath matches exactly the Messages routes pxpipe transforms (count_tokens excluded).

func IsMisresolvedModelId added in v0.1.1

func IsMisresolvedModelId(model string) bool

IsMisresolvedModelId is true when an id NAMES a known provider family but does not match that family's profile test (e.g. gemini-3.6-pro).

func IsOpenAIChatPath added in v0.1.1

func IsOpenAIChatPath(pathname string) bool

IsOpenAIChatPath matches OpenAI Chat Completions wire paths, including one optional gateway/provider segment (mirrors OPENAI_CHAT_PATH in proxy.ts).

func IsOpenAIResponsesPath added in v0.1.1

func IsOpenAIResponsesPath(pathname string) bool

IsOpenAIResponsesPath matches OpenAI Responses wire paths (mirrors OPENAI_RESPONSES_PATH in proxy.ts).

func IsSupportedGptModel added in v0.1.1

func IsSupportedGptModel(model string) bool

IsSupportedGptModel reports whether pxpipe may transform this model on the OpenAI Chat Completions / Responses surface (same allowlist scope).

func IsSupportedModel

func IsSupportedModel(model string) bool

IsSupportedModel reports whether pxpipe may transform this Anthropic model.

func NewHandler

func NewHandler(opts HandlerOptions) http.Handler

NewHandler returns an http.Handler that forwards everything to the configured provider upstream, rewriting POST bodies on supported Anthropic and OpenAI routes. Responses (including SSE streams) are forwarded byte-for-byte while Anthropic cache accounting is observed.

func OpenAIVisionTokens added in v0.2.0

func OpenAIVisionTokens(model string, width, height int) int

OpenAIVisionTokens returns the per-image input-token cost for the serving model. The name is retained for parity with the upstream OpenAI-path API.

func PrepareImagedRenderText added in v0.1.1

func PrepareImagedRenderText(text string, reflowEnabled bool) string

PrepareImagedRenderText mirrors prepareImagedRenderText in openai.ts.

func RenderTextToImages added in v0.2.0

func RenderTextToImages(text string, opts RenderOptions) (*render.RenderResult, error)

RenderTextToImages renders text with model-aware defaults.

func SetAllowedModelBases

func SetAllowedModelBases(list []string)

SetAllowedModelBases sets a runtime override; nil clears it, an empty slice compresses nothing, and "*" allows every correctly resolved model.

Types

type ApplicabilityInput added in v0.2.0

type ApplicabilityInput struct {
	Model     string
	Method    string
	Path      string
	BodyBytes *int64
}

ApplicabilityInput describes the request fields used by the public Anthropic Messages eligibility check. Empty Method or Path skips that check; nil BodyBytes means its size is unknown.

type ApplicabilityReason added in v0.2.0

type ApplicabilityReason string

ApplicabilityReason explains why an Anthropic Messages request is or is not eligible for transformation.

const (
	ApplicabilityReasonEligible          ApplicabilityReason = "eligible"
	ApplicabilityReasonUnsupportedModel  ApplicabilityReason = "unsupported_model"
	ApplicabilityReasonUnsupportedMethod ApplicabilityReason = "unsupported_method"
	ApplicabilityReasonUnsupportedPath   ApplicabilityReason = "unsupported_path"
	ApplicabilityReasonEmptyBody         ApplicabilityReason = "empty_body"
)

type ApplicabilityResult added in v0.2.0

type ApplicabilityResult struct {
	Eligible bool
	Reason   ApplicabilityReason
}

ApplicabilityResult is the outcome of ShouldTransformAnthropicMessages.

func ShouldTransformAnthropicMessages added in v0.2.0

func ShouldTransformAnthropicMessages(input ApplicabilityInput) ApplicabilityResult

ShouldTransformAnthropicMessages reports whether an Anthropic Messages request is eligible for transformation.

type EnvFields

type EnvFields struct {
	Cwd       string `json:"cwd,omitempty"`
	IsGitRepo *bool  `json:"isGitRepo,omitempty"`
	GitBranch string `json:"gitBranch,omitempty"`
	Platform  string `json:"platform,omitempty"`
	OSVersion string `json:"osVersion,omitempty"`
	Today     string `json:"today,omitempty"`
}

EnvFields carries telemetry parsed from Claude Code's <env>/git blocks.

type FactSheetEntry

type FactSheetEntry struct {
	Token string
	Count int
}

FactSheetEntry is a kept token plus its occurrence count in the scanned text.

func ExtractFactSheetEntries

func ExtractFactSheetEntries(text string) []FactSheetEntry

ExtractFactSheetEntries mirrors TS extractFactSheetEntries: whitespace-split chunks, fixed pattern order, offset-level dedup within a chunk, then substring collapse (length-desc) and tier-budgeted selection.

type GptFlatExact added in v0.1.1

type GptFlatExact struct {
	WidthPx  int `json:"widthPx"`
	HeightPx int `json:"heightPx"`
	Tokens   int `json:"tokens"`
}

GptFlatExact is a measured exact-canvas override for the flat regime.

type GptHistoryOptions added in v0.2.0

type GptHistoryOptions struct {
	KeepTail          *int
	MaxImages         *int
	KeepRecentPairs   *int
	ResponsesMode     *string
	MinCollapsePrefix *int
	MinCollapseTokens *int
	Cols              *int
	CollapseChunk     *int
	FreezeChunk       *int
	SectionTokens     *int
	MaxHeightPx       *int
	Style             *render.RenderStyle
	Reflow            *bool
}

GptHistoryOptions contains optional GPT history-collapse overrides. Nil fields inherit the model profile or built-in defaults.

type GptHistoryProfile added in v0.1.1

type GptHistoryProfile struct {
	MaxImages         int    `json:"maxImages"`
	KeepTail          int    `json:"keepTail"`
	KeepRecentPairs   int    `json:"keepRecentPairs"`
	MinCollapseTokens int    `json:"minCollapseTokens"`
	ResponsesMode     string `json:"responsesMode"`  // "pairs" | "mixed"
	Framing           string `json:"framing"`        // "full" | "compact"
	FactSheetScope    string `json:"factSheetScope"` // "per-segment" | "combined"
}

GptHistoryProfile carries model-specific history coverage knobs.

type GptModelProfile added in v0.1.1

type GptModelProfile struct {
	Vision        GptVisionCost `json:"vision"`
	CacheReadRate float64       `json:"cacheReadRate"`
	OutputRate    float64       `json:"outputRate"`
	StripCols     int           `json:"stripCols"`
	MaxHeightPx   int           `json:"maxHeightPx"`
	// MinCompressTokens nil preserves the legacy character floor.
	MinCompressTokens *int `json:"minCompressTokens,omitempty"`
	// VisionTier "" = standard; only Claude profiles set it.
	VisionTier                string              `json:"visionTier,omitempty"`
	FactSheetFormat           string              `json:"factSheetFormat"` // "full" | "compact"
	History                   GptHistoryProfile   `json:"history"`
	Style                     render.RenderStyle  `json:"style"`
	HistoryStripCols          *int                `json:"historyStripCols,omitempty"`
	HistoryStyle              *render.RenderStyle `json:"historyStyle,omitempty"`
	MaxSerializedRequestBytes int                 `json:"maxSerializedRequestBytes,omitempty"`
	ExactStaticBaseline       bool                `json:"exactStaticBaseline,omitempty"`
}

GptModelProfile is the complete per-model render + pricing profile.

func ResolveGptProfile added in v0.1.1

func ResolveGptProfile(model string) *GptModelProfile

ResolveGptProfile resolves the render/pricing profile for a model id, mirroring resolveGptProfile in gpt-model-profiles.ts.

type GptVisionCost added in v0.1.1

type GptVisionCost struct {
	Regime             string        `json:"regime"`
	Base               float64       `json:"base,omitempty"`
	PerTile            float64       `json:"perTile,omitempty"`
	Multiplier         float64       `json:"multiplier,omitempty"`
	PatchCap           int           `json:"patchCap,omitempty"`
	TokensPerMegapixel float64       `json:"tokensPerMegapixel,omitempty"`
	Tokens             int           `json:"tokens,omitempty"`
	Exact              *GptFlatExact `json:"exact,omitempty"`
}

GptVisionCost is the image-token cost model, tagged by Regime:

tile:    OpenAI legacy. 2048/768 downscale, then Base + PerTile per 512-px tile.
patch:   OpenAI 32-px patches × Multiplier. PatchCap 0 bills original dims.
patch28: Anthropic 28-px patches after the tier downscale.
mpix:    megapixels × TokensPerMegapixel, min 1 (Grok).
flat:    one fixed charge per image (Gemini), with optional exact override.

func ResolveVisionCost added in v0.2.0

func ResolveVisionCost(model string) GptVisionCost

ResolveVisionCost returns the model's configured image-token cost regime.

type HandlerOptions

type HandlerOptions struct {
	// AnthropicUpstream is the Anthropic API base. Default https://api.anthropic.com.
	AnthropicUpstream *url.URL
	// OpenAIUpstream is the OpenAI API base. Default https://api.openai.com.
	OpenAIUpstream *url.URL
	// APIKey overrides or supplies the Anthropic x-api-key header.
	APIKey string
	// AuthToken overrides or supplies the Anthropic authorization bearer.
	AuthToken string
	// AuthTokenFunc resolves the Anthropic authorization bearer per request.
	// When set, it takes precedence over AuthToken.
	AuthTokenFunc func() string
	// OpenAIAPIKey overrides or supplies the OpenAI authorization bearer.
	OpenAIAPIKey string
	// Transform supplies per-request transform options; Model is filled from
	// the request body. Nil = defaults. Ignored when TransformFunc is set.
	Transform *TransformOptions
	// TransformFunc resolves transform options per request. Its result takes
	// precedence over Transform; nil = defaults.
	TransformFunc func() *TransformOptions
	// Transport is used for upstream requests. Nil = http.DefaultTransport.
	Transport http.RoundTripper
	// UpstreamFor optionally resolves the upstream per request. A non-nil result
	// overrides the protocol default while nil retains normal upstream routing.
	UpstreamFor func(r *http.Request, protocol Protocol) *url.URL
	// OnResult observes every transform outcome (nil-safe). Called before the
	// request is forwarded; the result must not be mutated.
	OnResult func(r *http.Request, res *TransformResult)
	// OnResponseComplete observes a response after its body reaches a clean EOF.
	// Usage is populated for Anthropic JSON and SSE responses when available.
	OnResponseComplete func(r *http.Request, res ResponseResult)
	// MaxBodyBytes caps transformable request bodies (0 = 16 MiB default).
	// Bodies over the cap receive a provider-shaped 413 response.
	MaxBodyBytes int64
	// ProtocolOf overrides wire-protocol detection by request path. Nil uses
	// DefaultProtocolOf. Return ProtocolNone to pass a request through
	// untransformed.
	ProtocolOf func(path string) Protocol
	// RewritePath optionally maps the outbound path after protocol detection and
	// before upstream selection. It is useful with custom ProtocolOf routes.
	RewritePath func(path string, protocol Protocol) string
	// UpstreamHeadersTimeout aborts when upstream response headers do not arrive.
	// Nil defaults to 5 minutes; zero or a negative duration disables it.
	UpstreamHeadersTimeout *time.Duration
	// UpstreamIdleTimeout aborts a response stream after no bytes arrive.
	// Nil defaults to 2 minutes; zero or a negative duration disables it.
	UpstreamIdleTimeout *time.Duration
	// DuplicateHold rejects an identical in-flight request during this window.
	// Nil defaults to 1 minute; zero or a negative duration disables it.
	DuplicateHold *time.Duration
}

HandlerOptions configures the embeddable reverse-proxy handler.

type KeepSharpBlock

type KeepSharpBlock struct {
	Kind      string
	Text      string
	ToolUseID string
}

KeepSharpBlock describes a live-region block offered to the KeepSharp predicate.

type Protocol added in v0.1.2

type Protocol int

Protocol identifies the wire protocol of a request body, i.e. which API shape pxpipe should transform it as.

const (
	// ProtocolNone marks requests pxpipe forwards untransformed.
	ProtocolNone Protocol = iota
	// ProtocolAnthropicMessages is the Anthropic Messages API.
	ProtocolAnthropicMessages
	// ProtocolOpenAIChat is the OpenAI Chat Completions API.
	ProtocolOpenAIChat
	// ProtocolOpenAIResponses is the OpenAI Responses API.
	ProtocolOpenAIResponses
)

func DefaultProtocolOf added in v0.1.2

func DefaultProtocolOf(pathname string) Protocol

DefaultProtocolOf is the built-in path matcher used when HandlerOptions.ProtocolOf is nil. It recognizes the Anthropic Messages routes plus the OpenAI Chat Completions / Responses routes (with one optional gateway/provider prefix segment).

type Reason

type Reason string

Reason classifies a TransformAnthropicMessages outcome.

const (
	ReasonApplied          Reason = "applied"
	ReasonUnsupportedModel Reason = "unsupported_model"
	ReasonParseError       Reason = "parse_error"
	ReasonBelowMinChars    Reason = "below_min_chars"
	ReasonBelowMinTokens   Reason = "below_min_tokens"
	ReasonNotProfitable    Reason = "not_profitable"
	ReasonCompressDisabled Reason = "compress_disabled"
	ReasonImageLimit       Reason = "image_limit"
	ReasonTransformError   Reason = "transform_error"
	ReasonPassthrough      Reason = "passthrough"
)

type RecoverableBlock

type RecoverableBlock struct {
	ID         string `json:"id"`
	Kind       string `json:"kind"`
	ToolUseID  string `json:"toolUseId,omitempty"`
	Text       string `json:"text"`
	ImageCount int    `json:"imageCount"`
}

RecoverableBlock carries the original text of an imaged block when EmitRecoverable is set.

type RenderOptions added in v0.2.0

type RenderOptions struct {
	Model            string
	Cols             int
	Shrink           *bool
	Reflow           bool
	MaxCharsPerImage int
	Style            *render.RenderStyle
	MaxHeightPx      int
}

RenderOptions configures RenderTextToImages. When Model is set, its profile supplies unset geometry and style values.

type ResponseResult added in v0.3.0

type ResponseResult struct {
	StatusCode int
	Usage      *ResponseUsage
}

ResponseResult describes a completed upstream response.

type ResponseUsage added in v0.3.0

type ResponseUsage struct {
	InputTokens              int64
	OutputTokens             int64
	CacheCreationInputTokens int64
	CacheReadInputTokens     int64
}

ResponseUsage contains provider-reported token usage.

type ResponsesComposition added in v0.1.1

type ResponsesComposition struct {
	Instructions       int `json:"instructions"`
	SystemDeveloper    int `json:"systemDeveloper"`
	UserAssistant      int `json:"userAssistant"`
	FunctionCalls      int `json:"functionCalls"`
	FunctionOutputs    int `json:"functionOutputs"`
	ReasoningEncrypted int `json:"reasoningEncrypted"`
	CompactionOpaque   int `json:"compactionOpaque"`
	ToolsJSON          int `json:"toolsJson"`
	Other              int `json:"other"`
	TotalLocal         int `json:"totalLocal"`
	ImageParts         int `json:"imageParts"`

	CompletedFunctionPairs    *int     `json:"completedFunctionPairs,omitempty"`
	RecentNativeFunctionPairs *int     `json:"recentNativeFunctionPairs,omitempty"`
	OldFunctionPairs          *int     `json:"oldFunctionPairs,omitempty"`
	OpenFunctionCalls         *int     `json:"openFunctionCalls,omitempty"`
	OrphanFunctionOutputs     *int     `json:"orphanFunctionOutputs,omitempty"`
	MalformedFunctionItems    *int     `json:"malformedFunctionItems,omitempty"`
	ImageableFunctionCalls    *int     `json:"imageableFunctionCalls,omitempty"`
	ImageableFunctionOutputs  *int     `json:"imageableFunctionOutputs,omitempty"`
	CollapsedFunctionPairs    *int     `json:"collapsedFunctionPairs,omitempty"`
	CollapsedFunctionCalls    *int     `json:"collapsedFunctionCalls,omitempty"`
	CollapsedFunctionOutputs  *int     `json:"collapsedFunctionOutputs,omitempty"`
	BarrierTypes              []string `json:"barrierTypes,omitempty"`
}

ResponsesComposition is the local o200k decomposition of a Responses request plus the planner's native-tool-state classification.

type TransformInfo

type TransformInfo struct {
	Compressed        bool   `json:"compressed"`
	Reason            string `json:"reason,omitempty"`
	OrigChars         int    `json:"origChars"`
	CompressedChars   int    `json:"compressedChars"`
	ImageCount        int    `json:"imageCount"`
	ImageBytes        int    `json:"imageBytes"`
	ImagePixels       int    `json:"imagePixels,omitempty"`
	OutgoingTextChars int    `json:"outgoingTextChars,omitempty"`

	SerializedRequestBytes int    `json:"serializedRequestBytes,omitempty"`
	SizeLimitOutcome       string `json:"sizeLimitOutcome,omitempty"`

	PinChars               int                `json:"pinChars,omitempty"`
	PinError               string             `json:"pinError,omitempty"`
	BillingLine            string             `json:"billingLine,omitempty"`
	StaticChars            int                `json:"staticChars"`
	DynamicChars           int                `json:"dynamicChars"`
	DynamicBlockCount      int                `json:"dynamicBlockCount"`
	UnknownStaticTags      []string           `json:"unknownStaticTags,omitempty"`
	ChurningStaticTags     []string           `json:"churningStaticTags,omitempty"`
	Env                    *EnvFields         `json:"env,omitempty"`
	SystemSha8             string             `json:"systemSha8,omitempty"`
	FirstUserSha8          string             `json:"firstUserSha8,omitempty"`
	FirstImagePNG          []byte             `json:"-"`
	FirstImageWidth        int                `json:"firstImageWidth,omitempty"`
	FirstImageHeight       int                `json:"firstImageHeight,omitempty"`
	ImagePNGs              [][]byte           `json:"-"`
	ImageDims              []imageDim         `json:"imageDims,omitempty"`
	ImageSourceText        string             `json:"imageSourceText,omitempty"`
	ToolResultImgs         int                `json:"toolResultImgs,omitempty"`
	NativeImages           int                `json:"nativeImages,omitempty"`
	NativeImageBytes       int                `json:"nativeImageBytes,omitempty"`
	ImageBudgetSkips       int                `json:"imageBudgetSkips,omitempty"`
	ImageByteSkips         int                `json:"imageByteSkips,omitempty"`
	ImageBytesNearLimit    bool               `json:"imageBytesNearLimit,omitempty"`
	WireImages             int                `json:"wireImages,omitempty"`
	ToolDocsChars          int                `json:"toolDocsChars,omitempty"`
	DroppedChars           int                `json:"droppedChars"`
	DroppedCodepointsTop   map[string]int     `json:"droppedCodepointsTop,omitempty"`
	PassthroughReasons     map[string]int     `json:"passthroughReasons,omitempty"`
	GateEval               *slabGateEval      `json:"gateEval,omitempty"`
	BucketChars            map[string]int     `json:"bucketChars,omitempty"`
	HistoryTextChars       int                `json:"historyTextChars,omitempty"`
	KeptSharpBlocks        int                `json:"keptSharpBlocks,omitempty"`
	Recoverable            []RecoverableBlock `json:"recoverable,omitempty"`
	TruncatedToolResults   int                `json:"truncatedToolResults,omitempty"`
	OmittedChars           int                `json:"omittedChars,omitempty"`
	CollapsedTurns         int                `json:"collapsedTurns,omitempty"`
	CollapsedChars         int                `json:"collapsedChars,omitempty"`
	CollapsedImages        int                `json:"collapsedImages,omitempty"`
	HistoryImageSha        string             `json:"historyImageSha,omitempty"`
	HistoryFreezeStep      int                `json:"historyFreezeStep,omitempty"`
	HistoryPackFill        bool               `json:"historyPackFill,omitempty"`
	HistoryBudgetTrimmed   bool               `json:"historyBudgetTrimmed,omitempty"`
	CachePrefixSha8        string             `json:"cachePrefixSha8,omitempty"`
	CachePrefixBytes       int                `json:"cachePrefixBytes,omitempty"`
	CachePrefixToolsSha8   string             `json:"cachePrefixToolsSha8,omitempty"`
	CachePrefixSystemSha8  string             `json:"cachePrefixSystemSha8,omitempty"`
	CachePrefixHeadSha8    string             `json:"cachePrefixHeadSha8,omitempty"`
	CachePrefixMarkedSha8  string             `json:"cachePrefixMarkedSha8,omitempty"`
	CachePrefixMarkedBytes int                `json:"cachePrefixMarkedBytes,omitempty"`
	CachePrefixMarkerPos   string             `json:"cachePrefixMarkerPos,omitempty"`
	HistoryReason          string             `json:"historyReason,omitempty"`

	// GPT-path (OpenAI Chat/Responses) fields.
	ImageTokens          int                   `json:"imageTokens,omitempty"`
	BaselineImagedTokens int                   `json:"baselineImagedTokens,omitempty"`
	NativeInjectedTokens int                   `json:"nativeInjectedTokens,omitempty"`
	ImageSourceTexts     []string              `json:"imageSourceTexts,omitempty"`
	ResponsesComposition *ResponsesComposition `json:"responsesComposition,omitempty"`
	// contains filtered or unexported fields
}

TransformInfo is the per-request diagnostic block (Anthropic-path subset of the TS TransformInfo).

func TransformOpenAIChatCompletions added in v0.1.1

func TransformOpenAIChatCompletions(body []byte, opts *TransformOptions) ([]byte, *TransformInfo)

TransformOpenAIChatCompletions ports transformOpenAIChatCompletions.

func TransformOpenAIResponses added in v0.1.1

func TransformOpenAIResponses(body []byte, opts *TransformOptions) ([]byte, *TransformInfo)

TransformOpenAIResponses ports transformOpenAIResponses.

func TransformRequest

func TransformRequest(body []byte, opts *TransformOptions) (outBody []byte, info *TransformInfo)

TransformRequest is the Go port of pxpipe's transformRequest.

type TransformInput

type TransformInput struct {
	Body    []byte
	Model   string
	Options *TransformOptions
}

TransformInput is the library-level input for one Anthropic Messages body.

type TransformOptions

type TransformOptions struct {
	Model                      string
	Compress                   *bool
	CompressTools              *bool
	CompressToolResults        *bool
	MinCompressChars           *int
	MinToolResultChars         *int
	Cols                       *int
	MaxImagesPerToolResult     *int
	MaxImageBytes              *int
	CharsPerToken              *float64
	HistoryAmortizationHorizon *int
	PriorWarmTokens            *float64
	PriorWarmImageTokens       *float64
	Reflow                     *bool
	KeepSharp                  func(KeepSharpBlock) bool
	EmitRecoverable            bool
	// CollapseHistory gates GPT-path history imaging (default true).
	CollapseHistory *bool
	// GptHistory overrides GPT-path history-collapse tuning.
	GptHistory *GptHistoryOptions
	// contains filtered or unexported fields
}

TransformOptions mirrors the TS TransformOptions surface that applies to the Anthropic path. Nil-able fields distinguish "unset → default".

type TransformResult

type TransformResult struct {
	Body    []byte
	Model   string
	Applied bool
	Reason  Reason
	Detail  string
	Info    *TransformInfo
	Cache   struct {
		OwnsCacheControl bool
		MarkerCount      int
	}
}

TransformResult is the library-level outcome, including cache_control ownership so hosts do not stack a second marker injector.

func TransformAnthropicMessages

func TransformAnthropicMessages(input TransformInput) *TransformResult

TransformAnthropicMessages is the model-gated library entry mirroring the TS transformAnthropicMessages wrapper.

Directories

Path Synopsis
cmd
pxpipe command
internal
app
atlas
Package atlas loads the pre-baked glyph atlases (Spleen 5x8 + JetBrains Mono 10/12/14; 1-bit and 8-bit grayscale companions) dumped from the pxpipe TS reference implementation.
Package atlas loads the pre-baked glyph atlases (Spleen 5x8 + JetBrains Mono 10/12/14; 1-bit and 8-bit grayscale companions) dumped from the pxpipe TS reference implementation.
o200k
Package o200k provides an offline o200k_base token counter.
Package o200k provides an offline o200k_base token counter.
Package render ports pxpipe's text→PNG renderer: it wraps text onto a fixed-pitch glyph grid (Spleen 5x8 or JetBrains Mono atlases) and encodes dense grayscale/RGB PNG pages sized for Anthropic's vision tiling.
Package render ports pxpipe's text→PNG renderer: it wraps text onto a fixed-pitch glyph grid (Spleen 5x8 or JetBrains Mono atlases) and encodes dense grayscale/RGB PNG pages sized for Anthropic's vision tiling.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL