English version: README.md
这个仓库当前的定位,是一个最小化的多 session Codex gateway,用于验证 codex app-server 能不能通过 Rust HTTP/SSE 服务对外暴露。
架构说明: docs/architecture.md
API 文档: docs/api.md
整体链路是:
- 外部客户端调用 Rust HTTP API
- Rust 服务为每个 session 创建一个 Codex bridge
- 每个 bridge 启动一个自己的本地
codex app-server子进程 codex app-server的通知通过 SSE 回推给对应 session 的客户端
官方参考:
rust-src/main.rs:Rust HTTP 服务本体,提供 session API、SSE、健康检查和静态文件rust-src/bridge.rs:协议桥接层,负责initialize、account/read、model/list、thread/start、turn/start和通知处理rust-src/runtime.rs:运行时辅助模块,负责 API key 登录和openai_base_url覆盖rust-src/session_manager.rs:多 session 生命周期管理,包含 TTL 回收rust-src/cli.rs:单次 CLI 冒烟验证public/index.html:最小化 Web UIpublic/app.js:浏览器端逻辑,会自动创建自己的 session 并订阅自己的 SSEpublic/styles.css:样式Dockerfile:多阶段容器镜像,负责构建 Rust 二进制并安装 Codex CLI
现在已经不是单个全局共享会话了。
POST /api/sessions会创建一个新 session- 每个 session 拥有一个自己的
CodexAppServerBridge - 每个 bridge 拥有一个自己的
codex app-server子进程 /state、/events、/turn、/thread/new都是 session 级接口- session 会在空闲超时后自动清理,也可以手动
DELETE /api/sessions/:id
这意味着多个调用方不会再共用同一个 thread 或 transcript。
GET /healthzGET /readyz
POST /api/sessions- 请求体:
{ "model": "可选模型 ID" } - 返回:
{ ok, sessionId, session, state }
- 请求体:
GET /api/sessions/:id/state- 返回 session 信息和当前 bridge 状态快照
GET /api/sessions/:id/events- 只属于该 session 的 SSE 流
POST /api/sessions/:id/turn- 请求体:
{ "prompt": "..." }
- 请求体:
POST /api/sessions/:id/turn/interrupt- 请求停止当前正在运行的 turn,同时保留 session
POST /api/sessions/:id/thread/new- 请求体:
{ "model": "可选模型 ID" }
- 请求体:
DELETE /api/sessions/:id- 关闭该 session 及其子进程
codex app-server会以sandbox_mode="danger-full-access"和approval_policy="never"启动- 如果旧版或新版 approval request 仍然出现,Gateway 会自动接受
- dynamic tool call 会收到结构化 tool result;不支持的 tool 会显式失败,而不是把 turn 卡住
- 仍然依赖交互式 UI 的 server 发起请求会被显式拒绝或取消
- session 状态只存在内存里,不持久化
- gateway 鉴权是可选的,只有设置
CODEX_GATEWAY_JWT_SECRET时才会开启 - 单个 session 同一时间只能有一个 active turn
- 正在运行的 turn 可以被 interrupt,不需要删除整个 session
启动服务:
CODEX_GATEWAY_OPENAI_API_KEY=sk-... \
CODEX_GATEWAY_OPENAI_BASE_URL=https://sub2api-xnldrpuk.usw-1.sealos.app \
CODEX_GATEWAY_JWT_SECRET=replace-with-your-hs256-secret \
cargo run --bin codex-gateway打开:
http://127.0.0.1:1317
页面会优先恢复上一次仍然存活的 session,失败时尝试恢复上一次的 thread,并自动连接它自己的 SSE 流。开启 JWT 鉴权后,需要先在侧边栏的 Auth 输入框里填入 Bearer token。
cargo run --bin codex-gateway-cli --或自定义 prompt:
cargo run --bin codex-gateway-cli -- "Reply with exactly the single word ready."如果你要快速验证这个项目能不能跑,建议按这个顺序:
- 执行
cargo run --bin codex-gateway - 访问
http://127.0.0.1:1317/healthz - 访问
http://127.0.0.1:1317/readyz - 打开
http://127.0.0.1:1317 - 等页面里的
Status变成ready - 发送
Reply with exactly the single word ready. Do not call tools. - 确认 Transcript 里出现
ready
如果你想直接验证 API,而不是页面:
创建 session:
curl -X POST http://127.0.0.1:1317/api/sessions \
-H 'Content-Type: application/json' \
-d '{}'发送 turn:
curl -X POST http://127.0.0.1:1317/api/sessions/<SESSION_ID>/turn \
-H 'Content-Type: application/json' \
-d '{"prompt":"Reply with exactly the single word ready. Do not call tools."}'查看状态:
curl http://127.0.0.1:1317/api/sessions/<SESSION_ID>/state如果 transcript 里出现 ready,就说明 gateway、bridge 和 codex app-server 之间的链路已经跑通。
gateway 自有配置统一使用 CODEX_GATEWAY_ 前缀,便于和 Codex CLI 原生变量区分。
CODEX_GATEWAY_HOST:Rust 服务监听地址,默认0.0.0.0CODEX_GATEWAY_PORT:监听端口,默认1317CODEX_GATEWAY_CWD:传给thread/start的工作目录,默认仓库根目录CODEX_GATEWAY_CODEX_BIN:codex可执行文件路径,默认从PATH查找CODEX_GATEWAY_MODEL:新 bridge 默认模型CODEX_GATEWAY_OPENAI_API_KEY:启动时用于执行codex login --with-api-key的 API keyCODEX_GATEWAY_OPENAI_BASE_URL:推荐使用的上游 OpenAI-compatiblebase_url。设置后,gateway 会把它配置成一个关闭 websocket 的自定义 Codex providerCODEX_GATEWAY_MAX_SESSIONS:最大同时在线 session 数,默认12CODEX_GATEWAY_SESSION_TTL_MS:空闲 session TTL,默认1800000CODEX_GATEWAY_SESSION_SWEEP_INTERVAL_MS:清理扫描间隔,默认60000CODEX_GATEWAY_CODEX_HOME:Codex 运行目录,包含认证缓存、日志、历史和配置;Docker 默认值是/codex-homeCODEX_GATEWAY_DEBUG:设为1时输出原始 bridge 消息,便于调试CODEX_GATEWAY_JWT_SECRET:可选的 HS256 JWT secret。设置后,除了/healthz和/readyz外,其他路由都需要合法的 Bearer token
容器镜像会先构建 Rust gateway 二进制,再通过 npm install -g @openai/codex 在 Linux 中安装 Codex CLI,这和官方 Quickstart 一致。
构建镜像:
docker build -t codex-gateway .运行容器:
docker run --rm \
-p 1317:1317 \
-e CODEX_GATEWAY_OPENAI_API_KEY=sk-... \
-e CODEX_GATEWAY_OPENAI_BASE_URL=https://sub2api-xnldrpuk.usw-1.sealos.app \
-e CODEX_GATEWAY_JWT_SECRET=replace-with-your-hs256-secret \
-e CODEX_GATEWAY_HOST=0.0.0.0 \
-e CODEX_GATEWAY_PORT=1317 \
-e CODEX_GATEWAY_MAX_SESSIONS=8 \
codex-gateway说明:
- 如果设置了
CODEX_GATEWAY_OPENAI_API_KEY,容器会在启动 gateway 前自动执行codex login --with-api-key CODEX_GATEWAY_OPENAI_BASE_URL是把 Codex 指向第三方 OpenAI-compatible endpoint 的推荐方式;gateway 会把它映射成自定义 provider,而不是内建openaiprovider- 如果设置了
CODEX_GATEWAY_JWT_SECRET,普通 HTTP 请求需要带Authorization: Bearer <jwt>;内置 Web UI 也支持在侧边栏直接填写 token - 普通 API key 启动不需要挂载
CODEX_GATEWAY_CODEX_HOME;只有在你希望容器重启后保留 Codex 状态时才需要挂载 - 如果要让 Codex 在容器里操作别的工作目录,需要同时设置
CODEX_GATEWAY_CWD并挂载对应路径 - 这是 PoC 部署方式,不是生产加固版本
- 容器启动后,验证方法和本地运行时完全一样
GitHub Actions 可以在推送到 main 以及版本 tag(例如 v0.5.1)后,把镜像发布到 GHCR。
发布出来的 tag 规则:
ghcr.io/labring/codex-gateway:main表示当前main分支最新镜像ghcr.io/labring/codex-gateway:sha-<commit>表示每次发布对应的提交镜像- 推送版本 tag 时,会额外发布
v0.5.1、0.5.1、0.5、0和latest
拉取当前 main 镜像:
docker pull ghcr.io/labring/codex-gateway:main运行方式和本地构建镜像一致:
docker run --rm \
-p 1317:1317 \
-e CODEX_GATEWAY_OPENAI_API_KEY=sk-... \
-e CODEX_GATEWAY_OPENAI_BASE_URL=https://sub2api-xnldrpuk.usw-1.sealos.app \
-e CODEX_GATEWAY_HOST=0.0.0.0 \
-e CODEX_GATEWAY_PORT=1317 \
-e CODEX_GATEWAY_MAX_SESSIONS=8 \
ghcr.io/labring/codex-gateway:main如果包可见性是私有的,拉取前需要先登录 GHCR。
- 没有内建限流
- 没有持久化 session
- 没有审批 UI,因为当前 Gateway 默认使用最高权限
- 每个活跃 session 都会占用一个
codex app-server子进程 - 浏览器可以通过保存
sessionId和threadId做刷新恢复,但 gateway session 本身仍然只存在内存里