Files
codex/sdk/python/src/openai_codex/_login.py
Ahmed Ibrahim 0db49a7e6a [codex] Rename Python SDK AppServerConfig to CodexConfig (#24800)
## Why

`AppServerConfig` is exported as part of the ergonomic Python SDK
surface and passed to `Codex(...)` and `AsyncCodex(...)`. That name
exposes the underlying app-server transport at the same layer where
users are configuring the Codex client. `CodexConfig` makes the common
callsite read naturally and names the object it configures.

## What changed

- Renamed the public configuration dataclass from `AppServerConfig` to
`CodexConfig`.
- Updated `Codex`, `AsyncCodex`, and the transport clients to accept
`CodexConfig`.
- Updated binary-resolution messages, package exports, docs, examples,
and related coverage to use the new public name.

## API impact

```python
from openai_codex import Codex, CodexConfig

with Codex(config=CodexConfig(codex_bin="/path/to/codex")) as codex:
    ...
```

Callers should now import and construct `CodexConfig`; `AppServerConfig`
is no longer part of the Python SDK surface.

## Validation

- `uv run --frozen --extra dev ruff check src/openai_codex scripts
examples tests`
- Tests are deferred to online CI for this PR.
2026-05-27 16:10:15 -07:00

173 lines
5.8 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import Protocol
from .async_client import AsyncCodexClient
from .client import CodexClient
from .generated.v2_all import (
AccountLoginCompletedNotification,
CancelLoginAccountResponse,
ChatgptDeviceCodeLoginAccountParams,
ChatgptDeviceCodeLoginAccountResponse,
ChatgptLoginAccountParams,
ChatgptLoginAccountResponse,
LoginAccountParams,
)
class _AsyncLoginOwner(Protocol):
"""Subset of AsyncCodex needed by async login handles."""
_client: AsyncCodexClient
async def _ensure_initialized(self) -> None:
"""Ensure the owning SDK client has a live Codex connection."""
...
def start_chatgpt_login(client: CodexClient) -> ChatgptLoginHandle:
"""Start browser ChatGPT login and return the handle for that attempt."""
response = client.account_login_start(
LoginAccountParams(
root=ChatgptLoginAccountParams(type="chatgpt"),
)
)
response_root = response.root
if not isinstance(response_root, ChatgptLoginAccountResponse):
raise RuntimeError(f"unexpected ChatGPT login response: {response_root!r}")
return ChatgptLoginHandle(
client,
response_root.login_id,
response_root.auth_url,
)
async def async_start_chatgpt_login(owner: _AsyncLoginOwner) -> AsyncChatgptLoginHandle:
"""Start async browser ChatGPT login and return that attempt's handle."""
response = await owner._client.account_login_start(
LoginAccountParams(
root=ChatgptLoginAccountParams(type="chatgpt"),
)
)
response_root = response.root
if not isinstance(response_root, ChatgptLoginAccountResponse):
raise RuntimeError(f"unexpected ChatGPT login response: {response_root!r}")
return AsyncChatgptLoginHandle(
owner,
response_root.login_id,
response_root.auth_url,
)
def start_device_code_login(client: CodexClient) -> DeviceCodeLoginHandle:
"""Start device-code ChatGPT login and return the handle for that attempt."""
response = client.account_login_start(
LoginAccountParams(
root=ChatgptDeviceCodeLoginAccountParams(type="chatgptDeviceCode"),
)
)
response_root = response.root
if not isinstance(response_root, ChatgptDeviceCodeLoginAccountResponse):
raise RuntimeError(f"unexpected device-code login response: {response_root!r}")
return DeviceCodeLoginHandle(
client,
response_root.login_id,
response_root.verification_url,
response_root.user_code,
)
async def async_start_device_code_login(
owner: _AsyncLoginOwner,
) -> AsyncDeviceCodeLoginHandle:
"""Start async device-code ChatGPT login and return that attempt's handle."""
response = await owner._client.account_login_start(
LoginAccountParams(
root=ChatgptDeviceCodeLoginAccountParams(type="chatgptDeviceCode"),
)
)
response_root = response.root
if not isinstance(response_root, ChatgptDeviceCodeLoginAccountResponse):
raise RuntimeError(f"unexpected device-code login response: {response_root!r}")
return AsyncDeviceCodeLoginHandle(
owner,
response_root.login_id,
response_root.verification_url,
response_root.user_code,
)
@dataclass(slots=True)
class ChatgptLoginHandle:
"""Live browser-login attempt returned by `Codex.login_chatgpt()`."""
_client: CodexClient
login_id: str
auth_url: str
def wait(self) -> AccountLoginCompletedNotification:
"""Wait for this browser login attempt's completion notification."""
return self._client.wait_for_login_completed(self.login_id)
def cancel(self) -> CancelLoginAccountResponse:
"""Cancel this browser login attempt."""
return self._client.account_login_cancel(self.login_id)
@dataclass(slots=True)
class DeviceCodeLoginHandle:
"""Live device-code login attempt returned by `Codex.login_chatgpt_device_code()`."""
_client: CodexClient
login_id: str
verification_url: str
user_code: str
def wait(self) -> AccountLoginCompletedNotification:
"""Wait for this device-code login attempt's completion notification."""
return self._client.wait_for_login_completed(self.login_id)
def cancel(self) -> CancelLoginAccountResponse:
"""Cancel this device-code login attempt."""
return self._client.account_login_cancel(self.login_id)
@dataclass(slots=True)
class AsyncChatgptLoginHandle:
"""Live browser-login attempt returned by `AsyncCodex.login_chatgpt()`."""
_codex: _AsyncLoginOwner
login_id: str
auth_url: str
async def wait(self) -> AccountLoginCompletedNotification:
"""Wait for this browser login attempt's completion notification."""
await self._codex._ensure_initialized()
return await self._codex._client.wait_for_login_completed(self.login_id)
async def cancel(self) -> CancelLoginAccountResponse:
"""Cancel this browser login attempt."""
await self._codex._ensure_initialized()
return await self._codex._client.account_login_cancel(self.login_id)
@dataclass(slots=True)
class AsyncDeviceCodeLoginHandle:
"""Live device-code attempt returned by `AsyncCodex.login_chatgpt_device_code()`."""
_codex: _AsyncLoginOwner
login_id: str
verification_url: str
user_code: str
async def wait(self) -> AccountLoginCompletedNotification:
"""Wait for this device-code login attempt's completion notification."""
await self._codex._ensure_initialized()
return await self._codex._client.wait_for_login_completed(self.login_id)
async def cancel(self) -> CancelLoginAccountResponse:
"""Cancel this device-code login attempt."""
await self._codex._ensure_initialized()
return await self._codex._client.account_login_cancel(self.login_id)