80237bcabc
CI / Adapter unit tests (push) Successful in 35s
CI / Template validation (static) (push) Successful in 40s
CI / Adapter unit tests (pull_request) Successful in 32s
CI / Template validation (static) (pull_request) Successful in 1m25s
CI / Template validation (runtime) (push) Successful in 2m43s
CI / T4 tier-4 conformance (live) (push) Successful in 2m40s
CI / T4 tier-4 conformance (live) (pull_request) Successful in 50s
CI / validate (push) Successful in 9s
CI / Template validation (runtime) (pull_request) Successful in 3m13s
CI / validate (pull_request) Successful in 4s
Replace the hardcoded codex_minimax_config.sh single-provider routing
with a generic providers: registry in config.yaml and a Python adapter
layer (provider_config.py) that resolves the picked provider against
the env + writes ~/.codex/config.toml accordingly. Shape mirrors the
claude-code template's provider registry / _resolve_provider /
_project_vendor_auth pattern (PR template-claude-code#24), adapted to
codex's file-based config (config.toml + auth.json) rather than the
Anthropic SDK's env-var contract.
Three auth modes supported in the registry:
- chatgpt_subscription (CODEX_AUTH_JSON / CODEX_CHATGPT_AUTH_JSON)
- openai_api (OPENAI_API_KEY)
- openai_compat_responses (third-party Responses-API endpoints +
vendor env key, e.g. MiniMax token-plan)
Resolution honors the verified prod precedence from PR#11: when a
subscription credential is present it wins over a model-prefix match,
so prod-Reviewer / prod-Researcher workspaces with both CODEX_AUTH_JSON
and MINIMAX_API_KEY set continue to route through the subscription
(NO model_provider override → built-in OpenAI/Responses path). The
verified gpt-5.5 + 5.4 + 5.3-codex roster is preserved unchanged.
Backward compat: codex_minimax_config.sh stays in the image as a
fallback for one release so external ops scripts + the existing test
fixtures that exec it directly keep working; start.sh prefers the new
python helper when available.
Adding a new codex-compatible provider is now a one-entry config.yaml
edit instead of a code change in the boot scripts.
Tests:
- tests/test_provider_abstraction.py (new, 16 cases) — registry load,
resolution precedence (subscription / explicit / model-prefix /
credential-aware), render output for each auth mode, idempotent
write + stale-override cleanup, fail-closed on misconfigured entry.
- tests/test_modernization_pr1.py updated: roster assertion relaxed
from equality to "verified OpenAI set is a subset of the model
list" (the multi-provider abstraction adds third-party entries
alongside the OpenAI roster); per-model required_env validated
against the providers registry rather than hardcoded to
OPENAI_API_KEY.
Prior art:
- template-claude-code config.yaml providers registry +
adapter.py _load_providers / _resolve_provider / _project_vendor_auth
- OpenClaw multi-provider routing (internal#440)
- PR#11 subscription-wins-over-minimax precedence (internal#513)
Tracks internal#514. NOT a fix for the MiniMax Chat-vs-Responses
incompatibility on CLI 0.130 — that remains a downstream-vendor gap
and is now data in the YAML registry (wire_api: responses) so a future
shim flips a YAML field rather than touching boot scripts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
105 lines
3.5 KiB
Python
105 lines
3.5 KiB
Python
"""CLI entry point — render ``~/.codex/config.toml`` from the providers registry.
|
|
|
|
Invoked from ``start.sh`` (replacing the old hardcoded
|
|
``codex_minimax_config.sh`` invocation). The actual logic lives in
|
|
``provider_config.py`` so the adapter and the boot script share one
|
|
implementation; this file is just an ``argparse``-free wrapper that
|
|
loads the YAML registry, resolves the provider against the current
|
|
env, and writes the config.toml.
|
|
|
|
Exit codes:
|
|
0 — wrote a config.toml (compat provider with model_provider override).
|
|
0 — wrote nothing (built-in OpenAI mode; codex uses its native default).
|
|
2 — registry / env misconfig (raised ValueError); we print the message
|
|
so ``start.sh`` can surface it to ``docker logs`` and the operator.
|
|
|
|
Usage:
|
|
python3 render_provider_toml.py # auto-resolve
|
|
python3 render_provider_toml.py --provider X # explicit provider
|
|
python3 render_provider_toml.py --model Y # explicit model
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Allow running from /usr/local/bin or /app via either copy.
|
|
_HERE = Path(__file__).resolve().parent
|
|
if str(_HERE) not in sys.path:
|
|
sys.path.insert(0, str(_HERE))
|
|
|
|
|
|
def main() -> int:
|
|
logging.basicConfig(
|
|
level=os.environ.get("CODEX_PROVIDER_LOG_LEVEL", "INFO"),
|
|
format="[codex-provider] %(message)s",
|
|
)
|
|
parser = argparse.ArgumentParser(
|
|
description="Render ~/.codex/config.toml from the providers registry"
|
|
)
|
|
parser.add_argument("--provider", default="", help="explicit provider name")
|
|
parser.add_argument("--model", default="", help="explicit model id")
|
|
parser.add_argument(
|
|
"--codex-home",
|
|
default=os.environ.get("CODEX_HOME") or "",
|
|
help="override $CODEX_HOME (default: $CODEX_HOME or ~/.codex)",
|
|
)
|
|
parser.add_argument(
|
|
"--workspace-config",
|
|
default=os.environ.get("WORKSPACE_CONFIG_PATH", "/configs"),
|
|
help="workspace config dir (for the per-workspace YAML fallback)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
from provider_config import (
|
|
load_providers, resolve_provider, write_config_toml,
|
|
)
|
|
except ImportError as exc:
|
|
print(f"[codex-provider] FATAL: provider_config import failed: {exc}",
|
|
file=sys.stderr)
|
|
return 2
|
|
|
|
providers = load_providers(workspace_config_path=args.workspace_config)
|
|
explicit_provider = args.provider or os.environ.get("MODEL_PROVIDER", "")
|
|
model = args.model or os.environ.get("MODEL", "")
|
|
|
|
try:
|
|
picked = resolve_provider(
|
|
model or None, providers,
|
|
explicit_provider=explicit_provider or None,
|
|
)
|
|
except ValueError as exc:
|
|
print(f"[codex-provider] {exc}", file=sys.stderr)
|
|
return 2
|
|
|
|
try:
|
|
written = write_config_toml(
|
|
picked,
|
|
model=model or None,
|
|
codex_home=args.codex_home or None,
|
|
)
|
|
except ValueError as exc:
|
|
print(f"[codex-provider] {exc}", file=sys.stderr)
|
|
return 2
|
|
|
|
if written:
|
|
print(
|
|
f"[codex-provider] wrote {written} provider={picked['name']} "
|
|
f"auth_mode={picked['auth_mode']}"
|
|
)
|
|
else:
|
|
print(
|
|
f"[codex-provider] no config.toml override needed "
|
|
f"(provider={picked['name']} auth_mode={picked['auth_mode']}); "
|
|
"codex will use its built-in OpenAI/Responses path"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|