fix(image-gen): honor scoped Codex base URL for native images
(cherry picked from commit f09df8fffa7277bebe909abee5841fa3d0379237)
This commit is contained in:
@@ -191,9 +191,13 @@ def _post_image_request(
|
||||
"""POST to the native Codex images endpoint; return the decoded JSON body plus
|
||||
``imagegen_request_id`` (backend correlation id, for support tickets)."""
|
||||
import httpx
|
||||
from agent.auxiliary_client import _codex_base_url_override
|
||||
from agent.codex_headers import codex_cloudflare_headers
|
||||
|
||||
headers = codex_cloudflare_headers(token)
|
||||
# Match the text auxiliary route, including profile-scoped overrides. Resolve
|
||||
# per request: a multiplexed process can serve different Codex gateways.
|
||||
base_url = _codex_base_url_override() or _CODEX_BASE_URL
|
||||
headers = codex_cloudflare_headers(token, base_url=base_url)
|
||||
headers.update({
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json",
|
||||
@@ -202,7 +206,7 @@ def _post_image_request(
|
||||
path, body = _build_image_request(prompt=prompt, size=size, quality=quality, input_images=input_images)
|
||||
timeout = httpx.Timeout(300.0, connect=30.0, read=300.0, write=60.0, pool=30.0)
|
||||
with httpx.Client(timeout=timeout, headers=headers) as http:
|
||||
response = http.post(f"{_CODEX_BASE_URL}/{path}", json=body)
|
||||
response = http.post(f"{base_url}/{path}", json=body)
|
||||
if response.status_code >= 400:
|
||||
raise RuntimeError(
|
||||
f"Codex images API returned HTTP {response.status_code}: "
|
||||
|
||||
@@ -118,6 +118,68 @@ class TestAvailability:
|
||||
|
||||
|
||||
class TestGenerate:
|
||||
@pytest.mark.parametrize("edit", [False, True])
|
||||
def test_custom_codex_route_keeps_image_request_on_configured_host(
|
||||
self, provider, codex_backend, monkeypatch, edit,
|
||||
):
|
||||
monkeypatch.setenv("HERMES_CODEX_BASE_URL", "https://images.example.test/codex/")
|
||||
kwargs = {"image_url": "data:image/png;base64," + _b64_png()} if edit else {}
|
||||
result = provider.generate("a cat", **kwargs)
|
||||
assert result["success"] is True
|
||||
(request,) = codex_backend["requests"]
|
||||
path = "images/edits" if edit else "images/generations"
|
||||
assert str(request.url) == f"https://images.example.test/codex/{path}"
|
||||
assert request.headers["Authorization"] == "Bearer codex-token"
|
||||
assert request.headers["originator"] == "codex_cli_rs"
|
||||
|
||||
@pytest.mark.parametrize("override", ["", " "])
|
||||
def test_blank_override_preserves_official_route(
|
||||
self, provider, codex_backend, monkeypatch, override,
|
||||
):
|
||||
monkeypatch.setenv("HERMES_CODEX_BASE_URL", override)
|
||||
assert provider.generate("a cat")["success"] is True
|
||||
(request,) = codex_backend["requests"]
|
||||
assert str(request.url) == "https://chatgpt.com/backend-api/codex/images/generations"
|
||||
assert request.headers["originator"] == "hermes-agent"
|
||||
|
||||
def test_profile_scope_routes_each_request_without_borrowing_process_override(
|
||||
self, provider, codex_backend, monkeypatch,
|
||||
):
|
||||
from agent import secret_scope
|
||||
from agent.secret_scope import reset_secret_scope, set_secret_scope
|
||||
|
||||
monkeypatch.setattr(secret_scope, "_MULTIPLEX_ACTIVE", True)
|
||||
monkeypatch.setenv("HERMES_CODEX_BASE_URL", "https://other-profile.example.test/codex")
|
||||
for scope, expected in [
|
||||
({"HERMES_CODEX_BASE_URL": "https://profile.example.test/images-api/"},
|
||||
"https://profile.example.test/images-api/images/generations"),
|
||||
({}, "https://chatgpt.com/backend-api/codex/images/generations"),
|
||||
]:
|
||||
token = set_secret_scope(scope)
|
||||
try:
|
||||
assert provider.generate("a cat")["success"] is True
|
||||
finally:
|
||||
reset_secret_scope(token)
|
||||
assert str(codex_backend["requests"][-1].url) == expected
|
||||
|
||||
@pytest.mark.parametrize("failure", ["http", "empty", "exception"])
|
||||
def test_custom_route_failure_never_retries_on_official_host(
|
||||
self, provider, codex_backend, monkeypatch, failure,
|
||||
):
|
||||
monkeypatch.setenv("HERMES_CODEX_BASE_URL", "https://images.example.test/codex")
|
||||
|
||||
def respond(request):
|
||||
if failure == "exception":
|
||||
raise httpx.ConnectError("fixture connection failed", request=request)
|
||||
if failure == "http":
|
||||
return httpx.Response(403, json={"error": {"message": "denied"}})
|
||||
return httpx.Response(200, json={"data": []})
|
||||
|
||||
codex_backend["respond"] = respond
|
||||
assert provider.generate("a cat")["success"] is False
|
||||
(request,) = codex_backend["requests"]
|
||||
assert request.url.host == "images.example.test"
|
||||
|
||||
def test_returns_auth_error_without_codex_token(self, provider, monkeypatch):
|
||||
monkeypatch.setattr(codex_plugin, "_read_codex_access_token", lambda: None)
|
||||
result = provider.generate("a cat")
|
||||
|
||||
Reference in New Issue
Block a user