119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
||
"""修复龙虾U盘AI (simpleapi.quwanzhi.com) 502:禁用 auto 模型,固定 gpt-5.4。"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import shutil
|
||
import subprocess
|
||
import time
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
|
||
CODEX_HOME = Path.home() / ".codex"
|
||
COCKPIT = Path.home() / ".antigravity_cockpit"
|
||
APIKEY_ID = "codex_apikey_5df7aea89c1abb33b2043a4d3cad9071"
|
||
PROVIDER = "codex_local_access"
|
||
MODEL = "gpt-5.4" # simpleapi /responses:auto→502,gpt-5.4/5.5 可用
|
||
PROXY_BASE = "http://127.0.0.1:63849/v1" # 本地代理把 auto 改写为 gpt-5.4
|
||
|
||
|
||
def backup(path: Path) -> None:
|
||
if path.exists():
|
||
shutil.copy2(path, path.with_name(f"{path.name}.bak.{datetime.now().strftime('%Y%m%d%H%M%S')}"))
|
||
|
||
|
||
def load_api_key() -> tuple[str, str]:
|
||
acct = json.loads((COCKPIT / "codex_accounts" / f"{APIKEY_ID}.json").read_text(encoding="utf-8"))
|
||
key = acct.get("openai_api_key") or ""
|
||
base = (acct.get("api_base_url") or "").rstrip("/")
|
||
if not key or not base:
|
||
raise SystemExit("Cockpit 龙虾账号缺少 openai_api_key 或 api_base_url")
|
||
return key, base
|
||
|
||
|
||
def apply_config(api_key: str, base_url: str) -> None:
|
||
cfg = CODEX_HOME / "config.toml"
|
||
text = cfg.read_text(encoding="utf-8") if cfg.exists() else ""
|
||
lines = text.splitlines()
|
||
out, skip = [], False
|
||
for line in lines:
|
||
s = line.strip()
|
||
if s.startswith("model_provider = "):
|
||
continue
|
||
if s.startswith("model = "):
|
||
out.append(f'model = "{MODEL}"')
|
||
continue
|
||
if s.startswith("[model_providers."):
|
||
skip = True
|
||
continue
|
||
if skip:
|
||
if s.startswith("[") and not s.startswith("[model_providers."):
|
||
skip = False
|
||
out.append(line)
|
||
continue
|
||
out.append(line)
|
||
while out and not out[-1].strip():
|
||
out.pop()
|
||
|
||
provider_blocks = [
|
||
"",
|
||
f"[model_providers.{PROVIDER}]",
|
||
'name = "龙虾U盘AI"',
|
||
f'base_url = "{PROXY_BASE}" # lobster proxy: auto->gpt-5.4',
|
||
'wire_api = "responses"',
|
||
"requires_openai_auth = false",
|
||
"supports_websockets = false",
|
||
f'experimental_bearer_token = "{api_key}"',
|
||
"",
|
||
]
|
||
|
||
# model_provider 必须写在顶层(紧挨 model/notify),禁止落在 [features] 等表内
|
||
inserted = False
|
||
final: list[str] = []
|
||
for line in out:
|
||
final.append(line)
|
||
if not inserted and (
|
||
line.startswith("notify = ")
|
||
or (line.startswith("sandbox_mode = ") and "model_provider" not in "\n".join(final))
|
||
):
|
||
final.append(f'model_provider = "{PROVIDER}"')
|
||
inserted = True
|
||
if not inserted:
|
||
# 回退:插在第一个 [section] 之前
|
||
idx = next((i for i, l in enumerate(final) if l.startswith("[")), len(final))
|
||
final.insert(idx, f'model_provider = "{PROVIDER}"')
|
||
final.insert(idx, "")
|
||
final.extend(provider_blocks)
|
||
|
||
backup(cfg)
|
||
cfg.write_text("\n".join(final) + "\n", encoding="utf-8")
|
||
|
||
auth = CODEX_HOME / "auth.json"
|
||
backup(auth)
|
||
auth.write_text(
|
||
json.dumps({"auth_mode": "apikey", "OPENAI_API_KEY": api_key}, ensure_ascii=False, indent=2) + "\n",
|
||
encoding="utf-8",
|
||
)
|
||
|
||
|
||
def restart_codex(api_key: str) -> None:
|
||
cli = Path("/Applications/Codex.app/Contents/Resources/codex")
|
||
if cli.exists():
|
||
subprocess.run([str(cli), "logout"], check=False, capture_output=True)
|
||
subprocess.run([str(cli), "login", "--with-api-key"], input=api_key.encode(), check=False, capture_output=True)
|
||
subprocess.run(["killall", "Codex"], check=False)
|
||
time.sleep(1.5)
|
||
subprocess.run(["/usr/bin/open", "-a", "Codex"], check=True)
|
||
|
||
|
||
def main() -> None:
|
||
api_key, base_url = load_api_key()
|
||
apply_config(api_key, base_url)
|
||
restart_codex(api_key)
|
||
print(f"[OK] 龙虾U盘AI 已配置 model={MODEL} provider={PROVIDER} base={base_url}")
|
||
print("[WARN] Codex 内勿选 auto 模型,请用 gpt-5.4 或 gpt-5.5")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|