## Why The Python SDK can already create threads and run turns, but authentication still has to be arranged outside the SDK. App-server already exposes account login, account inspection, logout, and `account/login/completed` notifications, so SDK users currently have to work around a missing public client layer for a core setup step. This change makes authentication a normal SDK workflow while preserving the backend flow shape: API-key login completes immediately, and interactive ChatGPT flows return live handles that complete later through app-server notifications. ## What changed - Added public sync and async auth methods on `Codex` / `AsyncCodex`: - `login_api_key(...)` - `login_chatgpt()` - `login_chatgpt_device_code()` - `account(...)` - `logout()` - Added public browser-login and device-code handle types with attempt-local `wait()` and `cancel()` helpers. Cancellation stays on the handle instead of a root-level SDK method. - Extended the Python app-server client and notification router so login completion events are routed by `login_id` without consuming unrelated global notifications. - Kept login request/handle logic in a focused internal `_login.py` module so `api.py` remains the public facade instead of absorbing more auth plumbing. - Exported the new handle types plus curated account/login response types from the SDK surfaces. - Updated SDK docs, added sync/async login walkthrough examples, and added a notebook login walkthrough cell. ## Verification Added SDK coverage for: - API-key login, account readback, and logout through the app-server harness in both sync and async clients. - Browser login cancellation plus `handle.wait()` completion through the real app-server boundary used by the Python SDK harness. - Waiter routing that stays scoped across replaced interactive login attempts, plus async handle cancellation coverage. - Login notification demuxing, replay of early completion events, and async client delegation. - Public export/signature assertions. - Real integration-suite smoke coverage for the new examples and notebook login cell.
3.8 KiB
Getting Started
This is the fastest path from install to a multi-turn thread using the public SDK surface.
The SDK is experimental, so the public API and runtime requirements may keep evolving before the first public release.
1) Install
From repo root:
cd sdk/python
uv sync
source .venv/bin/activate
Requirements:
- Python
>=3.10 - uv
- installed
openai-codex-cli-binruntime package, or an explicitcodex_binoverride
2) Authenticate when needed
Existing Codex auth state is reused automatically. To authenticate from the SDK, use the flow that fits your app:
from openai_codex import Codex
with Codex() as codex:
codex.login_api_key("sk-...")
account = codex.account()
print(account.account)
Interactive ChatGPT browser login returns a handle that carries the URL and the matching completion event:
with Codex() as codex:
login = codex.login_chatgpt()
print(login.auth_url)
completed = login.wait()
print(completed.success)
Device-code login works the same way with
login_chatgpt_device_code(), which exposes verification_url, user_code,
and wait().
3) Run your first turn (sync)
from openai_codex import Codex
with Codex() as codex:
server = codex.metadata.serverInfo
print("Server:", None if server is None else server.name, None if server is None else server.version)
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
result = thread.run("Say hello in one sentence.")
print("Thread:", thread.id)
print("Text:", result.final_response)
print("Items:", len(result.items))
What happened:
Codex()started and initializedcodex app-server.thread_start(...)created a thread.thread.run("...")started a turn, consumed events until completion, and returned the final assistant response plus collected items and usage.result.final_responseisNonewhen no final-answer or phase-less assistant message item completes for the turn.- use
thread.turn(...)when you need aTurnHandlefor streaming, steering, interrupting, or turn IDs/status - one client can consume multiple active turns concurrently; turn streams are routed by turn ID
4) Continue the same thread (multi-turn)
from openai_codex import Codex
with Codex() as codex:
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
first = thread.run("Summarize Rust ownership in 2 bullets.")
second = thread.run("Now explain it to a Python developer.")
print("first:", first.final_response)
print("second:", second.final_response)
5) Async parity
Use async with AsyncCodex() as the normal async entrypoint. AsyncCodex
initializes lazily, and context entry makes startup/shutdown explicit.
import asyncio
from openai_codex import AsyncCodex
async def main() -> None:
async with AsyncCodex() as codex:
thread = await codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
result = await thread.run("Continue where we left off.")
print(result.final_response)
asyncio.run(main())
6) Resume an existing thread
from openai_codex import Codex
THREAD_ID = "thr_123" # replace with a real id
with Codex() as codex:
thread = codex.thread_resume(THREAD_ID)
result = thread.run("Continue where we left off.")
print(result.final_response)
7) Public app-server types
The convenience wrappers live at the package root. Public app-server value and event types live under:
from openai_codex.types import ThreadReadResponse, Turn, TurnStatus
8) Next stops
- API surface and signatures:
docs/api-reference.md - Common decisions/pitfalls:
docs/faq.md - End-to-end runnable examples:
examples/README.md