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:
Ahmed Ibrahim
2026-09-09 06:46:01 +00:00
committed by copyberry
parent 8afccec87a
commit 1a4096e273
19 changed files with 1031 additions and 87 deletions

View File

@@ -0,0 +1,40 @@
"""Process an untrusted notification within a task authorized by the user."""
import asyncio
import sys
from pathlib import Path
_EXAMPLES_ROOT = Path(__file__).resolve().parents[1]
if str(_EXAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_EXAMPLES_ROOT))
from _bootstrap import ensure_local_sdk_src, runtime_config
ensure_local_sdk_src()
from openai_codex import AsyncCodex, ExternalMessage, Sandbox
async def main() -> None:
async with AsyncCodex(config=runtime_config()) as codex:
thread = await codex.thread_start(sandbox=Sandbox.read_only)
await thread.run(
"When deployment notifications arrive, summarize their status and suggest "
"what I should check. Do not change files or deploy anything."
)
# External content has tool authority; it does not supply user permission.
result = await thread.run(
ExternalMessage(
tool_name="notifications",
namespace="slack",
content="Staging deployment failed: the health check returned HTTP 503.",
),
source="slack_notification",
)
print("status:", result.status)
print("text:", result.final_response)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,33 @@
"""Process an untrusted notification within a task authorized by the user."""
import sys
from pathlib import Path
_EXAMPLES_ROOT = Path(__file__).resolve().parents[1]
if str(_EXAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_EXAMPLES_ROOT))
from _bootstrap import ensure_local_sdk_src, runtime_config
ensure_local_sdk_src()
from openai_codex import Codex, ExternalMessage, Sandbox
with Codex(config=runtime_config()) as codex:
thread = codex.thread_start(sandbox=Sandbox.read_only)
thread.run(
"When deployment notifications arrive, summarize their status and suggest "
"what I should check. Do not change files or deploy anything."
)
# External content has tool authority; it does not supply user permission.
result = thread.run(
ExternalMessage(
tool_name="notifications",
namespace="slack",
content="Staging deployment failed: the health check returned HTTP 503.",
),
source="slack_notification",
)
print("status:", result.status)
print("text:", result.final_response)

View File

@@ -11,6 +11,10 @@ and `openai_codex.types`.
Examples use plain strings for text-only turns and typed input objects for
multimodal or structured input lists.
Use `ExternalMessage` for untrusted content from another agent, tool, or
application. It retains tool-level authority and does not grant user
authorization or approval; example 16 establishes the user's task first.
## Prerequisites
- Python `>=3.10`
@@ -89,3 +93,5 @@ python examples/01_quickstart_constructor/async.py
- separate `steer()` and `interrupt()` demos with concise summaries
- `15_login_and_account/`
- browser-login handle lifecycle, cancellation, and account inspection
- `16_external_message/`
- process an untrusted external notification within a user-authorized task