Repository navigation
fix(codex): strip server-generated IDs from response items in input to prevent 404 errors - #1397
Conversation
Port prompt caching and header alignment from the official openai/codex Rust client implementation (codex-rs). Changes based on openai/codex source analysis: 1. Inject prompt_cache_key into request body (ref: core/src/client.rs:853) - Uses workspaceId as stable cache partition key - Clients can override by sending their own prompt_cache_key 2. Set session_id header for cache affinity (ref: codex-api/src/requests/headers.rs) - Enables server-side prompt cache routing 3. Set originator header = 'codex_cli_rs' (ref: login/src/auth/default_client.rs:34) - Identifies client type to backend 4. Default store=false (ref: core/src/client.rs:862) - Matches official client: store: provider.is_azure_responses_endpoint() - Only set when client doesn't specify, don't override client's choice 5. Delete previous_response_id (ref: codex-api/src/common.rs:187) - Official client sets None for HTTP; stale IDs cause 404 with store=false 6. Fix max_tokens/max_output_tokens passthrough bug - Move deletion before nativeCodexPassthrough return - Codex API rejects these regardless of request format
Agent-Logs-Url: https://github.com/uwuclxdy/OmniRoute/sessions/cb9cd4a9-4f1e-4201-8327-a26c0f2c87d0 Co-authored-by: uwuclxdy <37777261+uwuclxdy@users.noreply.github.com>
Proxy clients like OpenClaw use response chaining via previous_response_id which requires store=true. The official Codex TUI sets store=false because it sends full history each turn, but that pattern doesn't apply to proxies. Follow CLIProxyAPI approach: don't touch store or previous_response_id, let the client decide.
The Codex API defaults store to false when not specified. Proxy clients like OpenClaw rely on response chaining via previous_response_id, which requires store=true so response items are persisted server-side. Default to store=true when the client doesn't specify it explicitly.
The /codex/responses endpoint does not persist response items even when store=true is sent in the request. Proxy clients like OpenClaw include string references (e.g. "rs_<id>") in the input array expecting the server to expand them inline. Since items were never persisted, these references cause 404 errors on the 3rd+ turn of a conversation. This adds stripStoredItemReferences() which: - Removes string items matching rs_/resp_/msg_ patterns from input - Removes object items with type "item_reference" - Deletes previous_response_id when stored refs are stripped The official Codex CLI never uses stored item references — it rebuilds full history each turn. This aligns OmniRoute with that pattern.
The previous fix only caught bare string references ("rs_...") and
explicit item_reference objects. But OpenClaw includes full response
items from previous turns (reasoning blocks, function_calls) with
their server-assigned `id` fields (rs_, fc_ prefixes). The Codex
backend validates these IDs against its persistence store and returns
404 when items are not found.
Updated stripStoredItemReferences() to:
1. Strip `id` field from objects whose id matches server-generated
patterns (rs_, fc_, resp_, msg_) — preserving the item content
but preventing backend ID validation
2. Always delete previous_response_id (matching official Codex CLI
and CLIProxyAPI behavior)
3. Continue filtering bare string refs and item_reference objects
Refs:
- codex-rs common.rs:187 — previous_response_id: None
- CLIProxyAPI codex_executor.go:115 — DeleteBytes("previous_response_id")
There was a problem hiding this comment.
Code Review
This pull request enhances the Codex executor by improving backend compatibility and adding support for prompt caching. Key updates include the addition of stripStoredItemReferences to remove server-generated IDs and previous_response_id from requests to prevent 404 errors, defaulting the store parameter to true, and injecting cache-related headers and keys (originator, session_id, prompt_cache_key). Additionally, unsupported token limit parameters are now removed before passthrough. A critical typo was identified in the getResponsesSubpath regular expression that would cause standard endpoint matching to fail.
| function getResponsesSubpath(endpointPath: unknown): string | null { | ||
| const normalizedEndpoint = String(endpointPath || "").replace(/\/+$/, ""); | ||
| const match = normalizedEndpoint.match(/(?:^|\/)responses(?:(\/.*))?$/i); | ||
| const match = normalizedEndpoint.match(/(?:^|\/)responses(?:(\/.*)) ?$/i); |
There was a problem hiding this comment.
The regular expression contains a typo: ? (two spaces and a question mark) has been added at the end. This will cause the match to fail for standard endpoint paths like /responses or /responses/compact because it will expect trailing spaces in the URL. It should be reverted to the original ? to correctly match the optional subpath.
| const match = normalizedEndpoint.match(/(?:^|\/)responses(?:(\/.*)) ?$/i); | |
| const match = normalizedEndpoint.match(/(?:^|\/)responses(?:(\/.*))?$/i); |
Restores the original regex pattern for getResponsesSubpath. The previous commit accidentally introduced " ?" (two spaces + ?) instead of "?" in the regex, which would fail to match standard endpoint paths like /responses or /responses/compact. Reported by: gemini-code-assist[bot]
e57126a
into
cheaper-inference:release/v3.6.9
|
Thanks @Gi99lin for this great contribution! 🎉 The fix has been integrated into the release branch and will be part of the next release. We appreciate your effort! |
…o prevent 404 errors (cheaper-inference#1397) Integrated into release/v3.6.9
…o prevent 404 errors (cheaper-inference#1397) Integrated into release/v3.6.9
Problem
When proxy clients like OpenClaw send Responses API format requests through OmniRoute to the Codex
/codex/responsesendpoint, requests after the 2nd turn fail with 404 errors:Root cause: OpenClaw includes full response items from previous turns in the
inputarray, carrying server-assignedidfields (prefixed withrs_,fc_). The Codex backend validates these IDs against its persistence store but the/codex/responsesendpoint does not actually persist response items — so the lookup always fails.Evidence
Verified by inspecting the request payload on the 3rd turn:
{ "id": "rs_02c78f217bfad...", // ← server tries to find this in store → 404 "type": "reasoning", "summary": [] }, { "type": "function_call", "id": "fc_02c78f217bfad...", // ← same problem "call_id": "call_5NF4nZx...", // ← client-side ID, not affected "name": "read", "arguments": "..." }First 2 turns succeed because only the 3rd+ turn includes response items from previous turns.
What this PR changes
stripStoredItemReferences()— comprehensive ID sanitizationThe function now handles three cases:
"rs_abc123"){ type: "item_reference" }){ id: "rs_...", type: "reasoning" })idfield, keep item contentThis preserves the full conversation context (reasoning blocks, function calls, outputs) while removing only the server-generated identifiers that the backend cannot validate.
Always delete
previous_response_idThe
/codex/responsesendpoint does not persist responses, so anyprevious_response_idreference will cause a 404. This matches the behavior of both:previous_response_id: None(codex-api/src/common.rs:187)sjson.DeleteBytes(body, "previous_response_id")(codex_executor.go:115)Prompt cache support
Also includes cache affinity improvements:
prompt_cache_keyderived from workspace IDsession_idheader for backend cache routingoriginator: "codex_cli_rs"headerTesting
Verified on a production-like deployment (homelab) with OpenClaw → spawn-sanitizer → OmniRoute → Codex:
cached_tokens > 0)