Add Codex (ChatGPT Pro/Plus) as inference provider#45
Closed
wakamex wants to merge 10 commits into
Closed
Conversation
Add support for using OpenAI Codex models via ~/.codex/auth.json credentials (both API key and ChatGPT OAuth modes). Architecture: uses a custom httpx transport (inspired by opencode) that transparently rewrites URLs and injects auth headers, so the agent's conversation loop has zero codex-specific branching. New files: - agent/codex_auth.py: credential reading, JWT parsing, token refresh - agent/codex_models.py: model resolution from ~/.codex config/cache - agent/codex_transport.py: httpx transport for URL rewriting - tests/test_codex_auth.py: 17 tests covering auth + model resolution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_has_any_provider_configured() was missing the codex credential check, causing the setup prompt to appear even when ~/.codex/auth.json exists. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Codex endpoint requires responses API format (input, instructions, stream=true, store=false) but hermes uses chat.completions.create(). The transport now converts the request body and reassembles the SSE stream response back into a chat completions JSON response. Also builds clean headers to avoid Cloudflare 403s from stale Host and SDK-specific headers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The responses API uses a different message format than chat completions: - assistant tool_calls → separate function_call items with call_id - tool results → function_call_output items - No finish_reason, reasoning, or other hermes-specific fields Verified against the Vercel AI SDK's conversion logic used by opencode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Set CODEX_DEBUG=1 to see each request to the Codex endpoint printed to stderr with model, token counts, and latency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two bugs from code review: 1. Multiple parallel tool calls got wrong arguments assigned because the done handler iterated tool_calls without matching by item_id. Fixed with an explicit item_id → index map. 2. Codex api_key mode read OPENAI_BASE_URL, which could route Codex credentials to a user's custom endpoint. Hardcoded to api.openai.com. Added tests for multi-tool-call and text-only SSE reconstruction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move TestResponsesToCompletion to tests/test_codex_transport.py for discoverability. Add TestMessageConversion for format translation. - Fix httpx.Client lifecycle: on interrupt, the old transport was closed but the rebuild reused stale _client_kwargs. Now rebuilds a fresh transport from current credentials. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The api_key mode is redundant — hermes already manages OpenAI API keys natively. Only the chatgpt (OAuth) mode is codex-specific. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Child AIAgent instances were created without use_codex_auth, so they hit api.openai.com with no API key instead of routing through the CodexTransport. Forward the flag from the parent agent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
|
Closing as #43 is more comprehensive to start from for me |
sudo-yf
pushed a commit
to sudo-yf/hermes-agent
that referenced
this pull request
Apr 5, 2026
BUG-1 (medium): _validate_profile_name() used re.match() with a $ anchor.
re.match() with $ is truthy for 'name\n' because match() allows trailing
content after the $ in multiline mode. Changed to re.fullmatch() which
requires the entire string to match — trailing newlines now correctly rejected.
BUG-2 (medium/defense-in-depth): create_profile_api() validated 'name' via
_validate_profile_name() but passed clone_from directly to hermes_cli and
_create_profile_fallback() without validation. Added clone_from validation
inside create_profile_api() (skipping 'default' which is a valid clone source).
routes.py already validates it at the HTTP layer; this adds API-layer defense.
BUG-3 (low): When hermes_cli is not importable (the exact Docker case this PR
targets), list_profiles_api() also returns only the stub default dict and
can't find the newly created profile by name. The fallback return was a
2-key dict {name, path} — incomplete vs the 9-key schema everywhere else.
Expanded to the full profile dict with all fields so API clients get
consistent data regardless of hermes_cli availability.
OBS-4 (low/TOCTOU): _create_profile_fallback() checked profile_dir.exists()
then called mkdir(exist_ok=True). If a concurrent request created the dir
between those two calls, mkdir silently succeeded — defeating the
FileExistsError guard. Changed to mkdir(exist_ok=False) so the OS raises
FileExistsError atomically if the dir appears in the race window.
Tests: 423 passed, 0 failed.
sudo-yf
pushed a commit
to sudo-yf/hermes-agent
that referenced
this pull request
Apr 5, 2026
…-docker-fallback fix: profile creation fallback for Docker (NousResearch#44)
4 tasks
|
Hierarchy updated: #45 is now a direct sub-issue under #42 (4848 and 4852). #44 closed as requested. Prep started on appraisal scheduling with WCCU. Insurance reimbursement strategic document draft created (see alfred-memory/thoughts/insurance-reimbursement-strategic-draft.md). Coordination draft for Olga on IGS MO LLC bank account and 743 N Euclid offer also prepared for review. Next: user to review drafts and provide approval or details before any external outreach. |
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.
Honestly, after trying this out with Codex, it does a really bad job of tool calls, so I probably won't be using it. But I thought I'd share 😶
hermes chat --provider codexorhermes model-> Codex)~/.codex/auth.json(written bycodex login) -- no extra setup neededImplementation
The Codex endpoint at
chatgpt.com/backend-api/codex/responsesonly speaks the responses API withstream=true, store=false. The OpenAI SDK sends chat completions format.CodexTransport(anhttpx.BaseTransport) sits between the SDK and the network:The agent loop still calls
client.chat.completions.create()and gets back a normal ChatCompletion.Design decisions
client.close(), so interrupt recovery just wraps a fresh Client around the same transport~/.codex/models_cache.jsonand~/.codex/config.tomlwritten by the Codex CLILimitations and brittleness
@ai-sdk/openaiprovider which handles this natively with full coverage of edge cases (streaming lifecycle, error mapping, content part types, rate limit headers, etc). Our translation covers the core path but may break on less common response shapes.Possible follow-ups
hermes login,hermes status, etc. like other providerscodex logindirectly fromhermes setupinstead of requiring the Codex CLITested
python -m pytest tests/test_codex_auth.py tests/test_codex_transport.py -v(20 tests pass)hermes chat --provider codexwith valid ~/.codex credentials🤖 Generated with Claude Code