mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Add untrusted external messages to the Python SDK (#44086)
## Why Applications need to deliver content from other agents, tools, or services with tool-level authority, without treating it as user input or granting authorization. ## What changed - Export `ExternalMessage` for sync and async `run(...)` and `turn(...)`, accepting text or structured content with a tool name and optional namespace. Send it through `toolOutput` and require CLI 0.151.0 or newer. - Support starting a turn or joining an active regular turn while preserving external content as function output in history. Keep external messages separate from user-input lists and `steer(...)`. - Give turn handles independent subscriptions, replaying completed items and latest usage to joining handles. Release consumed transient events and clean up subscriptions on closure, failure, or cancellation. - Document the authority boundary and add sync and async examples. ## Testing Add coverage for wire representations, input validation, runtime compatibility, tool authority across resume, active-turn joins, and tool-output truncation. Add subscription tests for replay, concurrent consumers, early completion, cancellation, and cleanup. GitOrigin-RevId: 6106327085fd9c4bd11b71e20b3d8e74738b8bb5
This commit is contained in:
@@ -3,12 +3,14 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import threading
|
||||
from collections.abc import Iterator
|
||||
from concurrent.futures import Future
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from contextvars import copy_context
|
||||
from typing import AsyncIterator, Callable, ParamSpec, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ._goal import _GoalOperationState
|
||||
from ._message_router import _TurnSubscription
|
||||
from .client import CodexClient, CodexConfig
|
||||
from .generated.v2_all import (
|
||||
AccountLoginCompletedNotification,
|
||||
@@ -48,6 +50,9 @@ ModelT = TypeVar("ModelT", bound=BaseModel)
|
||||
ParamsT = ParamSpec("ParamsT")
|
||||
ReturnT = TypeVar("ReturnT")
|
||||
|
||||
# Bound workers while allowing cancellation cleanup to outlive an asyncio waiter.
|
||||
_TURN_START_EXECUTOR = ThreadPoolExecutor(thread_name_prefix="codex-turn-start")
|
||||
|
||||
|
||||
class AsyncCodexClient:
|
||||
"""Async wrapper around CodexClient using thread offloading."""
|
||||
@@ -97,6 +102,9 @@ class AsyncCodexClient:
|
||||
"""Initialize the Codex session."""
|
||||
return await self._call_sync(self._sync.initialize)
|
||||
|
||||
def _subscribe_turn_notifications(self, turn_id: str) -> _TurnSubscription:
|
||||
return self._sync._subscribe_turn_notifications(turn_id)
|
||||
|
||||
def register_turn_notifications(self, turn_id: str) -> None:
|
||||
"""Register a turn notification queue on the wrapped sync client."""
|
||||
self._sync.register_turn_notifications(turn_id)
|
||||
@@ -287,8 +295,23 @@ class AsyncCodexClient:
|
||||
input_items: list[JsonObject] | JsonObject | str,
|
||||
params: V2TurnStartParams | JsonObject | None = None,
|
||||
) -> TurnStartResponse:
|
||||
"""Start a turn using the wrapped sync client."""
|
||||
return await self._call_sync(self._sync.turn_start, thread_id, input_items, params)
|
||||
"""Start a turn, releasing an unclaimed result if the caller is cancelled."""
|
||||
operation = _TURN_START_EXECUTOR.submit(
|
||||
copy_context().run, self._sync.turn_start, thread_id, input_items, params
|
||||
)
|
||||
try:
|
||||
return await asyncio.wrap_future(operation)
|
||||
except asyncio.CancelledError:
|
||||
|
||||
def discard_cancelled_result(completed: Future[TurnStartResponse]) -> None:
|
||||
try:
|
||||
started = completed.result()
|
||||
except BaseException:
|
||||
return
|
||||
self._sync._subscribe_turn_notifications(started.turn.id).close()
|
||||
|
||||
operation.add_done_callback(discard_cancelled_result)
|
||||
raise
|
||||
|
||||
async def turn_interrupt(self, thread_id: str, turn_id: str) -> TurnInterruptResponse:
|
||||
"""Interrupt a turn using the wrapped sync client."""
|
||||
|
||||
Reference in New Issue
Block a user