mirror of
https://github.com/openai/codex.git
synced 2026-09-11 20:36:49 +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:
@@ -31,6 +31,7 @@ from openai_codex import (
|
||||
LocalImageInput,
|
||||
SkillInput,
|
||||
MentionInput,
|
||||
ExternalMessage,
|
||||
)
|
||||
from openai_codex.types import (
|
||||
Account,
|
||||
@@ -201,7 +202,7 @@ These options have the same behavior on sync and async `run(...)` and `turn(...)
|
||||
| `turn_service_tier: str | None = None` | Overrides the tier for a newly started turn only. `None` inherits the thread setting; `"default"` selects standard speed. Does not change the thread default and is ignored when input joins an active turn. |
|
||||
| `source: str | None = None` | Labels the caller that initiated a new turn, such as `"review_ui"`. This is metadata; it does not schedule work or grant authority. Ignored when input joins an active turn. |
|
||||
|
||||
`turn_service_tier`, `source`, and explicit `include_turns`
|
||||
`ExternalMessage`, `turn_service_tier`, `source`, and explicit `include_turns`
|
||||
on resume/fork require Codex CLI 0.151.0 or newer. The SDK raises `CodexError`
|
||||
before sending these options to an older runtime, which would otherwise ignore
|
||||
them. Published SDK releases install a matching runtime automatically; when
|
||||
@@ -267,7 +268,7 @@ Behavior notes:
|
||||
|
||||
InputItem = TextInput | ImageInput | LocalImageInput | SkillInput | MentionInput
|
||||
Input = list[InputItem] | InputItem
|
||||
RunInput = Input | str
|
||||
RunInput = Input | str | ExternalMessage
|
||||
```
|
||||
|
||||
Use `ImageInput` with a base64-encoded `data:image/...` URL. HTTP and HTTPS image URLs are
|
||||
@@ -276,6 +277,58 @@ deprecated; download remote images and pass their local paths with `LocalImageIn
|
||||
Use a plain `str` as shorthand for `TextInput(...)` anywhere a turn input is accepted:
|
||||
`thread.run("...")`, `thread.turn("...")`, and `turn.steer("...")`.
|
||||
|
||||
### ExternalMessage
|
||||
|
||||
`ExternalMessage` supplies **untrusted content** from another agent, tool, or
|
||||
application. Content reaches the model with tool-level authority, below user
|
||||
and developer instructions. It does not establish user authorization or
|
||||
approval. Keep the thread's sandbox and approval policies appropriate for the
|
||||
work the user has authorized.
|
||||
|
||||
```python
|
||||
from openai_codex import ExternalMessage
|
||||
|
||||
message = ExternalMessage(
|
||||
tool_name="notifications",
|
||||
namespace="slack",
|
||||
content="Deployment notification: the staging checks failed.",
|
||||
)
|
||||
result = thread.run(message)
|
||||
```
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `tool_name: str` | Required, nonempty name of the tool or application delivering the message. |
|
||||
| `content` | Required text, or a sequence of structured content dictionaries or generated `FunctionCallOutputContentItem` models. Structured image content requires inline data URLs. |
|
||||
| `namespace: str | None = None` | Optional namespace for the tool name. |
|
||||
|
||||
Pass one `ExternalMessage` as the complete input to `run(...)` or `turn(...)`.
|
||||
It starts a turn when the thread is idle or joins an active regular turn. It
|
||||
appears in saved history and item notifications as a `functionCallOutput`
|
||||
item, retaining tool authority. No preceding tool call or call ID is required.
|
||||
Tool names and namespaces identify the source; they are not proof of its
|
||||
identity or permission to act.
|
||||
|
||||
When a message joins an active turn, both handles can stream or collect the
|
||||
result independently. A joining handle receives previously completed items and
|
||||
the latest usage, followed by live notifications. Consumed transient events such
|
||||
as token deltas are discarded. Both handles collect the complete result, and
|
||||
closing one stream leaves the other active.
|
||||
|
||||
The async calls use the same object:
|
||||
|
||||
```python
|
||||
result = await async_thread.run(message)
|
||||
```
|
||||
|
||||
Use `await async_thread.turn(message)` to collect a handle for streaming and
|
||||
interruption. An `ExternalMessage` cannot be mixed into a user-input list.
|
||||
`TurnHandle.steer(...)` accepts user input; deliver an external message to an
|
||||
active turn through `thread.turn(message)`.
|
||||
|
||||
See the [external message examples](../examples/16_external_message) for a user
|
||||
request followed by an external notification.
|
||||
|
||||
## Public Types
|
||||
|
||||
The SDK wrappers return and accept public Codex protocol models wherever possible:
|
||||
|
||||
Reference in New Issue
Block a user