Skip to content

Add optional Codex ChatGPT subscription provider - #46

Open
dhruv-anand-aintech wants to merge 1 commit into
laude-institute:mainfrom
dhruv-anand-aintech:feat/codex-chatgpt-provider
Open

Add optional Codex ChatGPT subscription provider#46
dhruv-anand-aintech wants to merge 1 commit into
laude-institute:mainfrom
dhruv-anand-aintech:feat/codex-chatgpt-provider

Conversation

@dhruv-anand-aintech

Copy link
Copy Markdown

Summary

  • add an optional codex provider backed by the official codex app-server JSONL protocol
  • preserve the existing API-key providers and make Codex selection explicit through SHELLM_PROVIDER=codex
  • stream assistant messages and reasoning summaries, record subscription billing provenance, and fail clearly on unsupported interactive approvals
  • add a mocked provider test and document ChatGPT-managed Codex setup

Why

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.sh
  • tests/run-all.sh llm (70 passed, 0 failed)
  • local smoke test with the installed Codex CLI, ChatGPT-managed auth, and gpt-5.6-luna

Notes

The current app-server turn completion event does not expose token counts to this adapter, so usage records identify billing_source=chatgpt_subscription and set usage_available=false rather than inventing counts.

@nickjalbert nickjalbert left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

berataydin pushed a commit to berataydin/headlong that referenced this pull request Aug 31, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants