Codex backend plumbing: credential names, error types, payload adaptation.
Split out of :mod:symfonic.core.codex_provider (378 lines against the
300-line budget). codex_provider is the ModelProvider implementation;
this module is everything it needs to talk to the Codex backend and nothing to
do with the provider protocol -- the environment variables the token is read
from, the headers the backend attributes usage by, the two error types callers
catch, and the ChatOpenAI subclass that rewrites system -> developer.
codex_provider re-exports CodexCredentialError,
CodexProductionMisuseError and _token_fingerprint, which is how the
existing tests and adopters import them.
CodexCredentialError
Bases: RuntimeError
Raised when a ChatGPT subscription token cannot be resolved.
CodexProductionMisuseError
Bases: RuntimeError
Raised when CodexOAuthProvider is used in a production-like env.
install_auth_failure_log_hook
install_auth_failure_log_hook(kwargs: dict[str, Any], *, logger: Logger) -> None
Attach a log-only 401/403 response hook to sync + async transports.
Composes onto an adopter-supplied http_client / http_async_client
if present; otherwise builds fresh httpx clients (adopting any resolved
timeout). Purely observability โ see _oauth_errors for the
never-raise / body-untouched contract.
Source code in src/symfonic/core/codex_backend.py
| def install_auth_failure_log_hook(
kwargs: dict[str, Any], *, logger: logging.Logger
) -> None:
"""Attach a log-only 401/403 response hook to sync + async transports.
Composes onto an adopter-supplied ``http_client`` / ``http_async_client``
if present; otherwise builds fresh httpx clients (adopting any resolved
``timeout``). Purely observability โ see ``_oauth_errors`` for the
never-raise / body-untouched contract.
"""
import httpx
from ._oauth_errors import (
make_auth_failure_log_hook,
make_auth_failure_log_hook_async,
)
remediation = (
"Your ChatGPT/Codex subscription token is expired or invalid. Run "
"`codex login` (or re-export OPENAI_CHATGPT_ACCESS_TOKEN), then "
"retry."
)
resp_sync = make_auth_failure_log_hook(
logger, provider="Codex", remediation=remediation
)
resp_async = make_auth_failure_log_hook_async(
logger, provider="Codex", remediation=remediation
)
timeout_val = kwargs.get("timeout")
httpx_timeout = (
httpx.Timeout(float(timeout_val))
if isinstance(timeout_val, (int, float))
else httpx.Timeout(60.0)
)
existing_async = kwargs.get("http_async_client")
if existing_async is not None:
existing_async.event_hooks.setdefault("response", []).append(resp_async)
else:
kwargs["http_async_client"] = httpx.AsyncClient(
timeout=httpx_timeout, event_hooks={"response": [resp_async]}
)
existing_sync = kwargs.get("http_client")
if existing_sync is not None:
existing_sync.event_hooks.setdefault("response", []).append(resp_sync)
else:
kwargs["http_client"] = httpx.Client(
timeout=httpx_timeout, event_hooks={"response": [resp_sync]}
)
|