2025-05-16 14:38:08 -07:00
use std ::collections ::HashMap ;
2026-01-12 15:12:59 -08:00
use std ::path ::Path ;
2026-02-24 09:48:57 -08:00
use std ::path ::PathBuf ;
2025-05-16 14:38:08 -07:00
2025-10-27 16:58:10 +00:00
use codex_utils_image ::load_and_resize_to_fit ;
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
use serde ::Deserialize ;
2025-07-23 10:37:45 -07:00
use serde ::Deserializer ;
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
use serde ::Serialize ;
2025-05-07 08:37:48 -07:00
use serde ::ser ::Serializer ;
2025-09-08 14:54:47 -07:00
use ts_rs ::TS ;
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
2026-01-17 17:31:14 -08:00
use crate ::config_types ::CollaborationMode ;
2026-01-12 15:12:59 -08:00
use crate ::config_types ::SandboxMode ;
use crate ::protocol ::AskForApproval ;
2026-01-17 17:31:14 -08:00
use crate ::protocol ::COLLABORATION_MODE_CLOSE_TAG ;
use crate ::protocol ::COLLABORATION_MODE_OPEN_TAG ;
2026-01-12 15:12:59 -08:00
use crate ::protocol ::NetworkAccess ;
use crate ::protocol ::SandboxPolicy ;
use crate ::protocol ::WritableRoot ;
2025-10-20 13:34:44 -07:00
use crate ::user_input ::UserInput ;
2026-01-28 01:43:17 -07:00
use codex_execpolicy ::Policy ;
2025-10-29 12:11:44 +00:00
use codex_git ::GhostCommit ;
2025-10-27 16:58:10 +00:00
use codex_utils_image ::error ::ImageProcessingError ;
2025-10-20 11:45:11 -07:00
use schemars ::JsonSchema ;
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
use crate ::mcp ::CallToolResult ;
2025-12-10 09:18:48 -08:00
/// Controls whether a command should use the session sandbox or bypass it.
#[ derive(
Debug , Clone , Copy , Default , Eq , Hash , PartialEq , Serialize , Deserialize , JsonSchema , TS ,
) ]
#[ serde(rename_all = " snake_case " ) ]
pub enum SandboxPermissions {
/// Run with the configured sandbox
#[ default ]
UseDefault ,
/// Request to run outside the sandbox
RequireEscalated ,
2026-02-24 09:48:57 -08:00
/// Request to run in the sandbox with additional per-command permissions.
WithAdditionalPermissions ,
2025-12-10 09:18:48 -08:00
}
impl SandboxPermissions {
2026-02-24 09:48:57 -08:00
/// True if SandboxPermissions requires full unsandboxed execution (i.e. RequireEscalated)
2025-12-10 09:18:48 -08:00
pub fn requires_escalated_permissions ( self ) -> bool {
matches! ( self , SandboxPermissions ::RequireEscalated )
}
2026-02-24 09:48:57 -08:00
/// True if SandboxPermissions requires permissions beyond UseDefault
pub fn requires_additional_permissions ( self ) -> bool {
! matches! ( self , SandboxPermissions ::UseDefault )
}
}
#[ derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS) ]
2026-02-24 19:35:28 -08:00
pub struct FileSystemPermissions {
pub read : Option < Vec < PathBuf > > ,
pub write : Option < Vec < PathBuf > > ,
2026-02-24 09:48:57 -08:00
}
2026-02-24 19:35:28 -08:00
impl FileSystemPermissions {
2026-02-24 09:48:57 -08:00
pub fn is_empty ( & self ) -> bool {
2026-02-24 19:35:28 -08:00
self . read . is_none ( ) & & self . write . is_none ( )
}
}
#[ derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS) ]
pub struct MacOsPermissions {
pub preferences : Option < MacOsPreferencesValue > ,
pub automations : Option < MacOsAutomationValue > ,
pub accessibility : Option < bool > ,
pub calendar : Option < bool > ,
}
impl MacOsPermissions {
pub fn is_empty ( & self ) -> bool {
self . preferences . is_none ( )
& & self . automations . is_none ( )
& & self . accessibility . is_none ( )
& & self . calendar . is_none ( )
}
}
#[ derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS) ]
#[ serde(untagged) ]
pub enum MacOsPreferencesValue {
Bool ( bool ) ,
Mode ( String ) ,
}
#[ derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS) ]
#[ serde(untagged) ]
pub enum MacOsAutomationValue {
Bool ( bool ) ,
BundleIds ( Vec < String > ) ,
}
#[ derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS) ]
pub struct PermissionProfile {
pub network : Option < bool > ,
pub file_system : Option < FileSystemPermissions > ,
pub macos : Option < MacOsPermissions > ,
}
impl PermissionProfile {
pub fn is_empty ( & self ) -> bool {
self . network . is_none ( )
& & self
. file_system
. as_ref ( )
. map ( FileSystemPermissions ::is_empty )
. unwrap_or ( true )
& & self
. macos
. as_ref ( )
. map ( MacOsPermissions ::is_empty )
. unwrap_or ( true )
2026-02-24 09:48:57 -08:00
}
2025-12-10 09:18:48 -08:00
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
#[ serde(tag = " type " , rename_all = " snake_case " ) ]
pub enum ResponseInputItem {
Message {
role : String ,
content : Vec < ContentItem > ,
} ,
FunctionCallOutput {
call_id : String ,
output : FunctionCallOutputPayload ,
} ,
fix: introduce ResponseInputItem::McpToolCallOutput variant (#1151)
The output of an MCP server tool call can be one of several types, but
to date, we treated all outputs as text by showing the serialized JSON
as the "tool output" in Codex:
https://github.com/openai/codex/blob/25a9949c49194d5a64de54a11bcc5b4724ac9bd5/codex-rs/mcp-types/src/lib.rs#L96-L101
This PR adds support for the `ImageContent` variant so we can now
display an image output from an MCP tool call.
In making this change, we introduce a new
`ResponseInputItem::McpToolCallOutput` variant so that we can work with
the `mcp_types::CallToolResult` directly when the function call is made
to an MCP server.
Though arguably the more significant change is the introduction of
`HistoryCell::CompletedMcpToolCallWithImageOutput`, which is a cell that
uses `ratatui_image` to render an image into the terminal. To support
this, we introduce `ImageRenderCache`, cache a
`ratatui_image::picker::Picker`, and `ensure_image_cache()` to cache the
appropriate scaled image data and dimensions based on the current
terminal size.
To test, I created a minimal `package.json`:
```json
{
"name": "kitty-mcp",
"version": "1.0.0",
"type": "module",
"description": "MCP that returns image of kitty",
"main": "index.js",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0"
}
}
```
with the following `index.js` to define the MCP server:
```js
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
const IMAGE_URI = "image://Ada.png";
const server = new McpServer({
name: "Demo",
version: "1.0.0",
});
server.tool(
"get-cat-image",
"If you need a cat image, this tool will provide one.",
async () => ({
content: [
{ type: "image", data: await getAdaPngBase64(), mimeType: "image/png" },
],
})
);
server.resource("Ada the Cat", IMAGE_URI, async (uri) => {
const base64Image = await getAdaPngBase64();
return {
contents: [
{
uri: uri.href,
mimeType: "image/png",
blob: base64Image,
},
],
};
});
async function getAdaPngBase64() {
const __dirname = new URL(".", import.meta.url).pathname;
// From https://github.com/benjajaja/ratatui-image/blob/9705ce2c59ec669abbce2924cbfd1f5ae22c9860/assets/Ada.png
const filePath = join(__dirname, "Ada.png");
const imageData = await readFile(filePath);
const base64Image = imageData.toString("base64");
return base64Image;
}
const transport = new StdioServerTransport();
await server.connect(transport);
```
With the local changes from this PR, I added the following to my
`config.toml`:
```toml
[mcp_servers.kitty]
command = "node"
args = ["/Users/mbolin/code/kitty-mcp/index.js"]
```
Running the TUI from source:
```
cargo run --bin codex -- --model o3 'I need a picture of a cat'
```
I get:
<img width="732" alt="image"
src="https://github.com/user-attachments/assets/bf80b721-9ca0-4d81-aec7-77d6899e2869"
/>
Now, that said, I have only tested in iTerm and there is definitely some
funny business with getting an accurate character-to-pixel ratio
(sometimes the `CompletedMcpToolCallWithImageOutput` thinks it needs 10
rows to render instead of 4), so there is still work to be done here.
2025-05-28 19:03:17 -07:00
McpToolCallOutput {
call_id : String ,
result : Result < CallToolResult , String > ,
} ,
2025-08-22 13:42:34 -07:00
CustomToolCallOutput {
call_id : String ,
output : String ,
} ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
#[ serde(tag = " type " , rename_all = " snake_case " ) ]
pub enum ContentItem {
InputText { text : String } ,
InputImage { image_url : String } ,
OutputText { text : String } ,
}
2026-02-02 18:52:26 -08:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS) ]
#[ serde(rename_all = " snake_case " ) ]
2026-02-06 18:39:52 -08:00
/// Classifies an assistant message as interim commentary or final answer text.
///
/// Providers do not emit this consistently, so callers must treat `None` as
/// "phase unknown" and keep compatibility behavior for legacy models.
2026-02-02 18:52:26 -08:00
pub enum MessagePhase {
2026-02-06 18:39:52 -08:00
/// Mid-turn assistant text (for example preamble/progress narration).
///
/// Additional tool calls or assistant output may follow before turn
/// completion.
2026-02-02 18:52:26 -08:00
Commentary ,
2026-02-06 18:39:52 -08:00
/// The assistant's terminal answer text for the current turn.
2026-02-02 18:52:26 -08:00
FinalAnswer ,
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
#[ serde(tag = " type " , rename_all = " snake_case " ) ]
pub enum ResponseItem {
Message {
2025-10-30 11:18:53 -07:00
#[ serde(default, skip_serializing) ]
#[ ts(skip) ]
2025-07-23 10:37:45 -07:00
id : Option < String > ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
role : String ,
content : Vec < ContentItem > ,
2026-01-22 09:27:48 -08:00
// Do not use directly, no available consistently across all providers.
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
end_turn : Option < bool > ,
2026-02-02 18:52:26 -08:00
// Optional output-message phase (for example: "commentary", "final_answer").
2026-02-06 18:39:52 -08:00
// Availability varies by provider/model, so downstream consumers must
// preserve fallback behavior when this is absent.
2026-02-02 18:52:26 -08:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
phase : Option < MessagePhase > ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
} ,
2025-05-10 21:43:27 -07:00
Reasoning {
2025-09-09 14:47:06 -07:00
#[ serde(default, skip_serializing) ]
2025-10-30 11:18:53 -07:00
#[ ts(skip) ]
2025-05-10 21:43:27 -07:00
id : String ,
summary : Vec < ReasoningItemReasoningSummary > ,
2025-08-13 18:39:58 -07:00
#[ serde(default, skip_serializing_if = " should_serialize_reasoning_content " ) ]
2025-10-30 11:18:53 -07:00
#[ ts(optional) ]
2025-08-05 01:56:13 -07:00
content : Option < Vec < ReasoningItemContent > > ,
2025-07-23 10:37:45 -07:00
encrypted_content : Option < String > ,
2025-05-10 21:43:27 -07:00
} ,
2025-05-16 14:38:08 -07:00
LocalShellCall {
2026-02-03 11:31:57 +00:00
/// Legacy id field retained for compatibility with older payloads.
2025-10-30 11:18:53 -07:00
#[ serde(default, skip_serializing) ]
#[ ts(skip) ]
2025-05-16 14:38:08 -07:00
id : Option < String > ,
/// Set when using the Responses API.
call_id : Option < String > ,
status : LocalShellStatus ,
action : LocalShellAction ,
} ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
FunctionCall {
2025-10-30 11:18:53 -07:00
#[ serde(default, skip_serializing) ]
#[ ts(skip) ]
2025-07-23 10:37:45 -07:00
id : Option < String > ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
name : String ,
// The Responses API returns the function call arguments as a *string* that contains
// JSON, not as an already‑ parsed object. We keep it as a raw string here and let
2026-02-03 11:31:57 +00:00
// Session::handle_function_call parse it into a Value.
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
arguments : String ,
call_id : String ,
} ,
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
// NOTE: The `output` field for `function_call_output` uses a dedicated payload type with
// custom serialization. On the wire it is either:
// - a plain string (`content`)
// - an array of structured content items (`content_items`)
// We keep this behavior centralized in `FunctionCallOutputPayload`.
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
FunctionCallOutput {
call_id : String ,
output : FunctionCallOutputPayload ,
} ,
2025-08-22 13:42:34 -07:00
CustomToolCall {
2025-10-30 11:18:53 -07:00
#[ serde(default, skip_serializing) ]
#[ ts(skip) ]
2025-08-22 13:42:34 -07:00
id : Option < String > ,
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
2025-10-30 11:18:53 -07:00
#[ ts(optional) ]
2025-08-22 13:42:34 -07:00
status : Option < String > ,
call_id : String ,
name : String ,
input : String ,
} ,
CustomToolCallOutput {
call_id : String ,
output : String ,
} ,
2025-08-28 19:24:38 -07:00
// Emitted by the Responses API when the agent triggers a web search.
// Example payload (from SSE `response.output_item.done`):
// {
// "id":"ws_...",
// "type":"web_search_call",
// "status":"completed",
// "action": {"type":"search","query":"weather: San Francisco, CA"}
// }
WebSearchCall {
2025-10-30 11:18:53 -07:00
#[ serde(default, skip_serializing) ]
#[ ts(skip) ]
2025-08-28 19:24:38 -07:00
id : Option < String > ,
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
2025-10-30 11:18:53 -07:00
#[ ts(optional) ]
2025-08-28 19:24:38 -07:00
status : Option < String > ,
2026-01-26 19:33:48 -08:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
action : Option < WebSearchAction > ,
2025-08-28 19:24:38 -07:00
} ,
2025-10-27 10:09:10 +00:00
// Generated by the harness but considered exactly as a model response.
GhostSnapshot {
2025-10-27 10:55:29 +00:00
ghost_commit : GhostCommit ,
2025-10-27 10:09:10 +00:00
} ,
2025-12-12 10:05:02 -08:00
#[ serde(alias = " compaction_summary " ) ]
Compaction {
2025-11-18 16:51:16 +00:00
encrypted_content : String ,
} ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
#[ serde(other) ]
Other ,
}
2026-01-19 21:59:36 -08:00
pub const BASE_INSTRUCTIONS_DEFAULT : & str = include_str! ( " prompts/base_instructions/default.md " ) ;
/// Base instructions for the model in a thread. Corresponds to the `instructions` field in the ResponsesAPI.
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
#[ serde(rename = " base_instructions " , rename_all = " snake_case " ) ]
pub struct BaseInstructions {
pub text : String ,
}
impl Default for BaseInstructions {
fn default ( ) -> Self {
Self {
text : BASE_INSTRUCTIONS_DEFAULT . to_string ( ) ,
}
}
}
2026-01-12 15:12:59 -08:00
/// Developer-provided guidance that is injected into a turn as a developer role
/// message.
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
#[ serde(rename = " developer_instructions " , rename_all = " snake_case " ) ]
pub struct DeveloperInstructions {
text : String ,
}
const APPROVAL_POLICY_NEVER : & str = include_str! ( " prompts/permissions/approval_policy/never.md " ) ;
const APPROVAL_POLICY_UNLESS_TRUSTED : & str =
include_str! ( " prompts/permissions/approval_policy/unless_trusted.md " ) ;
const APPROVAL_POLICY_ON_FAILURE : & str =
include_str! ( " prompts/permissions/approval_policy/on_failure.md " ) ;
2026-01-28 01:43:17 -07:00
const APPROVAL_POLICY_ON_REQUEST_RULE : & str =
include_str! ( " prompts/permissions/approval_policy/on_request_rule.md " ) ;
2026-02-24 09:48:57 -08:00
const APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION : & str =
include_str! ( " prompts/permissions/approval_policy/on_request_rule_request_permission.md " ) ;
2026-01-12 15:12:59 -08:00
const SANDBOX_MODE_DANGER_FULL_ACCESS : & str =
include_str! ( " prompts/permissions/sandbox_mode/danger_full_access.md " ) ;
const SANDBOX_MODE_WORKSPACE_WRITE : & str =
include_str! ( " prompts/permissions/sandbox_mode/workspace_write.md " ) ;
const SANDBOX_MODE_READ_ONLY : & str = include_str! ( " prompts/permissions/sandbox_mode/read_only.md " ) ;
impl DeveloperInstructions {
pub fn new < T : Into < String > > ( text : T ) -> Self {
Self { text : text . into ( ) }
}
2026-02-24 09:48:57 -08:00
pub fn from (
approval_policy : AskForApproval ,
exec_policy : & Policy ,
request_permission_enabled : bool ,
) -> DeveloperInstructions {
2026-02-19 11:41:49 -08:00
let on_request_instructions = | | {
2026-02-24 09:48:57 -08:00
let on_request_rule = if request_permission_enabled {
APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION
} else {
APPROVAL_POLICY_ON_REQUEST_RULE
} ;
2026-02-19 11:41:49 -08:00
let command_prefixes = format_allow_prefixes ( exec_policy . get_allowed_prefixes ( ) ) ;
match command_prefixes {
Some ( prefixes ) = > {
format! (
2026-02-24 09:48:57 -08:00
" {on_request_rule} \n ## Approved command prefixes \n The following prefix rules have already been approved: {prefixes} "
2026-02-19 11:41:49 -08:00
)
}
2026-02-24 09:48:57 -08:00
None = > on_request_rule . to_string ( ) ,
2026-02-19 11:41:49 -08:00
}
} ;
2026-01-28 01:43:17 -07:00
let text = match approval_policy {
AskForApproval ::Never = > APPROVAL_POLICY_NEVER . to_string ( ) ,
AskForApproval ::UnlessTrusted = > APPROVAL_POLICY_UNLESS_TRUSTED . to_string ( ) ,
AskForApproval ::OnFailure = > APPROVAL_POLICY_ON_FAILURE . to_string ( ) ,
2026-02-19 11:41:49 -08:00
AskForApproval ::OnRequest = > on_request_instructions ( ) ,
AskForApproval ::Reject ( reject_config ) = > {
let on_request_instructions = on_request_instructions ( ) ;
let sandbox_approval = reject_config . sandbox_approval ;
let rules = reject_config . rules ;
let mcp_elicitations = reject_config . mcp_elicitations ;
format! (
" {on_request_instructions} \n \n \
Approval policy is ` reject ` . \ n \
- ` sandbox_approval ` : { sandbox_approval } \ n \
- ` rules ` : { rules } \ n \
- ` mcp_elicitations ` : { mcp_elicitations } \ n \
When a category is ` true ` , requests in that category are auto - rejected instead of prompting the user . "
)
2026-01-28 01:43:17 -07:00
}
} ;
DeveloperInstructions ::new ( text )
}
2026-01-12 15:12:59 -08:00
pub fn into_text ( self ) -> String {
self . text
}
pub fn concat ( self , other : impl Into < DeveloperInstructions > ) -> Self {
let mut text = self . text ;
2026-01-28 01:43:17 -07:00
if ! text . ends_with ( '\n' ) {
text . push ( '\n' ) ;
}
2026-01-12 15:12:59 -08:00
text . push_str ( & other . into ( ) . text ) ;
Self { text }
}
2026-02-04 21:50:38 -08:00
pub fn model_switch_message ( model_instructions : String ) -> Self {
DeveloperInstructions ::new ( format! (
" <model_switch> \n The user was previously using a different model. Please continue the conversation according to the following instructions: \n \n {model_instructions} \n </model_switch> "
) )
}
2026-01-22 12:04:23 -08:00
pub fn personality_spec_message ( spec : String ) -> Self {
let message = format! (
" <personality_spec> The user has requested a new communication style. Future messages should adhere to the following personality: \n {spec} </personality_spec> "
) ;
DeveloperInstructions ::new ( message )
}
2026-01-12 15:12:59 -08:00
pub fn from_policy (
sandbox_policy : & SandboxPolicy ,
approval_policy : AskForApproval ,
2026-01-28 01:43:17 -07:00
exec_policy : & Policy ,
2026-01-12 15:12:59 -08:00
cwd : & Path ,
2026-02-24 09:48:57 -08:00
request_permission_enabled : bool ,
2026-01-12 15:12:59 -08:00
) -> Self {
let network_access = if sandbox_policy . has_full_network_access ( ) {
NetworkAccess ::Enabled
} else {
NetworkAccess ::Restricted
} ;
let ( sandbox_mode , writable_roots ) = match sandbox_policy {
SandboxPolicy ::DangerFullAccess = > ( SandboxMode ::DangerFullAccess , None ) ,
2026-02-11 18:31:14 -08:00
SandboxPolicy ::ReadOnly { .. } = > ( SandboxMode ::ReadOnly , None ) ,
2026-01-12 15:12:59 -08:00
SandboxPolicy ::ExternalSandbox { .. } = > ( SandboxMode ::DangerFullAccess , None ) ,
SandboxPolicy ::WorkspaceWrite { .. } = > {
let roots = sandbox_policy . get_writable_roots_with_cwd ( cwd ) ;
( SandboxMode ::WorkspaceWrite , Some ( roots ) )
}
} ;
DeveloperInstructions ::from_permissions_with_network (
sandbox_mode ,
network_access ,
approval_policy ,
2026-01-28 01:43:17 -07:00
exec_policy ,
2026-01-12 15:12:59 -08:00
writable_roots ,
2026-02-24 09:48:57 -08:00
request_permission_enabled ,
2026-01-12 15:12:59 -08:00
)
}
2026-01-17 17:31:14 -08:00
/// Returns developer instructions from a collaboration mode if they exist and are non-empty.
pub fn from_collaboration_mode ( collaboration_mode : & CollaborationMode ) -> Option < Self > {
2026-01-23 17:00:23 -08:00
collaboration_mode
. settings
2026-01-17 17:31:14 -08:00
. developer_instructions
. as_ref ( )
. filter ( | instructions | ! instructions . is_empty ( ) )
. map ( | instructions | {
DeveloperInstructions ::new ( format! (
" {COLLABORATION_MODE_OPEN_TAG}{instructions}{COLLABORATION_MODE_CLOSE_TAG} "
) )
} )
}
2026-01-12 15:12:59 -08:00
fn from_permissions_with_network (
sandbox_mode : SandboxMode ,
network_access : NetworkAccess ,
approval_policy : AskForApproval ,
2026-01-28 01:43:17 -07:00
exec_policy : & Policy ,
2026-01-12 15:12:59 -08:00
writable_roots : Option < Vec < WritableRoot > > ,
2026-02-24 09:48:57 -08:00
request_permission_enabled : bool ,
2026-01-12 15:12:59 -08:00
) -> Self {
let start_tag = DeveloperInstructions ::new ( " <permissions instructions> " ) ;
let end_tag = DeveloperInstructions ::new ( " </permissions instructions> " ) ;
start_tag
. concat ( DeveloperInstructions ::sandbox_text (
sandbox_mode ,
network_access ,
) )
2026-02-24 09:48:57 -08:00
. concat ( DeveloperInstructions ::from (
approval_policy ,
exec_policy ,
request_permission_enabled ,
) )
2026-01-12 15:12:59 -08:00
. concat ( DeveloperInstructions ::from_writable_roots ( writable_roots ) )
. concat ( end_tag )
}
fn from_writable_roots ( writable_roots : Option < Vec < WritableRoot > > ) -> Self {
let Some ( roots ) = writable_roots else {
return DeveloperInstructions ::new ( " " ) ;
} ;
if roots . is_empty ( ) {
return DeveloperInstructions ::new ( " " ) ;
}
let roots_list : Vec < String > = roots
. iter ( )
. map ( | r | format! ( " ` {} ` " , r . root . to_string_lossy ( ) ) )
. collect ( ) ;
let text = if roots_list . len ( ) = = 1 {
format! ( " The writable root is {} . " , roots_list [ 0 ] )
} else {
format! ( " The writable roots are {} . " , roots_list . join ( " , " ) )
} ;
DeveloperInstructions ::new ( text )
}
fn sandbox_text ( mode : SandboxMode , network_access : NetworkAccess ) -> DeveloperInstructions {
let template = match mode {
SandboxMode ::DangerFullAccess = > SANDBOX_MODE_DANGER_FULL_ACCESS . trim_end ( ) ,
SandboxMode ::WorkspaceWrite = > SANDBOX_MODE_WORKSPACE_WRITE . trim_end ( ) ,
SandboxMode ::ReadOnly = > SANDBOX_MODE_READ_ONLY . trim_end ( ) ,
} ;
let text = template . replace ( " {network_access} " , & network_access . to_string ( ) ) ;
DeveloperInstructions ::new ( text )
}
}
2026-02-01 18:26:15 -08:00
const MAX_RENDERED_PREFIXES : usize = 100 ;
const MAX_ALLOW_PREFIX_TEXT_BYTES : usize = 5000 ;
const TRUNCATED_MARKER : & str = " ... \n [Some commands were truncated] " ;
pub fn format_allow_prefixes ( prefixes : Vec < Vec < String > > ) -> Option < String > {
let mut truncated = false ;
if prefixes . len ( ) > MAX_RENDERED_PREFIXES {
truncated = true ;
}
let mut prefixes = prefixes ;
prefixes . sort_by ( | a , b | {
a . len ( )
. cmp ( & b . len ( ) )
. then_with ( | | prefix_combined_str_len ( a ) . cmp ( & prefix_combined_str_len ( b ) ) )
. then_with ( | | a . cmp ( b ) )
} ) ;
let full_text = prefixes
2026-01-28 01:43:17 -07:00
. into_iter ( )
2026-02-01 18:26:15 -08:00
. take ( MAX_RENDERED_PREFIXES )
. map ( | prefix | format! ( " - {} " , render_command_prefix ( & prefix ) ) )
. collect ::< Vec < _ > > ( )
. join ( " \n " ) ;
// truncate to last UTF8 char
let mut output = full_text ;
let byte_idx = output
. char_indices ( )
. nth ( MAX_ALLOW_PREFIX_TEXT_BYTES )
. map ( | ( i , _ ) | i ) ;
if let Some ( byte_idx ) = byte_idx {
truncated = true ;
output = output [ .. byte_idx ] . to_string ( ) ;
2026-01-28 01:43:17 -07:00
}
2026-02-01 18:26:15 -08:00
if truncated {
Some ( format! ( " {output} {TRUNCATED_MARKER} " ) )
} else {
Some ( output )
}
}
fn prefix_combined_str_len ( prefix : & [ String ] ) -> usize {
prefix . iter ( ) . map ( String ::len ) . sum ( )
2026-01-28 01:43:17 -07:00
}
fn render_command_prefix ( prefix : & [ String ] ) -> String {
let tokens = prefix
. iter ( )
. map ( | token | serde_json ::to_string ( token ) . unwrap_or_else ( | _ | format! ( " {token:?} " ) ) )
. collect ::< Vec < _ > > ( )
. join ( " , " ) ;
format! ( " [ {tokens} ] " )
}
2026-01-12 15:12:59 -08:00
impl From < DeveloperInstructions > for ResponseItem {
fn from ( di : DeveloperInstructions ) -> Self {
ResponseItem ::Message {
id : None ,
role : " developer " . to_string ( ) ,
content : vec ! [ ContentItem ::InputText {
text : di . into_text ( ) ,
} ] ,
2026-01-22 09:27:48 -08:00
end_turn : None ,
2026-02-02 18:52:26 -08:00
phase : None ,
2026-01-12 15:12:59 -08:00
}
}
}
impl From < SandboxMode > for DeveloperInstructions {
fn from ( mode : SandboxMode ) -> Self {
let network_access = match mode {
SandboxMode ::DangerFullAccess = > NetworkAccess ::Enabled ,
SandboxMode ::WorkspaceWrite | SandboxMode ::ReadOnly = > NetworkAccess ::Restricted ,
} ;
DeveloperInstructions ::sandbox_text ( mode , network_access )
}
}
2025-08-13 18:39:58 -07:00
fn should_serialize_reasoning_content ( content : & Option < Vec < ReasoningItemContent > > ) -> bool {
match content {
Some ( content ) = > ! content
. iter ( )
. any ( | c | matches! ( c , ReasoningItemContent ::ReasoningText { .. } ) ) ,
None = > false ,
}
}
2025-10-27 16:58:10 +00:00
fn local_image_error_placeholder (
path : & std ::path ::Path ,
error : impl std ::fmt ::Display ,
) -> ContentItem {
ContentItem ::InputText {
text : format ! (
" Codex could not read the local image at `{}`: {} " ,
path . display ( ) ,
error
) ,
}
}
Label attached images so agent can understand in-message labels (#8950)
Agent wouldn't "see" attached images and would instead try to use the
view_file tool:
<img width="1516" height="504" alt="image"
src="https://github.com/user-attachments/assets/68a705bb-f962-4fc1-9087-e932a6859b12"
/>
In this PR, we wrap image content items in XML tags with the name of
each image (now just a numbered name like `[Image #1]`), so that the
model can understand inline image references (based on name). We also
put the image content items above the user message which the model seems
to prefer (maybe it's more used to definitions being before references).
We also tweak the view_file tool description which seemed to help a bit
Results on a simple eval set of images:
Before
<img width="980" height="310" alt="image"
src="https://github.com/user-attachments/assets/ba838651-2565-4684-a12e-81a36641bf86"
/>
After
<img width="918" height="322" alt="image"
src="https://github.com/user-attachments/assets/10a81951-7ee6-415e-a27e-e7a3fd0aee6f"
/>
```json
[
{
"id": "single_describe",
"prompt": "Describe the attached image in one sentence.",
"images": ["image_a.png"]
},
{
"id": "single_color",
"prompt": "What is the dominant color in the image? Answer with a single color word.",
"images": ["image_b.png"]
},
{
"id": "orientation_check",
"prompt": "Is the image portrait or landscape? Answer in one sentence.",
"images": ["image_c.png"]
},
{
"id": "detail_request",
"prompt": "Look closely at the image and call out any small details you notice.",
"images": ["image_d.png"]
},
{
"id": "two_images_compare",
"prompt": "I attached two images. Are they the same or different? Briefly explain.",
"images": ["image_a.png", "image_b.png"]
},
{
"id": "two_images_captions",
"prompt": "Provide a short caption for each image (Image 1, Image 2).",
"images": ["image_c.png", "image_d.png"]
},
{
"id": "multi_image_rank",
"prompt": "Rank the attached images from most colorful to least colorful.",
"images": ["image_a.png", "image_b.png", "image_c.png"]
},
{
"id": "multi_image_choice",
"prompt": "Which image looks more vibrant? Answer with 'Image 1' or 'Image 2'.",
"images": ["image_b.png", "image_d.png"]
}
]
```
2026-01-09 21:33:45 -08:00
pub const VIEW_IMAGE_TOOL_NAME : & str = " view_image " ;
const IMAGE_OPEN_TAG : & str = " <image> " ;
const IMAGE_CLOSE_TAG : & str = " </image> " ;
const LOCAL_IMAGE_OPEN_TAG_PREFIX : & str = " <image name= " ;
const LOCAL_IMAGE_OPEN_TAG_SUFFIX : & str = " > " ;
const LOCAL_IMAGE_CLOSE_TAG : & str = IMAGE_CLOSE_TAG ;
pub fn image_open_tag_text ( ) -> String {
IMAGE_OPEN_TAG . to_string ( )
}
pub fn image_close_tag_text ( ) -> String {
IMAGE_CLOSE_TAG . to_string ( )
}
pub fn local_image_label_text ( label_number : usize ) -> String {
format! ( " [Image # {label_number} ] " )
}
pub fn local_image_open_tag_text ( label_number : usize ) -> String {
let label = local_image_label_text ( label_number ) ;
format! ( " {LOCAL_IMAGE_OPEN_TAG_PREFIX} {label} {LOCAL_IMAGE_OPEN_TAG_SUFFIX} " )
}
pub fn is_local_image_open_tag_text ( text : & str ) -> bool {
text . strip_prefix ( LOCAL_IMAGE_OPEN_TAG_PREFIX )
. is_some_and ( | rest | rest . ends_with ( LOCAL_IMAGE_OPEN_TAG_SUFFIX ) )
}
pub fn is_local_image_close_tag_text ( text : & str ) -> bool {
is_image_close_tag_text ( text )
}
pub fn is_image_open_tag_text ( text : & str ) -> bool {
text = = IMAGE_OPEN_TAG
}
pub fn is_image_close_tag_text ( text : & str ) -> bool {
text = = IMAGE_CLOSE_TAG
}
2025-11-17 17:10:53 +01:00
fn invalid_image_error_placeholder (
path : & std ::path ::Path ,
error : impl std ::fmt ::Display ,
) -> ContentItem {
ContentItem ::InputText {
text : format ! (
" Image located at `{}` is invalid: {} " ,
path . display ( ) ,
error
) ,
}
}
2025-12-10 02:28:41 +08:00
fn unsupported_image_error_placeholder ( path : & std ::path ::Path , mime : & str ) -> ContentItem {
ContentItem ::InputText {
text : format ! (
" Codex cannot attach image at `{}`: unsupported image format `{}`. " ,
path . display ( ) ,
mime
) ,
}
}
Label attached images so agent can understand in-message labels (#8950)
Agent wouldn't "see" attached images and would instead try to use the
view_file tool:
<img width="1516" height="504" alt="image"
src="https://github.com/user-attachments/assets/68a705bb-f962-4fc1-9087-e932a6859b12"
/>
In this PR, we wrap image content items in XML tags with the name of
each image (now just a numbered name like `[Image #1]`), so that the
model can understand inline image references (based on name). We also
put the image content items above the user message which the model seems
to prefer (maybe it's more used to definitions being before references).
We also tweak the view_file tool description which seemed to help a bit
Results on a simple eval set of images:
Before
<img width="980" height="310" alt="image"
src="https://github.com/user-attachments/assets/ba838651-2565-4684-a12e-81a36641bf86"
/>
After
<img width="918" height="322" alt="image"
src="https://github.com/user-attachments/assets/10a81951-7ee6-415e-a27e-e7a3fd0aee6f"
/>
```json
[
{
"id": "single_describe",
"prompt": "Describe the attached image in one sentence.",
"images": ["image_a.png"]
},
{
"id": "single_color",
"prompt": "What is the dominant color in the image? Answer with a single color word.",
"images": ["image_b.png"]
},
{
"id": "orientation_check",
"prompt": "Is the image portrait or landscape? Answer in one sentence.",
"images": ["image_c.png"]
},
{
"id": "detail_request",
"prompt": "Look closely at the image and call out any small details you notice.",
"images": ["image_d.png"]
},
{
"id": "two_images_compare",
"prompt": "I attached two images. Are they the same or different? Briefly explain.",
"images": ["image_a.png", "image_b.png"]
},
{
"id": "two_images_captions",
"prompt": "Provide a short caption for each image (Image 1, Image 2).",
"images": ["image_c.png", "image_d.png"]
},
{
"id": "multi_image_rank",
"prompt": "Rank the attached images from most colorful to least colorful.",
"images": ["image_a.png", "image_b.png", "image_c.png"]
},
{
"id": "multi_image_choice",
"prompt": "Which image looks more vibrant? Answer with 'Image 1' or 'Image 2'.",
"images": ["image_b.png", "image_d.png"]
}
]
```
2026-01-09 21:33:45 -08:00
pub fn local_image_content_items_with_label_number (
path : & std ::path ::Path ,
label_number : Option < usize > ,
) -> Vec < ContentItem > {
match load_and_resize_to_fit ( path ) {
Ok ( image ) = > {
let mut items = Vec ::with_capacity ( 3 ) ;
if let Some ( label_number ) = label_number {
items . push ( ContentItem ::InputText {
text : local_image_open_tag_text ( label_number ) ,
} ) ;
}
items . push ( ContentItem ::InputImage {
image_url : image . into_data_url ( ) ,
} ) ;
if label_number . is_some ( ) {
items . push ( ContentItem ::InputText {
text : LOCAL_IMAGE_CLOSE_TAG . to_string ( ) ,
} ) ;
}
items
}
Err ( err ) = > {
if matches! ( & err , ImageProcessingError ::Read { .. } ) {
vec! [ local_image_error_placeholder ( path , & err ) ]
} else if err . is_invalid_image ( ) {
vec! [ invalid_image_error_placeholder ( path , & err ) ]
} else {
let Some ( mime_guess ) = mime_guess ::from_path ( path ) . first ( ) else {
return vec! [ local_image_error_placeholder (
path ,
" unsupported MIME type (unknown) " ,
) ] ;
} ;
let mime = mime_guess . essence_str ( ) . to_owned ( ) ;
if ! mime . starts_with ( " image/ " ) {
return vec! [ local_image_error_placeholder (
path ,
format! ( " unsupported MIME type ` {mime} ` " ) ,
) ] ;
}
vec! [ unsupported_image_error_placeholder ( path , & mime ) ]
}
}
}
}
2025-04-25 12:08:18 -07:00
impl From < ResponseInputItem > for ResponseItem {
fn from ( item : ResponseInputItem ) -> Self {
match item {
2025-07-23 10:37:45 -07:00
ResponseInputItem ::Message { role , content } = > Self ::Message {
role ,
content ,
id : None ,
2026-01-22 09:27:48 -08:00
end_turn : None ,
2026-02-02 18:52:26 -08:00
phase : None ,
2025-07-23 10:37:45 -07:00
} ,
2025-04-25 12:08:18 -07:00
ResponseInputItem ::FunctionCallOutput { call_id , output } = > {
Self ::FunctionCallOutput { call_id , output }
}
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
ResponseInputItem ::McpToolCallOutput { call_id , result } = > {
let output = match result {
Ok ( result ) = > FunctionCallOutputPayload ::from ( & result ) ,
Err ( tool_call_err ) = > FunctionCallOutputPayload {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
body : FunctionCallOutputBody ::Text ( format! ( " err: {tool_call_err:?} " ) ) ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
success : Some ( false ) ,
} ,
} ;
Self ::FunctionCallOutput { call_id , output }
}
2025-08-22 13:42:34 -07:00
ResponseInputItem ::CustomToolCallOutput { call_id , output } = > {
Self ::CustomToolCallOutput { call_id , output }
}
2025-04-25 12:08:18 -07:00
}
}
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
2025-05-16 14:38:08 -07:00
#[ serde(rename_all = " snake_case " ) ]
pub enum LocalShellStatus {
Completed ,
InProgress ,
Incomplete ,
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
2025-05-16 14:38:08 -07:00
#[ serde(tag = " type " , rename_all = " snake_case " ) ]
pub enum LocalShellAction {
Exec ( LocalShellExecAction ) ,
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
2025-05-16 14:38:08 -07:00
pub struct LocalShellExecAction {
pub command : Vec < String > ,
pub timeout_ms : Option < u64 > ,
pub working_directory : Option < String > ,
pub env : Option < HashMap < String , String > > ,
pub user : Option < String > ,
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
2025-08-28 19:24:38 -07:00
#[ serde(tag = " type " , rename_all = " snake_case " ) ]
pub enum WebSearchAction {
Search {
2025-11-20 20:45:28 -08:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
query : Option < String > ,
2026-01-30 16:37:56 -08:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
queries : Option < Vec < String > > ,
2025-11-20 20:45:28 -08:00
} ,
OpenPage {
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
url : Option < String > ,
} ,
FindInPage {
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
url : Option < String > ,
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
pattern : Option < String > ,
2025-08-28 19:24:38 -07:00
} ,
2025-11-20 20:45:28 -08:00
2025-08-28 19:24:38 -07:00
#[ serde(other) ]
Other ,
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
2025-05-10 21:43:27 -07:00
#[ serde(tag = " type " , rename_all = " snake_case " ) ]
pub enum ReasoningItemReasoningSummary {
SummaryText { text : String } ,
}
2025-10-20 11:45:11 -07:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
2025-08-05 01:56:13 -07:00
#[ serde(tag = " type " , rename_all = " snake_case " ) ]
pub enum ReasoningItemContent {
ReasoningText { text : String } ,
2025-08-13 18:39:58 -07:00
Text { text : String } ,
2025-08-05 01:56:13 -07:00
}
2025-10-20 13:34:44 -07:00
impl From < Vec < UserInput > > for ResponseInputItem {
fn from ( items : Vec < UserInput > ) -> Self {
Label attached images so agent can understand in-message labels (#8950)
Agent wouldn't "see" attached images and would instead try to use the
view_file tool:
<img width="1516" height="504" alt="image"
src="https://github.com/user-attachments/assets/68a705bb-f962-4fc1-9087-e932a6859b12"
/>
In this PR, we wrap image content items in XML tags with the name of
each image (now just a numbered name like `[Image #1]`), so that the
model can understand inline image references (based on name). We also
put the image content items above the user message which the model seems
to prefer (maybe it's more used to definitions being before references).
We also tweak the view_file tool description which seemed to help a bit
Results on a simple eval set of images:
Before
<img width="980" height="310" alt="image"
src="https://github.com/user-attachments/assets/ba838651-2565-4684-a12e-81a36641bf86"
/>
After
<img width="918" height="322" alt="image"
src="https://github.com/user-attachments/assets/10a81951-7ee6-415e-a27e-e7a3fd0aee6f"
/>
```json
[
{
"id": "single_describe",
"prompt": "Describe the attached image in one sentence.",
"images": ["image_a.png"]
},
{
"id": "single_color",
"prompt": "What is the dominant color in the image? Answer with a single color word.",
"images": ["image_b.png"]
},
{
"id": "orientation_check",
"prompt": "Is the image portrait or landscape? Answer in one sentence.",
"images": ["image_c.png"]
},
{
"id": "detail_request",
"prompt": "Look closely at the image and call out any small details you notice.",
"images": ["image_d.png"]
},
{
"id": "two_images_compare",
"prompt": "I attached two images. Are they the same or different? Briefly explain.",
"images": ["image_a.png", "image_b.png"]
},
{
"id": "two_images_captions",
"prompt": "Provide a short caption for each image (Image 1, Image 2).",
"images": ["image_c.png", "image_d.png"]
},
{
"id": "multi_image_rank",
"prompt": "Rank the attached images from most colorful to least colorful.",
"images": ["image_a.png", "image_b.png", "image_c.png"]
},
{
"id": "multi_image_choice",
"prompt": "Which image looks more vibrant? Answer with 'Image 1' or 'Image 2'.",
"images": ["image_b.png", "image_d.png"]
}
]
```
2026-01-09 21:33:45 -08:00
let mut image_index = 0 ;
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
Self ::Message {
role : " user " . to_string ( ) ,
content : items
. into_iter ( )
Label attached images so agent can understand in-message labels (#8950)
Agent wouldn't "see" attached images and would instead try to use the
view_file tool:
<img width="1516" height="504" alt="image"
src="https://github.com/user-attachments/assets/68a705bb-f962-4fc1-9087-e932a6859b12"
/>
In this PR, we wrap image content items in XML tags with the name of
each image (now just a numbered name like `[Image #1]`), so that the
model can understand inline image references (based on name). We also
put the image content items above the user message which the model seems
to prefer (maybe it's more used to definitions being before references).
We also tweak the view_file tool description which seemed to help a bit
Results on a simple eval set of images:
Before
<img width="980" height="310" alt="image"
src="https://github.com/user-attachments/assets/ba838651-2565-4684-a12e-81a36641bf86"
/>
After
<img width="918" height="322" alt="image"
src="https://github.com/user-attachments/assets/10a81951-7ee6-415e-a27e-e7a3fd0aee6f"
/>
```json
[
{
"id": "single_describe",
"prompt": "Describe the attached image in one sentence.",
"images": ["image_a.png"]
},
{
"id": "single_color",
"prompt": "What is the dominant color in the image? Answer with a single color word.",
"images": ["image_b.png"]
},
{
"id": "orientation_check",
"prompt": "Is the image portrait or landscape? Answer in one sentence.",
"images": ["image_c.png"]
},
{
"id": "detail_request",
"prompt": "Look closely at the image and call out any small details you notice.",
"images": ["image_d.png"]
},
{
"id": "two_images_compare",
"prompt": "I attached two images. Are they the same or different? Briefly explain.",
"images": ["image_a.png", "image_b.png"]
},
{
"id": "two_images_captions",
"prompt": "Provide a short caption for each image (Image 1, Image 2).",
"images": ["image_c.png", "image_d.png"]
},
{
"id": "multi_image_rank",
"prompt": "Rank the attached images from most colorful to least colorful.",
"images": ["image_a.png", "image_b.png", "image_c.png"]
},
{
"id": "multi_image_choice",
"prompt": "Which image looks more vibrant? Answer with 'Image 1' or 'Image 2'.",
"images": ["image_b.png", "image_d.png"]
}
]
```
2026-01-09 21:33:45 -08:00
. flat_map ( | c | match c {
2026-01-14 16:41:50 -08:00
UserInput ::Text { text , .. } = > vec! [ ContentItem ::InputText { text } ] ,
tui: preserve remote image attachments across resume/backtrack (#10590)
## Summary
This PR makes app-server-provided image URLs first-class attachments in
TUI, so they survive resume/backtrack/history recall and are resubmitted
correctly.
<img width="715" height="491" alt="Screenshot 2026-02-12 at 8 27 08 PM"
src="https://github.com/user-attachments/assets/226cbd35-8f0c-4e51-a13e-459ef5dd1927"
/>
Can delete the attached image upon backtracking:
<img width="716" height="301" alt="Screenshot 2026-02-12 at 8 27 31 PM"
src="https://github.com/user-attachments/assets/4558d230-f1bd-4eed-a093-8e1ab9c6db27"
/>
In both history and composer, remote images are rendered as normal
`[Image #N]` placeholders, with numbering unified with local images.
## What changed
- Plumb remote image URLs through TUI message state:
- `UserHistoryCell`
- `BacktrackSelection`
- `ChatComposerHistory::HistoryEntry`
- `ChatWidget::UserMessage`
- Show remote images as placeholder rows inside the composer box (above
textarea), and in history cells.
- Support keyboard selection/deletion for remote image rows in composer
(`Up`/`Down`, `Delete`/`Backspace`).
- Preserve remote-image-only turns in local composer history (Up/Down
recall), including restore after backtrack.
- Ensure submit/queue/backtrack resubmit include remote images in model
input (`UserInput::Image`), and keep request shape stable for
remote-image-only turns.
- Keep image numbering contiguous across remote + local images:
- remote images occupy `[Image #1]..[Image #M]`
- local images start at `[Image #M+1]`
- deletion renumbers consistently.
- In protocol conversion, increment shared image index for remote images
too, so mixed remote/local image tags stay in a single sequence.
- Simplify restore logic to trust in-memory attachment order (no
placeholder-number parsing path).
- Backtrack/replay rollback handling now queues trims through
`AppEvent::ApplyThreadRollback` and syncs transcript overlay/deferred
lines after trims, so overlay/transcript state stays consistent.
- Trim trailing blank rendered lines from user history rendering to
avoid oversized blank padding.
## Docs + tests
- Updated: `docs/tui-chat-composer.md` (remote image flow,
selection/deletion, numbering offsets)
- Added/updated tests across `tui/src/chatwidget/tests.rs`,
`tui/src/app.rs`, `tui/src/app_backtrack.rs`, `tui/src/history_cell.rs`,
and `tui/src/bottom_pane/chat_composer.rs`
- Added snapshot coverage for remote image composer states, including
deleting the first of two remote images.
## Validation
- `just fmt`
- `cargo test -p codex-tui`
## Codex author
`codex fork 019c2636-1571-74a1-8471-15a3b1c3f49d`
2026-02-13 14:54:06 -08:00
UserInput ::Image { image_url } = > {
image_index + = 1 ;
vec! [
ContentItem ::InputText {
text : image_open_tag_text ( ) ,
} ,
ContentItem ::InputImage { image_url } ,
ContentItem ::InputText {
text : image_close_tag_text ( ) ,
} ,
]
}
Label attached images so agent can understand in-message labels (#8950)
Agent wouldn't "see" attached images and would instead try to use the
view_file tool:
<img width="1516" height="504" alt="image"
src="https://github.com/user-attachments/assets/68a705bb-f962-4fc1-9087-e932a6859b12"
/>
In this PR, we wrap image content items in XML tags with the name of
each image (now just a numbered name like `[Image #1]`), so that the
model can understand inline image references (based on name). We also
put the image content items above the user message which the model seems
to prefer (maybe it's more used to definitions being before references).
We also tweak the view_file tool description which seemed to help a bit
Results on a simple eval set of images:
Before
<img width="980" height="310" alt="image"
src="https://github.com/user-attachments/assets/ba838651-2565-4684-a12e-81a36641bf86"
/>
After
<img width="918" height="322" alt="image"
src="https://github.com/user-attachments/assets/10a81951-7ee6-415e-a27e-e7a3fd0aee6f"
/>
```json
[
{
"id": "single_describe",
"prompt": "Describe the attached image in one sentence.",
"images": ["image_a.png"]
},
{
"id": "single_color",
"prompt": "What is the dominant color in the image? Answer with a single color word.",
"images": ["image_b.png"]
},
{
"id": "orientation_check",
"prompt": "Is the image portrait or landscape? Answer in one sentence.",
"images": ["image_c.png"]
},
{
"id": "detail_request",
"prompt": "Look closely at the image and call out any small details you notice.",
"images": ["image_d.png"]
},
{
"id": "two_images_compare",
"prompt": "I attached two images. Are they the same or different? Briefly explain.",
"images": ["image_a.png", "image_b.png"]
},
{
"id": "two_images_captions",
"prompt": "Provide a short caption for each image (Image 1, Image 2).",
"images": ["image_c.png", "image_d.png"]
},
{
"id": "multi_image_rank",
"prompt": "Rank the attached images from most colorful to least colorful.",
"images": ["image_a.png", "image_b.png", "image_c.png"]
},
{
"id": "multi_image_choice",
"prompt": "Which image looks more vibrant? Answer with 'Image 1' or 'Image 2'.",
"images": ["image_b.png", "image_d.png"]
}
]
```
2026-01-09 21:33:45 -08:00
UserInput ::LocalImage { path } = > {
image_index + = 1 ;
local_image_content_items_with_label_number ( & path , Some ( image_index ) )
}
2026-01-28 19:51:58 -08:00
UserInput ::Skill { .. } | UserInput ::Mention { .. } = > Vec ::new ( ) , // Tool bodies are injected later in core
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
} )
. collect ::< Vec < ContentItem > > ( ) ,
}
}
}
2025-05-04 10:57:12 -07:00
/// If the `name` of a `ResponseItem::FunctionCall` is either `container.exec`
feat: shell_command tool (#6510)
This adds support for a new variant of the shell tool behind a flag. To
test, run `codex` with `--enable shell_command_tool`, which will
register the tool with Codex under the name `shell_command` that accepts
the following shape:
```python
{
command: str
workdir: str | None,
timeout_ms: int | None,
with_escalated_permissions: bool | None,
justification: str | None,
}
```
This is comparable to the existing tool registered under
`shell`/`container.exec`. The primary difference is that it accepts
`command` as a `str` instead of a `str[]`. The `shell_command` tool
executes by running `execvp(["bash", "-lc", command])`, though the exact
arguments to `execvp(3)` depend on the user's default shell.
The hypothesis is that this will simplify things for the model. For
example, on Windows, instead of generating:
```json
{"command": ["pwsh.exe", "-NoLogo", "-Command", "ls -Name"]}
```
The model could simply generate:
```json
{"command": "ls -Name"}
```
As part of this change, I extracted some logic out of `user_shell.rs` as
`Shell::derive_exec_args()` so that it can be reused in
`codex-rs/core/src/tools/handlers/shell.rs`. Note the original code
generated exec arg lists like:
```javascript
["bash", "-lc", command]
["zsh", "-lc", command]
["pwsh.exe", "-NoProfile", "-Command", command]
```
Using `-l` for Bash and Zsh, but then specifying `-NoProfile` for
PowerShell seemed inconsistent to me, so I changed this in the new
implementation while also adding a `use_login_shell: bool` option to
make this explicit. If we decide to add a `login: bool` to
`ShellCommandToolCallParams` like we have for unified exec:
https://github.com/openai/codex/blob/807e2c27f0a9f2e85c50e7e6df5533f0d9b853c7/codex-rs/core/src/tools/handlers/unified_exec.rs#L33-L34
Then this should make it straightforward to support.
2025-11-12 08:18:57 -08:00
/// or `shell`, the `arguments` field should deserialize to this struct.
2025-10-20 11:45:11 -07:00
#[ derive(Deserialize, Debug, Clone, PartialEq, JsonSchema, TS) ]
2025-05-04 10:57:12 -07:00
pub struct ShellToolCallParams {
pub command : Vec < String > ,
pub workdir : Option < String > ,
2025-08-21 19:58:07 -07:00
/// This is the maximum time in milliseconds that the command is allowed to run.
#[ serde(alias = " timeout " ) ]
2025-05-04 10:57:12 -07:00
pub timeout_ms : Option < u64 > ,
2025-12-10 09:18:48 -08:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
pub sandbox_permissions : Option < SandboxPermissions > ,
2026-01-28 01:43:17 -07:00
/// Suggests a command prefix to persist for future sessions
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
pub prefix_rule : Option < Vec < String > > ,
2026-02-24 09:48:57 -08:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
2026-02-24 19:35:28 -08:00
pub additional_permissions : Option < PermissionProfile > ,
feat: shell_command tool (#6510)
This adds support for a new variant of the shell tool behind a flag. To
test, run `codex` with `--enable shell_command_tool`, which will
register the tool with Codex under the name `shell_command` that accepts
the following shape:
```python
{
command: str
workdir: str | None,
timeout_ms: int | None,
with_escalated_permissions: bool | None,
justification: str | None,
}
```
This is comparable to the existing tool registered under
`shell`/`container.exec`. The primary difference is that it accepts
`command` as a `str` instead of a `str[]`. The `shell_command` tool
executes by running `execvp(["bash", "-lc", command])`, though the exact
arguments to `execvp(3)` depend on the user's default shell.
The hypothesis is that this will simplify things for the model. For
example, on Windows, instead of generating:
```json
{"command": ["pwsh.exe", "-NoLogo", "-Command", "ls -Name"]}
```
The model could simply generate:
```json
{"command": "ls -Name"}
```
As part of this change, I extracted some logic out of `user_shell.rs` as
`Shell::derive_exec_args()` so that it can be reused in
`codex-rs/core/src/tools/handlers/shell.rs`. Note the original code
generated exec arg lists like:
```javascript
["bash", "-lc", command]
["zsh", "-lc", command]
["pwsh.exe", "-NoProfile", "-Command", command]
```
Using `-l` for Bash and Zsh, but then specifying `-NoProfile` for
PowerShell seemed inconsistent to me, so I changed this in the new
implementation while also adding a `use_login_shell: bool` option to
make this explicit. If we decide to add a `login: bool` to
`ShellCommandToolCallParams` like we have for unified exec:
https://github.com/openai/codex/blob/807e2c27f0a9f2e85c50e7e6df5533f0d9b853c7/codex-rs/core/src/tools/handlers/unified_exec.rs#L33-L34
Then this should make it straightforward to support.
2025-11-12 08:18:57 -08:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub justification : Option < String > ,
}
/// If the `name` of a `ResponseItem::FunctionCall` is `shell_command`, the
/// `arguments` field should deserialize to this struct.
#[ derive(Deserialize, Debug, Clone, PartialEq, JsonSchema, TS) ]
pub struct ShellCommandToolCallParams {
pub command : String ,
pub workdir : Option < String > ,
2025-12-05 11:03:25 -08:00
/// Whether to run the shell with login shell semantics
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub login : Option < bool > ,
feat: shell_command tool (#6510)
This adds support for a new variant of the shell tool behind a flag. To
test, run `codex` with `--enable shell_command_tool`, which will
register the tool with Codex under the name `shell_command` that accepts
the following shape:
```python
{
command: str
workdir: str | None,
timeout_ms: int | None,
with_escalated_permissions: bool | None,
justification: str | None,
}
```
This is comparable to the existing tool registered under
`shell`/`container.exec`. The primary difference is that it accepts
`command` as a `str` instead of a `str[]`. The `shell_command` tool
executes by running `execvp(["bash", "-lc", command])`, though the exact
arguments to `execvp(3)` depend on the user's default shell.
The hypothesis is that this will simplify things for the model. For
example, on Windows, instead of generating:
```json
{"command": ["pwsh.exe", "-NoLogo", "-Command", "ls -Name"]}
```
The model could simply generate:
```json
{"command": "ls -Name"}
```
As part of this change, I extracted some logic out of `user_shell.rs` as
`Shell::derive_exec_args()` so that it can be reused in
`codex-rs/core/src/tools/handlers/shell.rs`. Note the original code
generated exec arg lists like:
```javascript
["bash", "-lc", command]
["zsh", "-lc", command]
["pwsh.exe", "-NoProfile", "-Command", command]
```
Using `-l` for Bash and Zsh, but then specifying `-NoProfile` for
PowerShell seemed inconsistent to me, so I changed this in the new
implementation while also adding a `use_login_shell: bool` option to
make this explicit. If we decide to add a `login: bool` to
`ShellCommandToolCallParams` like we have for unified exec:
https://github.com/openai/codex/blob/807e2c27f0a9f2e85c50e7e6df5533f0d9b853c7/codex-rs/core/src/tools/handlers/unified_exec.rs#L33-L34
Then this should make it straightforward to support.
2025-11-12 08:18:57 -08:00
/// This is the maximum time in milliseconds that the command is allowed to run.
#[ serde(alias = " timeout " ) ]
pub timeout_ms : Option < u64 > ,
2025-12-10 09:18:48 -08:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
pub sandbox_permissions : Option < SandboxPermissions > ,
2026-01-28 01:43:17 -07:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
pub prefix_rule : Option < Vec < String > > ,
2026-02-24 09:48:57 -08:00
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
#[ ts(optional) ]
2026-02-24 19:35:28 -08:00
pub additional_permissions : Option < PermissionProfile > ,
2025-08-05 20:44:20 -07:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub justification : Option < String > ,
2025-05-04 10:57:12 -07:00
}
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
/// Responses API compatible content items that can be returned by a tool call.
/// This is a subset of ContentItem with the types we support as function call outputs.
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
#[ serde(tag = " type " , rename_all = " snake_case " ) ]
pub enum FunctionCallOutputContentItem {
// Do not rename, these are serialized and used directly in the responses API.
InputText { text : String } ,
// Do not rename, these are serialized and used directly in the responses API.
InputImage { image_url : String } ,
}
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
/// Converts structured function-call output content into plain text for
/// human-readable surfaces.
///
/// This conversion is intentionally lossy:
/// - only `input_text` items are included
/// - image items are ignored
///
/// We use this helper where callers still need a string representation (for
/// example telemetry previews or legacy string-only output paths) while keeping
/// the original multimodal `content_items` as the authoritative payload sent to
/// the model.
pub fn function_call_output_content_items_to_text (
content_items : & [ FunctionCallOutputContentItem ] ,
) -> Option < String > {
let text_segments = content_items
. iter ( )
. filter_map ( | item | match item {
FunctionCallOutputContentItem ::InputText { text } if ! text . trim ( ) . is_empty ( ) = > {
Some ( text . as_str ( ) )
}
FunctionCallOutputContentItem ::InputText { .. }
| FunctionCallOutputContentItem ::InputImage { .. } = > None ,
} )
. collect ::< Vec < _ > > ( ) ;
if text_segments . is_empty ( ) {
None
} else {
Some ( text_segments . join ( " \n " ) )
}
}
impl From < crate ::dynamic_tools ::DynamicToolCallOutputContentItem >
for FunctionCallOutputContentItem
{
fn from ( item : crate ::dynamic_tools ::DynamicToolCallOutputContentItem ) -> Self {
match item {
crate ::dynamic_tools ::DynamicToolCallOutputContentItem ::InputText { text } = > {
Self ::InputText { text }
}
crate ::dynamic_tools ::DynamicToolCallOutputContentItem ::InputImage { image_url } = > {
Self ::InputImage { image_url }
}
}
}
}
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
/// The payload we send back to OpenAI when reporting a tool call result.
///
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
/// `body` serializes directly as the wire value for `function_call_output.output`.
/// `success` remains internal metadata for downstream handling.
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
#[ derive(Debug, Default, Clone, PartialEq, JsonSchema, TS) ]
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
pub struct FunctionCallOutputPayload {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
pub body : FunctionCallOutputBody ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
pub success : Option < bool > ,
}
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS) ]
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
#[ serde(untagged) ]
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
pub enum FunctionCallOutputBody {
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
Text ( String ) ,
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
ContentItems ( Vec < FunctionCallOutputContentItem > ) ,
}
impl FunctionCallOutputBody {
/// Best-effort conversion of a function-call output body to plain text for
/// human-readable surfaces.
///
/// This conversion is intentionally lossy when the body contains content
/// items: image entries are dropped and text entries are joined with
/// newlines.
pub fn to_text ( & self ) -> Option < String > {
match self {
Self ::Text ( content ) = > Some ( content . clone ( ) ) ,
Self ::ContentItems ( items ) = > function_call_output_content_items_to_text ( items ) ,
}
}
}
impl Default for FunctionCallOutputBody {
fn default ( ) -> Self {
Self ::Text ( String ::new ( ) )
}
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
}
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
impl FunctionCallOutputPayload {
pub fn from_text ( content : String ) -> Self {
Self {
body : FunctionCallOutputBody ::Text ( content ) ,
success : None ,
}
}
pub fn from_content_items ( content_items : Vec < FunctionCallOutputContentItem > ) -> Self {
Self {
body : FunctionCallOutputBody ::ContentItems ( content_items ) ,
success : None ,
}
}
pub fn text_content ( & self ) -> Option < & str > {
match & self . body {
FunctionCallOutputBody ::Text ( content ) = > Some ( content ) ,
FunctionCallOutputBody ::ContentItems ( _ ) = > None ,
}
}
pub fn text_content_mut ( & mut self ) -> Option < & mut String > {
match & mut self . body {
FunctionCallOutputBody ::Text ( content ) = > Some ( content ) ,
FunctionCallOutputBody ::ContentItems ( _ ) = > None ,
}
}
pub fn content_items ( & self ) -> Option < & [ FunctionCallOutputContentItem ] > {
match & self . body {
FunctionCallOutputBody ::Text ( _ ) = > None ,
FunctionCallOutputBody ::ContentItems ( items ) = > Some ( items ) ,
}
}
pub fn content_items_mut ( & mut self ) -> Option < & mut Vec < FunctionCallOutputContentItem > > {
match & mut self . body {
FunctionCallOutputBody ::Text ( _ ) = > None ,
FunctionCallOutputBody ::ContentItems ( items ) = > Some ( items ) ,
}
}
}
// `function_call_output.output` is encoded as either:
// - an array of structured content items
// - a plain string
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
impl Serialize for FunctionCallOutputPayload {
fn serialize < S > ( & self , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
match & self . body {
FunctionCallOutputBody ::Text ( content ) = > serializer . serialize_str ( content ) ,
FunctionCallOutputBody ::ContentItems ( items ) = > items . serialize ( serializer ) ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
}
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
}
}
2025-07-23 10:37:45 -07:00
impl < ' de > Deserialize < ' de > for FunctionCallOutputPayload {
fn deserialize < D > ( deserializer : D ) -> Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
let body = FunctionCallOutputBody ::deserialize ( deserializer ) ? ;
Ok ( FunctionCallOutputPayload {
body ,
success : None ,
} )
2025-07-23 10:37:45 -07:00
}
}
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
impl From < & CallToolResult > for FunctionCallOutputPayload {
fn from ( call_tool_result : & CallToolResult ) -> Self {
let CallToolResult {
content ,
structured_content ,
is_error ,
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
meta : _ ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
} = call_tool_result ;
let is_success = is_error ! = & Some ( true ) ;
if let Some ( structured_content ) = structured_content
& & ! structured_content . is_null ( )
{
match serde_json ::to_string ( structured_content ) {
Ok ( serialized_structured_content ) = > {
return FunctionCallOutputPayload {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
body : FunctionCallOutputBody ::Text ( serialized_structured_content ) ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
success : Some ( is_success ) ,
} ;
}
Err ( err ) = > {
return FunctionCallOutputPayload {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
body : FunctionCallOutputBody ::Text ( err . to_string ( ) ) ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
success : Some ( false ) ,
} ;
}
}
}
let serialized_content = match serde_json ::to_string ( content ) {
Ok ( serialized_content ) = > serialized_content ,
Err ( err ) = > {
return FunctionCallOutputPayload {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
body : FunctionCallOutputBody ::Text ( err . to_string ( ) ) ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
success : Some ( false ) ,
} ;
}
} ;
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
let content_items = convert_mcp_content_to_items ( content ) ;
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
let body = match content_items {
Some ( content_items ) = > FunctionCallOutputBody ::ContentItems ( content_items ) ,
None = > FunctionCallOutputBody ::Text ( serialized_content ) ,
} ;
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
FunctionCallOutputPayload {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
body ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
success : Some ( is_success ) ,
}
}
}
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
fn convert_mcp_content_to_items (
contents : & [ serde_json ::Value ] ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
) -> Option < Vec < FunctionCallOutputContentItem > > {
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
#[ derive(serde::Deserialize) ]
#[ serde(tag = " type " ) ]
enum McpContent {
#[ serde(rename = " text " ) ]
Text { text : String } ,
#[ serde(rename = " image " ) ]
Image {
data : String ,
#[ serde(rename = " mimeType " , alias = " mime_type " ) ]
mime_type : Option < String > ,
} ,
#[ serde(other) ]
Unknown ,
}
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
let mut saw_image = false ;
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
let mut items = Vec ::with_capacity ( contents . len ( ) ) ;
for content in contents {
let item = match serde_json ::from_value ::< McpContent > ( content . clone ( ) ) {
Ok ( McpContent ::Text { text } ) = > FunctionCallOutputContentItem ::InputText { text } ,
Ok ( McpContent ::Image { data , mime_type } ) = > {
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
saw_image = true ;
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
let image_url = if data . starts_with ( " data: " ) {
data
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
} else {
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
let mime_type = mime_type . unwrap_or_else ( | | " application/octet-stream " . into ( ) ) ;
format! ( " data: {mime_type} ;base64, {data} " )
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
} ;
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
FunctionCallOutputContentItem ::InputImage { image_url }
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
}
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
Ok ( McpContent ::Unknown ) | Err ( _ ) = > FunctionCallOutputContentItem ::InputText {
text : serde_json ::to_string ( content ) . unwrap_or_else ( | _ | " <content> " . to_string ( ) ) ,
} ,
} ;
items . push ( item ) ;
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
}
if saw_image { Some ( items ) } else { None }
}
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
// Implement Display so callers can treat the payload like a plain string when logging or doing
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
// trivial substring checks in tests (existing tests call `.contains()` on the output). For
// `ContentItems`, Display emits a JSON representation.
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
impl std ::fmt ::Display for FunctionCallOutputPayload {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
match & self . body {
FunctionCallOutputBody ::Text ( content ) = > f . write_str ( content ) ,
FunctionCallOutputBody ::ContentItems ( items ) = > {
let content = serde_json ::to_string ( items ) . unwrap_or_default ( ) ;
f . write_str ( content . as_str ( ) )
}
}
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
}
}
2025-09-03 22:34:50 -07:00
// (Moved event mapping logic into codex-core to avoid coupling protocol to UI-facing events.)
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2026-01-12 15:12:59 -08:00
use crate ::config_types ::SandboxMode ;
use crate ::protocol ::AskForApproval ;
2025-09-23 13:31:36 -07:00
use anyhow ::Result ;
2026-01-28 01:43:17 -07:00
use codex_execpolicy ::Policy ;
2025-12-02 09:21:30 +00:00
use pretty_assertions ::assert_eq ;
2026-01-12 15:12:59 -08:00
use std ::path ::PathBuf ;
2025-10-27 16:58:10 +00:00
use tempfile ::tempdir ;
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
#[ test ]
fn convert_mcp_content_to_items_preserves_data_urls ( ) {
let contents = vec! [ serde_json ::json! ( {
" type " : " image " ,
" data " : " data:image/png;base64,Zm9v " ,
" mimeType " : " image/png " ,
} ) ] ;
let items = convert_mcp_content_to_items ( & contents ) . expect ( " expected image items " ) ;
assert_eq! (
items ,
vec! [ FunctionCallOutputContentItem ::InputImage {
image_url : " data:image/png;base64,Zm9v " . to_string ( ) ,
} ]
) ;
}
#[ test ]
fn convert_mcp_content_to_items_builds_data_urls_when_missing_prefix ( ) {
let contents = vec! [ serde_json ::json! ( {
" type " : " image " ,
" data " : " Zm9v " ,
" mimeType " : " image/png " ,
} ) ] ;
let items = convert_mcp_content_to_items ( & contents ) . expect ( " expected image items " ) ;
assert_eq! (
items ,
vec! [ FunctionCallOutputContentItem ::InputImage {
image_url : " data:image/png;base64,Zm9v " . to_string ( ) ,
} ]
) ;
}
#[ test ]
fn convert_mcp_content_to_items_returns_none_without_images ( ) {
let contents = vec! [ serde_json ::json! ( {
" type " : " text " ,
" text " : " hello " ,
} ) ] ;
assert_eq! ( convert_mcp_content_to_items ( & contents ) , None ) ;
}
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
#[ test ]
fn function_call_output_content_items_to_text_joins_text_segments ( ) {
let content_items = vec! [
FunctionCallOutputContentItem ::InputText {
text : " line 1 " . to_string ( ) ,
} ,
FunctionCallOutputContentItem ::InputImage {
image_url : " data:image/png;base64,AAA " . to_string ( ) ,
} ,
FunctionCallOutputContentItem ::InputText {
text : " line 2 " . to_string ( ) ,
} ,
] ;
let text = function_call_output_content_items_to_text ( & content_items ) ;
assert_eq! ( text , Some ( " line 1 \n line 2 " . to_string ( ) ) ) ;
}
#[ test ]
fn function_call_output_content_items_to_text_ignores_blank_text_and_images ( ) {
let content_items = vec! [
FunctionCallOutputContentItem ::InputText {
text : " " . to_string ( ) ,
} ,
FunctionCallOutputContentItem ::InputImage {
image_url : " data:image/png;base64,AAA " . to_string ( ) ,
} ,
] ;
let text = function_call_output_content_items_to_text ( & content_items ) ;
assert_eq! ( text , None ) ;
}
#[ test ]
fn function_call_output_body_to_text_returns_plain_text_content ( ) {
let body = FunctionCallOutputBody ::Text ( " ok " . to_string ( ) ) ;
let text = body . to_text ( ) ;
assert_eq! ( text , Some ( " ok " . to_string ( ) ) ) ;
}
#[ test ]
fn function_call_output_body_to_text_uses_content_item_fallback ( ) {
let body = FunctionCallOutputBody ::ContentItems ( vec! [
FunctionCallOutputContentItem ::InputText {
text : " line 1 " . to_string ( ) ,
} ,
FunctionCallOutputContentItem ::InputImage {
image_url : " data:image/png;base64,AAA " . to_string ( ) ,
} ,
] ) ;
let text = body . to_text ( ) ;
assert_eq! ( text , Some ( " line 1 " . to_string ( ) ) ) ;
}
2026-01-12 15:12:59 -08:00
#[ test ]
fn converts_sandbox_mode_into_developer_instructions ( ) {
2026-01-28 01:43:17 -07:00
let workspace_write : DeveloperInstructions = SandboxMode ::WorkspaceWrite . into ( ) ;
2026-01-12 15:12:59 -08:00
assert_eq! (
2026-01-28 01:43:17 -07:00
workspace_write ,
2026-01-12 15:12:59 -08:00
DeveloperInstructions ::new (
" Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `workspace-write`: The sandbox permits reading files, and editing files in `cwd` and `writable_roots`. Editing files in other directories requires approval. Network access is restricted. "
)
) ;
2026-01-28 01:43:17 -07:00
let read_only : DeveloperInstructions = SandboxMode ::ReadOnly . into ( ) ;
2026-01-12 15:12:59 -08:00
assert_eq! (
2026-01-28 01:43:17 -07:00
read_only ,
2026-01-12 15:12:59 -08:00
DeveloperInstructions ::new (
" Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `read-only`: The sandbox only permits reading files. Network access is restricted. "
)
) ;
}
#[ test ]
fn builds_permissions_with_network_access_override ( ) {
let instructions = DeveloperInstructions ::from_permissions_with_network (
SandboxMode ::WorkspaceWrite ,
NetworkAccess ::Enabled ,
AskForApproval ::OnRequest ,
2026-01-28 01:43:17 -07:00
& Policy ::empty ( ) ,
2026-01-12 15:12:59 -08:00
None ,
2026-02-24 09:48:57 -08:00
false ,
2026-01-12 15:12:59 -08:00
) ;
let text = instructions . into_text ( ) ;
assert! (
text . contains ( " Network access is enabled. " ) ,
" expected network access to be enabled in message "
) ;
assert! (
2026-02-16 14:30:23 -08:00
text . contains ( " How to request escalation " ) ,
2026-01-12 15:12:59 -08:00
" expected approval guidance to be included "
) ;
}
#[ test ]
fn builds_permissions_from_policy ( ) {
let policy = SandboxPolicy ::WorkspaceWrite {
writable_roots : vec ! [ ] ,
2026-02-11 18:31:14 -08:00
read_only_access : Default ::default ( ) ,
2026-01-12 15:12:59 -08:00
network_access : true ,
exclude_tmpdir_env_var : false ,
exclude_slash_tmp : false ,
} ;
let instructions = DeveloperInstructions ::from_policy (
& policy ,
AskForApproval ::UnlessTrusted ,
2026-01-28 01:43:17 -07:00
& Policy ::empty ( ) ,
2026-01-12 15:12:59 -08:00
& PathBuf ::from ( " /tmp " ) ,
2026-02-24 09:48:57 -08:00
false ,
2026-01-12 15:12:59 -08:00
) ;
let text = instructions . into_text ( ) ;
assert! ( text . contains ( " Network access is enabled. " ) ) ;
assert! ( text . contains ( " `approval_policy` is `unless-trusted` " ) ) ;
}
2026-01-28 01:43:17 -07:00
#[ test ]
2026-02-16 14:30:23 -08:00
fn includes_request_rule_instructions_for_on_request ( ) {
2026-01-28 01:43:17 -07:00
let mut exec_policy = Policy ::empty ( ) ;
exec_policy
. add_prefix_rule (
& [ " git " . to_string ( ) , " pull " . to_string ( ) ] ,
codex_execpolicy ::Decision ::Allow ,
)
. expect ( " add rule " ) ;
let instructions = DeveloperInstructions ::from_permissions_with_network (
SandboxMode ::WorkspaceWrite ,
NetworkAccess ::Enabled ,
AskForApproval ::OnRequest ,
& exec_policy ,
None ,
2026-02-24 09:48:57 -08:00
false ,
2026-01-28 01:43:17 -07:00
) ;
let text = instructions . into_text ( ) ;
assert! ( text . contains ( " prefix_rule " ) ) ;
assert! ( text . contains ( " Approved command prefixes " ) ) ;
assert! ( text . contains ( r # "["git", "pull"]"# ) ) ;
}
2026-02-24 09:48:57 -08:00
#[ test ]
fn includes_request_permission_rule_instructions_for_on_request_when_enabled ( ) {
let instructions = DeveloperInstructions ::from_permissions_with_network (
SandboxMode ::WorkspaceWrite ,
NetworkAccess ::Enabled ,
AskForApproval ::OnRequest ,
& Policy ::empty ( ) ,
None ,
true ,
) ;
let text = instructions . into_text ( ) ;
assert! ( text . contains ( " with_additional_permissions " ) ) ;
assert! ( text . contains ( " additional_permissions " ) ) ;
}
2026-02-01 18:26:15 -08:00
#[ test ]
fn render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical ( ) {
let prefixes = vec! [
vec! [ " b " . to_string ( ) , " zz " . to_string ( ) ] ,
vec! [ " aa " . to_string ( ) ] ,
vec! [ " b " . to_string ( ) ] ,
vec! [ " a " . to_string ( ) , " b " . to_string ( ) , " c " . to_string ( ) ] ,
vec! [ " a " . to_string ( ) ] ,
vec! [ " b " . to_string ( ) , " a " . to_string ( ) ] ,
] ;
let output = format_allow_prefixes ( prefixes ) . expect ( " rendered list " ) ;
assert_eq! (
output ,
r #" - [ " a " ]
- [ " b " ]
- [ " aa " ]
- [ " b " , " a " ]
- [ " b " , " zz " ]
- [ " a " , " b " , " c " ] " #
. to_string ( ) ,
) ;
}
#[ test ]
fn render_command_prefix_list_limits_output_to_max_prefixes ( ) {
let prefixes = ( 0 .. ( MAX_RENDERED_PREFIXES + 5 ) )
. map ( | i | vec! [ format! ( " {i:03} " ) ] )
. collect ::< Vec < _ > > ( ) ;
let output = format_allow_prefixes ( prefixes ) . expect ( " rendered list " ) ;
assert_eq! ( output . ends_with ( TRUNCATED_MARKER ) , true ) ;
eprintln! ( " output: {output} " ) ;
assert_eq! ( output . lines ( ) . count ( ) , MAX_RENDERED_PREFIXES + 1 ) ;
}
#[ test ]
fn format_allow_prefixes_limits_output ( ) {
let mut exec_policy = Policy ::empty ( ) ;
for i in 0 .. 200 {
exec_policy
. add_prefix_rule (
& [ format! ( " tool- {i:03} " ) , " x " . repeat ( 500 ) ] ,
codex_execpolicy ::Decision ::Allow ,
)
. expect ( " add rule " ) ;
}
let output =
format_allow_prefixes ( exec_policy . get_allowed_prefixes ( ) ) . expect ( " formatted prefixes " ) ;
assert! (
output . len ( ) < = MAX_ALLOW_PREFIX_TEXT_BYTES + TRUNCATED_MARKER . len ( ) ,
" output length exceeds expected limit: {output} " ,
) ;
}
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
#[ test ]
2025-09-23 13:31:36 -07:00
fn serializes_success_as_plain_string ( ) -> Result < ( ) > {
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
let item = ResponseInputItem ::FunctionCallOutput {
call_id : " call1 " . into ( ) ,
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
output : FunctionCallOutputPayload ::from_text ( " ok " . into ( ) ) ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
} ;
2025-09-23 13:31:36 -07:00
let json = serde_json ::to_string ( & item ) ? ;
let v : serde_json ::Value = serde_json ::from_str ( & json ) ? ;
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
// Success case -> output should be a plain string
assert_eq! ( v . get ( " output " ) . unwrap ( ) . as_str ( ) . unwrap ( ) , " ok " ) ;
2025-09-23 13:31:36 -07:00
Ok ( ( ) )
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
}
#[ test ]
2025-09-23 13:31:36 -07:00
fn serializes_failure_as_string ( ) -> Result < ( ) > {
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
let item = ResponseInputItem ::FunctionCallOutput {
call_id : " call1 " . into ( ) ,
output : FunctionCallOutputPayload {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
body : FunctionCallOutputBody ::Text ( " bad " . into ( ) ) ,
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
success : Some ( false ) ,
} ,
} ;
2025-09-23 13:31:36 -07:00
let json = serde_json ::to_string ( & item ) ? ;
let v : serde_json ::Value = serde_json ::from_str ( & json ) ? ;
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
assert_eq! ( v . get ( " output " ) . unwrap ( ) . as_str ( ) . unwrap ( ) , " bad " ) ;
2025-09-23 13:31:36 -07:00
Ok ( ( ) )
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
}
2025-05-04 10:57:12 -07:00
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
#[ test ]
fn serializes_image_outputs_as_array ( ) -> Result < ( ) > {
let call_tool_result = CallToolResult {
content : vec ! [
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
serde_json ::json! ( { " type " :" text " , " text " :" caption " } ) ,
serde_json ::json! ( { " type " :" image " , " data " :" BASE64 " , " mimeType " :" image/png " } ) ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
] ,
structured_content : None ,
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
is_error : Some ( false ) ,
meta : None ,
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
} ;
let payload = FunctionCallOutputPayload ::from ( & call_tool_result ) ;
assert_eq! ( payload . success , Some ( true ) ) ;
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
let Some ( items ) = payload . content_items ( ) else {
panic! ( " expected content items " ) ;
} ;
let items = items . to_vec ( ) ;
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
assert_eq! (
items ,
vec! [
FunctionCallOutputContentItem ::InputText {
text : " caption " . into ( ) ,
} ,
FunctionCallOutputContentItem ::InputImage {
image_url : " data:image/png;base64,BASE64 " . into ( ) ,
} ,
]
) ;
let item = ResponseInputItem ::FunctionCallOutput {
call_id : " call1 " . into ( ) ,
output : payload ,
} ;
let json = serde_json ::to_string ( & item ) ? ;
let v : serde_json ::Value = serde_json ::from_str ( & json ) ? ;
let output = v . get ( " output " ) . expect ( " output field " ) ;
assert! ( output . is_array ( ) , " expected array output " ) ;
Ok ( ( ) )
}
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
#[ test ]
fn preserves_existing_image_data_urls ( ) -> Result < ( ) > {
let call_tool_result = CallToolResult {
content : vec ! [ serde_json ::json! ( {
" type " : " image " ,
" data " : " data:image/png;base64,BASE64 " ,
" mimeType " : " image/png "
} ) ] ,
structured_content : None ,
is_error : Some ( false ) ,
meta : None ,
} ;
let payload = FunctionCallOutputPayload ::from ( & call_tool_result ) ;
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
let Some ( items ) = payload . content_items ( ) else {
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
panic! ( " expected content items " ) ;
} ;
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
let items = items . to_vec ( ) ;
feat: replace custom mcp-types crate with equivalents from rmcp (#10349)
We started working with MCP in Codex before
https://crates.io/crates/rmcp was mature, so we had our own crate for
MCP types that was generated from the MCP schema:
https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md
Now that `rmcp` is more mature, it makes more sense to use their MCP
types in Rust, as they handle details (like the `_meta` field) that our
custom version ignored. Though one advantage that our custom types had
is that our generated types implemented `JsonSchema` and `ts_rs::TS`,
whereas the types in `rmcp` do not. As such, part of the work of this PR
is leveraging the adapters between `rmcp` types and the serializable
types that are API for us (app server and MCP) introduced in #10356.
Note this PR results in a number of changes to
`codex-rs/app-server-protocol/schema`, which merit special attention
during review. We must ensure that these changes are still
backwards-compatible, which is possible because we have:
```diff
- export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, };
+ export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, };
```
so `ContentBlock` has been replaced with the more general `JsonValue`.
Note that `ContentBlock` was defined as:
```typescript
export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource;
```
so the deletion of those individual variants should not be a cause of
great concern.
Similarly, we have the following change in
`codex-rs/app-server-protocol/schema/typescript/Tool.ts`:
```
- export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, };
+ export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };
```
so:
- `annotations?: ToolAnnotations` ➡️ `JsonValue`
- `inputSchema: ToolInputSchema` ➡️ `JsonValue`
- `outputSchema?: ToolOutputSchema` ➡️ `JsonValue`
and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349).
* #10357
* __->__ #10349
* #10356
2026-02-02 17:41:55 -08:00
assert_eq! (
items ,
vec! [ FunctionCallOutputContentItem ::InputImage {
image_url : " data:image/png;base64,BASE64 " . into ( ) ,
} ]
) ;
Ok ( ( ) )
}
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
#[ test ]
fn deserializes_array_payload_into_items ( ) -> Result < ( ) > {
let json = r #" [
{ " type " : " input_text " , " text " : " note " } ,
{ " type " : " input_image " , " image_url " : " data:image/png;base64,XYZ " }
] " #;
let payload : FunctionCallOutputPayload = serde_json ::from_str ( json ) ? ;
assert_eq! ( payload . success , None ) ;
let expected_items = vec! [
FunctionCallOutputContentItem ::InputText {
text : " note " . into ( ) ,
} ,
FunctionCallOutputContentItem ::InputImage {
image_url : " data:image/png;base64,XYZ " . into ( ) ,
} ,
] ;
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
assert_eq! (
payload . body ,
FunctionCallOutputBody ::ContentItems ( expected_items . clone ( ) )
) ;
assert_eq! (
serde_json ::to_string ( & payload ) ? ,
serde_json ::to_string ( & expected_items ) ?
) ;
[MCP] Render MCP tool call result images to the model (#5600)
It's pretty amazing we have gotten here without the ability for the
model to see image content from MCP tool calls.
This PR builds off of 4391 and fixes #4819. I would like @KKcorps to get
adequete credit here but I also want to get this fix in ASAP so I gave
him a week to update it and haven't gotten a response so I'm going to
take it across the finish line.
This test highlights how absured the current situation is. I asked the
model to read this image using the Chrome MCP
<img width="2378" height="674" alt="image"
src="https://github.com/user-attachments/assets/9ef52608-72a2-4423-9f5e-7ae36b2b56e0"
/>
After this change, it correctly outputs:
> Captured the page: image dhows a dark terminal-style UI labeled
`OpenAI Codex (v0.0.0)` with prompt `model: gpt-5-codex medium` and
working directory `/codex/codex-rs`
(and more)
Before this change, it said:
> Took the full-page screenshot you asked for. It shows a long,
horizontally repeating pattern of stylized people in orange, light-blue,
and mustard clothing, holding hands in alternating poses against a white
background. No text or other graphics-just rows of flat illustration
stretching off to the right.
Without this change, the Figma, Playwright, Chrome, and other visual MCP
servers are pretty much entirely useless.
I tested this change with the openai respones api as well as a third
party completions api
2025-10-27 14:55:57 -07:00
Ok ( ( ) )
}
2025-12-02 09:21:30 +00:00
#[ test ]
fn deserializes_compaction_alias ( ) -> Result < ( ) > {
2025-12-12 10:05:02 -08:00
let json = r # "{"type":"compaction_summary","encrypted_content":"abc"}"# ;
2025-12-02 09:21:30 +00:00
let item : ResponseItem = serde_json ::from_str ( json ) ? ;
assert_eq! (
item ,
2025-12-12 10:05:02 -08:00
ResponseItem ::Compaction {
2025-12-02 09:21:30 +00:00
encrypted_content : " abc " . into ( ) ,
}
) ;
Ok ( ( ) )
}
2025-11-20 20:45:28 -08:00
#[ test ]
fn roundtrips_web_search_call_actions ( ) -> Result < ( ) > {
let cases = vec! [
(
r #" {
" type " : " web_search_call " ,
" status " : " completed " ,
" action " : {
" type " : " search " ,
2026-01-30 16:37:56 -08:00
" query " : " weather seattle " ,
" queries " : [ " weather seattle " , " seattle weather now " ]
2025-11-20 20:45:28 -08:00
}
} " #,
2026-01-26 19:33:48 -08:00
None ,
Some ( WebSearchAction ::Search {
2025-11-20 20:45:28 -08:00
query : Some ( " weather seattle " . into ( ) ) ,
2026-01-30 16:37:56 -08:00
queries : Some ( vec! [ " weather seattle " . into ( ) , " seattle weather now " . into ( ) ] ) ,
2026-01-26 19:33:48 -08:00
} ) ,
2025-11-20 20:45:28 -08:00
Some ( " completed " . into ( ) ) ,
2026-01-26 19:33:48 -08:00
true ,
2025-11-20 20:45:28 -08:00
) ,
(
r #" {
" type " : " web_search_call " ,
" status " : " open " ,
" action " : {
" type " : " open_page " ,
" url " : " https://example.com "
}
} " #,
2026-01-26 19:33:48 -08:00
None ,
Some ( WebSearchAction ::OpenPage {
2025-11-20 20:45:28 -08:00
url : Some ( " https://example.com " . into ( ) ) ,
2026-01-26 19:33:48 -08:00
} ) ,
2025-11-20 20:45:28 -08:00
Some ( " open " . into ( ) ) ,
2026-01-26 19:33:48 -08:00
true ,
2025-11-20 20:45:28 -08:00
) ,
(
r #" {
" type " : " web_search_call " ,
" status " : " in_progress " ,
" action " : {
" type " : " find_in_page " ,
" url " : " https://example.com/docs " ,
" pattern " : " installation "
}
} " #,
2026-01-26 19:33:48 -08:00
None ,
Some ( WebSearchAction ::FindInPage {
2025-11-20 20:45:28 -08:00
url : Some ( " https://example.com/docs " . into ( ) ) ,
pattern : Some ( " installation " . into ( ) ) ,
2026-01-26 19:33:48 -08:00
} ) ,
Some ( " in_progress " . into ( ) ) ,
true ,
) ,
(
r #" {
" type " : " web_search_call " ,
" status " : " in_progress " ,
" id " : " ws_partial "
} " #,
Some ( " ws_partial " . into ( ) ) ,
None ,
2025-11-20 20:45:28 -08:00
Some ( " in_progress " . into ( ) ) ,
2026-01-26 19:33:48 -08:00
false ,
2025-11-20 20:45:28 -08:00
) ,
] ;
2026-01-26 19:33:48 -08:00
for ( json_literal , expected_id , expected_action , expected_status , expect_roundtrip ) in cases
{
2025-11-20 20:45:28 -08:00
let parsed : ResponseItem = serde_json ::from_str ( json_literal ) ? ;
let expected = ResponseItem ::WebSearchCall {
2026-01-26 19:33:48 -08:00
id : expected_id . clone ( ) ,
2025-11-20 20:45:28 -08:00
status : expected_status . clone ( ) ,
action : expected_action . clone ( ) ,
} ;
assert_eq! ( parsed , expected ) ;
let serialized = serde_json ::to_value ( & parsed ) ? ;
2026-01-26 19:33:48 -08:00
let mut expected_serialized : serde_json ::Value = serde_json ::from_str ( json_literal ) ? ;
if ! expect_roundtrip & & let Some ( obj ) = expected_serialized . as_object_mut ( ) {
obj . remove ( " id " ) ;
}
assert_eq! ( serialized , expected_serialized ) ;
2025-11-20 20:45:28 -08:00
}
Ok ( ( ) )
}
2025-05-04 10:57:12 -07:00
#[ test ]
2025-09-23 13:31:36 -07:00
fn deserialize_shell_tool_call_params ( ) -> Result < ( ) > {
2025-05-04 10:57:12 -07:00
let json = r #" {
" command " : [ " ls " , " -l " ] ,
" workdir " : " /tmp " ,
" timeout " : 1000
} " #;
2025-09-23 13:31:36 -07:00
let params : ShellToolCallParams = serde_json ::from_str ( json ) ? ;
2025-05-04 10:57:12 -07:00
assert_eq! (
ShellToolCallParams {
command : vec ! [ " ls " . to_string ( ) , " -l " . to_string ( ) ] ,
workdir : Some ( " /tmp " . to_string ( ) ) ,
timeout_ms : Some ( 1000 ) ,
2025-12-10 09:18:48 -08:00
sandbox_permissions : None ,
2026-01-28 01:43:17 -07:00
prefix_rule : None ,
2026-02-24 09:48:57 -08:00
additional_permissions : None ,
2025-08-05 20:44:20 -07:00
justification : None ,
2025-05-04 10:57:12 -07:00
} ,
params
) ;
2025-09-23 13:31:36 -07:00
Ok ( ( ) )
2025-05-04 10:57:12 -07:00
}
2025-10-27 16:58:10 +00:00
Label attached images so agent can understand in-message labels (#8950)
Agent wouldn't "see" attached images and would instead try to use the
view_file tool:
<img width="1516" height="504" alt="image"
src="https://github.com/user-attachments/assets/68a705bb-f962-4fc1-9087-e932a6859b12"
/>
In this PR, we wrap image content items in XML tags with the name of
each image (now just a numbered name like `[Image #1]`), so that the
model can understand inline image references (based on name). We also
put the image content items above the user message which the model seems
to prefer (maybe it's more used to definitions being before references).
We also tweak the view_file tool description which seemed to help a bit
Results on a simple eval set of images:
Before
<img width="980" height="310" alt="image"
src="https://github.com/user-attachments/assets/ba838651-2565-4684-a12e-81a36641bf86"
/>
After
<img width="918" height="322" alt="image"
src="https://github.com/user-attachments/assets/10a81951-7ee6-415e-a27e-e7a3fd0aee6f"
/>
```json
[
{
"id": "single_describe",
"prompt": "Describe the attached image in one sentence.",
"images": ["image_a.png"]
},
{
"id": "single_color",
"prompt": "What is the dominant color in the image? Answer with a single color word.",
"images": ["image_b.png"]
},
{
"id": "orientation_check",
"prompt": "Is the image portrait or landscape? Answer in one sentence.",
"images": ["image_c.png"]
},
{
"id": "detail_request",
"prompt": "Look closely at the image and call out any small details you notice.",
"images": ["image_d.png"]
},
{
"id": "two_images_compare",
"prompt": "I attached two images. Are they the same or different? Briefly explain.",
"images": ["image_a.png", "image_b.png"]
},
{
"id": "two_images_captions",
"prompt": "Provide a short caption for each image (Image 1, Image 2).",
"images": ["image_c.png", "image_d.png"]
},
{
"id": "multi_image_rank",
"prompt": "Rank the attached images from most colorful to least colorful.",
"images": ["image_a.png", "image_b.png", "image_c.png"]
},
{
"id": "multi_image_choice",
"prompt": "Which image looks more vibrant? Answer with 'Image 1' or 'Image 2'.",
"images": ["image_b.png", "image_d.png"]
}
]
```
2026-01-09 21:33:45 -08:00
#[ test ]
fn wraps_image_user_input_with_tags ( ) -> Result < ( ) > {
let image_url = " data:image/png;base64,abc " . to_string ( ) ;
let item = ResponseInputItem ::from ( vec! [ UserInput ::Image {
image_url : image_url . clone ( ) ,
} ] ) ;
match item {
ResponseInputItem ::Message { content , .. } = > {
let expected = vec! [
ContentItem ::InputText {
text : image_open_tag_text ( ) ,
} ,
ContentItem ::InputImage { image_url } ,
ContentItem ::InputText {
text : image_close_tag_text ( ) ,
} ,
] ;
assert_eq! ( content , expected ) ;
}
other = > panic! ( " expected message response but got {other:?} " ) ,
}
Ok ( ( ) )
}
tui: preserve remote image attachments across resume/backtrack (#10590)
## Summary
This PR makes app-server-provided image URLs first-class attachments in
TUI, so they survive resume/backtrack/history recall and are resubmitted
correctly.
<img width="715" height="491" alt="Screenshot 2026-02-12 at 8 27 08 PM"
src="https://github.com/user-attachments/assets/226cbd35-8f0c-4e51-a13e-459ef5dd1927"
/>
Can delete the attached image upon backtracking:
<img width="716" height="301" alt="Screenshot 2026-02-12 at 8 27 31 PM"
src="https://github.com/user-attachments/assets/4558d230-f1bd-4eed-a093-8e1ab9c6db27"
/>
In both history and composer, remote images are rendered as normal
`[Image #N]` placeholders, with numbering unified with local images.
## What changed
- Plumb remote image URLs through TUI message state:
- `UserHistoryCell`
- `BacktrackSelection`
- `ChatComposerHistory::HistoryEntry`
- `ChatWidget::UserMessage`
- Show remote images as placeholder rows inside the composer box (above
textarea), and in history cells.
- Support keyboard selection/deletion for remote image rows in composer
(`Up`/`Down`, `Delete`/`Backspace`).
- Preserve remote-image-only turns in local composer history (Up/Down
recall), including restore after backtrack.
- Ensure submit/queue/backtrack resubmit include remote images in model
input (`UserInput::Image`), and keep request shape stable for
remote-image-only turns.
- Keep image numbering contiguous across remote + local images:
- remote images occupy `[Image #1]..[Image #M]`
- local images start at `[Image #M+1]`
- deletion renumbers consistently.
- In protocol conversion, increment shared image index for remote images
too, so mixed remote/local image tags stay in a single sequence.
- Simplify restore logic to trust in-memory attachment order (no
placeholder-number parsing path).
- Backtrack/replay rollback handling now queues trims through
`AppEvent::ApplyThreadRollback` and syncs transcript overlay/deferred
lines after trims, so overlay/transcript state stays consistent.
- Trim trailing blank rendered lines from user history rendering to
avoid oversized blank padding.
## Docs + tests
- Updated: `docs/tui-chat-composer.md` (remote image flow,
selection/deletion, numbering offsets)
- Added/updated tests across `tui/src/chatwidget/tests.rs`,
`tui/src/app.rs`, `tui/src/app_backtrack.rs`, `tui/src/history_cell.rs`,
and `tui/src/bottom_pane/chat_composer.rs`
- Added snapshot coverage for remote image composer states, including
deleting the first of two remote images.
## Validation
- `just fmt`
- `cargo test -p codex-tui`
## Codex author
`codex fork 019c2636-1571-74a1-8471-15a3b1c3f49d`
2026-02-13 14:54:06 -08:00
#[ test ]
fn mixed_remote_and_local_images_share_label_sequence ( ) -> Result < ( ) > {
let image_url = " data:image/png;base64,abc " . to_string ( ) ;
let dir = tempdir ( ) ? ;
let local_path = dir . path ( ) . join ( " local.png " ) ;
2026-02-13 16:53:15 -08:00
// A tiny valid PNG (1x1) so this test doesn't depend on cross-crate file paths, which
// break under Bazel sandboxing.
const TINY_PNG_BYTES : & [ u8 ] = & [
137 , 80 , 78 , 71 , 13 , 10 , 26 , 10 , 0 , 0 , 0 , 13 , 73 , 72 , 68 , 82 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 ,
8 , 6 , 0 , 0 , 0 , 31 , 21 , 196 , 137 , 0 , 0 , 0 , 11 , 73 , 68 , 65 , 84 , 120 , 156 , 99 , 96 , 0 , 2 ,
0 , 0 , 5 , 0 , 1 , 122 , 94 , 171 , 63 , 0 , 0 , 0 , 0 , 73 , 69 , 78 , 68 , 174 , 66 , 96 , 130 ,
] ;
std ::fs ::write ( & local_path , TINY_PNG_BYTES ) ? ;
tui: preserve remote image attachments across resume/backtrack (#10590)
## Summary
This PR makes app-server-provided image URLs first-class attachments in
TUI, so they survive resume/backtrack/history recall and are resubmitted
correctly.
<img width="715" height="491" alt="Screenshot 2026-02-12 at 8 27 08 PM"
src="https://github.com/user-attachments/assets/226cbd35-8f0c-4e51-a13e-459ef5dd1927"
/>
Can delete the attached image upon backtracking:
<img width="716" height="301" alt="Screenshot 2026-02-12 at 8 27 31 PM"
src="https://github.com/user-attachments/assets/4558d230-f1bd-4eed-a093-8e1ab9c6db27"
/>
In both history and composer, remote images are rendered as normal
`[Image #N]` placeholders, with numbering unified with local images.
## What changed
- Plumb remote image URLs through TUI message state:
- `UserHistoryCell`
- `BacktrackSelection`
- `ChatComposerHistory::HistoryEntry`
- `ChatWidget::UserMessage`
- Show remote images as placeholder rows inside the composer box (above
textarea), and in history cells.
- Support keyboard selection/deletion for remote image rows in composer
(`Up`/`Down`, `Delete`/`Backspace`).
- Preserve remote-image-only turns in local composer history (Up/Down
recall), including restore after backtrack.
- Ensure submit/queue/backtrack resubmit include remote images in model
input (`UserInput::Image`), and keep request shape stable for
remote-image-only turns.
- Keep image numbering contiguous across remote + local images:
- remote images occupy `[Image #1]..[Image #M]`
- local images start at `[Image #M+1]`
- deletion renumbers consistently.
- In protocol conversion, increment shared image index for remote images
too, so mixed remote/local image tags stay in a single sequence.
- Simplify restore logic to trust in-memory attachment order (no
placeholder-number parsing path).
- Backtrack/replay rollback handling now queues trims through
`AppEvent::ApplyThreadRollback` and syncs transcript overlay/deferred
lines after trims, so overlay/transcript state stays consistent.
- Trim trailing blank rendered lines from user history rendering to
avoid oversized blank padding.
## Docs + tests
- Updated: `docs/tui-chat-composer.md` (remote image flow,
selection/deletion, numbering offsets)
- Added/updated tests across `tui/src/chatwidget/tests.rs`,
`tui/src/app.rs`, `tui/src/app_backtrack.rs`, `tui/src/history_cell.rs`,
and `tui/src/bottom_pane/chat_composer.rs`
- Added snapshot coverage for remote image composer states, including
deleting the first of two remote images.
## Validation
- `just fmt`
- `cargo test -p codex-tui`
## Codex author
`codex fork 019c2636-1571-74a1-8471-15a3b1c3f49d`
2026-02-13 14:54:06 -08:00
let item = ResponseInputItem ::from ( vec! [
UserInput ::Image {
image_url : image_url . clone ( ) ,
} ,
UserInput ::LocalImage { path : local_path } ,
] ) ;
match item {
ResponseInputItem ::Message { content , .. } = > {
assert_eq! (
content . first ( ) ,
Some ( & ContentItem ::InputText {
text : image_open_tag_text ( ) ,
} )
) ;
assert_eq! ( content . get ( 1 ) , Some ( & ContentItem ::InputImage { image_url } ) ) ;
assert_eq! (
content . get ( 2 ) ,
Some ( & ContentItem ::InputText {
text : image_close_tag_text ( ) ,
} )
) ;
assert_eq! (
content . get ( 3 ) ,
Some ( & ContentItem ::InputText {
text : local_image_open_tag_text ( 2 ) ,
} )
) ;
assert! ( matches! (
content . get ( 4 ) ,
Some ( ContentItem ::InputImage { .. } )
) ) ;
assert_eq! (
content . get ( 5 ) ,
Some ( & ContentItem ::InputText {
text : image_close_tag_text ( ) ,
} )
) ;
}
other = > panic! ( " expected message response but got {other:?} " ) ,
}
Ok ( ( ) )
}
2025-10-27 16:58:10 +00:00
#[ test ]
fn local_image_read_error_adds_placeholder ( ) -> Result < ( ) > {
let dir = tempdir ( ) ? ;
let missing_path = dir . path ( ) . join ( " missing-image.png " ) ;
let item = ResponseInputItem ::from ( vec! [ UserInput ::LocalImage {
path : missing_path . clone ( ) ,
} ] ) ;
match item {
ResponseInputItem ::Message { content , .. } = > {
assert_eq! ( content . len ( ) , 1 ) ;
match & content [ 0 ] {
ContentItem ::InputText { text } = > {
let display_path = missing_path . display ( ) . to_string ( ) ;
assert! (
text . contains ( & display_path ) ,
" placeholder should mention missing path: {text} "
) ;
assert! (
text . contains ( " could not read " ) ,
" placeholder should mention read issue: {text} "
) ;
}
other = > panic! ( " expected placeholder text but found {other:?} " ) ,
}
}
other = > panic! ( " expected message response but got {other:?} " ) ,
}
Ok ( ( ) )
}
2025-10-28 14:52:51 -07:00
#[ test ]
fn local_image_non_image_adds_placeholder ( ) -> Result < ( ) > {
let dir = tempdir ( ) ? ;
let json_path = dir . path ( ) . join ( " example.json " ) ;
std ::fs ::write ( & json_path , br # "{"hello":"world"}"# ) ? ;
let item = ResponseInputItem ::from ( vec! [ UserInput ::LocalImage {
path : json_path . clone ( ) ,
} ] ) ;
match item {
ResponseInputItem ::Message { content , .. } = > {
assert_eq! ( content . len ( ) , 1 ) ;
match & content [ 0 ] {
ContentItem ::InputText { text } = > {
assert! (
text . contains ( " unsupported MIME type `application/json` " ) ,
" placeholder should mention unsupported MIME: {text} "
) ;
assert! (
text . contains ( & json_path . display ( ) . to_string ( ) ) ,
" placeholder should mention path: {text} "
) ;
}
other = > panic! ( " expected placeholder text but found {other:?} " ) ,
}
}
other = > panic! ( " expected message response but got {other:?} " ) ,
}
Ok ( ( ) )
}
2025-12-10 02:28:41 +08:00
#[ test ]
fn local_image_unsupported_image_format_adds_placeholder ( ) -> Result < ( ) > {
let dir = tempdir ( ) ? ;
let svg_path = dir . path ( ) . join ( " example.svg " ) ;
std ::fs ::write (
& svg_path ,
br #" <?xml version= " 1.0 " encoding= " UTF - 8 " ?>
< svg xmlns = " http://www.w3.org/2000/svg " width = " 1 " height = " 1 " > < / svg > " #,
) ? ;
let item = ResponseInputItem ::from ( vec! [ UserInput ::LocalImage {
path : svg_path . clone ( ) ,
} ] ) ;
match item {
ResponseInputItem ::Message { content , .. } = > {
assert_eq! ( content . len ( ) , 1 ) ;
let expected = format! (
" Codex cannot attach image at `{}`: unsupported image format `image/svg+xml`. " ,
svg_path . display ( )
) ;
match & content [ 0 ] {
ContentItem ::InputText { text } = > assert_eq! ( text , & expected ) ,
other = > panic! ( " expected placeholder text but found {other:?} " ) ,
}
}
other = > panic! ( " expected message response but got {other:?} " ) ,
}
Ok ( ( ) )
}
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
}