mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
sdk/python: add first-class login support (#23093)
## 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.
This commit is contained in:
committed by
GitHub
Unverified
parent
0445b290fe
commit
4c89772314
@@ -12,6 +12,10 @@ from openai_codex import (
|
||||
Codex,
|
||||
AsyncCodex,
|
||||
ApprovalMode,
|
||||
ChatgptLoginHandle,
|
||||
DeviceCodeLoginHandle,
|
||||
AsyncChatgptLoginHandle,
|
||||
AsyncDeviceCodeLoginHandle,
|
||||
RunResult,
|
||||
Thread,
|
||||
AsyncThread,
|
||||
@@ -26,6 +30,11 @@ from openai_codex import (
|
||||
MentionInput,
|
||||
)
|
||||
from openai_codex.types import (
|
||||
Account,
|
||||
AccountLoginCompletedNotification,
|
||||
CancelLoginAccountResponse,
|
||||
CancelLoginAccountStatus,
|
||||
GetAccountResponse,
|
||||
InitializeResponse,
|
||||
ThreadItem,
|
||||
ThreadTokenUsage,
|
||||
@@ -47,6 +56,11 @@ Properties/methods:
|
||||
|
||||
- `metadata -> InitializeResponse`
|
||||
- `close() -> None`
|
||||
- `login_api_key(api_key: str) -> None`
|
||||
- `login_chatgpt() -> ChatgptLoginHandle`
|
||||
- `login_chatgpt_device_code() -> DeviceCodeLoginHandle`
|
||||
- `account(*, refresh_token: bool = False) -> GetAccountResponse`
|
||||
- `logout() -> None`
|
||||
- `thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox=None) -> Thread`
|
||||
- `thread_list(*, archived=None, cursor=None, cwd=None, limit=None, model_providers=None, sort_key=None, source_kinds=None) -> ThreadListResponse`
|
||||
- `thread_resume(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, personality=None, sandbox=None) -> Thread`
|
||||
@@ -82,6 +96,11 @@ Properties/methods:
|
||||
|
||||
- `metadata -> InitializeResponse`
|
||||
- `close() -> Awaitable[None]`
|
||||
- `login_api_key(api_key: str) -> Awaitable[None]`
|
||||
- `login_chatgpt() -> Awaitable[AsyncChatgptLoginHandle]`
|
||||
- `login_chatgpt_device_code() -> Awaitable[AsyncDeviceCodeLoginHandle]`
|
||||
- `account(*, refresh_token: bool = False) -> Awaitable[GetAccountResponse]`
|
||||
- `logout() -> Awaitable[None]`
|
||||
- `thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox=None) -> Awaitable[AsyncThread]`
|
||||
- `thread_list(*, archived=None, cursor=None, cwd=None, limit=None, model_providers=None, sort_key=None, source_kinds=None) -> Awaitable[ThreadListResponse]`
|
||||
- `thread_resume(thread_id: str, *, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, model=None, model_provider=None, personality=None, sandbox=None) -> Awaitable[AsyncThread]`
|
||||
@@ -97,6 +116,30 @@ async with AsyncCodex() as codex:
|
||||
...
|
||||
```
|
||||
|
||||
## Login handles
|
||||
|
||||
### ChatgptLoginHandle / AsyncChatgptLoginHandle
|
||||
|
||||
- `login_id: str`
|
||||
- `auth_url: str`
|
||||
- `wait() -> AccountLoginCompletedNotification`
|
||||
- `cancel() -> CancelLoginAccountResponse`
|
||||
|
||||
Async handle methods return awaitables.
|
||||
|
||||
### DeviceCodeLoginHandle / AsyncDeviceCodeLoginHandle
|
||||
|
||||
- `login_id: str`
|
||||
- `verification_url: str`
|
||||
- `user_code: str`
|
||||
- `wait() -> AccountLoginCompletedNotification`
|
||||
- `cancel() -> CancelLoginAccountResponse`
|
||||
|
||||
Async handle methods return awaitables.
|
||||
|
||||
`wait()` consumes only the completion notification for its matching login
|
||||
attempt. API-key login completes synchronously and does not return a handle.
|
||||
|
||||
## Thread / AsyncThread
|
||||
|
||||
`Thread` and `AsyncThread` share the same shape and intent.
|
||||
@@ -176,6 +219,11 @@ The SDK wrappers return and accept public app-server models wherever possible:
|
||||
|
||||
```python
|
||||
from openai_codex.types import (
|
||||
Account,
|
||||
AccountLoginCompletedNotification,
|
||||
CancelLoginAccountResponse,
|
||||
CancelLoginAccountStatus,
|
||||
GetAccountResponse,
|
||||
ThreadReadResponse,
|
||||
Turn,
|
||||
TurnStatus,
|
||||
|
||||
+10
-1
@@ -23,6 +23,16 @@ Choose `run()` for most apps. Choose `stream()` for progress UIs, custom timeout
|
||||
|
||||
If your app is not already async, stay with `Codex`.
|
||||
|
||||
## How do I log in?
|
||||
|
||||
- `login_api_key(...)` authenticates immediately with an API key.
|
||||
- `login_chatgpt()` starts browser login and returns a handle with `auth_url`.
|
||||
- `login_chatgpt_device_code()` starts device-code login and returns a handle
|
||||
with `verification_url` and `user_code`.
|
||||
- Interactive handles expose `wait()` for the matching
|
||||
`account/login/completed` notification and `cancel()` to stop that attempt.
|
||||
- `account()` reads the current account state, and `logout()` clears it.
|
||||
|
||||
## Public kwargs are snake_case
|
||||
|
||||
Public API keyword names are snake_case. The SDK still maps them to wire camelCase under the hood.
|
||||
@@ -56,7 +66,6 @@ Common causes:
|
||||
|
||||
- published runtime package (`openai-codex-cli-bin`) is not installed
|
||||
- local `codex_bin` override points to a missing file
|
||||
- local auth/session is missing
|
||||
- incompatible/old app-server
|
||||
|
||||
## Why does a turn "hang"?
|
||||
|
||||
@@ -19,9 +19,37 @@ Requirements:
|
||||
- Python `>=3.10`
|
||||
- uv
|
||||
- installed `openai-codex-cli-bin` runtime package, or an explicit `codex_bin` override
|
||||
- local Codex auth/session configured
|
||||
|
||||
## 2) Run your first turn (sync)
|
||||
## 2) Authenticate when needed
|
||||
|
||||
Existing Codex auth state is reused automatically. To authenticate from the SDK,
|
||||
use the flow that fits your app:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
```python
|
||||
from openai_codex import Codex
|
||||
@@ -47,7 +75,7 @@ What happened:
|
||||
- use `thread.turn(...)` when you need a `TurnHandle` for streaming, steering, interrupting, or turn IDs/status
|
||||
- one client can consume multiple active turns concurrently; turn streams are routed by turn ID
|
||||
|
||||
## 3) Continue the same thread (multi-turn)
|
||||
## 4) Continue the same thread (multi-turn)
|
||||
|
||||
```python
|
||||
from openai_codex import Codex
|
||||
@@ -62,7 +90,7 @@ with Codex() as codex:
|
||||
print("second:", second.final_response)
|
||||
```
|
||||
|
||||
## 4) Async parity
|
||||
## 5) Async parity
|
||||
|
||||
Use `async with AsyncCodex()` as the normal async entrypoint. `AsyncCodex`
|
||||
initializes lazily, and context entry makes startup/shutdown explicit.
|
||||
@@ -82,7 +110,7 @@ async def main() -> None:
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 5) Resume an existing thread
|
||||
## 6) Resume an existing thread
|
||||
|
||||
```python
|
||||
from openai_codex import Codex
|
||||
@@ -95,7 +123,7 @@ with Codex() as codex:
|
||||
print(result.final_response)
|
||||
```
|
||||
|
||||
## 6) Public app-server types
|
||||
## 7) Public app-server types
|
||||
|
||||
The convenience wrappers live at the package root. Public app-server value and
|
||||
event types live under:
|
||||
@@ -104,7 +132,7 @@ event types live under:
|
||||
from openai_codex.types import ThreadReadResponse, Turn, TurnStatus
|
||||
```
|
||||
|
||||
## 7) Next stops
|
||||
## 8) Next stops
|
||||
|
||||
- API surface and signatures: `docs/api-reference.md`
|
||||
- Common decisions/pitfalls: `docs/faq.md`
|
||||
|
||||
Reference in New Issue
Block a user