mirror of
https://github.com/openai/codex.git
synced 2026-05-25 05:24:37 +00:00
Route Python SDK turn notifications by ID (#21778)
## Why The Python SDK previously protected the stdio transport with a single active turn-consumer guard. That avoided competing reads from stdout, but it also meant one `Codex`/`AsyncCodex` client could not stream multiple active turns at the same time. Notifications could also arrive before the caller received a `TurnHandle` and registered for streaming, so the SDK needed an explicit routing layer instead of letting individual API calls read directly from the shared transport. ## What Changed - Added a private `MessageRouter` that owns per-request response queues, per-turn notification queues, pending turn-notification replay, and global notification delivery behind a single stdout reader thread. - Generated typed notification routing metadata so turn IDs come from known payload shapes instead of router-side attribute guessing, with explicit fallback handling for unknown notification payloads. - Updated sync and async turn streaming so `TurnHandle.stream()`/`run()` and `stream_text()` consume only notifications for their own turn ID, while `AsyncAppServerClient` no longer serializes all transport calls behind one async lock. - Cleared pending turn-notification buffers when unregistered turns complete so never-consumed turn handles do not leave stale queues behind. - Removed the internal stream-until helper now that turn completion waiting can register directly with routed turn notifications. - Updated Python SDK docs and focused tests for concurrent transport calls, interleaved turn routing, buffered early notifications, unknown notification routing, async delegation, and routed turn completion behavior. ## Validation - `uv run --extra dev ruff format scripts/update_sdk_artifacts.py src/codex_app_server/_message_router.py src/codex_app_server/client.py src/codex_app_server/generated/notification_registry.py tests/test_client_rpc_methods.py tests/test_public_api_runtime_behavior.py tests/test_async_client_behavior.py` - `uv run --extra dev ruff check scripts/update_sdk_artifacts.py src/codex_app_server/_message_router.py src/codex_app_server/client.py src/codex_app_server/generated/notification_registry.py tests/test_client_rpc_methods.py tests/test_public_api_runtime_behavior.py tests/test_async_client_behavior.py` - `uv run --extra dev pytest tests/test_client_rpc_methods.py tests/test_public_api_runtime_behavior.py tests/test_async_client_behavior.py` - `git diff --check` --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -130,3 +130,43 @@ NOTIFICATION_MODELS: dict[str, type[BaseModel]] = {
|
||||
"windows/worldWritableWarning": WindowsWorldWritableWarningNotification,
|
||||
"windowsSandbox/setupCompleted": WindowsSandboxSetupCompletedNotification,
|
||||
}
|
||||
|
||||
DIRECT_TURN_ID_NOTIFICATION_TYPES: tuple[type[BaseModel], ...] = (
|
||||
AgentMessageDeltaNotification,
|
||||
CommandExecutionOutputDeltaNotification,
|
||||
ContextCompactedNotification,
|
||||
ErrorNotification,
|
||||
FileChangeOutputDeltaNotification,
|
||||
FileChangePatchUpdatedNotification,
|
||||
HookCompletedNotification,
|
||||
HookStartedNotification,
|
||||
ItemCompletedNotification,
|
||||
ItemGuardianApprovalReviewCompletedNotification,
|
||||
ItemGuardianApprovalReviewStartedNotification,
|
||||
ItemStartedNotification,
|
||||
McpToolCallProgressNotification,
|
||||
ModelReroutedNotification,
|
||||
ModelVerificationNotification,
|
||||
PlanDeltaNotification,
|
||||
ReasoningSummaryPartAddedNotification,
|
||||
ReasoningSummaryTextDeltaNotification,
|
||||
ReasoningTextDeltaNotification,
|
||||
TerminalInteractionNotification,
|
||||
ThreadGoalUpdatedNotification,
|
||||
ThreadTokenUsageUpdatedNotification,
|
||||
TurnDiffUpdatedNotification,
|
||||
TurnPlanUpdatedNotification,
|
||||
)
|
||||
|
||||
NESTED_TURN_NOTIFICATION_TYPES: tuple[type[BaseModel], ...] = (
|
||||
TurnCompletedNotification,
|
||||
TurnStartedNotification,
|
||||
)
|
||||
|
||||
|
||||
def notification_turn_id(payload: BaseModel) -> str | None:
|
||||
if isinstance(payload, DIRECT_TURN_ID_NOTIFICATION_TYPES):
|
||||
return payload.turn_id if isinstance(payload.turn_id, str) else None
|
||||
if isinstance(payload, NESTED_TURN_NOTIFICATION_TYPES):
|
||||
return payload.turn.id
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user