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:
@@ -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:
|
||||
|
||||
@@ -37,6 +37,36 @@ Choose `run()` for most apps. Choose `stream()` for progress UIs, custom timeout
|
||||
|
||||
If your app is not already async, stay with `Codex`.
|
||||
|
||||
## How do I pass untrusted external content?
|
||||
|
||||
Use `ExternalMessage` for messages from other agents, tools, or applications:
|
||||
|
||||
```python
|
||||
from openai_codex import ExternalMessage
|
||||
|
||||
result = thread.run(ExternalMessage(
|
||||
tool_name="notifications",
|
||||
namespace="slack",
|
||||
content="Deployment notification: the staging checks failed.",
|
||||
))
|
||||
```
|
||||
|
||||
The content has tool-level authority, below user and developer instructions.
|
||||
It does not authorize actions or approve requests. Establish the user's task
|
||||
separately and keep the thread's sandbox and approval policies in place.
|
||||
Plain strings and `TextInput` represent user input.
|
||||
|
||||
An external message starts a turn or joins an active regular turn and is
|
||||
preserved in history. Pass it as the entire input to `thread.run(...)` or
|
||||
`thread.turn(...)`; the async methods accept the same object. See the
|
||||
[API reference](api-reference.md#externalmessage) and
|
||||
[runnable example](../examples/16_external_message).
|
||||
|
||||
External messages and the new `include_turns`, `turn_service_tier`, and `source`
|
||||
options require CLI 0.151.0 or newer. If a custom executable is too old, the SDK
|
||||
raises `CodexError` before sending the request. Upgrade that executable or use
|
||||
the runtime installed with a matching SDK release.
|
||||
|
||||
## Does `include_turns=False` remove the conversation's context?
|
||||
|
||||
No. On `thread_resume(...)` and `thread_fork(...)`, it only skips loading turn
|
||||
|
||||
@@ -70,6 +70,11 @@ with Codex() as codex:
|
||||
Use `Thread.turn(...)` when you need a `TurnHandle` for streaming, steering,
|
||||
or interrupting an active turn.
|
||||
|
||||
For **untrusted content** from another agent, tool, or application, pass an
|
||||
[`ExternalMessage`](api-reference.md#externalmessage). It retains tool-level
|
||||
authority and does not establish user authorization or approval. Plain strings
|
||||
and `TextInput` represent user input.
|
||||
|
||||
## 4. Choose Sandbox Access
|
||||
|
||||
Use one enum for the initial thread and later turn overrides:
|
||||
|
||||
Reference in New Issue
Block a user