fix(provider-codex): sync Codex Responses fingerprint with official CLI#107
Open
M1k0t0 wants to merge 21 commits into
Open
fix(provider-codex): sync Codex Responses fingerprint with official CLI#107M1k0t0 wants to merge 21 commits into
M1k0t0 wants to merge 21 commits into
Conversation
Menci
reviewed
Jun 25, 2026
Owner
There was a problem hiding this comment.
I don't want to allow some concepts of ONE provider to the whole gateway.
abb0262 to
dcc4f4a
Compare
Contributor
Author
|
I agree with the concern, and I changed the implementation to keep Codex-specific concepts out of the gateway. In the latest version, the gateway no longer knows about Codex session ids, turn ids, account ids, or Codex-specific metadata keys. Those are all owned by The gateway-side changes are now limited to provider-neutral lifecycle hooks:
|
61259c8 to
ec75d53
Compare
Use the official codex_cli_rs data-plane fingerprint for Codex models and responses, normalize session_id to session-id, and synthesize Floway-owned Codex metadata instead of forwarding raw downstream x-codex-* values.
Clone the official Codex turn metadata shape, keep session/thread scope stable, and generate Codex-shaped UUIDv7 session, window, and turn identifiers.
Store Floway-owned Codex session metadata on Responses snapshots, validate snapshot metadata JSON on read, and clear pending metadata after each attempted snapshot commit.
Persist Codex window scope alongside the Floway-owned session id and format upstream window ids as session generation markers instead of random UUIDs.
Prefer caller-supplied Codex session ids in the gateway before falling back to previous_response_id snapshot metadata. Reuse a Floway-owned Codex turn id across repeated provider dispatches in one gateway attempt and mark compaction requests with compaction turn metadata.
ec75d53 to
a494d48
Compare
…tion_id The Codex upstream's `client_metadata['x-codex-installation-id']` is meant to look like one persisted device per account; the previous SHA-256 derivation of `(upstreamId, accountId)` was stable per account but exposed the gateway's internal keying as the fingerprint. Mint a fresh UUIDv4 at OAuth import time, persist it on the credential row, and read it back at request time. Migration 0047 backfills openaiDeviceId on existing state_json rows (per CodexUpstreamConfig's exactly-one-account invariant) so the field can be required end-to-end without an in-code legacy fallback.
The previous build path overwrote every identity surface from gateway defaults, throwing away parent_thread_id, custom turn_started_at_unix_ms, and the caller's own installation id even when they were the more accurate fingerprint. Identity now resolves through a single ordered chain (dedicated header → caller body `client_metadata` key → parsed `x-codex-turn-metadata` key → gateway default) for every mirrored id and is projected back onto every surface so a caller that splits its identity across surfaces gets consistent values out. Specifics: - thread-id and x-client-request-id pass through; default to session-id / thread-id only when the caller did not supply them. Sub-agent flows that want a distinct thread or per-request id under the same session now work. - x-codex-window-id passes through for direct provider callers too (gateway-routed calls still hit the FLOWAY_CODEX_WINDOW_ID_HEADER resolver set by the responses-state hook). - client_metadata is merged key by key: caller's identity-mirror keys get absorbed into identity at build time; their non-identity extras (custom telemetry) survive into the outbound body. - x-codex-turn-metadata's parsed JSON is merged the same way, picking up fields the official CLI sets but Floway has no opinion on (turn_started_at_unix_ms, sandbox, workspaces, parent_thread_id). - installation_id resolution gains a top tier: client_metadata['x-codex-installation-id'] (real client) → parsed x-codex-turn-metadata installation_id → account.openaiDeviceId.
…rough The build-from-clean test stopped asserting `installation_id` against the v4 shape now that the field passes through the caller-supplied value verbatim; remove the helper to satisfy lint.
…ller supplies none A stateless OpenAI Responses client that re-sends the full conversation every turn used to get a fresh UUIDv7 session-id on each request, which collapses chatgpt.com's prompt-cache hit rate to roughly zero for that shape of caller. Restore a content-derived fallback ahead of the random mint: SHA-256 of `instructions + JSON(first input item)`. Hashing the *first item* keeps the id stable as later turns append tail items, which is what makes the upstream prompt cache continue to hit across turns of the same conversation. The seed is type-agnostic so a post-compaction snapshot (where the leading item is a `type: compaction` blob with no preceding user message) still produces a stable id within that new context window — the alternative of only seeding on user messages would drop the cache on every post-compaction turn. Stateful callers using `previous_response_id` reach this code path with input already expanded from the snapshot in attempt.ts, so they pass the same first item across turns and get the same id without ever needing the snapshot-bound session resolver to fire.
…ctually runs `prepareCodexResponsesRequest` (the provider's `prepareResponsesRequest` hook) runs before the interceptor chain and always writes `FLOWAY_CODEX_SESSION_ID_HEADER`, so the derive-from-input fallback in `inject-session-id.ts` could never reach production — the interceptor saw the FLOWAY header already populated and returned. The unit tests exercised the path only by bypassing the prepare hook, giving false coverage. Move the derivation into `ensureCodexSessionId` (responses-state.ts) where it sits between the snapshot-metadata lookup and the UUIDv7 fallback, so a stateless caller that re-sends the full conversation each turn actually gets a stable session-id. Extend `ProviderResponsesRequestContext` with `payload` so the hook can see the input. Delete the now-redundant `inject-session-id.ts` interceptor (fetch.ts's identity-resolution chain still covers direct provider callers that bypass the gateway hook).
…SessionId removal `synthesize-metadata-user-id.ts` had two comments pointing at the deleted `injectSessionId` interceptor by name. Update the references to name the live Codex equivalents (`responses-state.ts`'s derivation chain, and `sha256Uuid` in `provider-codex/ids.ts`).
…seed tests
`deriveSessionIdFromInput` advertises a U+0001 separator between
instructions and the first-item JSON via its inline comment, but the
template literal carried plain concatenation — a crafted `instructions`
ending in `{"role":"user","content":"hello"}` could collide with another
conversation whose actual first item is that exact string. Add the
separator the comment already documents.
Round 2 review also flagged that two coverage cases from the deleted
`inject-session-id_test.ts` were not migrated when the derive logic
moved into `responses-state.ts`: distinct-instructions and
distinct-first-item should both yield distinct session-ids. Without
them, a regression that dropped either seed component from the hash
input would pass every other test. Add both as separate cases.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #92.
Context
#92 narrowed the Codex Responses 403 to request fingerprint differences:
/codex/modelsworked, the official Codex CLI worked from the same egress path, but Floway/backend-api/codex/responsesstill received upstream HTML 403s.This PR continues that work by aligning Floway’s Codex Responses request shape with the official
openai/codexRust client instead of only matching another relay implementation.Thanks to @Menci for the detailed #92 review, the follow-up items called out there have been addressed in this PR.
Changes
/codex/responses.session-id,thread-id,x-client-request-id,x-codex-window-id,x-codex-turn-metadata,client_metadata, andprompt_cache_keyusing the official Codex semantics.session_idto canonicalsession-idat the provider boundary.responses_snapshots.metadata_jsonvia D1 migration~88% cache hitcomment while rewriting session-id injection, replacing it with qualitative Codex session/cache scope wording. Note: the~88% cache hitcomment called out in fix(provider-codex): match CLIProxyAPI response headers #92 was already present on upstream/main before this branch.Verification
pnpm vitest run --project @floway-dev/provider-codex packages/provider-codex/src/fetch_test.ts packages/provider-codex/src/compaction_test.ts packages/provider-codex/src/interceptors/responses/inject-session-id_test.tspnpm vitest run --project @floway-dev/gateway packages/gateway/src/data-plane/llm/responses/serve_test.ts packages/gateway/src/data-plane/llm/responses/items/store_test.ts packages/gateway/src/repo/responses-items_test.ts packages/gateway/src/data-plane/shared/inbound-headers_test.tspnpm --filter @floway-dev/provider-codex run typecheck