Spaces:
Sleeping
Sleeping
| """Installed `fcc-codex` launcher.""" | |
| import json | |
| import os | |
| import sys | |
| from collections.abc import Mapping, Sequence | |
| from urllib.request import Request | |
| from free_claude_code.cli.local_http import ( | |
| open_local_request, | |
| with_local_proxy_bypass, | |
| ) | |
| from free_claude_code.cli.proxy_auth import proxy_auth_token | |
| from free_claude_code.config.paths import codex_model_catalog_path | |
| from free_claude_code.config.server_urls import local_proxy_root_url | |
| from free_claude_code.config.settings import Settings, get_settings | |
| from .codex_model_catalog import build_codex_model_catalog, write_codex_model_catalog | |
| from .common import ( | |
| PROXY_PREFLIGHT_TIMEOUT_SECONDS, | |
| preflight_proxy, | |
| resolve_client_binary, | |
| run_client_process, | |
| ) | |
| _CODEX_AUTH_ENV_KEY = "FCC_CODEX_API_KEY" | |
| _DISPLAY_NAME = "Codex CLI" | |
| _DEFAULT_BINARY = "codex" | |
| _INSTALL_HINT = "Install Codex with: npm install -g @openai/codex" | |
| # Preserve CODEX_HOME: it owns durable user configuration, not parent-task identity. | |
| _STRIPPED_CODEX_ENV_KEYS = frozenset( | |
| { | |
| "OPENAI_API_KEY", | |
| "OPENAI_BASE_URL", | |
| "OPENAI_API_BASE", | |
| "OPENAI_ORG_ID", | |
| "OPENAI_ORGANIZATION", | |
| "CODEX_API_KEY", | |
| "CODEX_INTERNAL_ORIGINATOR_OVERRIDE", | |
| "CODEX_PERMISSION_PROFILE", | |
| "CODEX_SHELL", | |
| "CODEX_THREAD_ID", | |
| _CODEX_AUTH_ENV_KEY, | |
| } | |
| ) | |
| def launch(argv: Sequence[str] | None = None) -> None: | |
| """Launch Codex CLI with Free Claude Code proxy configuration.""" | |
| settings = get_settings() | |
| proxy_root_url = local_proxy_root_url(settings) | |
| if error := preflight_proxy(proxy_root_url): | |
| print( | |
| f"Free Claude Code proxy is not reachable at {proxy_root_url}: {error}", | |
| file=sys.stderr, | |
| ) | |
| print("Start it in another terminal with: fcc-server", file=sys.stderr) | |
| raise SystemExit(1) | |
| binary_name = codex_binary_name() | |
| binary_path = resolve_client_binary( | |
| binary_name=binary_name, | |
| display_name=_DISPLAY_NAME, | |
| install_hint=_INSTALL_HINT, | |
| ) | |
| catalog_args = codex_model_catalog_config_args(proxy_root_url, settings) | |
| args = list(sys.argv[1:] if argv is None else argv) | |
| run_client_process( | |
| command=build_codex_launcher_command( | |
| binary_path=binary_path, | |
| argv=args, | |
| settings=settings, | |
| proxy_root_url=proxy_root_url, | |
| catalog_config_args=catalog_args, | |
| ), | |
| env=build_codex_launcher_env( | |
| proxy_root_url=proxy_root_url, | |
| auth_token=settings.anthropic_auth_token, | |
| base_env=os.environ, | |
| ), | |
| binary_name=binary_name, | |
| display_name=_DISPLAY_NAME, | |
| install_hint=_INSTALL_HINT, | |
| ) | |
| def codex_binary_name() -> str: | |
| """Return the Codex CLI binary name.""" | |
| return _DEFAULT_BINARY | |
| def build_codex_launcher_command( | |
| *, | |
| binary_path: str, | |
| argv: Sequence[str], | |
| settings: Settings, | |
| proxy_root_url: str, | |
| catalog_config_args: Sequence[str] = (), | |
| ) -> list[str]: | |
| """Return a Codex command with ephemeral FCC provider config.""" | |
| return [ | |
| binary_path, | |
| *catalog_config_args, | |
| *codex_config_args( | |
| api_url=_ensure_v1_url(proxy_root_url), | |
| model=getattr(settings, "model", None), | |
| ), | |
| *argv, | |
| ] | |
| def build_codex_launcher_env( | |
| *, | |
| proxy_root_url: str, | |
| auth_token: str, | |
| base_env: Mapping[str, str], | |
| ) -> dict[str, str]: | |
| """Return a Codex environment that targets the local proxy provider.""" | |
| env = with_local_proxy_bypass( | |
| { | |
| key: value | |
| for key, value in base_env.items() | |
| if key not in _STRIPPED_CODEX_ENV_KEYS and not key.startswith("OPENAI_") | |
| }, | |
| proxy_root_url=proxy_root_url, | |
| ) | |
| env[_CODEX_AUTH_ENV_KEY] = proxy_auth_token(auth_token) | |
| return env | |
| def codex_model_catalog_config_args( | |
| proxy_root_url: str, settings: Settings | |
| ) -> list[str]: | |
| """Prepare the generated Codex model catalog and return its config args.""" | |
| try: | |
| models_response = fetch_proxy_models_response( | |
| proxy_root_url, settings.anthropic_auth_token | |
| ) | |
| catalog = build_codex_model_catalog(models_response) | |
| models = catalog.get("models") | |
| if not isinstance(models, list) or not models: | |
| print( | |
| "Free Claude Code warning: Codex model catalog is empty; " | |
| "launching without model picker catalog.", | |
| file=sys.stderr, | |
| ) | |
| return [] | |
| catalog_path = codex_model_catalog_path() | |
| write_codex_model_catalog(catalog_path, catalog) | |
| except Exception as exc: | |
| print( | |
| "Free Claude Code warning: could not prepare Codex model catalog " | |
| f"({exc}); launching without model picker catalog.", | |
| file=sys.stderr, | |
| ) | |
| return [] | |
| return build_model_catalog_config_args(str(catalog_path)) | |
| def fetch_proxy_models_response( | |
| proxy_root_url: str, auth_token: str | |
| ) -> dict[str, object]: | |
| """Fetch the local proxy `/v1/models` response for Codex catalog generation.""" | |
| url = f"{proxy_root_url.rstrip('/')}/v1/models" | |
| headers: dict[str, str] = {} | |
| if token := auth_token.strip(): | |
| headers["Authorization"] = f"Bearer {token}" | |
| request = Request(url, headers=headers, method="GET") | |
| with open_local_request( | |
| request, timeout=PROXY_PREFLIGHT_TIMEOUT_SECONDS | |
| ) as response: | |
| payload = json.loads(response.read().decode("utf-8")) | |
| if not isinstance(payload, dict): | |
| raise ValueError("model list response was not a JSON object") | |
| return payload | |
| def build_model_catalog_config_args(catalog_path: str) -> list[str]: | |
| """Return Codex config args for a generated model catalog.""" | |
| return ["-c", _toml_assignment("model_catalog_json", catalog_path)] | |
| def codex_config_args(*, api_url: str, model: str | None = None) -> list[str]: | |
| """Return Codex `-c` assignments for the ephemeral FCC provider.""" | |
| args = [ | |
| "-c", | |
| _toml_assignment("model_provider", "fcc"), | |
| "-c", | |
| _toml_assignment("model_providers.fcc.name", "Free Claude Code"), | |
| "-c", | |
| _toml_assignment("model_providers.fcc.base_url", _ensure_v1_url(api_url)), | |
| "-c", | |
| _toml_assignment("model_providers.fcc.env_key", _CODEX_AUTH_ENV_KEY), | |
| "-c", | |
| _toml_assignment("model_providers.fcc.wire_api", "responses"), | |
| ] | |
| if model: | |
| args.extend(["-c", _toml_assignment("model", model)]) | |
| return args | |
| def _ensure_v1_url(url: str) -> str: | |
| stripped = url.rstrip("/") | |
| return stripped if stripped.endswith("/v1") else f"{stripped}/v1" | |
| def _toml_assignment(key: str, value: str) -> str: | |
| return f"{key}={json.dumps(value)}" | |