Skip to main content

Use Codex, Claude Code, and Gemini CLI with a Chat Completions server

This guide configures one immutable OpenAI-compatible /v1/chat/completions server as the upstream for three client protocols:

Codex (Responses) ───────────┐
Claude Code (Messages) ──────┼─> gpt2giga ─> /v1/chat/completions
Gemini CLI (GenerateContent) ┘

The route is a technical preview. gpt2giga translates only the reviewed chat-like subset and rejects unsupported meaning before contacting the upstream. It does not pretend that Chat Completions implements every feature of Responses, Messages, or GenerateContent.

What the upstream must support​

You need:

  • the exact Chat Completions endpoint or its API base URL;
  • the exact upstream model id;
  • the real context, input, and output token limits;
  • an optional bearer token;
  • non-streaming Chat Completions JSON;
  • either Chat Completions SSE with a terminal data: [DONE] frame or the explicit buffered mode described below;
  • function tools and tool calls if the coding clients should use local tools;
  • reasoning_effort plus textual reasoning_content if Codex reasoning should survive Responses translation and tool-call replay.

In the default SSE mode, the gateway requests stream usage with stream_options: {"include_usage": true}. Buffered mode instead sends stream: false and uses the usage object from the complete JSON response. Missing usage remains missing; the gateway never invents token counts.

1. Declare the upstream​

Create providers.yaml outside the repository:

schema_version: gpt2giga.provider-profiles.v3
profiles:
- profile_id: chat-only-upstream
provider_kind: openai_compatible
base_url: https://inference.example/v1/chat/completions
credential_env: CHAT_UPSTREAM_API_KEY
network_policy_ref: public-openai
tls_policy_ref: system-default
models:
- public_alias: bridge/chat-only
upstream_model: exact-upstream-model-id
capability_profile: chat-only-coding-v1
capabilities:
features:
- roles
- ordered_content_parts
- text
- generation_controls
- reasoning_controls_and_summaries
- function_tools
- tool_choice
- parallel_tool_calls
- tool_results
- stream_deltas
- stream_terminal_events
- stop_reason
- usage
- model_identity
- request_error_classes
- cancellation
- context_token_limits
limits:
context_window: 32768
max_input_tokens: 28672
max_output_tokens: 4096
support_status: technical_preview

Replace the endpoint, model id, and limits. The public alias is the model name used by all three clients; it is mapped to upstream_model only inside the gateway.

Remove credential_env when the upstream is intentionally keyless. When it is present, gpt2giga reads the named variable at startup and sends its value as a bearer token. A client cannot override the destination, upstream model, or upstream credential.

The features list is an allowlist, not a wish list. Add json_schema_output or image_references only after the exact upstream model has been verified to support them. Do not add count_tokens: a Chat Completions endpoint has no exact token-count operation.

Declare reasoning_controls_and_summaries only when the model accepts the standard top-level reasoning_effort and returns textual reasoning_content. Native replay of that field on an assistant tool-call message is preferred. If the upstream chat template rejects such history with the confirmed 5xx error described below, gpt2giga can continue through one built-in text replay. Supported effort values are model-specific. For example, a model may accept xhigh while rejecting max; use the highest value verified against that exact endpoint.

The endpoint may also be written as https://inference.example/v1; gpt2giga then appends chat/completions. A full endpoint ending in /chat/completions is used unchanged.

Buffered upstream responses​

Some OpenAI-compatible servers accept stream: true but still return one complete application/json Chat Completion. Enable delayed stream synthesis for such a server in its v3 profile:

upstream_stream_mode: buffered

The gateway then sends stream: false upstream, validates the complete response, and emits one downstream text delta or one complete delta per function call, followed by the terminal event and usage. This is sufficient for ordinary Codex, Claude Code, and Gemini CLI tool loops.

Buffered mode is not token-by-token upstream streaming. The first content or function-call event arrives only after the model has finished the complete response, so model TTFT and incremental cancellation cannot be measured through this mode. The default is strict sse; gpt2giga never enables buffering merely because an upstream violated its declared streaming contract. Provider-specific fields remain outside the bridge unless an explicit capability admits them. With reasoning_controls_and_summaries, reasoning_content is emitted before the answer or function call as a Responses reasoning summary in both true SSE and buffered mode.

Local upstream​

For a server on the same host, use the explicit loopback exception:

base_url: http://127.0.0.1:8001/v1/chat/completions
allow_loopback: true
network_policy_ref: loopback-development
upstream_stream_mode: buffered # only when this server has no real SSE

Direct private, link-local, and metadata-network destinations are rejected. For a server reachable only through a VPN or private subnet, create an SSH or equivalent tunnel to loopback and use the loopback profile.

Docker with reverse-mode mitmproxy​

The regular deploy/observability.yaml stack configures a forward proxy through HTTP_PROXY and HTTPS_PROXY. The OpenAI-compatible adapter deliberately does not consume those environment variables. To run this bridge in Docker and inspect every upstream request, use the dedicated reverse-mode stack:

cp .env.example .env
# Set GPT2GIGA_API_KEY in .env before starting the stack.

docker compose --env-file .env \
-f deploy/chat-completions-observability.yaml up -d --build

The stack mounts deploy/providers.chat-completions.example.yaml, where the gateway sends requests to http://127.0.0.1:8080/v1/chat/completions. gpt2giga and mitmproxy share one network namespace, so this loopback address reaches mitmproxy. mitmproxy forwards to the Docker host at http://host.docker.internal:29999 by default. Override either input in .env when needed:

MITMPROXY_UPSTREAM_URL=http://host.docker.internal:29999
GPT2GIGA_PROVIDER_CONFIG_FILE=/absolute/path/to/providers.yaml

The example does not advertise parallel_tool_calls; add that capability only after the upstream completes two independent function calls in one assistant turn and accepts both tool results on the next turn.

The host tunnel must already accept requests on port 29999. Docker cannot repair or replace that tunnel. After startup:

curl -fsS http://127.0.0.1:8090/ready
curl -fsS \
-H "Authorization: Bearer $GPT2GIGA_API_KEY" \
http://127.0.0.1:8090/v1/models

Open http://127.0.0.1:8081 for the mitmproxy UI. Stop the stack with the same Compose file and down.

2. Start gpt2giga​

From a source checkout:

export CHAT_UPSTREAM_API_KEY='<upstream-bearer-token>'
export GPT2GIGA_CONFIG=/absolute/path/to/providers.yaml
export GPT2GIGA_ENABLE_API_KEY_AUTH=True
export GPT2GIGA_API_KEY='<local-gateway-key>'

uv run gpt2giga

For a keyless upstream, omit CHAT_UPSTREAM_API_KEY. With an installed package, run gpt2giga instead of uv run gpt2giga.

Check readiness and the static model alias without contacting the inference server:

curl -fsS http://127.0.0.1:8090/ready
curl -fsS \
-H "Authorization: Bearer $GPT2GIGA_API_KEY" \
http://127.0.0.1:8090/v1/models

Then test the actual translation:

curl -fsS http://127.0.0.1:8090/v1/responses \
-H "Authorization: Bearer $GPT2GIGA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"bridge/chat-only","input":"Reply only OK"}'

3. Connect Codex​

Add the provider to ~/.codex/config.toml:

[model_providers.gpt2giga_chat]
name = "gpt2giga Chat bridge"
base_url = "http://127.0.0.1:8090/v1"
env_key = "GPT2GIGA_API_KEY"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = false

Create the separate profile file ~/.codex/chat-bridge.config.toml:

model = "bridge/chat-only"
model_provider = "gpt2giga_chat"
model_context_window = 32768
model_auto_compact_token_limit = 24576
model_reasoning_summary = "auto"
model_reasoning_effort = "xhigh"
web_search = "disabled"

Export the gateway key and select the profile:

export GPT2GIGA_API_KEY='<local-gateway-key>'
codex --profile chat-bridge

Minimal Codex benchmark command​

For a hermetic CLI benchmark, bypass user MCP, app, plugin, and multi-agent configuration and provide the bridge settings explicitly:

codex -a never exec --json \
--ignore-user-config \
--strict-config \
--skip-git-repo-check \
--ephemeral \
--sandbox workspace-write \
--disable apps \
--disable plugins \
--disable multi_agent \
--disable apply_patch_freeform \
-m bridge/chat-only \
-c model_provider=gpt2giga_chat \
-c model_providers.gpt2giga_chat.name=gpt2giga_chat \
-c model_providers.gpt2giga_chat.base_url=http://127.0.0.1:8090/v1 \
-c model_providers.gpt2giga_chat.env_key=GPT2GIGA_API_KEY \
-c model_providers.gpt2giga_chat.wire_api=responses \
-c model_providers.gpt2giga_chat.supports_websockets=false \
-c model_context_window=32768 \
-c model_auto_compact_token_limit=24576 \
-c model_reasoning_summary=auto \
-c model_reasoning_effort=xhigh \
-c web_search=disabled \
'Complete the benchmark task.'

The same command string can be passed to a benchmark runner such as harness_bench run-cli. Codex CLI 0.146.0 was verified with this exact minimal surface: exec_command, write_stdin, update_plan, request_user_input, and view_image, all represented as ordinary function tools. A live two-turn probe completed a streamed exec_command, returned its result, and received the final model answer through the Chat Completions upstream.

Codex CLI 0.146.0 rejects model_supports_reasoning_summaries when --strict-config is enabled, so the pinned command above deliberately omits it. That version accepts model_reasoning_summary and model_reasoning_effort; the latter is sufficient for the verified model to return reasoning_content. Re-check strict configuration keys when upgrading Codex because the published configuration reference can describe a newer CLI.

Responses custom/freeform tools are deliberately outside this bridge subset. In particular, do not enable apply_patch_freeform. With the unknown public model alias used above, current Codex falls back to shell-based edits through exec_command; it does not expose apply_patch. If a benchmark requires the custom apply_patch wire contract itself, it needs a native Responses upstream or a separately reviewed custom-tool extension.

Codex uses the Responses wire API. The bridge accepts its stateless Responses envelope, maps developer messages to Chat Completions system messages, flattens namespace tools before the upstream call, and restores their namespace in returned function calls. It accepts the server-generated item ids replayed by Codex and preserves function call ids through the following function_call_output turn.

When reasoning is enabled, the bridge sends the requested Responses effort as Chat Completions reasoning_effort. Returned reasoning_content becomes a reasoning output item with one summary_text part. If Codex replays that item before a function call, gpt2giga restores the text as assistant reasoning_content on the next Chat Completions request. The common stateless hint include: ["reasoning.encrypted_content"] is accepted, but the bridge does not claim to produce OpenAI-encrypted reasoning state: the textual summary is the replay mechanism.

This is an explicit compatibility projection of provider reasoning text, not an OpenAI-generated reasoning summary. The upstream in the verified example does not report a usable reasoning-token count, so gpt2giga leaves that detail unavailable instead of deriving it from text.

Broken upstream tool-history templates​

Some inference servers accept the first function call but fail on the next request while rendering assistant tool calls and tool results, for example with Failed to apply chat template: Object of type Undefined is not JSON serializable. gpt2giga handles this inside the OpenAI-compatible adapter; no mitmproxy script or compatibility environment variable is required.

The original standards-shaped request is always sent first. Only when that same profile and model returns a 5xx whose detail contains Failed to apply chat template, and the request contains tool history, gpt2giga retries once. The retry keeps the current tool definitions, tool choice, reasoning effort, and generation controls, but projects prior assistant reasoning, function calls, and tool results into explicit text. Tool outputs are labelled as untrusted data. The model can then issue the next ordinary function call or finish the task.

Other failures are not retried. Neither is an SSE failure after the upstream has emitted a content chunk. gpt2giga does not execute, rename, or invent a tool, and a second failure is returned to the client. A successful recovery is reported as metadata.gpt2giga_chat_template_fallback="tool_history_text_replay"; the marker is also present on the created and completed Responses stream objects.

Codex requests /v1/models?client_version=... using a private catalog shape that includes Codex-owned base instructions and tool metadata. For that exact request, gpt2giga returns a valid empty Codex catalog without contacting the inference server. An explicitly selected custom model then keeps Codex's own fallback instructions and tool metadata, while the explicit context and compaction values above replace the fallback token limits. Ordinary OpenAI clients still receive the static aliases in the standard data list. Always pass -m or set model when using this bridge; the empty Codex catalog cannot select a default model for the client.

Codex configuration fields and profile-file precedence are documented in the official Codex configuration reference.

4. Connect Claude Code​

Keep Claude Code inside the stateless Messages subset and align its limits with the provider profile:

export ANTHROPIC_BASE_URL=http://127.0.0.1:8090
export ANTHROPIC_API_KEY="$GPT2GIGA_API_KEY"
export ANTHROPIC_MODEL=bridge/chat-only
export ANTHROPIC_SMALL_FAST_MODEL=bridge/chat-only

export CLAUDE_CODE_MAX_CONTEXT_TOKENS=32768
export CLAUDE_CODE_MAX_OUTPUT_TOKENS=4096
export MAX_THINKING_TOKENS=0
export CLAUDE_CODE_DISABLE_THINKING=1
export DISABLE_PROMPT_CACHING=1
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1

claude --model bridge/chat-only

ANTHROPIC_API_KEY makes Claude Code send the local gateway key as x-api-key. The bridge also accepts bearer authentication.

Thinking and prompt caching are disabled because Chat Completions cannot preserve those Anthropic semantics. CLAUDE_CODE_MAX_OUTPUT_TOKENS must not exceed capabilities.limits.max_output_tokens; an unknown Claude model otherwise defaults to a much larger output limit. Claude Code can use ordinary function tools and streaming Messages events through this route.

Some Claude Code workflows call /v1/messages/count_tokens. This bridge rejects that operation instead of returning an estimate. Core chat and tool execution do not require an exact count-token endpoint, but a feature that does will remain unavailable.

See the official Claude Code environment-variable reference for the client-side variables.

5. Connect Gemini CLI​

Gemini CLI gives unknown model names its built-in chat-base configuration, which currently adds topK and thinkingConfig. Those controls have no exact Chat Completions representation and are rejected. Define a minimal custom alias whose key is the exact public model id.

Merge this block into ~/.gemini/settings.json:

{
"security": {
"auth": {
"selectedType": "gemini-api-key"
}
},
"modelConfigs": {
"customAliases": {
"bridge/chat-only": {
"modelConfig": {
"model": "bridge/chat-only",
"generateContentConfig": {
"temperature": 1,
"topP": 0.95,
"maxOutputTokens": 4096
}
}
}
}
}
}

Do not extend chat-base, and do not add topK or thinkingConfig for this route. The alias key must remain bridge/chat-only: after a tool call, Gemini CLI continues with the resolved model id, so a differently named alias would only control the first request.

Start Gemini CLI with the public model id:

export GOOGLE_GEMINI_BASE_URL=http://127.0.0.1:8090
export GEMINI_API_KEY="$GPT2GIGA_API_KEY"
export GEMINI_MODEL=bridge/chat-only
export OTEL_SDK_DISABLED=true

gemini --model bridge/chat-only

Gemini sends GEMINI_API_KEY as Google-style API-key authentication, which the gateway accepts as its local API key. The provider profile then resolves the public model id to the exact upstream model. Gemini CLI's synthetic skip_thought_signature_validator marker is accepted only as a client-side no-op; an actual thought signature remains unsupported.

If GEMINI_CLI_HOME is set, Gemini CLI treats it as a home root. Put the global settings at $GEMINI_CLI_HOME/.gemini/settings.json, not directly in $GEMINI_CLI_HOME.

If the alias is stored in project-local .gemini/settings.json, trust that workspace before use. In headless runs, set GEMINI_CLI_TRUST_WORKSPACE=true before starting Gemini CLI so the workspace settings are loaded.

The model configuration format is described in the official Gemini CLI advanced model configuration guide.

Supported subset and deliberate limits​

The bridge supports text roles, common generation controls, function tools and results, tool choice where the target protocol can express it, streaming, terminal stop reasons, usage when returned by the upstream, cancellation, and declared context limits, and profile-gated Responses reasoning controls and summary replay.

A complete stateless tool loop is supported: the client sends function definitions, receives a streamed or non-streamed call, executes it locally, and replays the call plus its result in the next request. The bridge preserves the call id and translates the next turn back to Chat Completions. This was verified for Responses, Anthropic Messages, and Gemini GenerateContent.

If an OpenAI-compatible upstream returns the confirmed 5xx chat-template error for that tool history, the adapter performs the single text replay described above. The recovery is shared by the Responses, Messages, and GenerateContent facades because it runs after their requests have entered the same normalized Chat Completions path.

It does not emulate:

  • Responses state such as previous_response_id, conversations, background jobs, or durable store behavior;
  • native encrypted reasoning state, provider-independent thinking controls, or exact reasoning-token accounting;
  • prompt-cache semantics and exact cached-token accounting;
  • hosted web search, computer use, code execution, or provider-native tools;
  • Responses custom/freeform tools such as apply_patch;
  • files, audio, and unsupported multimodal content;
  • exact token counting;
  • Gemini safety settings, cached content, topK, or thinking configuration.

Operational no-op fields emitted by the tested clients are accepted where they do not change the translated request. A semantic field outside the reviewed subset returns unsupported_semantic before upstream I/O.

Troubleshooting​

SymptomMeaning and action
unknown_model_aliasThe client must use the exact public_alias, including case and punctuation.
credential_unavailable at startupSet the variable named by credential_env, or remove that field for a deliberately keyless upstream.
unsupported_semanticThe client sent meaning that Chat Completions cannot preserve. Disable that client feature; do not add a capability unless the upstream was actually verified.
Request rejected for token limitsAlign the client context/output settings with the exact values in the profile.
Gemini request mentions topK or thinkingConfigDefine the minimal custom alias under the exact public id bridge/chat-only; a differently named alias applies only to the first tool-loop request.
Claude asks for count_tokensThat operation is deliberately unsupported for a Chat Completions-only upstream.
Codex model-metadata warningKeep the explicit context and compaction values in the Codex profile; standard requests still proceed.
Upstream rejects reasoning_effort=maxEffort values are model-specific. Use the highest value verified for that model, such as xhigh, and keep the capability profile tied to it.
Response metadata contains gpt2giga_chat_template_fallback=tool_history_text_replayThe upstream rejected native tool history and the single built-in text replay succeeded. No mitmproxy compatibility layer is involved.
chat_template_application_failed reaches the clientThe request had no replayable tool history or the one fallback attempt also failed. Inspect or replace the upstream chat template; gpt2giga does not retry again.
provider_protocol_error during streamingVerify that the upstream emits valid Chat Completions SSE, a terminal choice, optional usage in the correct order, and [DONE].

For the profile schema and routing security contract, see Provider profiles and model aliases. For the semantic admission rules, see Bridge compatibility, loss, and errors.