Skip to main content

LangChain ChatModel for OpenAI Codex Plus / Pro (ChatGPT-account subscription protocol, not api.openai.com).

Project description

langchain-codex-plus

LangChain ChatModel for OpenAI's ChatGPT-account-backed Codex — the subscription protocol (Codex Plus / Pro plans), NOT the public api.openai.com API.

What this is

OpenAI's Codex CLI signs you in with a ChatGPT account (browser OAuth) and routes traffic through:

https://chatgpt.com/backend-api/codex/responses

— a different protocol than api.openai.com/v1/chat/completions. It has its own request shape, its own auth (OAuth bearer instead of OPENAI_API_KEY), and exposes quota-window utilization via response headers (x-codex-primary-*, x-codex-secondary-*).

This package wraps that protocol in a LangChain BaseChatModel so you can use a Codex Plus subscription from any LangChain-built agent the way you'd use ChatOpenAI or ChatAnthropic.

What this is NOT

  • Not for api.openai.com traffic — use langchain-openai for that.
  • Not for Claude — use langchain-anthropic or langchain-claude-code.
  • Not a re-implementation of the Codex CLI's agent loop — just the chat-model surface.

Status

Alpha. v0.0.1. 134 tests + a gated real-account smoke test pass.

Auth

Run codex login once. The CLI writes OAuth credentials to $CODEX_HOME/auth.json (defaults to ~/.codex/auth.json). This package reads the file directly — there's no separate setup.

from langchain_codex_plus import ChatCodexPlus

llm = ChatCodexPlus(model="gpt-5.4")
llm.invoke("Say ok.")

When the access token expires (~1h TTL), a 401 response triggers an automatic refresh against auth.openai.com/oauth/token, then the call retries once. Permanent refresh failures (expired / revoked / already-used refresh token) raise CodexAuthRefreshError with permanent=True — the operator must re-run codex login. Opt out with auto_refresh=False if you want to handle 401s yourself.

Tool calling

Use bind_tools exactly like ChatOpenAI.bind_tools:

from langchain_core.tools import tool
from langchain_codex_plus import ChatCodexPlus

@tool
def get_weather(location: str) -> str:
    """Look up the weather."""
    return f"sunny in {location}"

llm = ChatCodexPlus().bind_tools([get_weather])
msg = llm.invoke("Weather in Boston?")
# msg.tool_calls → [{"name": "get_weather", "args": {"location": "Boston"}, "id": "call_..."}]

Send tool results back via ToolMessage(content=..., tool_call_id=...) — the protocol layer serializes them as Codex function_call_output entries.

Multimodal

HumanMessage content can be a list mixing text and image blocks:

from langchain_core.messages import HumanMessage

llm.invoke([HumanMessage(content=[
    {"type": "text", "text": "What's in this image?"},
    {"type": "image_url", "image_url": "https://example.com/cat.png"},
])])

Both LangChain image-block conventions are accepted ({type: image_url, image_url: {url, detail}} and {type: image, source_type: "url"|"base64", ...}). Base64 data is auto-encoded as a data: URL.

Stop sequences

Codex's /codex/responses rejects the stop parameter, so we match client-side. Streaming uses a buffered matcher so stop sequences split across SSE chunks (the common tokenization case) still truncate cleanly:

llm.invoke("Count from 1 to 100", stop=["50"])
# → "1, 2, 3, ... 49, "

Rate-limit hook

Every successful /codex/responses response carries quota headers (x-codex-primary-* / -secondary-*). The chat model parses these into a CodexRateLimits dataclass and (optionally) calls a callback so your monitoring layer can persist them:

from langchain_codex_plus import ChatCodexPlus, CodexRateLimits

def on_rate_limits(rl: CodexRateLimits) -> None:
    print(f"5h: {rl.primary.used_percent}% / 7d: {rl.secondary.used_percent}%")

llm = ChatCodexPlus(model="gpt-5.4", rate_limit_callback=on_rate_limits)

Callback exceptions are caught and logged — they never break the response path.

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langchain_codex_plus-0.0.3.tar.gz (133.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langchain_codex_plus-0.0.3-py3-none-any.whl (34.3 kB view details)

Uploaded Python 3

File details

Details for the file langchain_codex_plus-0.0.3.tar.gz.

File metadata

  • Download URL: langchain_codex_plus-0.0.3.tar.gz
  • Upload date:
  • Size: 133.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for langchain_codex_plus-0.0.3.tar.gz
Algorithm Hash digest
SHA256 bc7ddcec7d0d73c5e0f7bc0e44c4748a3df4b7cff3e7c8c45f09538493dc1f81
MD5 68adc22a267002125c413e1338da1792
BLAKE2b-256 2fb358d2f72aa7ce22a3bac9bf4b1d8094eaab98a18b891038f37b0ab31fe39d

See more details on using hashes here.

Provenance

The following attestation bundles were made for langchain_codex_plus-0.0.3.tar.gz:

Publisher: publish.yml on jasoncarreira/langchain-codex-plus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file langchain_codex_plus-0.0.3-py3-none-any.whl.

File metadata

File hashes

Hashes for langchain_codex_plus-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e44da5fa3dcea1417b84571f484f49b05dfb807455cf07f6de721e2c6e2dac53
MD5 f3c45dfac2ef3c560a1b5cef935747f6
BLAKE2b-256 92f704810b71c8c66987cdc7495fd23b9f2a6fdc016368ed13530bd5afac94e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for langchain_codex_plus-0.0.3-py3-none-any.whl:

Publisher: publish.yml on jasoncarreira/langchain-codex-plus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page