test(codex): pin proxy override on rotation and model.base_url; document HERMES_CODEX_BASE_URL
Two invariant tests (red on origin/main): a 401 rotation onto a Codex pool row keeps the HERMES_CODEX_BASE_URL target, and model.base_url under model.provider: openai-codex resolves for pool credentials. Adds the previously undocumented HERMES_CODEX_BASE_URL row to the environment variables reference so proxy users can find the knob and its reach.
This commit is contained in:
@@ -109,3 +109,25 @@ def test_credential_rotation_does_not_carry_global_headers_across_routes():
|
||||
headers = agent._client_kwargs["default_headers"]
|
||||
assert "Authorization" not in headers
|
||||
assert headers["X-Route"] == "b"
|
||||
|
||||
|
||||
def test_codex_rotation_keeps_proxy_override(monkeypatch):
|
||||
"""#40913: a 401/429 rotation adopts the pool row, whose stored URL is the canonical ChatGPT
|
||||
endpoint; with HERMES_CODEX_BASE_URL set the rotated client must keep targeting the proxy."""
|
||||
from agent.credential_pool import PooledCredential
|
||||
|
||||
monkeypatch.setenv("HERMES_CODEX_BASE_URL", "http://127.0.0.1:8787/backend-api/codex/")
|
||||
entry = PooledCredential(provider="openai-codex", id="second", label="second", auth_type="oauth",
|
||||
priority=1, source="manual:device_code", access_token="tok-second",
|
||||
base_url="https://chatgpt.com/backend-api/codex")
|
||||
agent = SimpleNamespace(
|
||||
api_mode="codex_responses", provider="openai-codex", model="gpt-5.3-codex", api_key="tok-first",
|
||||
base_url="http://127.0.0.1:8787/backend-api/codex",
|
||||
_client_kwargs={"api_key": "tok-first", "base_url": "http://127.0.0.1:8787/backend-api/codex"},
|
||||
_reapply_route_client_config=MagicMock(), _replace_primary_openai_client=MagicMock(),
|
||||
)
|
||||
|
||||
assert AIAgent._swap_credential(agent, entry) is True
|
||||
assert agent.base_url == "http://127.0.0.1:8787/backend-api/codex"
|
||||
assert agent._client_kwargs["base_url"] == "http://127.0.0.1:8787/backend-api/codex"
|
||||
assert agent.api_key == "tok-second"
|
||||
|
||||
@@ -110,6 +110,33 @@ def test_codex_pool_honors_hermes_codex_base_url(monkeypatch):
|
||||
assert resolved["base_url"] == "http://127.0.0.1:8787/v1"
|
||||
|
||||
|
||||
def test_codex_pool_honors_model_base_url(monkeypatch):
|
||||
"""#40913: model.base_url under provider openai-codex is the secondary proxy override; the
|
||||
canonical URL stored on the pool row must not shadow it."""
|
||||
class _Entry:
|
||||
access_token = "pool-token"
|
||||
source = "manual"
|
||||
base_url = "https://chatgpt.com/backend-api/codex"
|
||||
|
||||
class _Pool:
|
||||
def has_credentials(self):
|
||||
return True
|
||||
|
||||
def select(self, **_kwargs):
|
||||
return _Entry()
|
||||
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openai-codex")
|
||||
monkeypatch.setattr(rp, "load_pool", lambda provider: _Pool())
|
||||
monkeypatch.delenv("HERMES_CODEX_BASE_URL", raising=False)
|
||||
monkeypatch.setattr(rp, "_get_model_config", lambda: {
|
||||
"provider": "openai-codex", "default": "gpt-5.3-codex", "base_url": "http://127.0.0.1:8400/backend-api/codex/"})
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="openai-codex")
|
||||
|
||||
assert resolved["base_url"] == "http://127.0.0.1:8400/backend-api/codex"
|
||||
assert resolved["api_mode"] == "codex_responses"
|
||||
|
||||
|
||||
class TestCustomProviderPoolLoopbackNoKeyExemption:
|
||||
"""Regression for issue #86864: legacy custom_providers configs often
|
||||
used short/placeholder api_keys ('123', 'm') for local no-auth
|
||||
|
||||
@@ -23,6 +23,7 @@ Hermes reads environment variables from the process environment and, for user-ma
|
||||
| `AI_GATEWAY_BASE_URL` | Override AI Gateway base URL (default: `https://ai-gateway.vercel.sh/v1`) |
|
||||
| `OPENAI_API_KEY` | API key for custom OpenAI-compatible endpoints (used with `OPENAI_BASE_URL`) |
|
||||
| `OPENAI_BASE_URL` | Base URL for custom endpoint (VLLM, SGLang, etc.) |
|
||||
| `HERMES_CODEX_BASE_URL` | Route the `openai-codex` (ChatGPT subscription) provider through a proxy instead of the default Codex backend. Applies everywhere the credential is used: pool resolution, auxiliary/raw clients, and 401/429 credential rotation. `model.base_url` under `model.provider: openai-codex` is the secondary override when this is unset. |
|
||||
| `LM_API_KEY` | API key for LM Studio (`lmstudio` provider). Often a placeholder for local servers |
|
||||
| `LM_BASE_URL` | LM Studio base URL (default: `http://localhost:1234/v1`) |
|
||||
| `COPILOT_GITHUB_TOKEN` | GitHub token for Copilot API — first priority (OAuth `gho_*` or fine-grained PAT `github_pat_*`; classic PATs `ghp_*` are **not supported**) |
|
||||
|
||||
Reference in New Issue
Block a user