Spaces:
Sleeping
Sleeping
File size: 6,847 Bytes
2415446 0a54372 2415446 0a54372 2415446 0a54372 2415446 0a54372 2415446 0a54372 2415446 0a54372 2415446 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | """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)}"
|