mirror of
https://github.com/openai/codex.git
synced 2026-09-10 20:26:47 +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:
40
sdk/python/examples/16_external_message/async.py
Normal file
40
sdk/python/examples/16_external_message/async.py
Normal 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())
|
||||
33
sdk/python/examples/16_external_message/sync.py
Normal file
33
sdk/python/examples/16_external_message/sync.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user