Skip to content

Commit 22a8827

Browse files
ZD Studiosclaude
andcommitted
feat: Codex as an OpenAI-compatible API — use your ChatGPT subscription
services/codex_api.py is the mirror image of claude-code-api: it wraps the local `codex` CLI so anything speaking the OpenAI chat API can drive your ChatGPT/Codex subscription. No API key — auth is whatever `codex login` established. POST /v1/chat/completions GET /v1/models GET /health Wired end to end: a `codex` service on :8010 (aios start codex), a chat target and dashboard chip/panel, an entry in the hub's own /v1/models, the interconnect URL for other agents, and option 2 in the setup wizard next to the Claude subscription — so both subscription routes are offered before any API key is. Three real bugs found by testing it against the live CLI rather than assuming: - Passing a model name that the account can't use makes codex exit 1 with "model is not supported". The bridge now sends no -m at all unless explicitly asked, letting codex use whatever it's configured for. - Codex is a *workspace* agent. Pointed at the AIOS repo with -C it answered every question with "Ready. What would you like me to work on in <repo>?" instead of the question. It now runs in a neutral scratch dir, overridable with AIOS_CODEX_WORKDIR when repo context is actually wanted. - The big one: on Windows `codex` resolves to codex.CMD, and cmd.exe mangles newlines inside an argv argument — so every multi-line prompt (i.e. every real one, since system+user get flattened) silently arrived truncated and produced a generic greeting. The prompt is now piped via stdin with "-", which is correct on both platforms. Uses --output-last-message rather than scraping stdout, because codex streams reasoning, tool calls and token counts to the terminal and the final answer can't be reliably picked out of that. Verified live: healthy on :8010, correct answers to both a factual and a coding question through the bridge, and through the hub after restart — "`zip()` pairs items from multiple iterables into tuples, stopping when the shortest is exhausted." Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a1b5e2b commit 22a8827

6 files changed

Lines changed: 305 additions & 15 deletions

File tree

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# 🧠 The AI OS
44

5-
### Nine open-source AI projects. One operating system. One Control Room.
5+
### Ten open-source AI projects. One operating system. One Control Room.
66

77
<p align="center">
88
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-8B5CF6?style=for-the-badge" alt="MIT License"></a>
@@ -24,6 +24,7 @@
2424
<img src="https://img.shields.io/badge/+_Caveman-say_less-A16207?style=flat-square" alt="Caveman">
2525
<img src="https://img.shields.io/badge/+_Ponytail-build_less-DB2777?style=flat-square" alt="Ponytail">
2626
<img src="https://img.shields.io/badge/+_Ruflo-314_MCP_tools-7C3AED?style=flat-square" alt="Ruflo">
27+
<img src="https://img.shields.io/badge/+_Codex-ChatGPT_sub-10A37F?style=flat-square" alt="Codex">
2728
</p>
2829

2930
**[Install](#-install-one-line)** · **[Control Room](#-the-control-room)** · **[Architecture](#️-architecture)** · **[Commands](#️-command-reference)** · **[Full docs](README.aios.md)** · **[Website](https://zdstudios.github.io/AIOS/)**
@@ -32,7 +33,7 @@
3233

3334
---
3435

35-
**The AI OS** takes nine independent open-source AI projects and makes them work as **one auto-configured
36+
**The AI OS** takes ten independent open-source AI projects and makes them work as **one auto-configured
3637
system** you drive from a single command — **`aios`** — and a single web **Control Room** where you can
3738
**talk to everything and have the agents talk to each other**.
3839

@@ -42,7 +43,7 @@ together, mounts your shared skills, and adds **OpenUI** generative-UI context t
4243

4344
> You can't fuse three runtimes and multiple package managers into a single file — that would just break
4445
> everything. So "one file" here means **one control surface** (`aios`) + **one dashboard** (the Hub) over
45-
> nine real, unmodified projects. It installs, configures, wires, runs, tests, and debugs all of them.
46+
> ten real, unmodified projects. It installs, configures, wires, runs, tests, and debugs all of them.
4647
4748
<div align="center">
4849

@@ -115,6 +116,7 @@ aios url # open the Control Room
115116
| **openclaw** | Multi-channel messaging gateway + plugin host | `18789` | [openclaw.ai](https://github.com/openclaw/openclaw) |
116117
| **CrewAI** | Multi-agent orchestration — role-based crews | `4788` | [crewaiinc/crewai](https://github.com/crewaiinc/crewai) |
117118
| **claude-code** | Claude Code as an OpenAI-compatible API | `8000` | [codingworkflow/claude-code-api](https://github.com/codingworkflow/claude-code-api) |
119+
| **Codex** | OpenAI Codex CLI as an OpenAI-compatible API — your ChatGPT sub, no API key | `8010` | [openai/codex](https://github.com/openai/codex) |
118120
| **LifeOS** | Shared identity + skills mounted into the agents || [danielmiessler/LifeOS](https://github.com/danielmiessler/LifeOS) |
119121

120122
Plus the integrations that glue it together — these aren't services, they're mounted into every agent:

aios.config.example.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ services:
2929
claudecode: # claude-code-api — Claude Code as an OpenAI-compatible API (6th agent)
3030
enabled: true
3131
port: 8000
32+
codex: # ChatGPT/Codex via the local `codex` CLI (no API key)
33+
enabled: true
34+
port: 8010
35+
model: "" # blank = whatever codex itself is configured to use
36+
sandbox: read-only # read-only | workspace-write | danger-full-access
3237
openclaw_proxy: # frame-stripping proxy so openclaw's control-UI embeds in the hub
3338
enabled: true
3439
port: 8791
@@ -100,6 +105,7 @@ health:
100105
openclaw: http://127.0.0.1:18789/
101106
crewai: http://127.0.0.1:4788/health
102107
claudecode: http://127.0.0.1:8000/health
108+
codex: http://127.0.0.1:8010/health
103109
openclaw_proxy: http://127.0.0.1:8791/
104110
hermes_proxy: http://127.0.0.1:8792/
105111
hub: http://127.0.0.1:8787/health

aios.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ def child_env(extra: dict | None = None) -> dict:
340340
"gemini": "GEMINI_API_KEY",
341341
# Claude Pro/Max via claude-code-api (OpenAI-compatible; auth is the Claude CLI login).
342342
"claudecode": "OPENAI_API_KEY",
343+
# ChatGPT/Codex via the local codex CLI (auth is `codex login`, no API key).
344+
"codex": "OPENAI_API_KEY",
343345
}
344346
PASSTHROUGH_KEYS = [
345347
"OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY",
@@ -436,6 +438,22 @@ def service_specs(cfg: dict) -> dict:
436438
"health": cfg_get(cfg, "health.claudecode", f"http://127.0.0.1:{port}/health"),
437439
"tool": "uv",
438440
}
441+
# Codex CLI as an OpenAI-compatible API (mirror of claude-code-api). Pure
442+
# stdlib, so it runs on the same interpreter as aios itself.
443+
if cfg_get(cfg, "services.codex.enabled", True):
444+
cxp = int(cfg_get(cfg, "services.codex.port", 8010))
445+
specs["codex"] = {
446+
"enabled": True,
447+
"cwd": ROOT,
448+
"cmd": [sys.executable, str(ROOT / "services" / "codex_api.py")],
449+
"port": cxp,
450+
"health": cfg_get(cfg, "health.codex", f"http://127.0.0.1:{cxp}/health"),
451+
"tool": "python",
452+
"env": {"AIOS_CODEX_PORT": str(cxp),
453+
"AIOS_CODEX_MODEL": str(cfg_get(cfg, "services.codex.model", "")),
454+
"AIOS_CODEX_SANDBOX": str(cfg_get(cfg, "services.codex.sandbox", "read-only"))},
455+
}
456+
439457
# Frame-stripping reverse proxies so openclaw + hermes control-UIs embed in the hub.
440458
if cl and cfg_get(cfg, "services.openclaw_proxy.enabled", True):
441459
pport = int(cfg_get(cfg, "services.openclaw_proxy.port", 8791))
@@ -488,7 +506,7 @@ def service_specs(cfg: dict) -> dict:
488506

489507

490508
START_ORDER = ["opencode", "hermes", "hermes-gateway", "hermes-proxy", "openclaw",
491-
"openclaw-proxy", "crewai", "claudecode", "hub"]
509+
"openclaw-proxy", "crewai", "claudecode", "codex", "hub"]
492510

493511

494512
def openclaw_env() -> dict:
@@ -519,6 +537,7 @@ def interconnect_env(cfg: dict) -> dict:
519537
"AIOS_CREWAI_URL": f"http://127.0.0.1:{crp}",
520538
"AIOS_CREWAI_PORT": str(crp),
521539
"AIOS_CLAUDECODE_URL": f"http://127.0.0.1:{ccp}",
540+
"AIOS_CODEX_URL": f"http://127.0.0.1:{cfg_get(cfg, 'services.codex.port', 8010)}",
522541
"AIOS_OPENCLAWPROXY_URL": f"http://127.0.0.1:{pxp}/",
523542
"AIOS_HERMESPROXY_URL": f"http://127.0.0.1:{hxp}/",
524543
"AIOS_OPENCLAWOS_URL": f"http://127.0.0.1:{clp}/plugins/openclawos/",
@@ -897,15 +916,16 @@ def _wizard_env(args):
897916
elif interactive:
898917
say(f"\n {C.B}How should the agents talk to a model?{C.R}")
899918
say(f" {C.CYN}1{C.R} Claude Pro/Max subscription {C.GRY}— no API key, no per-token cost{C.R}")
900-
say(f" {C.CYN}2{C.R} OpenRouter {C.GRY}— one key, most models{C.R}")
901-
say(f" {C.CYN}3{C.R} Anthropic API key")
902-
say(f" {C.CYN}4{C.R} OpenAI API key")
903-
say(f" {C.CYN}5{C.R} Google Gemini API key")
919+
say(f" {C.CYN}2{C.R} ChatGPT / Codex subscription {C.GRY}— via the local codex CLI{C.R}")
920+
say(f" {C.CYN}3{C.R} OpenRouter {C.GRY}— one key, most models{C.R}")
921+
say(f" {C.CYN}4{C.R} Anthropic API key")
922+
say(f" {C.CYN}5{C.R} OpenAI API key")
923+
say(f" {C.CYN}6{C.R} Google Gemini API key")
904924
say(f" {C.GRY}s{C.R} skip for now")
905-
choice = input(f" Choice [1-5/s] ({data['AIOS_LLM_PROVIDER']}): ").strip().lower()
925+
choice = input(f" Choice [1-6/s] ({data['AIOS_LLM_PROVIDER']}): ").strip().lower()
906926

907-
by_num = {"1": "claude", "2": "openrouter", "3": "anthropic",
908-
"4": "openai", "5": "gemini", "s": "skip"}
927+
by_num = {"1": "claude", "2": "codex", "3": "openrouter", "4": "anthropic",
928+
"5": "openai", "6": "gemini", "s": "skip"}
909929
prov = by_num.get(choice, choice or data["AIOS_LLM_PROVIDER"])
910930

911931
if prov in ("skip", "none", "s"):
@@ -938,6 +958,21 @@ def _wizard_env(args):
938958
warn(f"could not launch the login: {e}")
939959
else:
940960
say(f" {C.GRY}Run `aios claude-login` when you're ready.{C.R}")
961+
elif prov in ("codex", "chatgpt", "openai-codex"):
962+
cxp = cfg_get(load_config(), "services.codex.port", 8010)
963+
data["AIOS_LLM_PROVIDER"] = "codex"
964+
data["AIOS_LLM_BASE_URL"] = f"http://127.0.0.1:{cxp}/v1"
965+
data["AIOS_LLM_API_KEY"] = "sk-aios-codex" # placeholder; auth is the CLI
966+
data["AIOS_DEFAULT_MODEL"] = "codex"
967+
ok("using your ChatGPT/Codex subscription via the codex CLI")
968+
cx = find_tool("codex")
969+
if not cx:
970+
warn("the `codex` CLI isn't installed yet — install it with:")
971+
say(f" {C.B}npm install -g @openai/codex{C.R}")
972+
say(f" {C.GRY}then run `codex login` to authorise your account.{C.R}")
973+
else:
974+
ok(f"found the codex CLI ({cx})")
975+
say(f" {C.GRY}If it isn't logged in yet, run `codex login`.{C.R}")
941976
else:
942977
data["AIOS_LLM_PROVIDER"] = prov
943978
data.pop("AIOS_LLM_BASE_URL", None) # don't leave a stale claude-code URL
@@ -2517,7 +2552,7 @@ def _print_urls(cfg):
25172552
("Agents & updates", ["updates", "update", "claude-login", "ruflo", "attach", "migrate", "wire"]),
25182553
("Advanced", ["test", "debug", "autostart", "install-cli", "bootstrap", "uninstall"]),
25192554
]
2520-
TAGLINE = "Nine open-source AI projects. One operating system."
2555+
TAGLINE = "Ten open-source AI projects. One operating system."
25212556

25222557

25232558
class AiosParser(argparse.ArgumentParser):

aios_hub.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def run_aios(*a, background=False):
101101
"openclaw": os.environ.get("AIOS_OPENCLAW_URL", "http://127.0.0.1:18789"),
102102
"crewai": os.environ.get("AIOS_CREWAI_URL", "http://127.0.0.1:4788"),
103103
"claudecode": os.environ.get("AIOS_CLAUDECODE_URL", "http://127.0.0.1:8000"),
104+
"codex": os.environ.get("AIOS_CODEX_URL", "http://127.0.0.1:8010"),
104105
"openclaw-os": os.environ.get("AIOS_OPENCLAWOS_URL", "http://127.0.0.1:18789/plugins/openclawos/"),
105106
}
106107
CLAUDECODE = os.environ.get("AIOS_CLAUDECODE_URL", "http://127.0.0.1:8000").rstrip("/")
@@ -118,7 +119,7 @@ def read_system_prompt() -> str:
118119

119120

120121
# Chat "targets" the AIOS API exposes as OpenAI-style models.
121-
TARGETS = ["brain", "team", "swarm", "opencode", "crewai", "claudecode", "fabric", "all"]
122+
TARGETS = ["brain", "team", "swarm", "opencode", "crewai", "claudecode", "codex", "fabric", "all"]
122123

123124
MEMORY_FILE = ROOT / ".aios" / "memory.json"
124125
USAGE_FILE = ROOT / ".aios" / "usage.json"
@@ -371,6 +372,24 @@ def ask_opencode(message: str) -> str:
371372
return f"⚠️ opencode error: {e}"
372373

373374

375+
def ask_codex(message: str, history: list | None = None) -> str:
376+
"""Codex CLI through its OpenAI-compatible bridge (services/codex_api.py)."""
377+
base = os.environ.get("AIOS_CODEX_URL", "http://127.0.0.1:8010").rstrip("/")
378+
msgs = (history or []) + [{"role": "user", "content": message}]
379+
try:
380+
d = post_json(base + "/v1/chat/completions",
381+
{"model": "codex", "messages": msgs}, timeout=620)
382+
return (d.get("choices") or [{}])[0].get("message", {}).get("content", "") or "(codex returned nothing)"
383+
except urllib.error.HTTPError as e:
384+
try:
385+
return "⚠️ " + json.loads(e.read()).get("error", {}).get("message", str(e))
386+
except Exception:
387+
return f"⚠️ codex error: {e}"
388+
except Exception as e:
389+
return (f"⚠️ codex-api not reachable ({e}). Start it with `aios start codex`, "
390+
f"and make sure the `codex` CLI is installed and logged in.")
391+
392+
374393
def ask_claudecode(message: str, history: list | None = None) -> str:
375394
"""Call claude-code-api's OpenAI-compatible endpoint."""
376395
msgs = (history or []) + [{"role": "user", "content": message}]
@@ -682,6 +701,9 @@ def route(target: str, message: str, history: list | None = None) -> str:
682701
return ask_opencode(message)
683702
if target in ("claudecode", "claude-code"):
684703
return ask_claudecode(message, history)
704+
if target == "codex":
705+
act("agent", "Handing off to Codex…")
706+
return ask_codex(message, history)
685707
if target == "fabric":
686708
# "pattern: text" → run that fabric pattern; else summarize by default.
687709
pat, _, txt = message.partition(":")
@@ -1184,6 +1206,7 @@ def claude_status() -> dict:
11841206
"opencode": "writing/editing/running code, repos, technical build tasks",
11851207
"crewai": "multi-step research or workflows that benefit from a crew of roles",
11861208
"claudecode": "Claude Code — coding with the Claude Code CLI",
1209+
"codex": "OpenAI Codex — coding via your ChatGPT/Codex subscription",
11871210
}
11881211

11891212

docs/dashboard.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@
236236
<button class="nav" data-v="openclaw" data-svc="openclaw" onclick="nav('openclaw')"><span class="ic">🦞</span> openclaw <span class="dot"></span></button>
237237
<button class="nav" data-v="crewai" data-svc="crewai" onclick="nav('crewai')"><span class="ic">👥</span> CrewAI <span class="dot"></span></button>
238238
<button class="nav" data-v="claudecode" data-svc="claudecode" onclick="nav('claudecode')"><span class="ic"></span> claude-code <span class="dot"></span></button>
239+
<button class="nav" data-v="codex" data-svc="codex" onclick="nav('codex')"><span class="ic"></span> Codex <span class="dot"></span></button>
239240

240241
<div class="sec">Dashboards</div>
241242
<button class="nav" data-v="openclawos" data-svc="openclaw-os" onclick="nav('openclawos')"><span class="ic">🪟</span> openclaw-os <span class="dot"></span></button>
@@ -280,6 +281,7 @@
280281
<button class="chip" data-t="crewai">👥 CrewAI</button>
281282
<button class="chip" data-t="opencode">⌨️ opencode</button>
282283
<button class="chip" data-t="claudecode">◈ claude-code</button>
284+
<button class="chip" data-t="codex" title="OpenAI Codex via your ChatGPT subscription">⌁ Codex</button>
283285
<button class="chip" data-t="swarm" title="Big task: decompose across specialist agents running in parallel">🐝 Swarm</button>
284286
<button class="chip" data-t="fabric" title="Run a Fabric pattern (type: pattern: your text)">🧵 Fabric</button>
285287
<button class="chip" data-t="all">🌐 All</button>
@@ -313,6 +315,8 @@
313315
<!-- INFO AGENTS (opencode / crewai / claude-code) -->
314316
<div id="opencode" class="view"><div class="vhead"><h2>⌨️ opencode</h2><span class="sub">coding-agent engine</span></div><div class="vbody pad" id="opencodeBody"></div></div>
315317
<div id="crewai" class="view"><div class="vhead"><h2>👥 CrewAI</h2><span class="sub">multi-agent crews</span></div><div class="vbody pad" id="crewaiBody"></div></div>
318+
<div id="codex" class="view"><div class="vhead"><h2>⌁ Codex</h2><span class="sub">OpenAI Codex via your ChatGPT subscription</span></div><div class="vbody pad" id="codexBody"></div></div>
319+
316320
<div id="claudecode" class="view"><div class="vhead"><h2>◈ claude-code</h2><span class="sub">Claude Code as an OpenAI-compatible API</span></div><div class="vbody pad" id="claudecodeBody"></div></div>
317321

318322
<!-- CANVAS (generative UI any agent can edit) -->
@@ -669,8 +673,8 @@ <h3 style="margin-top:26px;font-size:1rem;display:flex;align-items:center;gap:8p
669673
const same=(typeof u==='string')&&(u.startsWith('/')||u.startsWith(location.origin));
670674
if(AIOS_TOKEN&&same){o.headers=Object.assign({'Authorization':'Bearer '+AIOS_TOKEN},o.headers||{});}
671675
return _f(u,o);};})();
672-
const ROLES={opencode:"coding engine",hermes:"autonomous agent",openclaw:"channel gateway",crewai:"multi-agent crews",claudecode:"Claude Code API","openclaw-os":"dashboard",hub:"brain + interconnect"};
673-
const ICON={opencode:"⌨️",hermes:"☤",openclaw:"🦞",crewai:"👥",claudecode:"◈","openclaw-os":"🪟",hub:"◆",brain:"◆",team:"✦",all:"🌐"};
676+
const ROLES={opencode:"coding engine",hermes:"autonomous agent",openclaw:"channel gateway",crewai:"multi-agent crews",claudecode:"Claude Code API",codex:"Codex (ChatGPT sub)","openclaw-os":"dashboard",hub:"brain + interconnect"};
677+
const ICON={opencode:"⌨️",hermes:"☤",openclaw:"🦞",crewai:"👥",claudecode:"◈",codex:"⌁","openclaw-os":"🪟",hub:"◆",brain:"◆",team:"✦",all:"🌐"};
674678
let target="brain", peers={}, svc={}, history=[];
675679

676680
// ── themes (applied as CSS vars from JS → enables the theme store) ──
@@ -750,6 +754,7 @@ <h3 style="margin-top:26px;font-size:1rem;display:flex;align-items:center;gap:8p
750754
if(v==='openclaw')frame('clawFrame','clawNew',peers['openclaw-embed']||peers.openclaw);
751755
if(v==='opencode')agentPanel('opencode','opencodeBody','The headless coding-agent engine. Chat with it from the Chat tab (target “opencode”) or call its HTTP API/SDK.',peers.opencode);
752756
if(v==='crewai')agentPanel('crewai','crewaiBody','Role-based multi-agent crews. Chat with it from the Chat tab (target “CrewAI”). It can call the other agents via the hub.',peers.crewai);
757+
if(v==='codex')agentPanel('codex','codexBody','OpenAI Codex, wrapped as an OpenAI-compatible API by <span class="mono">services/codex_api.py</span>. Auth is your <span class="mono">codex login</span> — no API key. Chat with it here (target <b>Codex</b>) or POST to <span class="mono">'+(peers.codex||'http://127.0.0.1:8010')+'/v1</span>.',peers.codex);
753758
if(v==='claudecode')agentPanel('claudecode','claudecodeBody','Claude Code exposed as an OpenAI-compatible API. Chat with it (target “claude-code”), or point any tool at <span class="mono">'+(peers.claudecode||'http://127.0.0.1:8000')+'/v1</span>. Needs the <span class="mono">claude</span> CLI installed + authenticated.',peers.claudecode);
754759
if(v==='automations')loadSchedules();
755760
if(v==='canvas')loadCanvas();

0 commit comments

Comments
 (0)