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
@@ -8,7 +8,14 @@ from pydantic import BaseModel
|
||||
|
||||
from .client import AppServerClient, AppServerConfig
|
||||
from .generated.v2_all import (
|
||||
AccountLoginCompletedNotification,
|
||||
AgentMessageDeltaNotification,
|
||||
CancelLoginAccountResponse,
|
||||
GetAccountParams as V2GetAccountParams,
|
||||
GetAccountResponse,
|
||||
LoginAccountParams as V2LoginAccountParams,
|
||||
LoginAccountResponse,
|
||||
LogoutAccountResponse,
|
||||
ModelListResponse,
|
||||
ThreadArchiveResponse,
|
||||
ThreadCompactStartResponse,
|
||||
@@ -88,6 +95,14 @@ class AsyncAppServerClient:
|
||||
"""Register a turn notification queue on the wrapped sync client."""
|
||||
self._sync.register_turn_notifications(turn_id)
|
||||
|
||||
def register_login_notifications(self, login_id: str) -> None:
|
||||
"""Register a login notification queue on the wrapped sync client."""
|
||||
self._sync.register_login_notifications(login_id)
|
||||
|
||||
def unregister_login_notifications(self, login_id: str) -> None:
|
||||
"""Unregister a login notification queue on the wrapped sync client."""
|
||||
self._sync.unregister_login_notifications(login_id)
|
||||
|
||||
def unregister_turn_notifications(self, turn_id: str) -> None:
|
||||
"""Unregister a turn notification queue on the wrapped sync client."""
|
||||
self._sync.unregister_turn_notifications(turn_id)
|
||||
@@ -107,6 +122,28 @@ class AsyncAppServerClient:
|
||||
response_model=response_model,
|
||||
)
|
||||
|
||||
async def account_login_start(
|
||||
self,
|
||||
params: V2LoginAccountParams | JsonObject,
|
||||
) -> LoginAccountResponse:
|
||||
"""Start one account login attempt through the wrapped sync client."""
|
||||
return await self._call_sync(self._sync.account_login_start, params)
|
||||
|
||||
async def account_login_cancel(self, login_id: str) -> CancelLoginAccountResponse:
|
||||
"""Cancel one active account login attempt through the wrapped sync client."""
|
||||
return await self._call_sync(self._sync.account_login_cancel, login_id)
|
||||
|
||||
async def account_read(
|
||||
self,
|
||||
params: V2GetAccountParams | JsonObject | None = None,
|
||||
) -> GetAccountResponse:
|
||||
"""Read current account state through the wrapped sync client."""
|
||||
return await self._call_sync(self._sync.account_read, params)
|
||||
|
||||
async def account_logout(self) -> LogoutAccountResponse:
|
||||
"""Clear the active account session through the wrapped sync client."""
|
||||
return await self._call_sync(self._sync.account_logout)
|
||||
|
||||
async def thread_start(
|
||||
self, params: V2ThreadStartParams | JsonObject | None = None
|
||||
) -> ThreadStartResponse:
|
||||
@@ -211,10 +248,21 @@ class AsyncAppServerClient:
|
||||
"""Wait for the next global notification without blocking the event loop."""
|
||||
return await self._call_sync(self._sync.next_notification)
|
||||
|
||||
async def next_login_notification(self, login_id: str) -> Notification:
|
||||
"""Wait for the next notification routed to one login attempt."""
|
||||
return await self._call_sync(self._sync.next_login_notification, login_id)
|
||||
|
||||
async def next_turn_notification(self, turn_id: str) -> Notification:
|
||||
"""Wait for the next notification routed to one turn."""
|
||||
return await self._call_sync(self._sync.next_turn_notification, turn_id)
|
||||
|
||||
async def wait_for_login_completed(
|
||||
self,
|
||||
login_id: str,
|
||||
) -> AccountLoginCompletedNotification:
|
||||
"""Wait for the completion notification routed to one login attempt."""
|
||||
return await self._call_sync(self._sync.wait_for_login_completed, login_id)
|
||||
|
||||
async def wait_for_turn_completed(self, turn_id: str) -> TurnCompletedNotification:
|
||||
"""Wait for the completion notification routed to one turn."""
|
||||
return await self._call_sync(self._sync.wait_for_turn_completed, turn_id)
|
||||
|
||||
Reference in New Issue
Block a user