A small Linux x86_64 loopback service that supplies one bridge-host ChatGPT
OAuth identity to permitted Kepos peers. It is a transparent Codex Responses
relay, not an agent runtime: the connected client owns its request shape,
history, Lite rendering, cache lineage, continuations, tools, and compaction.
The bridge publishes four fixed sibling routes:
POST /codex/responsesforwards an HTTP/SSE Responses request. It retains a 64 MiB encoded request limit and streams the final upstream status, safe end-to-end headers, and bytes unchanged.GET /codex/responsesaccepts a WebSocket upgrade. It retains the client query and application headers, forwards Text and Binary frames unchanged, propagates Close, and lets the endpoint libraries handle Ping/Pong.POST /codex/imagesaccepts a JSON image request with a bridge-owned envelope: required nonblankmodelandprompt, plus optionalimagesof one through fivedata:image/...inputs. A prompt alone generates; images select editing and are normalized for the managed upstream. Every other top-level option is forwarded unchanged, while callerapi_keyis ignored and managed OAuth remains bridge-owned. Omitted image options use upstream defaults. The route retains a 32 MiB encoded limit and returns exactly{ "image_url": "data:image/png;base64,..." }.POST /codex/buffered/responsesaccepts one caller-supplied, non-streaming Responses request, including caller-supplied tool definitions. It retains the 64 MiB encoded request limit, replaces peer identity with managed OAuth, forces the Codex upstream request to stream, and returns one bufferedapplication/jsonResponses object. It removesmax_output_tokens; when it does, the response includesx-kepos-ignored-parameters: max_output_tokens; that limit is not enforced before or after generation. Requests withprevious_response_idorstream: trueare rejected. The adapter forwards tool definitions and preserves returned function calls, but the caller remains responsible for executing tools and supplying subsequent tool output.POST /codex/web-searchis a stateless text-search adapter. It accepts only{ "commands": { ... } }with one or more ofsearch_query(one to three queries),weather,sports(exactly one operation),finance, andtime. A search query is{ "q": string, "recency"?: non-negative integer, "domains"?: string[] }; weather is{ "location": string, "start"?: "YYYY-MM-DD", "duration"?: positive integer }; sports is one{ "fn": "schedule" | "standings", "league": "nba" | "wnba" | "nfl" | "nhl" | "mlb" | "epl" | "ncaamb" | "ncaawb" | "ipl", ... }(the bridge adds the upstream-onlytool: "sports"field); finance is{ "ticker": string, "type": "equity" | "fund" | "crypto" | "index", "market"?: string }; and time is{ "utc_offset": "+HH:MM" | "-HH:MM" }. Weather, finance, and time arrays are capped at 16 entries. All user strings must be non-blank, and dates/offsets must use their stated syntax. The bridge fixes the upstream model, short response length, settings, and token budget, supplies its managed OAuth identity, and rejects caller model, input, request ID, response length, continuation, navigation, image-search, and other stateful fields. The upstreamresultsarray is ordinary plaintext JSON and is preserved as opaque values; encryptedencrypted_outputcontinuation state and all other upstream fields are removed. Requests and successful responses are bounded at 64 KiB and 1 MiB, respectively, and failures are generic JSON errors.
Responses is JSON-opaque. The relay forwards client-controlled model,
instructions, Lite/beta/cache/session/thread/request headers, continuation
state, opaque checkpoints, and compressed bodies without decoding or
reserializing them. It removes peer Authorization, Proxy-Authorization,
X-Api-Key, Cookie, account, and FedRAMP identity headers; it then injects
the bridge's managed bearer/account identity. A pre-stream or pre-upgrade
upstream 401 receives one managed-OAuth refresh retry. The relay never exposes
upstream cookies or manufactures SSE events, Responses IDs, or protocol state.
/codex/buffered/responses is intentionally the exception to the transparent
relay's JSON-opaque contract. It preserves every compatible request field while
performing only the documented transport normalization. For a
successful upstream SSE response, it buffers at most 4 MiB, rebuilds ordered
output from response.output_item.done events, and combines it with the first
terminal response.completed, response.incomplete, or response.failed
object. It does not stream downstream, and it returns a generic 502 without
partial data when the successful SSE stream is oversized, malformed, or lacks a
terminal response. Non-successful upstream HTTP statuses and safe headers/bodies
remain recognizable. Downstream streaming and provider-managed continuation
workflows continue to use /codex/responses.
There is no /v1 alias, /compact route, model alias, client-facing mode
switch, or fixed --model/--instructions serve option. In particular,
/codex/buffered/responses is not a generic OpenAI Responses compatibility API.
Model and instructions belong to the caller. /codex/images is a managed-auth
image transport: the bridge does not choose, allowlist, or default image models
or provider image options. Callers own image model and option policy and must
send the required envelope fields.
Build the single Linux x86_64 release artifact on the target Linux host or
approved Linux build environment:
cargo build --release --target x86_64-unknown-linux-gnu
# target/x86_64-unknown-linux-gnu/release/kepos-codex-bridgeThe GitHub Actions workflow publishes the Linux amd64 image on every push to
main:
ghcr.io/lamplitisles/kepos-codex-bridge:latest
ghcr.io/lamplitisles/kepos-codex-bridge:sha-<commit>
The image runs as UID 10001, includes CA certificates for the managed OAuth
upstream, and has no container manifest, Helm chart, or non-Linux/amd64 build.
Keep the managed OAuth file private and writable only by its owner. It belongs on the bridge host; never copy it to a peer or put it in a Pi profile.
export KEPOS_CODEX_AUTH_FILE=/var/lib/kepos-codex-bridge/auth.json
kepos-codex-bridge login --auth-file "$KEPOS_CODEX_AUTH_FILE"
kepos-codex-bridge serve --auth-file "$KEPOS_CODEX_AUTH_FILE" --port 8787The bridge follows the same environment proxy policy as its HTTP client for
upstream Responses traffic. Set the variables before starting serve:
HTTPS_PROXY/https_proxyis used forwssupstreams, withALL_PROXY/all_proxyas a fallback.HTTP_PROXY/http_proxyis used forwsupstreams, withALL_PROXY/all_proxyas a fallback.NO_PROXY/no_proxyuses the standard comma-separated host, domain, IP, CIDR, and*bypass rules. The uppercase variable wins when both cases are set, matching the HTTP route.
Proxy URLs must name an HTTP CONNECT proxy. A WebSocket connection reports a generic 502 when the proxy, TCP/TLS path, or upstream upgrade cannot be established; the complete establishment attempt, including the one managed OAuth refresh retry, is bounded at 15 seconds. Once upgraded, the Responses stream remains long-lived and the relay's existing opaque frame, header, subprotocol, and managed-identity behavior is unchanged. The proxy policy is read when the bridge starts, so change the environment and restart the bridge when changing egress policy.
The listener is 127.0.0.1:8787 by default. It does not terminate TLS or
publish login, token inspection, logout, metrics, or admin endpoints.
Publish it as a named Kepos HTTP service targeting that loopback port, using the normal HTTP/WebSocket-over-Noise publisher path. Allowlist only the peer public keys authorized to use the bridge subscription:
service name: codex-bridge
publisher target: http://127.0.0.1:8787
paths: /codex/responses, /codex/buffered/responses, /codex/images, and /codex/web-search
transport: standard Kepos HTTP service (HTTP + WebSocket upgrade for Responses)
allowlist: approved peer public keys
Kepos peer identity and its publisher allowlist are the authorization boundary. An allowed peer can use the bridge account, so do not publish this service to untrusted peers. The bridge adds no bearer authentication, account multiplexing, token inspection, request-body logging, conversation persistence, or prompt-cache registry.
The implementation artifact is published by the repository's CI workflow after
the feature is merged to main; merging does not restart an existing Bridge
Pod. Any production image rollout or restart remains a separate operator
approval and deployment step.
Use a fresh, private test root for each probe. The examples use a synthetic,
non-secret JWT-shaped placeholder because Pi validates the local
chatgpt_account_id claim before connecting. It is ignored by the relay; never
substitute a real Codex OAuth token.
umask 077
ROOT=$(mktemp -d)
MODEL='<client-selected Codex model>'
RELAY='http://127.0.0.1:<bridge-port>/codex/responses'
PLACEHOLDER='eyJhbGciOiJub25lIn0.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL2F1dGgiOnsiY2hhdGdwdF9hY2NvdW50X2lkIjoidGVzdC1hY2NvdW50In19.dummy'
mkdir -p "$ROOT/pi-agent" "$ROOT/sessions"
cat >"$ROOT/pi-agent/models.json" <<EOF
{"providers":{"openai-codex":{"baseUrl":"$RELAY","apiKey":"$PLACEHOLDER"}}}
EOF
cat >"$ROOT/pi-agent/settings.json" <<EOF
{"defaultProvider":"openai-codex","defaultModel":"$MODEL","transport":"websocket"}
EOFBefore any paid request, read that test models.json, confirm the selected
model, and confirm a listener is bound at the loopback port. A system
HTTP_PROXY or Clash/Mihomo setting affects egress only; it does not configure
the model endpoint. The explicit route must be:
Pi -> 127.0.0.1:<bridge-port>/codex/responses -> managed Codex upstream
Do not run Pi OAuth login in this profile. Start Pi with the isolated profile, session directory, and exactly the Ogul compaction extension:
PI_CODING_AGENT_DIR="$ROOT/pi-agent" \
NO_PROXY=127.0.0.1,localhost no_proxy=127.0.0.1,localhost \
pi --session-dir "$ROOT/sessions" \
--provider openai-codex --model "$MODEL" \
--extension "$HOME/.pi/agent/npm/node_modules/@ogulcancelik/pi-codex-compaction/index.ts"Run one normal turn, invoke /compact manually, then run one normal follow-up.
Ogul owns the Remote Compaction V2 request and opaque checkpoint; the relay
neither interprets nor stores either. Retain only route confirmation,
success/failure, and numeric input/cache-read/cache-write usage. Do not retain
prompts, request bodies, checkpoints, OAuth data, or test session files.
Use the same isolated Pi profile and placeholder, install/load the package in that test-owned profile, and explicitly enable Lite. An environment override keeps the setting out of a live profile:
PI_CODING_AGENT_DIR="$ROOT/pi-agent" \
PI_OPENAI_CODEX_COMPAT_RESPONSES_LITE=on \
NO_PROXY=127.0.0.1,localhost no_proxy=127.0.0.1,localhost \
pi --session-dir "$ROOT/sessions" \
--provider openai-codex --model "$MODEL" \
--extension '<test-owned pi-openai-codex-compat extension path>'Again run normal → manual /compact → normal follow-up. Lite request fields,
additional_tools, client metadata, cache keys, and continuation state are
client-owned and pass through untouched except for managed identity.
Nanocodex needs no bridge-specific session adapter. Configure its public endpoints and a non-secret local API-key placeholder; choose its normal transport as needed:
let openai = OpenAi::builder("nonsecret-local-placeholder")
.model(Model::Luna)
.api_base_url("http://127.0.0.1:<bridge-port>/codex")
.websocket_url("ws://127.0.0.1:<bridge-port>/codex/responses")
.build()?;Its own Session::turn().create() and turn().compact() then use the selected
HTTP or WebSocket transport. The pinned client is verified against a hermetic
recording origin; it does not require a separate paid live run.
Hermetic relay and image checks require no live credentials or services:
cargo fmt --check
cargo test --all-targets
cargo clippy --all-targets --all-features -- -D warnings
cargo build --release --target x86_64-unknown-linux-gnuInstall Lefthook once, then activate the repository hooks:
lefthook installpre-commit verifies formatting. pre-push runs the hermetic test suite and
Clippy with warnings denied. GitHub Actions repeats those checks and the Linux
release build for every pull request.
For the explicitly approved live acceptance matrix, start a separate temporary loopback bridge using a dedicated, test-owned managed-auth file and port. Do not reuse, stop, or refresh an existing bridge process or its auth file. Verify the test profile route, listener, and model before each row; run the Stock Pi+Ogul and Pi compat Lite normal → compact → follow-up rows; then securely remove only the test root and the temporary bridge it started. A cache hit is an observed provider result, not a guarantee or CI assertion.
The bridge does not validate DSH. It does not validate or proxy Pi compat's
optional image or web companion endpoints. It is not a bridge-side Lite
renderer, Responses schema adapter, session/cache owner, compaction encoder,
durable store, generic reverse proxy, or cache-hit guarantee. Pi, Ogul,
Nanocodex, and pi-openai-codex-compat remain unmodified clients that own
reconnection, retry, continuation, history, and all client protocol state.