dspy-codex-auth
DSPy integration for using ChatGPT/Codex subscription credentials as a DSPy language model.
- It uses
openai-codex-authfor authentication, HTTP/WebSocket requests, and streamed-response handling. - It installs a DSPy
LMwrapper forcodex/...model strings. - It converts DSPy messages and options into native Codex requests, and converts shared client responses into DSPy's output, history, and usage formats.
Install
uv add dspy-codex-auth
The two packages have separate roles: openai-codex-auth provides the reusable
Codex client; dspy-codex-auth provides its DSPy adapter. Installing the adapter
brings in both packages. For ordinary Python without DSPy, install
openai-codex-auth and use CodexClient directly. No Pi plugin is needed.
Login
Sign in once with the Codex CLI and choose "Sign in with ChatGPT":
codex login
The adapter reads the account identity from ~/.codex/auth.json on each call,
including cache lookup, to keep cached results separate across accounts. The
shared client refreshes the token when an uncached request is sent. Constructing
an LM does not read credentials; credential errors surface on its first call.
Cached calls using file-backed auth still require a readable credential file.
CodexAuth and getauthtoken are re-exports from openai-codex-auth; pass
auth_storage= (a CodexAuth or a path) to install() or LM(...) to use a
different auth file.
This integration requires a file-backed ChatGPT login. Codex can also store credentials in the OS credential store, which this package does not read; see the Codex authentication documentation.
Basic Usage
import dspy
import dspy_codex_auth
dspy_codex_auth.install()
lm = dspy.LM("codex/gpt-5.6-luna", cache=False)
dspy.configure(lm=lm, adapter=dspy.JSONAdapter())
Use codex/<model> for the ChatGPT Codex subscription route. This is not the
OpenAI API key route and does not require OPENAI_API_KEY.
cache=False is recommended for Codex while iterating because stale DSPy cache
entries can preserve old empty-output responses across package upgrades.
Codex Transport Selection
Codex LMs accept codex_transport at construction time. Its accepted values
are exactly "auto", "http", and "websocket":
lm = dspy.LM(
"codex/gpt-5.6-luna",
codex_transport="auto",
codex_websocket_connect_timeout=10.0,
codex_websocket_idle_timeout=300.0,
cache=False,
)
"auto"is the default. It tries HTTP first and falls back to WebSocket only when HTTP returns the exact structured model-not-found error for the requested model. Other HTTP errors remain visible; message heuristics do not trigger a fallback."http"uses HTTP only and never falls back to WebSocket."websocket"uses WebSocket only.
The same three transport values and both timeout options can be overridden for one call without changing the LM defaults:
outputs = lm(
"Return exactly the single lowercase token: pink",
codex_transport="websocket",
codex_websocket_connect_timeout=10.0,
codex_websocket_idle_timeout=300.0,
)
The WebSocket connect timeout defaults to 10 seconds, and its per-event idle
timeout defaults to 300 seconds. Custom api_base values must use HTTP or
HTTPS; the WebSocket transport converts them to WS or WSS and appends the
/responses path. TLS uses Requests' CA bundle.
The shared client owns retries. num_retries counts additional attempts across
transports; retry delays begin at 0.5 seconds and double up to 8 seconds.
Auto selection may additionally make an HTTP routing probe. An explicit numeric
timeout also controls WebSocket idle time under the existing call/constructor
precedence; HTTP supports phase-specific httpx.Timeout values.
Authentication remains in the WebSocket handshake: the bearer credential is
never placed in the response.create data frame. HTTP requests use originator
dspy_codex_auth; WebSocket handshakes use originator codex_cli_rs. Both
preserve the honest DSPy/<version> User-Agent supplied by DSPy.
Swapping Models
Call dspy_codex_auth.install() once near process startup. Codex model strings
use subscription auth; non-Codex model strings continue through DSPy's normal LM
behavior.
import dspy
import dspy_codex_auth
dspy_codex_auth.install()
def configure_model(model: str, **kwargs):
lm = dspy.LM(model, **kwargs)
dspy.configure(lm=lm, adapter=dspy.JSONAdapter())
return lm
codex_lm = configure_model("codex/gpt-5.5", cache=False)
api_lm = configure_model("openai/gpt-5.5", api_key="...", cache=False)
Reasoning Summary
Pass reasoning_effort as usual. This package also supports
reasoning_summary, which maps to the Responses API reasoning.summary field.
lm = dspy.LM(
"codex/gpt-5.5",
cache=False,
reasoning_effort="medium",
reasoning_summary="detailed",
)
DSPy predictions expose declared output fields. The lower-level LM history can also include a returned reasoning summary:
summary = lm.history[-1]["outputs"][0].get("reasoning_content")
Fast Mode / Service Tier
Codex CLI config uses service_tier = "fast" for Fast mode. Internally, the
CLI normalizes that config value to the backend request tier priority; this
package does the same, so DSPy callers can use the documented Codex config
spelling directly:
lm = dspy.LM(
"codex/gpt-5.4",
cache=False,
service_tier="fast",
reasoning_effort="low",
)
service_tier="priority" and service_tier="flex" are also passed through.
Omit service_tier to use the account and model default.
For raw Codex CLI calls, use -c config overrides:
codex exec \
-m gpt-5.4 \
-c 'service_tier="fast"' \
-c 'model_reasoning_effort="low"' \
--json \
'Return exactly the single lowercase word: pink'
The DSPy-facing kwarg is reasoning_effort. For convenience, this package also
accepts Codex config-style aliases model_reasoning_effort and
model_reasoning_summary.
Relevant Codex docs:
OpenAI-Style Model String With Codex Auth
If you prefer to keep an openai/... model string and select Codex auth
explicitly:
lm = dspy_codex_auth.LM(
"openai/gpt-5.5",
auth_provider="codex",
cache=False,
reasoning_effort="medium",
reasoning_summary="detailed",
)
This is useful when the rest of your app treats model names as provider-neutral strings and you want auth selection to be a separate setting.
What It Fixes
The shared Codex client reconstructs missing output from streamed events, preserving provider metadata, message text, function calls, and reasoning summaries. This adapter converts that response into the objects DSPy expects. Codex requests no longer pass through LiteLLM's Responses transport.
A provider refusal raises openai_codex_auth.CodexError in this adapter so it
cannot become an empty DSPy prediction. Direct CodexClient callers receive
the native refusal item in CodexResponse.output.
The adapter and shared core together handle:
- DSPy few-shot and conversation-history assistant messages by encoding them as
Responses
output_textblocks, which supports optimizers such asLabeledFewShot. - GEPA reflection calls that invoke the LM with a plain prompt string.
response.output_item.doneresponse.output_text.doneresponse.output_text.deltaresponse.reasoning_summary_text.doneresponse.reasoning_summary_text.delta- streamed function-call output items
It also strips output-token cap fields that the Codex backend currently rejects:
max_tokensmax_output_tokensmax_completion_tokens
It normalizes service_tier="fast" to the Codex backend request value
service_tier="priority", matching Codex CLI behavior.
French Example
import dspy
import dspy_codex_auth
dspy_codex_auth.install()
lm = dspy.LM("codex/gpt-5.5", cache=False)
dspy.configure(lm=lm, adapter=dspy.JSONAdapter())
class TranslateFrenchToEnglish(dspy.Signature):
"""Translate the French input into short, natural English."""
french: str = dspy.InputField(desc="French sentence")
english: str = dspy.OutputField(desc="Natural English translation")
translator = dspy.Predict(TranslateFrenchToEnglish)
print(translator(french="merci beaucoup").english)
Math Example With Reasoning Summary
import dspy
import dspy_codex_auth
dspy_codex_auth.install()
lm = dspy.LM(
"codex/gpt-5.5",
cache=False,
reasoning_effort="medium",
reasoning_summary="detailed",
)
dspy.configure(lm=lm, adapter=dspy.JSONAdapter())
class SolveMath(dspy.Signature):
"""Solve the math problem. Return a concise numeric answer and a brief explanation."""
problem: str = dspy.InputField(desc="Math problem")
answer: str = dspy.OutputField(desc="Concise final answer")
explanation: str = dspy.OutputField(desc="Brief explanation")
solver = dspy.Predict(SolveMath)
pred = solver(
problem=(
"Compute the integral of the standard normal probability density "
"function from 0 to 1.5."
)
)
print(pred.answer)
print(pred.explanation)
print(lm.history[-1]["outputs"][0].get("reasoning_content"))
Attribution
dspy-codex-auth includes and adapts MIT-licensed DSPy integration code from
dspy-lm-auth:
https://github.com/MaximeRivest/dspy-lm-auth
The streamed-output reconstruction addresses a DSPy/Codex Responses streaming
compatibility issue that was also discussed in dspy-lm-auth PR #2:
https://github.com/MaximeRivest/dspy-lm-auth/pull/2
dspy-lm-auth is MIT-licensed. The original copyright notice is preserved in
THIRD_PARTY_NOTICES.md.
Development
uv sync --dev
uv run pytest
uv run ruff check .
uv build --no-sources
The default test command skips the real-network live transport test. Run it explicitly only with valid Codex subscription credentials:
DSPY_CODEX_AUTH_RUN_LIVE_TESTS=1 \
uv run pytest -m live tests/test_live_websocket.py
Release
Full PyPI update instructions are in RELEASING.md.
Short local release flow:
uv version --bump patch
rm -rf dist
uv build --no-sources
uv run --with twine python -m twine upload dist/*
PyPI releases are immutable, so every update needs a new version number.
Metadata
Release files for dspy-codex-auth 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| dspy_codex_auth-0.2.0.tar.gz | 12.1 kB | Without provenance | Details for dspy_codex_auth-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| dspy_codex_auth-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for dspy_codex_auth-0.2.0-py3-none-any.whl |
Total release size: 26.5 kB
Release files / dspy_codex_auth-0.2.0.tar.gz
| Download URL | dspy_codex_auth-0.2.0.tar.gz |
|---|---|
| Size | 12.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
79055bfcc96d25aa36fa783d63939c223a7e97519c736405b0325b803df36b82
|
|
BLAKE2b-256 checksum How to use checksums |
0a578c89df7c647fa92f75f36bab1f8ac22559d579d9c5b44b6a2ffbbf017039
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.4
|
Release files / dspy_codex_auth-0.2.0-py3-none-any.whl
| Download URL | dspy_codex_auth-0.2.0-py3-none-any.whl |
|---|---|
| Size | 14.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3ba804ee5c3e9d1f5cb844b9e2e652736c38371b2ae6d94f8ec18348468fa16f
|
|
BLAKE2b-256 checksum How to use checksums |
e461b07304a4ab8e1464b41303d4f58226b778505dd1e59c9a7f60a707b56526
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.4
|
Security for dspy-codex-auth 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.