Add optional Codex ChatGPT subscription provider - #46
Conversation
There was a problem hiding this comment.
From review:
1. `tools/codex-app-server-llm`: effort is read from `os.environ["LLM_EFFORT"]`, which is never set; accept it as a validated `--effort` flag instead (the adapter invoker passes one).
2. `tools/codex-app-server-llm`: `stderr=subprocess.DEVNULL` discards every Codex diagnostic (not signed in, unknown subcommand, model unavailable); capture it and include a bounded tail in the `fail()` message. Log unparseable stdout lines rather than `continue`.
3. `tools/codex-app-server-llm`: guard against internal wedging: `turn/start` is written from inside the stdout read loop, so a prompt larger than the pipe buffer can deadlock with stdout undrained — write from a separate thread or after draining. Also guard `proc.wait(timeout=2)` against `TimeoutExpired`. (The outer wall-clock deadline and kill are handled by the adapter invoker.)
4. `tools/codex-app-server-llm`: `thread/start` sends `cwd=os.getcwd()` with no explicit sandbox/approval policy; approvals are only rejected reactively, so a permissive operator Codex config could act on the host cwd outside Headlong's containment boundary. Request the most restrictive available mode explicitly, or pass a throwaway cwd, and document the requirement.
5. `tools/codex-app-server-llm`: the message array is flattened into `[role]\n{content}` blocks in one text item, so untrusted Slack/Telegram text can forge `[assistant]`/`[system]` turn boundaries. Use structured per-role input if the protocol allows, or a delimiter that user text cannot produce.
6. Tests: extend the mock into scenarios covering an `item/*/requestApproval` message, JSON-RPC `error` on ids 1/2/3, a non-`completed` turn status, malformed/non-JSON stdout, and a missing `codex` binary; assert non-zero exit and the stderr text for each.
Thanks for building this, and sorry the PR sat while I worked through the provider policy it raised. The review above covers the code changes needed. Here is the decision about where the integration belongs.
Headlong now has a written provider policy and a working adapter interface. bin/llm will not gain a Codex specific branch or a Python dependency. Instead, operators can set LLM_PROVIDER=adapter and LLM_ADAPTER=/path/to/executable to run an external provider adapter.
The adapter receives messages as JSON on stdin. bin/llm passes the model, token limit, effort, thinking, streaming, and system prompt through documented flags. The adapter can report token usage through LLM_USAGE_FILE. bin/llm continues to manage the deadline, child process cleanup, health marker, and usage ledger.
Your codex-app-server-llm tool already contains most of the provider logic needed for an adapter. If you are willing to rework the PR, I would like it to become the first adapter built against the new contract. The executable can remain under tools/ or live in its own repository. The Codex branch and other Codex specific behavior should come out of bin/llm.
The adapter should also address the findings from the review, including stderr handling, the effort flag, failure tests, and the sandbox behavior around thread/start.
Adapters are trusted local code and receive the caller's environment. Codex authentication, token storage, and token refresh therefore remain inside the adapter, which is the right place for subscription and OAuth based authentication.
Quick edit: One concrete implementation note: please also remove the SHELLM_PROVIDER forwarding change from bin/shellm. The merged adapter interface uses inherited LLM_PROVIDER, so the adapter rework should not require any bin/shellm changes
Settle how providers grow (design/providers.md): bin/llm stays the only completion path. Plain HTTP+JSON providers live in core, and OpenAI-compatible endpoints (Ollama, vLLM, LM Studio, proxies) are covered by one generic provider instead of one code branch each. A provider that needs a subprocess, another language, an SDK, or auth that is not a key in a header belongs outside core behind an adapter contract, specified in the doc but not yet implemented in bin/llm. The new openai-compatible provider takes LLM_API_URL (required, no default) plus an optional LLM_API_KEY sent as a bearer token, so local servers work with no cloud key and no dummy-key masquerade. It is never auto-detected; naming it explicitly is the loud path the repo already uses for env provider overrides. Unknown model names get a 16384 output cap under this provider instead of the 4096 fallback. Tested in tests/test_llm_openai_compatible.sh (keyless call, header behavior, required URL, caps). The cloc gate rises from 10,000 to 11,000 and the README and philosophy.md claims become "about 10K lines": main was at 9,998 before this change, so any further work needed a deliberate bump. The count was true at the launch post the week of 2026-08-24. Sets the direction for PR laude-institute#46 and issues laude-institute#65 and laude-institute#71; laude-institute#65's keyless Ollama acceptance test works with this plus SHELLM_MODEL.
Summary
codexprovider backed by the officialcodex app-serverJSONL protocolSHELLM_PROVIDER=codexWhy
Headlong currently requires a vendor API key. Codex already supports ChatGPT-managed authentication and token refresh. This adapter uses the supported app-server boundary instead of reading or replaying OAuth tokens or treating a ChatGPT login token as an OpenAI API key.
Validation
tests/test_codex_provider.shtests/run-all.sh llm(70 passed, 0 failed)gpt-5.6-lunaNotes
The current app-server turn completion event does not expose token counts to this adapter, so usage records identify
billing_source=chatgpt_subscriptionand setusage_available=falserather than inventing counts.