feat: wire native Codex UI as /console entry, keep classic console at /console-classic
ci / test (push) Successful in 45m25s
ci / test (push) Successful in 45m25s
- Vendor codex-ui-stack host/ as cloud/codex-ui-host/ (original/ not vendored, 650MB) - Bridge emulates 7 methods missing in codex 0.164.0-alpha.9, exposes 29 experimental-protocol methods (150 -> 179), Linux binary candidates, read-only POST /__codex_git - GET /console 302s to the UI host (:8095) when reachable, else falls back to the classic React console; GET /console-classic always serves it - Alignment tables: docs/codex-native-ui-alignment.md - Tests: test_console 7, new test_codex_ui_host 4, handler method pin 247 -> 249
This commit is contained in:
1 parent
41f3985248
commit
3e0ff898e0
20 files changed
+3018
-24
No files matched your search
+56
-12
@@ -54,6 +54,7 @@ from collections import deque
|
||||
from decimal import Decimal
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
import urllib.request
|
||||
|
||||
from cloud.agent_def import default_agents_dir as _agent_default_dir
|
||||
from cloud.agent_def import list_known_skills as _agent_known_skills
|
||||
@@ -1042,6 +1043,28 @@ def _db_backend_name():
|
||||
return (os.environ.get("CLOUD_DB_BACKEND") or "sqlite").strip().lower()
|
||||
|
||||
|
||||
def _codex_ui_url():
|
||||
"""原生 UI host 地址:环境变量 CODEX_UI_URL,未设则默认本机 :8095。"""
|
||||
return os.environ.get("CODEX_UI_URL") or "http://127.0.0.1:8095/"
|
||||
|
||||
|
||||
def _codex_ui_reachable():
|
||||
"""探测 UI host 是否可达:GET <CODEX_UI_URL 的 origin>/,2 秒超时。
|
||||
|
||||
任一异常(连接失败、超时、DNS 失败等)或非 200 一律视为不可达,
|
||||
调用方回落旧控制台;探测失败绝不影响 /console 本身的响应。"""
|
||||
try:
|
||||
parsed = urlparse(_codex_ui_url())
|
||||
if not parsed.netloc:
|
||||
return False
|
||||
probe = "{}://{}/".format(parsed.scheme or "http", parsed.netloc)
|
||||
request = urllib.request.Request(probe, method="GET")
|
||||
with urllib.request.urlopen(request, timeout=2) as resp:
|
||||
return resp.status == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _deployed_commit():
|
||||
"""当前上线代码的 commit:deploy.py 部署时在包根写的 DEPLOYED_COMMIT。
|
||||
|
||||
@@ -2329,6 +2352,29 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
self._responded = True
|
||||
self.wfile.write(body)
|
||||
|
||||
def _send_redirect(self, location):
|
||||
self.send_response(302)
|
||||
self.send_header("Location", location)
|
||||
self.send_header("Content-Length", "0")
|
||||
self.end_headers()
|
||||
# 发头即算已响应(与 _send_json/_send_html 同款约定)。
|
||||
self._responded = True
|
||||
|
||||
def _serve_console_classic(self):
|
||||
# 旧 React 控制台(cloud/console.html):只出静态壳,不走鉴权
|
||||
# 与限流(与 /health 同款 pre-auth 待遇)。壳里没有任何数据;
|
||||
# 数据一律凭用户在浏览器里填的 X-API-Key 调接口,key 只存在
|
||||
# 用户浏览器 localStorage。页面文件与 server.py 同目录,改动
|
||||
# 随部署走。/console(UI host 不可达时回落)与 /console-classic
|
||||
# 共用本方法,不要复制两份读文件代码。
|
||||
try:
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
with open(os.path.join(here, "console.html"),
|
||||
"r", encoding="utf-8") as f:
|
||||
self._send_html(200, f.read())
|
||||
except OSError:
|
||||
self._send_json(404, {"error": "console not found"})
|
||||
|
||||
def _read_body(self):
|
||||
try:
|
||||
length = int(self.headers.get("Content-Length") or 0)
|
||||
@@ -2616,18 +2662,16 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
"commit": _deployed_commit(),
|
||||
})
|
||||
return
|
||||
if path == "/console" and method == "GET":
|
||||
# 命令台 v1:只出静态壳,不走鉴权与限流(与 /health 同款
|
||||
# pre-auth 待遇)。壳里没有任何数据;数据一律凭用户在
|
||||
# 浏览器里填的 X-API-Key 调接口,key 只存在用户浏览器
|
||||
# localStorage。页面文件与 server.py 同目录,改动随部署走。
|
||||
try:
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
with open(os.path.join(here, "console.html"),
|
||||
"r", encoding="utf-8") as f:
|
||||
self._send_html(200, f.read())
|
||||
except OSError:
|
||||
self._send_json(404, {"error": "console not found"})
|
||||
if path in ("/console", "/console-classic") and method == "GET":
|
||||
# /console:原生 UI 入口(见 docs/codex-native-ui-alignment.md
|
||||
# §3):UI host 可达时 302 到 CODEX_UI_URL(默认
|
||||
# http://127.0.0.1:8095/),不可达时回落旧控制台;
|
||||
# /console-classic 永远 serve 旧控制台。两者都不走鉴权与
|
||||
# 限流(与 /health 同款 pre-auth 待遇)。
|
||||
if path == "/console" and _codex_ui_reachable():
|
||||
self._send_redirect(_codex_ui_url())
|
||||
else:
|
||||
self._serve_console_classic()
|
||||
return
|
||||
if path.startswith("/oauth/"):
|
||||
# OAuth 登录/回调是 pre-auth 端点(用户还没本站 API key,
|
||||
|
||||
Reference in new issue
Block a user