Skip to content

fix(codex): strip metadata and harden error converter (#51)#58

Closed
CaddyGlow wants to merge 3 commits intomainfrom
fix/issue-51-codex-metadata-strip
Closed

fix(codex): strip metadata and harden error converter (#51)#58
CaddyGlow wants to merge 3 commits intomainfrom
fix/issue-51-codex-metadata-strip

Conversation

@CaddyGlow
Copy link
Copy Markdown
Owner

Summary

  • Fixes use in claude code #51. When Claude Code CLI points at ccproxy's `/codex` endpoint, every request failed with `400 {"detail":"Unsupported parameter: metadata"}` because the `anthropic.messages -> openai.responses` converter copies Anthropic's `metadata.user_id` into the Responses body as `metadata`, which the Codex upstream (`chatgpt.com/backend-api/codex/responses`) rejects.
  • Codex adapter: add `"metadata"` to the unsupported-key strip list in `_sanitize_provider_body` (alongside `max_tokens`, `temperature`, etc). Validated live — `streaming_buffer_request_received` logs confirm the outgoing body no longer contains `metadata` and the upstream returns 200.
  • Error converter: `convert_anthropic_to_openai_error` now coerces non-Anthropic error shapes (Codex's FastAPI-style `{"detail":"..."}`) into a minimal Anthropic `ErrorResponse` envelope instead of raising `ValidationError`. Before this change, upstream 400s were being upgraded to 502s in the non-streaming error path.

Test plan

  • New regression test `test_sanitize_provider_body_strips_metadata` in `tests/plugins/codex/unit/test_adapter.py`
  • New regression tests in `tests/unit/services/adapters/test_simple_converters.py` covering Anthropic-native, FastAPI `detail`, and arbitrary-dict error shapes
  • `uv run pytest tests/plugins/codex/unit/test_adapter.py::TestCodexAdapter::test_sanitize_provider_body_strips_metadata tests/unit/services/adapters/test_simple_converters.py` (6 passed)
  • `make pre-commit`
  • Validated live against `make dev` with `ANTHROPIC_BASE_URL=http://127.0.0.1:8000/codex claude` — the metadata 400 is gone

Known follow-up (not in this PR)

Even with this fix, there's a separate streaming-display issue when Claude Code CLI targets `/codex`: the server emits valid Anthropic SSE chunks (`message_start`, `content_block_delta`, `message_stop`) and returns 200, but the CLI shows no visible response. Worth tracking separately — it's orthogonal to the metadata 400 this PR fixes.

…er (#51)

When Claude Code CLI points at ccproxy's /codex endpoint, the
anthropic.messages -> openai.responses converter copies Anthropic's
metadata.user_id into the Responses payload as "metadata". The Codex
upstream (chatgpt.com/backend-api/codex/responses) rejects this with
"Unsupported parameter: metadata", so every Claude Code -> codex
request was failing with HTTP 400.

- codex adapter: add "metadata" to the unsupported-key strip list in
  _sanitize_provider_body so it is removed before the upstream call,
  same as max_tokens/temperature.
- simple_converters: convert_anthropic_to_openai_error now coerces
  non-Anthropic error shapes (e.g. Codex's FastAPI-style
  {"detail": "..."}) into a minimal ErrorResponse envelope instead of
  raising ValidationError. Without this, upstream 400s were being
  upgraded to 502s in the non-streaming error path.

Adds regression tests for both the metadata strip and the error
converter (three shapes: Anthropic-native, FastAPI detail, and
arbitrary dict).
Copilot AI review requested due to automatic review settings April 13, 2026 10:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Codex /codex request failures caused by sending unsupported metadata to the Codex upstream and hardens Anthropic→OpenAI error conversion so non-Anthropic error payloads (e.g., FastAPI {"detail": ...}) don’t raise validation exceptions and incorrectly surface as 5xx errors.

Changes:

  • Strip metadata from Codex outbound request bodies in _sanitize_provider_body.
  • Update convert_anthropic_to_openai_error to coerce unexpected upstream error payload shapes into a minimal Anthropic error envelope before conversion.
  • Add regression tests for both the Codex body sanitization and error-shape coercion.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
ccproxy/plugins/codex/adapter.py Removes metadata (and other unsupported keys) from provider payloads sent to Codex.
ccproxy/services/adapters/simple_converters.py Coerces non-Anthropic error payloads into a valid Anthropic ErrorResponse prior to conversion.
tests/plugins/codex/unit/test_adapter.py Adds a regression test ensuring metadata is stripped by the Codex adapter sanitization step.
tests/unit/services/adapters/test_simple_converters.py Adds regression tests for Anthropic-native and non-Anthropic error payload shapes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +244 to +248
if not isinstance(data, dict) or "error" not in data:
message = ""
if isinstance(data, dict):
for key in ("detail", "message", "error_description"):
value = data.get(key)
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The coercion guard only runs when the payload is not a dict or is missing the top-level error key. If an upstream returns { "error": ... } in a non-Anthropic shape (e.g., error is a string or missing the discriminator type), ErrorResponse.model_validate(data) below will still raise ValidationError and can still upgrade an upstream 4xx into a 5xx. Consider try/except ValidationError around model_validate and falling back to the minimal envelope on validation failure.

Copilot uses AI. Check for mistakes.
The OpenAI Responses -> Anthropic and OpenAI Chat -> Anthropic stream
converters were emitting ContentBlockDeltaEvent with
delta=TextBlock(type="text") instead of delta=TextDelta(type="text_delta").
The Pydantic model accepts either (TextBlock is a tolerated fallback),
but the real Anthropic wire protocol and the Claude Code CLI's SDK
parser require type="text_delta". The effect was that Claude Code CLI
pointed at ccproxy's /codex endpoint received a 200 OK stream, parsed
message_start/content_block_start/content_block_stop/message_stop
correctly, but silently dropped every text delta — the user saw nothing.

Adds two regression tests pinning the on-the-wire type to text_delta
for both the Responses and Chat converters, including a check against
model_dump(by_alias=True) so the serialized payload can't drift.
When Claude Code CLI targets /codex, conversations with history and
tool-use cycles were dropped or mangled, and tool streaming events did
not match the official specs. Rewrite the Anthropic -> Responses input
translation and align tool streaming with Anthropic/OpenAI specs.

- anthropic_to_openai/requests.py: translate the full message list
  into Responses API input items (message / function_call /
  function_call_output), preserving interleaved text and tool_use
  ordering within assistant turns. Add a deterministic
  _clamp_call_id so tool_use/tool_result pairs stay intact when ids
  exceed OpenAI's 64-char limit. Accept LegacyCustomTool alongside
  Tool in the custom-tool mapping.

- common/streams.py: emit tool_use content_block_start with empty
  input {} and stream arguments via input_json_delta.partial_json,
  per Anthropic streaming spec. Official SDKs ignore input attached
  directly to the start event.

- openai_to_openai/streams.py: tool_call continuation chunks no
  longer re-emit id/name. Per OpenAI Chat streaming spec, those
  fields only appear on the first chunk for a given tool call.

- models/openai.py: FunctionCall.name is now Optional to support
  the continuation chunks above.

- services/adapters/delta_utils.py: identity fields (index, type,
  id, name, call_id) are overwritten instead of merged. Without
  this, providers that re-send these per chunk (e.g. the Codex
  Responses->Chat adapter) produced "shellshell..." /
  "fc_abc_fc_abc..." and broke downstream validation.

Tests cover the full tool cycle, interleaved assistant ordering,
list-form tool_result content, pending user text after a tool
result, long call_id clamping, LegacyCustomTool acceptance, tool_use
streaming events, and delta_utils identity handling.
@CaddyGlow
Copy link
Copy Markdown
Owner Author

Superseded by a broader PR that covers the full Codex <-> Claude Code integration (issues #16 and #51), not just the metadata strip.

@CaddyGlow CaddyGlow closed this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

use in claude code

2 participants