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
|
|
|
|
//! Defines the protocol for a Codex session between a client and an agent.
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! Uses a SQ (Submission Queue) / EQ (Event Queue) pattern to asynchronously communicate
|
|
|
|
|
|
//! between user and agent.
|
|
|
|
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
2026-01-14 08:30:46 -08:00
|
|
|
|
use std::ffi::OsStr;
|
2025-07-25 01:56:40 -07:00
|
|
|
|
use std::fmt;
|
2025-05-04 10:57:12 -07:00
|
|
|
|
use std::path::Path;
|
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 std::path::PathBuf;
|
2025-07-30 10:05:40 -07:00
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
use std::time::Duration;
|
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-07 17:04:53 +00:00
|
|
|
|
use crate::ThreadId;
|
2025-11-21 14:44:53 -08:00
|
|
|
|
use crate::approvals::ElicitationRequestEvent;
|
2026-01-16 21:51:25 -08:00
|
|
|
|
use crate::config_types::CollaborationMode;
|
2026-01-22 12:04:23 -08:00
|
|
|
|
use crate::config_types::Personality;
|
2025-09-07 20:22:25 -07:00
|
|
|
|
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
2026-01-27 11:04:23 -08:00
|
|
|
|
use crate::config_types::WindowsSandboxLevel;
|
2025-08-28 19:16:39 -07:00
|
|
|
|
use crate::custom_prompts::CustomPrompt;
|
2026-01-26 11:06:44 +01:00
|
|
|
|
use crate::dynamic_tools::DynamicToolCallRequest;
|
|
|
|
|
|
use crate::dynamic_tools::DynamicToolResponse;
|
2025-10-20 13:34:44 -07:00
|
|
|
|
use crate::items::TurnItem;
|
2025-09-07 20:22:25 -07:00
|
|
|
|
use crate::message_history::HistoryEntry;
|
2026-01-19 21:59:36 -08:00
|
|
|
|
use crate::models::BaseInstructions;
|
2025-09-14 09:23:31 -04:00
|
|
|
|
use crate::models::ContentItem;
|
2025-09-07 20:22:25 -07:00
|
|
|
|
use crate::models::ResponseItem;
|
2026-01-26 19:33:48 -08:00
|
|
|
|
use crate::models::WebSearchAction;
|
2025-09-08 14:48:48 -07:00
|
|
|
|
use crate::num_format::format_with_separators;
|
2025-12-03 12:30:43 -08:00
|
|
|
|
use crate::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
2025-09-07 20:22:25 -07:00
|
|
|
|
use crate::parse_command::ParsedCommand;
|
|
|
|
|
|
use crate::plan_tool::UpdatePlanArgs;
|
Feat: request user input tool (#9472)
### Summary
* Add `requestUserInput` tool that the model can use for gather
feedback/asking question mid turn.
### Tool input schema
```
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "requestUserInput input",
"type": "object",
"additionalProperties": false,
"required": ["questions"],
"properties": {
"questions": {
"type": "array",
"description": "Questions to show the user (1-3). Prefer 1 unless multiple independent decisions block progress.",
"minItems": 1,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "header", "question"],
"properties": {
"id": {
"type": "string",
"description": "Stable identifier for mapping answers (snake_case)."
},
"header": {
"type": "string",
"description": "Short header label shown in the UI (12 or fewer chars)."
},
"question": {
"type": "string",
"description": "Single-sentence prompt shown to the user."
},
"options": {
"type": "array",
"description": "Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with \"(Recommended)\". Only include \"Other\" option if we want to include a free form option. If the question is free form in nature, do not include any option.",
"minItems": 2,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["value", "label", "description"],
"properties": {
"value": {
"type": "string",
"description": "Machine-readable value (snake_case)."
},
"label": {
"type": "string",
"description": "User-facing label (1-5 words)."
},
"description": {
"type": "string",
"description": "One short sentence explaining impact/tradeoff if selected."
}
}
}
}
}
}
}
}
}
```
### Tool output schema
```
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "requestUserInput output",
"type": "object",
"additionalProperties": false,
"required": ["answers"],
"properties": {
"answers": {
"type": "object",
"description": "Map of question id to user answer.",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": ["selected"],
"properties": {
"selected": {
"type": "array",
"items": { "type": "string" }
},
"other": {
"type": ["string", "null"]
}
}
}
}
}
}
```
2026-01-19 10:17:30 -08:00
|
|
|
|
use crate::request_user_input::RequestUserInputResponse;
|
2025-10-20 13:34:44 -07:00
|
|
|
|
use crate::user_input::UserInput;
|
2025-12-12 15:25:22 -08:00
|
|
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
feat: support mcp_servers in config.toml (#829)
This adds initial support for MCP servers in the style of Claude Desktop
and Cursor. Note this PR is the bare minimum to get things working end
to end: all configured MCP servers are launched every time Codex is run,
there is no recovery for MCP servers that crash, etc.
(Also, I took some shortcuts to change some fields of `Session` to be
`pub(crate)`, which also means there are circular deps between
`codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
subsequent PR.)
`codex-rs/README.md` is updated as part of this PR to explain how to use
this feature. There is a bit of plumbing to route the new settings from
`Config` to the business logic in `codex.rs`. The most significant
chunks for new code are in `mcp_connection_manager.rs` (which defines
the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
responsible for tool calls.
This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
event types to the protocol, but does not add any handlers for them.
(See https://github.com/openai/codex/pull/836 for initial usage.)
To test, I added the following to my `~/.codex/config.toml`:
```toml
# Local build of https://github.com/hideya/mcp-server-weather-js
[mcp_servers.weather]
command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
args = []
```
And then I ran the following:
```
codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
[2025-05-06T22:40:05] Task started: 1
[2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
This Afternoon (Tue):
• Sunny, high near 69 °F
• West-southwest wind around 12 mph
Tonight:
• Partly cloudy, low around 52 °F
• SW wind 7–10 mph
...
```
Note that Codex itself is not able to make network calls, so it would
not normally be able to get live weather information like this. However,
the weather MCP is [currently] not run under the Codex sandbox, so it is
able to hit `api.weather.gov` and fetch current weather information.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
* #836
* __->__ #829
2025-05-06 15:47:59 -07:00
|
|
|
|
use mcp_types::CallToolResult;
|
2025-11-21 14:44:53 -08:00
|
|
|
|
use mcp_types::RequestId;
|
2025-10-16 22:05:15 -07:00
|
|
|
|
use mcp_types::Resource as McpResource;
|
|
|
|
|
|
use mcp_types::ResourceTemplate as McpResourceTemplate;
|
2025-08-19 09:00:31 -07:00
|
|
|
|
use mcp_types::Tool as McpTool;
|
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
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
|
use serde::Serialize;
|
2025-09-23 13:59:16 -07:00
|
|
|
|
use serde_json::Value;
|
2025-09-04 08:21:58 -07:00
|
|
|
|
use serde_with::serde_as;
|
2025-07-24 23:17:57 +02:00
|
|
|
|
use strum_macros::Display;
|
2025-12-12 15:25:22 -08:00
|
|
|
|
use tracing::error;
|
2025-08-18 13:08:53 -07:00
|
|
|
|
use ts_rs::TS;
|
2025-05-07 17:38:28 -07:00
|
|
|
|
|
2025-10-24 17:23:44 -05:00
|
|
|
|
pub use crate::approvals::ApplyPatchApprovalRequestEvent;
|
2025-11-21 14:44:53 -08:00
|
|
|
|
pub use crate::approvals::ElicitationAction;
|
2025-10-24 17:23:44 -05:00
|
|
|
|
pub use crate::approvals::ExecApprovalRequestEvent;
|
2025-12-04 02:39:48 -05:00
|
|
|
|
pub use crate::approvals::ExecPolicyAmendment;
|
Feat: request user input tool (#9472)
### Summary
* Add `requestUserInput` tool that the model can use for gather
feedback/asking question mid turn.
### Tool input schema
```
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "requestUserInput input",
"type": "object",
"additionalProperties": false,
"required": ["questions"],
"properties": {
"questions": {
"type": "array",
"description": "Questions to show the user (1-3). Prefer 1 unless multiple independent decisions block progress.",
"minItems": 1,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "header", "question"],
"properties": {
"id": {
"type": "string",
"description": "Stable identifier for mapping answers (snake_case)."
},
"header": {
"type": "string",
"description": "Short header label shown in the UI (12 or fewer chars)."
},
"question": {
"type": "string",
"description": "Single-sentence prompt shown to the user."
},
"options": {
"type": "array",
"description": "Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with \"(Recommended)\". Only include \"Other\" option if we want to include a free form option. If the question is free form in nature, do not include any option.",
"minItems": 2,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["value", "label", "description"],
"properties": {
"value": {
"type": "string",
"description": "Machine-readable value (snake_case)."
},
"label": {
"type": "string",
"description": "User-facing label (1-5 words)."
},
"description": {
"type": "string",
"description": "One short sentence explaining impact/tradeoff if selected."
}
}
}
}
}
}
}
}
}
```
### Tool output schema
```
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "requestUserInput output",
"type": "object",
"additionalProperties": false,
"required": ["answers"],
"properties": {
"answers": {
"type": "object",
"description": "Map of question id to user answer.",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": ["selected"],
"properties": {
"selected": {
"type": "array",
"items": { "type": "string" }
},
"other": {
"type": ["string", "null"]
}
}
}
}
}
}
```
2026-01-19 10:17:30 -08:00
|
|
|
|
pub use crate::request_user_input::RequestUserInputEvent;
|
2025-10-24 17:23:44 -05:00
|
|
|
|
|
2025-09-03 22:34:50 -07:00
|
|
|
|
/// Open/close tags for special user-input blocks. Used across crates to avoid
|
|
|
|
|
|
/// duplicated hardcoded strings.
|
|
|
|
|
|
pub const USER_INSTRUCTIONS_OPEN_TAG: &str = "<user_instructions>";
|
|
|
|
|
|
pub const USER_INSTRUCTIONS_CLOSE_TAG: &str = "</user_instructions>";
|
|
|
|
|
|
pub const ENVIRONMENT_CONTEXT_OPEN_TAG: &str = "<environment_context>";
|
|
|
|
|
|
pub const ENVIRONMENT_CONTEXT_CLOSE_TAG: &str = "</environment_context>";
|
2026-01-17 17:31:14 -08:00
|
|
|
|
pub const COLLABORATION_MODE_OPEN_TAG: &str = "<collaboration_mode>";
|
|
|
|
|
|
pub const COLLABORATION_MODE_CLOSE_TAG: &str = "</collaboration_mode>";
|
2025-09-08 14:54:47 -07:00
|
|
|
|
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
|
2025-09-03 22:34:50 -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
|
|
|
|
/// Submission Queue Entry - requests from user
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, 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
|
|
|
|
pub struct Submission {
|
|
|
|
|
|
/// Unique id for this Submission to correlate with Events
|
|
|
|
|
|
pub id: String,
|
|
|
|
|
|
/// Payload
|
|
|
|
|
|
pub op: Op,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-12 11:17:50 -08:00
|
|
|
|
/// Config payload for refreshing MCP servers.
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
|
|
|
|
|
|
pub struct McpServerRefreshConfig {
|
|
|
|
|
|
pub mcp_servers: Value,
|
|
|
|
|
|
pub mcp_oauth_credentials_store_mode: 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
|
|
|
|
/// Submission operation
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, 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
|
|
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
2025-05-08 21:46:06 -07:00
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
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
|
|
|
|
#[non_exhaustive]
|
|
|
|
|
|
pub enum Op {
|
|
|
|
|
|
/// Abort current task.
|
2025-08-17 21:40:31 -07:00
|
|
|
|
/// This server sends [`EventMsg::TurnAborted`] in response.
|
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
|
|
|
|
Interrupt,
|
|
|
|
|
|
|
2026-01-19 13:40:39 -08:00
|
|
|
|
/// Legacy user input.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Prefer [`Op::UserTurn`] so the caller provides full turn context
|
|
|
|
|
|
/// (cwd/approval/sandbox/model/etc.) for each turn.
|
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
|
|
|
|
UserInput {
|
|
|
|
|
|
/// User input items, see `InputItem`
|
2025-10-20 13:34:44 -07:00
|
|
|
|
items: Vec<UserInput>,
|
2026-01-05 10:27:00 -08:00
|
|
|
|
/// Optional JSON Schema used to constrain the final assistant message for this turn.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
final_output_json_schema: Option<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
|
|
|
|
},
|
|
|
|
|
|
|
2025-08-15 09:56:05 -07:00
|
|
|
|
/// Similar to [`Op::UserInput`], but contains additional context required
|
2026-01-07 17:04:53 +00:00
|
|
|
|
/// for a turn of a [`crate::codex_thread::CodexThread`].
|
2025-08-15 09:56:05 -07:00
|
|
|
|
UserTurn {
|
|
|
|
|
|
/// User input items, see `InputItem`
|
2025-10-20 13:34:44 -07:00
|
|
|
|
items: Vec<UserInput>,
|
2025-08-15 09:56:05 -07:00
|
|
|
|
|
|
|
|
|
|
/// `cwd` to use with the [`SandboxPolicy`] and potentially tool calls
|
|
|
|
|
|
/// such as `local_shell`.
|
|
|
|
|
|
cwd: PathBuf,
|
|
|
|
|
|
|
|
|
|
|
|
/// Policy to use for command approval.
|
|
|
|
|
|
approval_policy: AskForApproval,
|
|
|
|
|
|
|
|
|
|
|
|
/// Policy to use for tool calls such as `local_shell`.
|
|
|
|
|
|
sandbox_policy: SandboxPolicy,
|
|
|
|
|
|
|
2026-01-12 17:15:56 -08:00
|
|
|
|
/// Must be a valid model slug for the configured client session
|
2025-08-15 09:56:05 -07:00
|
|
|
|
/// associated with this conversation.
|
|
|
|
|
|
model: String,
|
|
|
|
|
|
|
|
|
|
|
|
/// Will only be honored if the model is configured to use reasoning.
|
2025-09-12 12:06:33 -07:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
effort: Option<ReasoningEffortConfig>,
|
2025-08-15 09:56:05 -07:00
|
|
|
|
|
|
|
|
|
|
/// Will only be honored if the model is configured to use reasoning.
|
|
|
|
|
|
summary: ReasoningSummaryConfig,
|
2025-09-23 13:59:16 -07:00
|
|
|
|
// The JSON schema to use for the final assistant message
|
|
|
|
|
|
final_output_json_schema: Option<Value>,
|
2026-01-16 21:51:25 -08:00
|
|
|
|
|
|
|
|
|
|
/// EXPERIMENTAL - set a pre-set collaboration mode.
|
|
|
|
|
|
/// Takes precedence over model, effort, and developer instructions if set.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
collaboration_mode: Option<CollaborationMode>,
|
2026-01-22 12:04:23 -08:00
|
|
|
|
|
|
|
|
|
|
/// Optional personality override for this turn.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
personality: Option<Personality>,
|
2025-08-15 09:56:05 -07:00
|
|
|
|
},
|
|
|
|
|
|
|
2025-08-18 12:59:19 -07:00
|
|
|
|
/// Override parts of the persistent turn context for subsequent turns.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// All fields are optional; when omitted, the existing value is preserved.
|
|
|
|
|
|
/// This does not enqueue any input – it only updates defaults used for
|
2026-01-19 13:40:39 -08:00
|
|
|
|
/// turns that rely on persistent session-level context (for example,
|
|
|
|
|
|
/// [`Op::UserInput`]).
|
2025-08-18 12:59:19 -07:00
|
|
|
|
OverrideTurnContext {
|
|
|
|
|
|
/// Updated `cwd` for sandbox/tool calls.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
cwd: Option<PathBuf>,
|
|
|
|
|
|
|
|
|
|
|
|
/// Updated command approval policy.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
approval_policy: Option<AskForApproval>,
|
|
|
|
|
|
|
|
|
|
|
|
/// Updated sandbox policy for tool calls.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
sandbox_policy: Option<SandboxPolicy>,
|
|
|
|
|
|
|
2026-01-27 11:04:23 -08:00
|
|
|
|
/// Updated Windows sandbox mode for tool execution.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
windows_sandbox_level: Option<WindowsSandboxLevel>,
|
|
|
|
|
|
|
2026-01-07 10:35:09 -08:00
|
|
|
|
/// Updated model slug. When set, the model info is derived
|
2025-08-18 12:59:19 -07:00
|
|
|
|
/// automatically.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
model: Option<String>,
|
|
|
|
|
|
|
|
|
|
|
|
/// Updated reasoning effort (honored only for reasoning-capable models).
|
2025-09-12 12:06:33 -07:00
|
|
|
|
///
|
|
|
|
|
|
/// Use `Some(Some(_))` to set a specific effort, `Some(None)` to clear
|
|
|
|
|
|
/// the effort, or `None` to leave the existing value unchanged.
|
2025-08-18 12:59:19 -07:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2025-09-12 12:06:33 -07:00
|
|
|
|
effort: Option<Option<ReasoningEffortConfig>>,
|
2025-08-18 12:59:19 -07:00
|
|
|
|
|
|
|
|
|
|
/// Updated reasoning summary preference (honored only for reasoning-capable models).
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
summary: Option<ReasoningSummaryConfig>,
|
2026-01-16 21:51:25 -08:00
|
|
|
|
|
|
|
|
|
|
/// EXPERIMENTAL - set a pre-set collaboration mode.
|
|
|
|
|
|
/// Takes precedence over model, effort, and developer instructions if set.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
collaboration_mode: Option<CollaborationMode>,
|
2026-01-22 12:04:23 -08:00
|
|
|
|
|
|
|
|
|
|
/// Updated personality preference.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
personality: Option<Personality>,
|
2025-08-18 12:59:19 -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
|
|
|
|
/// Approve a command execution
|
|
|
|
|
|
ExecApproval {
|
|
|
|
|
|
/// The id of the submission we are approving
|
|
|
|
|
|
id: String,
|
|
|
|
|
|
/// The user's decision in response to the request.
|
|
|
|
|
|
decision: ReviewDecision,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/// Approve a code patch
|
|
|
|
|
|
PatchApproval {
|
|
|
|
|
|
/// The id of the submission we are approving
|
|
|
|
|
|
id: String,
|
|
|
|
|
|
/// The user's decision in response to the request.
|
|
|
|
|
|
decision: ReviewDecision,
|
|
|
|
|
|
},
|
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
|
|
|
|
|
2025-11-21 14:44:53 -08:00
|
|
|
|
/// Resolve an MCP elicitation request.
|
|
|
|
|
|
ResolveElicitation {
|
|
|
|
|
|
/// Name of the MCP server that issued the request.
|
|
|
|
|
|
server_name: String,
|
|
|
|
|
|
/// Request identifier from the MCP server.
|
|
|
|
|
|
request_id: RequestId,
|
|
|
|
|
|
/// User's decision for the request.
|
|
|
|
|
|
decision: ElicitationAction,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
Feat: request user input tool (#9472)
### Summary
* Add `requestUserInput` tool that the model can use for gather
feedback/asking question mid turn.
### Tool input schema
```
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "requestUserInput input",
"type": "object",
"additionalProperties": false,
"required": ["questions"],
"properties": {
"questions": {
"type": "array",
"description": "Questions to show the user (1-3). Prefer 1 unless multiple independent decisions block progress.",
"minItems": 1,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "header", "question"],
"properties": {
"id": {
"type": "string",
"description": "Stable identifier for mapping answers (snake_case)."
},
"header": {
"type": "string",
"description": "Short header label shown in the UI (12 or fewer chars)."
},
"question": {
"type": "string",
"description": "Single-sentence prompt shown to the user."
},
"options": {
"type": "array",
"description": "Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with \"(Recommended)\". Only include \"Other\" option if we want to include a free form option. If the question is free form in nature, do not include any option.",
"minItems": 2,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["value", "label", "description"],
"properties": {
"value": {
"type": "string",
"description": "Machine-readable value (snake_case)."
},
"label": {
"type": "string",
"description": "User-facing label (1-5 words)."
},
"description": {
"type": "string",
"description": "One short sentence explaining impact/tradeoff if selected."
}
}
}
}
}
}
}
}
}
```
### Tool output schema
```
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "requestUserInput output",
"type": "object",
"additionalProperties": false,
"required": ["answers"],
"properties": {
"answers": {
"type": "object",
"description": "Map of question id to user answer.",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": ["selected"],
"properties": {
"selected": {
"type": "array",
"items": { "type": "string" }
},
"other": {
"type": ["string", "null"]
}
}
}
}
}
}
```
2026-01-19 10:17:30 -08:00
|
|
|
|
/// Resolve a request_user_input tool call.
|
|
|
|
|
|
#[serde(rename = "user_input_answer", alias = "request_user_input_response")]
|
|
|
|
|
|
UserInputAnswer {
|
|
|
|
|
|
/// Turn id for the in-flight request.
|
|
|
|
|
|
id: String,
|
|
|
|
|
|
/// User-provided answers.
|
|
|
|
|
|
response: RequestUserInputResponse,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-01-26 11:06:44 +01:00
|
|
|
|
/// Resolve a dynamic tool call request.
|
|
|
|
|
|
DynamicToolResponse {
|
|
|
|
|
|
/// Call id for the in-flight request.
|
|
|
|
|
|
id: String,
|
|
|
|
|
|
/// Tool output payload.
|
|
|
|
|
|
response: DynamicToolResponse,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
|
|
|
|
/// Append an entry to the persistent cross-session message history.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Note the entry is not guaranteed to be logged if the user has
|
|
|
|
|
|
/// history disabled, it matches the list of "sensitive" patterns, etc.
|
|
|
|
|
|
AddToHistory {
|
|
|
|
|
|
/// The message text to be stored.
|
|
|
|
|
|
text: String,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/// Request a single history entry identified by `log_id` + `offset`.
|
|
|
|
|
|
GetHistoryEntryRequest { offset: usize, log_id: u64 },
|
2025-07-23 15:03:26 -07:00
|
|
|
|
|
2025-08-19 09:00:31 -07:00
|
|
|
|
/// Request the list of MCP tools available across all configured servers.
|
|
|
|
|
|
/// Reply is delivered via `EventMsg::McpListToolsResponse`.
|
|
|
|
|
|
ListMcpTools,
|
|
|
|
|
|
|
2026-01-12 11:17:50 -08:00
|
|
|
|
/// Request MCP servers to reinitialize and refresh cached tool lists.
|
|
|
|
|
|
RefreshMcpServers { config: McpServerRefreshConfig },
|
|
|
|
|
|
|
2025-08-28 19:16:39 -07:00
|
|
|
|
/// Request the list of available custom prompts.
|
|
|
|
|
|
ListCustomPrompts,
|
|
|
|
|
|
|
2025-12-14 09:58:17 -08:00
|
|
|
|
/// Request the list of skills for the provided `cwd` values or the session default.
|
|
|
|
|
|
ListSkills {
|
|
|
|
|
|
/// Working directories to scope repo skills discovery.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// When empty, the session default working directory is used.
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
|
|
cwds: Vec<PathBuf>,
|
2025-12-17 01:35:49 -08:00
|
|
|
|
|
|
|
|
|
|
/// When true, recompute skills even if a cached result exists.
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
|
|
|
|
|
force_reload: bool,
|
2025-12-14 09:58:17 -08:00
|
|
|
|
},
|
|
|
|
|
|
|
2025-07-31 21:34:32 -07:00
|
|
|
|
/// Request the agent to summarize the current conversation context.
|
|
|
|
|
|
/// The agent will use its existing context (either conversation history or previous response id)
|
|
|
|
|
|
/// to generate a summary which will be returned as an AgentMessage event.
|
|
|
|
|
|
Compact,
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
|
2025-10-27 10:55:29 +00:00
|
|
|
|
/// Request Codex to undo a turn (turn are stacked so it is the same effect as CMD + Z).
|
|
|
|
|
|
Undo,
|
|
|
|
|
|
|
2026-01-06 13:23:48 -08:00
|
|
|
|
/// Request Codex to drop the last N user turns from in-memory context.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This does not attempt to revert local filesystem changes. Clients are
|
|
|
|
|
|
/// responsible for undoing any edits on disk.
|
|
|
|
|
|
ThreadRollback { num_turns: u32 },
|
|
|
|
|
|
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
/// Request a code review from the agent.
|
|
|
|
|
|
Review { review_request: ReviewRequest },
|
|
|
|
|
|
|
2025-07-23 15:03:26 -07:00
|
|
|
|
/// Request to shut down codex instance.
|
|
|
|
|
|
Shutdown,
|
feature: Add "!cmd" user shell execution (#2471)
feature: Add "!cmd" user shell execution
This change lets users run local shell commands directly from the TUI by
prefixing their input with ! (e.g. !ls). Output is truncated to keep the
exec cell usable, and Ctrl-C cleanly
interrupts long-running commands (e.g. !sleep 10000).
**Summary of changes**
- Route Op::RunUserShellCommand through a dedicated UserShellCommandTask
(core/src/tasks/user_shell.rs), keeping the task logic out of codex.rs.
- Reuse the existing tool router: the task constructs a ToolCall for the
local_shell tool and relies on ShellHandler, so no manual MCP tool
lookup is required.
- Emit exec lifecycle events (ExecCommandBegin/ExecCommandEnd) so the
TUI can show command metadata, live output, and exit status.
**End-to-end flow**
**TUI handling**
1. ChatWidget::submit_user_message (TUI) intercepts messages starting
with !.
2. Non-empty commands dispatch Op::RunUserShellCommand { command };
empty commands surface a help hint.
3. No UserInput items are created, so nothing is enqueued for the model.
**Core submission loop**
4. The submission loop routes the op to handlers::run_user_shell_command
(core/src/codex.rs).
5. A fresh TurnContext is created and Session::spawn_user_shell_command
enqueues UserShellCommandTask.
**Task execution**
6. UserShellCommandTask::run emits TaskStartedEvent, formats the
command, and prepares a ToolCall targeting local_shell.
7. ToolCallRuntime::handle_tool_call dispatches to ShellHandler.
**Shell tool runtime**
8. ShellHandler::run_exec_like launches the process via the unified exec
runtime, honoring sandbox and shell policies, and emits
ExecCommandBegin/End.
9. Stdout/stderr are captured for the UI, but the task does not turn the
resulting ToolOutput into a model response.
**Completion**
10. After ExecCommandEnd, the task finishes without an assistant
message; the session marks it complete and the exec cell displays the
final output.
**Conversation context**
- The command and its output never enter the conversation history or the
model prompt; the flow is local-only.
- Only exec/task events are emitted for UI rendering.
**Demo video**
https://github.com/user-attachments/assets/fcd114b0-4304-4448-a367-a04c43e0b996
2025-10-29 00:31:20 -07:00
|
|
|
|
|
|
|
|
|
|
/// Execute a user-initiated one-off shell command (triggered by "!cmd").
|
|
|
|
|
|
///
|
|
|
|
|
|
/// The command string is executed using the user's default shell and may
|
|
|
|
|
|
/// include shell syntax (pipes, redirects, etc.). Output is streamed via
|
2026-01-09 17:31:17 +00:00
|
|
|
|
/// `ExecCommand*` events and the UI regains control upon `TurnComplete`.
|
feature: Add "!cmd" user shell execution (#2471)
feature: Add "!cmd" user shell execution
This change lets users run local shell commands directly from the TUI by
prefixing their input with ! (e.g. !ls). Output is truncated to keep the
exec cell usable, and Ctrl-C cleanly
interrupts long-running commands (e.g. !sleep 10000).
**Summary of changes**
- Route Op::RunUserShellCommand through a dedicated UserShellCommandTask
(core/src/tasks/user_shell.rs), keeping the task logic out of codex.rs.
- Reuse the existing tool router: the task constructs a ToolCall for the
local_shell tool and relies on ShellHandler, so no manual MCP tool
lookup is required.
- Emit exec lifecycle events (ExecCommandBegin/ExecCommandEnd) so the
TUI can show command metadata, live output, and exit status.
**End-to-end flow**
**TUI handling**
1. ChatWidget::submit_user_message (TUI) intercepts messages starting
with !.
2. Non-empty commands dispatch Op::RunUserShellCommand { command };
empty commands surface a help hint.
3. No UserInput items are created, so nothing is enqueued for the model.
**Core submission loop**
4. The submission loop routes the op to handlers::run_user_shell_command
(core/src/codex.rs).
5. A fresh TurnContext is created and Session::spawn_user_shell_command
enqueues UserShellCommandTask.
**Task execution**
6. UserShellCommandTask::run emits TaskStartedEvent, formats the
command, and prepares a ToolCall targeting local_shell.
7. ToolCallRuntime::handle_tool_call dispatches to ShellHandler.
**Shell tool runtime**
8. ShellHandler::run_exec_like launches the process via the unified exec
runtime, honoring sandbox and shell policies, and emits
ExecCommandBegin/End.
9. Stdout/stderr are captured for the UI, but the task does not turn the
resulting ToolOutput into a model response.
**Completion**
10. After ExecCommandEnd, the task finishes without an assistant
message; the session marks it complete and the exec cell displays the
final output.
**Conversation context**
- The command and its output never enter the conversation history or the
model prompt; the flow is local-only.
- Only exec/task events are emitted for UI rendering.
**Demo video**
https://github.com/user-attachments/assets/fcd114b0-4304-4448-a367-a04c43e0b996
2025-10-29 00:31:20 -07:00
|
|
|
|
RunUserShellCommand {
|
|
|
|
|
|
/// The raw command string after '!'
|
|
|
|
|
|
command: String,
|
|
|
|
|
|
},
|
2025-12-03 12:30:43 -08:00
|
|
|
|
|
|
|
|
|
|
/// Request the list of available models.
|
|
|
|
|
|
ListModels,
|
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-06-24 22:19:21 -07:00
|
|
|
|
/// Determines the conditions under which the user is consulted to approve
|
|
|
|
|
|
/// running the command proposed by Codex.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(
|
|
|
|
|
|
Debug,
|
|
|
|
|
|
Clone,
|
|
|
|
|
|
Copy,
|
|
|
|
|
|
Default,
|
|
|
|
|
|
PartialEq,
|
|
|
|
|
|
Eq,
|
|
|
|
|
|
Hash,
|
|
|
|
|
|
Serialize,
|
|
|
|
|
|
Deserialize,
|
|
|
|
|
|
Display,
|
|
|
|
|
|
JsonSchema,
|
|
|
|
|
|
TS,
|
|
|
|
|
|
)]
|
2025-04-27 21:47:50 -07:00
|
|
|
|
#[serde(rename_all = "kebab-case")]
|
2025-07-24 23:17:57 +02:00
|
|
|
|
#[strum(serialize_all = "kebab-case")]
|
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 enum AskForApproval {
|
2025-06-24 22:19:21 -07:00
|
|
|
|
/// Under this policy, only "known safe" commands—as determined by
|
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
|
|
|
|
/// `is_safe_command()`—that **only read files** are auto‑approved.
|
|
|
|
|
|
/// Everything else will ask the user to approve.
|
2025-06-24 22:19:21 -07:00
|
|
|
|
#[serde(rename = "untrusted")]
|
2025-07-24 23:17:57 +02:00
|
|
|
|
#[strum(serialize = "untrusted")]
|
2025-06-25 12:26:13 -07:00
|
|
|
|
UnlessTrusted,
|
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
|
|
|
|
|
|
|
|
|
|
/// *All* commands are auto‑approved, but they are expected to run inside a
|
|
|
|
|
|
/// sandbox where network access is disabled and writes are confined to a
|
|
|
|
|
|
/// specific set of paths. If the command fails, it will be escalated to
|
|
|
|
|
|
/// the user to approve execution without a sandbox.
|
|
|
|
|
|
OnFailure,
|
|
|
|
|
|
|
2025-08-05 20:44:20 -07:00
|
|
|
|
/// The model decides when to ask the user for approval.
|
2025-08-07 09:27:38 -07:00
|
|
|
|
#[default]
|
2025-08-05 20:44:20 -07:00
|
|
|
|
OnRequest,
|
|
|
|
|
|
|
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
|
|
|
|
/// Never ask the user to approve commands. Failures are immediately returned
|
|
|
|
|
|
/// to the model, and never escalated to the user for approval.
|
|
|
|
|
|
Never,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 17:02:03 -08:00
|
|
|
|
/// Represents whether outbound network access is available to the agent.
|
|
|
|
|
|
#[derive(
|
|
|
|
|
|
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, Default, JsonSchema, TS,
|
|
|
|
|
|
)]
|
|
|
|
|
|
#[serde(rename_all = "kebab-case")]
|
|
|
|
|
|
#[strum(serialize_all = "kebab-case")]
|
|
|
|
|
|
pub enum NetworkAccess {
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
Restricted,
|
|
|
|
|
|
Enabled,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl NetworkAccess {
|
|
|
|
|
|
pub fn is_enabled(self) -> bool {
|
|
|
|
|
|
matches!(self, NetworkAccess::Enabled)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
/// Determines execution restrictions for model shell commands.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display, JsonSchema, TS)]
|
2025-08-06 01:13:31 -07:00
|
|
|
|
#[strum(serialize_all = "kebab-case")]
|
2025-11-13 16:25:17 -08:00
|
|
|
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
pub enum SandboxPolicy {
|
|
|
|
|
|
/// No restrictions whatsoever. Use with caution.
|
|
|
|
|
|
#[serde(rename = "danger-full-access")]
|
|
|
|
|
|
DangerFullAccess,
|
|
|
|
|
|
|
|
|
|
|
|
/// Read-only access to the entire file-system.
|
|
|
|
|
|
#[serde(rename = "read-only")]
|
|
|
|
|
|
ReadOnly,
|
|
|
|
|
|
|
2025-12-18 17:02:03 -08:00
|
|
|
|
/// Indicates the process is already in an external sandbox. Allows full
|
|
|
|
|
|
/// disk access while honoring the provided network setting.
|
|
|
|
|
|
#[serde(rename = "external-sandbox")]
|
|
|
|
|
|
ExternalSandbox {
|
|
|
|
|
|
/// Whether the external sandbox permits outbound network traffic.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
network_access: NetworkAccess,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
/// Same as `ReadOnly` but additionally grants write access to the current
|
|
|
|
|
|
/// working directory ("workspace").
|
|
|
|
|
|
#[serde(rename = "workspace-write")]
|
|
|
|
|
|
WorkspaceWrite {
|
|
|
|
|
|
/// Additional folders (beyond cwd and possibly TMPDIR) that should be
|
|
|
|
|
|
/// writable from within the sandbox.
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
2025-12-12 15:25:22 -08:00
|
|
|
|
writable_roots: Vec<AbsolutePathBuf>,
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
|
|
|
|
|
|
/// When set to `true`, outbound network access is allowed. `false` by
|
|
|
|
|
|
/// default.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
network_access: bool,
|
2025-08-01 14:15:55 -07:00
|
|
|
|
|
2025-08-07 00:17:00 -07:00
|
|
|
|
/// When set to `true`, will NOT include the per-user `TMPDIR`
|
|
|
|
|
|
/// environment variable among the default writable roots. Defaults to
|
|
|
|
|
|
/// `false`.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
exclude_tmpdir_env_var: bool,
|
|
|
|
|
|
|
|
|
|
|
|
/// When set to `true`, will NOT include the `/tmp` among the default
|
|
|
|
|
|
/// writable roots on UNIX. Defaults to `false`.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
exclude_slash_tmp: bool,
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
},
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 16:11:24 -07:00
|
|
|
|
/// A writable root path accompanied by a list of subpaths that should remain
|
|
|
|
|
|
/// read‑only even when the root is writable. This is primarily used to ensure
|
feat: if .codex is a sub-folder of a writable root, then make it read-only to the sandbox (#8088)
In preparation for in-repo configuration support, this updates
`WritableRoot::get_writable_roots_with_cwd()` to include the `.codex`
subfolder in `WritableRoot.read_only_subpaths`, if it exists, as we
already do for `.git`.
As noted, currently, like `.git`, `.codex` will only be read-only under
macOS Seatbelt, but we plan to bring support to other OSes, as well.
Updated the integration test in `seatbelt.rs` so that it actually
attempts to run the generated Seatbelt commands, verifying that:
- trying to write to `.codex/config.toml` in a writable root fails
- trying to write to `.git/hooks/pre-commit` in a writable root fails
- trying to write to the writable root containing the `.codex` and
`.git` subfolders succeeds
2025-12-15 22:54:43 -08:00
|
|
|
|
/// that folders containing files that could be modified to escalate the
|
|
|
|
|
|
/// privileges of the agent (e.g. `.codex`, `.git`, notably `.git/hooks`) under
|
|
|
|
|
|
/// a writable root are not modified by the agent.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, JsonSchema)]
|
2025-08-01 16:11:24 -07:00
|
|
|
|
pub struct WritableRoot {
|
2025-12-12 15:25:22 -08:00
|
|
|
|
pub root: AbsolutePathBuf,
|
fix: tighten up checks against writable folders for SandboxPolicy (#2338)
I was looking at the implementation of `Session::get_writable_roots()`,
which did not seem right, as it was a copy of writable roots, which is
not guaranteed to be in sync with the `sandbox_policy` field.
I looked at who was calling `get_writable_roots()` and its only call
site was `apply_patch()` in `codex-rs/core/src/apply_patch.rs`, which
took the roots and forwarded them to `assess_patch_safety()` in
`safety.rs`. I updated `assess_patch_safety()` to take `sandbox_policy:
&SandboxPolicy` instead of `writable_roots: &[PathBuf]` (and replaced
`Session::get_writable_roots()` with `Session::get_sandbox_policy()`).
Within `safety.rs`, it was fairly easy to update
`is_write_patch_constrained_to_writable_paths()` to work with
`SandboxPolicy`, and in particular, it is far more accurate because, for
better or worse, `SandboxPolicy::get_writable_roots_with_cwd()` _returns
an empty vec_ for `SandboxPolicy::DangerFullAccess`, suggesting that
_nothing_ is writable when in reality _everything_ is writable. With
this PR, `is_write_patch_constrained_to_writable_paths()` now does the
right thing for each variant of `SandboxPolicy`.
I thought this would be the end of the story, but it turned out that
`test_writable_roots_constraint()` in `safety.rs` needed to be updated,
as well. In particular, the test was writing to
`std::env::current_dir()` instead of a `TempDir`, which I suspect was a
holdover from earlier when `SandboxPolicy::WorkspaceWrite` would always
make `TMPDIR` writable on macOS, which made it hard to write tests to
verify `SandboxPolicy` in `TMPDIR`. Fortunately, we now have
`exclude_tmpdir_env_var` as an option on
`SandboxPolicy::WorkspaceWrite`, so I was able to update the test to
preserve the existing behavior, but to no longer write to
`std::env::current_dir()`.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/2338).
* #2345
* #2329
* #2343
* #2340
* __->__ #2338
2025-08-15 09:06:15 -07:00
|
|
|
|
|
feat: if .codex is a sub-folder of a writable root, then make it read-only to the sandbox (#8088)
In preparation for in-repo configuration support, this updates
`WritableRoot::get_writable_roots_with_cwd()` to include the `.codex`
subfolder in `WritableRoot.read_only_subpaths`, if it exists, as we
already do for `.git`.
As noted, currently, like `.git`, `.codex` will only be read-only under
macOS Seatbelt, but we plan to bring support to other OSes, as well.
Updated the integration test in `seatbelt.rs` so that it actually
attempts to run the generated Seatbelt commands, verifying that:
- trying to write to `.codex/config.toml` in a writable root fails
- trying to write to `.git/hooks/pre-commit` in a writable root fails
- trying to write to the writable root containing the `.codex` and
`.git` subfolders succeeds
2025-12-15 22:54:43 -08:00
|
|
|
|
/// By construction, these subpaths are all under `root`.
|
2025-12-12 15:25:22 -08:00
|
|
|
|
pub read_only_subpaths: Vec<AbsolutePathBuf>,
|
2025-08-01 16:11:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
fix: tighten up checks against writable folders for SandboxPolicy (#2338)
I was looking at the implementation of `Session::get_writable_roots()`,
which did not seem right, as it was a copy of writable roots, which is
not guaranteed to be in sync with the `sandbox_policy` field.
I looked at who was calling `get_writable_roots()` and its only call
site was `apply_patch()` in `codex-rs/core/src/apply_patch.rs`, which
took the roots and forwarded them to `assess_patch_safety()` in
`safety.rs`. I updated `assess_patch_safety()` to take `sandbox_policy:
&SandboxPolicy` instead of `writable_roots: &[PathBuf]` (and replaced
`Session::get_writable_roots()` with `Session::get_sandbox_policy()`).
Within `safety.rs`, it was fairly easy to update
`is_write_patch_constrained_to_writable_paths()` to work with
`SandboxPolicy`, and in particular, it is far more accurate because, for
better or worse, `SandboxPolicy::get_writable_roots_with_cwd()` _returns
an empty vec_ for `SandboxPolicy::DangerFullAccess`, suggesting that
_nothing_ is writable when in reality _everything_ is writable. With
this PR, `is_write_patch_constrained_to_writable_paths()` now does the
right thing for each variant of `SandboxPolicy`.
I thought this would be the end of the story, but it turned out that
`test_writable_roots_constraint()` in `safety.rs` needed to be updated,
as well. In particular, the test was writing to
`std::env::current_dir()` instead of a `TempDir`, which I suspect was a
holdover from earlier when `SandboxPolicy::WorkspaceWrite` would always
make `TMPDIR` writable on macOS, which made it hard to write tests to
verify `SandboxPolicy` in `TMPDIR`. Fortunately, we now have
`exclude_tmpdir_env_var` as an option on
`SandboxPolicy::WorkspaceWrite`, so I was able to update the test to
preserve the existing behavior, but to no longer write to
`std::env::current_dir()`.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/2338).
* #2345
* #2329
* #2343
* #2340
* __->__ #2338
2025-08-15 09:06:15 -07:00
|
|
|
|
impl WritableRoot {
|
2025-08-15 12:44:40 -07:00
|
|
|
|
pub fn is_path_writable(&self, path: &Path) -> bool {
|
fix: tighten up checks against writable folders for SandboxPolicy (#2338)
I was looking at the implementation of `Session::get_writable_roots()`,
which did not seem right, as it was a copy of writable roots, which is
not guaranteed to be in sync with the `sandbox_policy` field.
I looked at who was calling `get_writable_roots()` and its only call
site was `apply_patch()` in `codex-rs/core/src/apply_patch.rs`, which
took the roots and forwarded them to `assess_patch_safety()` in
`safety.rs`. I updated `assess_patch_safety()` to take `sandbox_policy:
&SandboxPolicy` instead of `writable_roots: &[PathBuf]` (and replaced
`Session::get_writable_roots()` with `Session::get_sandbox_policy()`).
Within `safety.rs`, it was fairly easy to update
`is_write_patch_constrained_to_writable_paths()` to work with
`SandboxPolicy`, and in particular, it is far more accurate because, for
better or worse, `SandboxPolicy::get_writable_roots_with_cwd()` _returns
an empty vec_ for `SandboxPolicy::DangerFullAccess`, suggesting that
_nothing_ is writable when in reality _everything_ is writable. With
this PR, `is_write_patch_constrained_to_writable_paths()` now does the
right thing for each variant of `SandboxPolicy`.
I thought this would be the end of the story, but it turned out that
`test_writable_roots_constraint()` in `safety.rs` needed to be updated,
as well. In particular, the test was writing to
`std::env::current_dir()` instead of a `TempDir`, which I suspect was a
holdover from earlier when `SandboxPolicy::WorkspaceWrite` would always
make `TMPDIR` writable on macOS, which made it hard to write tests to
verify `SandboxPolicy` in `TMPDIR`. Fortunately, we now have
`exclude_tmpdir_env_var` as an option on
`SandboxPolicy::WorkspaceWrite`, so I was able to update the test to
preserve the existing behavior, but to no longer write to
`std::env::current_dir()`.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/2338).
* #2345
* #2329
* #2343
* #2340
* __->__ #2338
2025-08-15 09:06:15 -07:00
|
|
|
|
// Check if the path is under the root.
|
|
|
|
|
|
if !path.starts_with(&self.root) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check if the path is under any of the read-only subpaths.
|
|
|
|
|
|
for subpath in &self.read_only_subpaths {
|
|
|
|
|
|
if path.starts_with(subpath) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
impl FromStr for SandboxPolicy {
|
|
|
|
|
|
type Err = serde_json::Error;
|
|
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
|
serde_json::from_str(s)
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -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-04-24 15:33:45 -07:00
|
|
|
|
impl SandboxPolicy {
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
/// Returns a policy with read-only disk access and no network.
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
pub fn new_read_only_policy() -> Self {
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
SandboxPolicy::ReadOnly
|
2025-04-24 15:33:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
/// Returns a policy that can read the entire disk, but can only write to
|
|
|
|
|
|
/// the current working directory and the per-user tmp dir on macOS. It does
|
|
|
|
|
|
/// not allow network access.
|
|
|
|
|
|
pub fn new_workspace_write_policy() -> Self {
|
|
|
|
|
|
SandboxPolicy::WorkspaceWrite {
|
2025-06-24 17:48:51 -07:00
|
|
|
|
writable_roots: vec![],
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
network_access: false,
|
2025-08-07 00:17:00 -07:00
|
|
|
|
exclude_tmpdir_env_var: false,
|
|
|
|
|
|
exclude_slash_tmp: false,
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 17:37:28 -07:00
|
|
|
|
/// Always returns `true`; restricting read access is not supported.
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
pub fn has_full_disk_read_access(&self) -> bool {
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
true
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn has_full_disk_write_access(&self) -> bool {
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
match self {
|
|
|
|
|
|
SandboxPolicy::DangerFullAccess => true,
|
2025-12-18 17:02:03 -08:00
|
|
|
|
SandboxPolicy::ExternalSandbox { .. } => true,
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
SandboxPolicy::ReadOnly => false,
|
|
|
|
|
|
SandboxPolicy::WorkspaceWrite { .. } => false,
|
|
|
|
|
|
}
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn has_full_network_access(&self) -> bool {
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
match self {
|
|
|
|
|
|
SandboxPolicy::DangerFullAccess => true,
|
2025-12-18 17:02:03 -08:00
|
|
|
|
SandboxPolicy::ExternalSandbox { network_access } => network_access.is_enabled(),
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
SandboxPolicy::ReadOnly => false,
|
|
|
|
|
|
SandboxPolicy::WorkspaceWrite { network_access, .. } => *network_access,
|
|
|
|
|
|
}
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 16:11:24 -07:00
|
|
|
|
/// Returns the list of writable roots (tailored to the current working
|
|
|
|
|
|
/// directory) together with subpaths that should remain read‑only under
|
|
|
|
|
|
/// each writable root.
|
|
|
|
|
|
pub fn get_writable_roots_with_cwd(&self, cwd: &Path) -> Vec<WritableRoot> {
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
match self {
|
|
|
|
|
|
SandboxPolicy::DangerFullAccess => Vec::new(),
|
2025-12-18 17:02:03 -08:00
|
|
|
|
SandboxPolicy::ExternalSandbox { .. } => Vec::new(),
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
SandboxPolicy::ReadOnly => Vec::new(),
|
2025-08-01 14:15:55 -07:00
|
|
|
|
SandboxPolicy::WorkspaceWrite {
|
|
|
|
|
|
writable_roots,
|
2025-08-07 00:17:00 -07:00
|
|
|
|
exclude_tmpdir_env_var,
|
|
|
|
|
|
exclude_slash_tmp,
|
|
|
|
|
|
network_access: _,
|
2025-08-01 14:15:55 -07:00
|
|
|
|
} => {
|
2025-08-01 16:11:24 -07:00
|
|
|
|
// Start from explicitly configured writable roots.
|
2025-12-12 15:25:22 -08:00
|
|
|
|
let mut roots: Vec<AbsolutePathBuf> = writable_roots.clone();
|
2025-08-01 16:11:24 -07:00
|
|
|
|
|
2025-08-07 00:17:00 -07:00
|
|
|
|
// Always include defaults: cwd, /tmp (if present on Unix), and
|
|
|
|
|
|
// on macOS, the per-user TMPDIR unless explicitly excluded.
|
2025-12-12 15:25:22 -08:00
|
|
|
|
// TODO(mbolin): cwd param should be AbsolutePathBuf.
|
|
|
|
|
|
let cwd_absolute = AbsolutePathBuf::from_absolute_path(cwd);
|
|
|
|
|
|
match cwd_absolute {
|
|
|
|
|
|
Ok(cwd) => {
|
|
|
|
|
|
roots.push(cwd);
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(e) => {
|
|
|
|
|
|
error!(
|
|
|
|
|
|
"Ignoring invalid cwd {:?} for sandbox writable root: {}",
|
|
|
|
|
|
cwd, e
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-07 00:17:00 -07:00
|
|
|
|
|
|
|
|
|
|
// Include /tmp on Unix unless explicitly excluded.
|
|
|
|
|
|
if cfg!(unix) && !exclude_slash_tmp {
|
2025-12-12 15:25:22 -08:00
|
|
|
|
#[allow(clippy::expect_used)]
|
|
|
|
|
|
let slash_tmp =
|
|
|
|
|
|
AbsolutePathBuf::from_absolute_path("/tmp").expect("/tmp is absolute");
|
|
|
|
|
|
if slash_tmp.as_path().is_dir() {
|
2025-08-07 00:17:00 -07:00
|
|
|
|
roots.push(slash_tmp);
|
2025-06-24 17:48:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 00:17:00 -07:00
|
|
|
|
// Include $TMPDIR unless explicitly excluded. On macOS, TMPDIR
|
|
|
|
|
|
// is per-user, so writes to TMPDIR should not be readable by
|
|
|
|
|
|
// other users on the system.
|
|
|
|
|
|
//
|
|
|
|
|
|
// By comparison, TMPDIR is not guaranteed to be defined on
|
|
|
|
|
|
// Linux or Windows, but supporting it here gives users a way to
|
|
|
|
|
|
// provide the model with their own temporary directory without
|
|
|
|
|
|
// having to hardcode it in the config.
|
|
|
|
|
|
if !exclude_tmpdir_env_var
|
|
|
|
|
|
&& let Some(tmpdir) = std::env::var_os("TMPDIR")
|
|
|
|
|
|
&& !tmpdir.is_empty()
|
|
|
|
|
|
{
|
2025-12-12 16:31:34 -08:00
|
|
|
|
match AbsolutePathBuf::from_absolute_path(PathBuf::from(&tmpdir)) {
|
|
|
|
|
|
Ok(tmpdir_path) => {
|
|
|
|
|
|
roots.push(tmpdir_path);
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(e) => {
|
|
|
|
|
|
error!(
|
|
|
|
|
|
"Ignoring invalid TMPDIR value {tmpdir:?} for sandbox writable root: {e}",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-12-12 15:25:22 -08:00
|
|
|
|
}
|
2025-08-07 00:17:00 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 16:11:24 -07:00
|
|
|
|
// For each root, compute subpaths that should remain read-only.
|
feat: redesign sandbox config (#1373)
This is a major redesign of how sandbox configuration works and aims to
fix https://github.com/openai/codex/issues/1248. Specifically, it
replaces `sandbox_permissions` in `config.toml` (and the
`-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
three variants:
```toml
# Safest option: full disk is read-only, but writes and network access are disallowed.
[sandbox]
mode = "read-only"
# The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
# writable_roots can be used to specify additional writable folders.
[sandbox]
mode = "workspace-write"
writable_roots = [] # Optional, defaults to the empty list.
network_access = false # Optional, defaults to false.
# Disable sandboxing: use at your own risk!!!
[sandbox]
mode = "danger-full-access"
```
This should make sandboxing easier to reason about. While we have
dropped support for `-s`, the way it works now is:
- no flags => `read-only`
- `--full-auto` => `workspace-write`
- currently, there is no way to specify `danger-full-access` via a CLI
flag, but we will revisit that as part of
https://github.com/openai/codex/issues/1254
Outstanding issue:
- As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
still conflating sandbox preferences with approval preferences in that
case, which needs to be cleaned up.
2025-06-24 16:59:47 -07:00
|
|
|
|
roots
|
2025-08-01 16:11:24 -07:00
|
|
|
|
.into_iter()
|
|
|
|
|
|
.map(|writable_root| {
|
2026-01-14 08:30:46 -08:00
|
|
|
|
let mut subpaths: Vec<AbsolutePathBuf> = Vec::new();
|
2025-12-12 15:25:22 -08:00
|
|
|
|
#[allow(clippy::expect_used)]
|
|
|
|
|
|
let top_level_git = writable_root
|
|
|
|
|
|
.join(".git")
|
|
|
|
|
|
.expect(".git is a valid relative path");
|
2026-01-14 08:30:46 -08:00
|
|
|
|
// This applies to typical repos (directory .git), worktrees/submodules
|
|
|
|
|
|
// (file .git with gitdir pointer), and bare repos when the gitdir is the
|
|
|
|
|
|
// writable root itself.
|
|
|
|
|
|
let top_level_git_is_file = top_level_git.as_path().is_file();
|
|
|
|
|
|
let top_level_git_is_dir = top_level_git.as_path().is_dir();
|
|
|
|
|
|
if top_level_git_is_dir || top_level_git_is_file {
|
|
|
|
|
|
if top_level_git_is_file
|
|
|
|
|
|
&& is_git_pointer_file(&top_level_git)
|
|
|
|
|
|
&& let Some(gitdir) = resolve_gitdir_from_file(&top_level_git)
|
|
|
|
|
|
&& !subpaths
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.any(|subpath| subpath.as_path() == gitdir.as_path())
|
|
|
|
|
|
{
|
|
|
|
|
|
subpaths.push(gitdir);
|
|
|
|
|
|
}
|
2025-08-01 16:11:24 -07:00
|
|
|
|
subpaths.push(top_level_git);
|
|
|
|
|
|
}
|
feat: if .codex is a sub-folder of a writable root, then make it read-only to the sandbox (#8088)
In preparation for in-repo configuration support, this updates
`WritableRoot::get_writable_roots_with_cwd()` to include the `.codex`
subfolder in `WritableRoot.read_only_subpaths`, if it exists, as we
already do for `.git`.
As noted, currently, like `.git`, `.codex` will only be read-only under
macOS Seatbelt, but we plan to bring support to other OSes, as well.
Updated the integration test in `seatbelt.rs` so that it actually
attempts to run the generated Seatbelt commands, verifying that:
- trying to write to `.codex/config.toml` in a writable root fails
- trying to write to `.git/hooks/pre-commit` in a writable root fails
- trying to write to the writable root containing the `.codex` and
`.git` subfolders succeeds
2025-12-15 22:54:43 -08:00
|
|
|
|
#[allow(clippy::expect_used)]
|
|
|
|
|
|
let top_level_codex = writable_root
|
|
|
|
|
|
.join(".codex")
|
|
|
|
|
|
.expect(".codex is a valid relative path");
|
|
|
|
|
|
if top_level_codex.as_path().is_dir() {
|
|
|
|
|
|
subpaths.push(top_level_codex);
|
|
|
|
|
|
}
|
2025-08-01 16:11:24 -07:00
|
|
|
|
WritableRoot {
|
|
|
|
|
|
root: writable_root,
|
|
|
|
|
|
read_only_subpaths: subpaths,
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect()
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-24 15:33:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
|
2026-01-14 08:30:46 -08:00
|
|
|
|
fn is_git_pointer_file(path: &AbsolutePathBuf) -> bool {
|
|
|
|
|
|
path.as_path().is_file() && path.as_path().file_name() == Some(OsStr::new(".git"))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn resolve_gitdir_from_file(dot_git: &AbsolutePathBuf) -> Option<AbsolutePathBuf> {
|
|
|
|
|
|
let contents = match std::fs::read_to_string(dot_git.as_path()) {
|
|
|
|
|
|
Ok(contents) => contents,
|
|
|
|
|
|
Err(err) => {
|
|
|
|
|
|
error!(
|
|
|
|
|
|
"Failed to read {path} for gitdir pointer: {err}",
|
|
|
|
|
|
path = dot_git.as_path().display()
|
|
|
|
|
|
);
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let trimmed = contents.trim();
|
|
|
|
|
|
let (_, gitdir_raw) = match trimmed.split_once(':') {
|
|
|
|
|
|
Some(parts) => parts,
|
|
|
|
|
|
None => {
|
|
|
|
|
|
error!(
|
|
|
|
|
|
"Expected {path} to contain a gitdir pointer, but it did not match `gitdir: <path>`.",
|
|
|
|
|
|
path = dot_git.as_path().display()
|
|
|
|
|
|
);
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
let gitdir_raw = gitdir_raw.trim();
|
|
|
|
|
|
if gitdir_raw.is_empty() {
|
|
|
|
|
|
error!(
|
|
|
|
|
|
"Expected {path} to contain a gitdir pointer, but it was empty.",
|
|
|
|
|
|
path = dot_git.as_path().display()
|
|
|
|
|
|
);
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
let base = match dot_git.as_path().parent() {
|
|
|
|
|
|
Some(base) => base,
|
|
|
|
|
|
None => {
|
|
|
|
|
|
error!(
|
|
|
|
|
|
"Unable to resolve parent directory for {path}.",
|
|
|
|
|
|
path = dot_git.as_path().display()
|
|
|
|
|
|
);
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
let gitdir_path = match AbsolutePathBuf::resolve_path_against_base(gitdir_raw, base) {
|
|
|
|
|
|
Ok(path) => path,
|
|
|
|
|
|
Err(err) => {
|
|
|
|
|
|
error!(
|
|
|
|
|
|
"Failed to resolve gitdir path {gitdir_raw} from {path}: {err}",
|
|
|
|
|
|
path = dot_git.as_path().display()
|
|
|
|
|
|
);
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
if !gitdir_path.as_path().exists() {
|
|
|
|
|
|
error!(
|
|
|
|
|
|
"Resolved gitdir path {path} does not exist.",
|
|
|
|
|
|
path = gitdir_path.as_path().display()
|
|
|
|
|
|
);
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
Some(gitdir_path)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
/// Event Queue Entry - events from agent
|
2025-10-20 13:34:44 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
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 Event {
|
|
|
|
|
|
/// Submission `id` that this event is correlated with.
|
|
|
|
|
|
pub id: String,
|
|
|
|
|
|
/// Payload
|
|
|
|
|
|
pub msg: EventMsg,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Response event from the agent
|
2025-09-14 17:34:33 -07:00
|
|
|
|
/// NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, Display, 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")]
|
2025-11-13 16:25:17 -08:00
|
|
|
|
#[ts(tag = "type")]
|
2025-07-28 10:26:27 -07:00
|
|
|
|
#[strum(serialize_all = "snake_case")]
|
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 enum EventMsg {
|
|
|
|
|
|
/// Error while executing a submission
|
2025-05-13 20:44:42 -07:00
|
|
|
|
Error(ErrorEvent),
|
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-31 13:27:33 -07:00
|
|
|
|
/// Warning issued while processing a submission. Unlike `Error`, this
|
2026-01-09 17:31:17 +00:00
|
|
|
|
/// indicates the turn continued but the user should still be notified.
|
2025-10-31 13:27:33 -07:00
|
|
|
|
Warning(WarningEvent),
|
|
|
|
|
|
|
2025-11-25 16:12:14 +00:00
|
|
|
|
/// Conversation history was compacted (either automatically or manually).
|
|
|
|
|
|
ContextCompacted(ContextCompactedEvent),
|
|
|
|
|
|
|
2026-01-06 13:23:48 -08:00
|
|
|
|
/// Conversation history was rolled back by dropping the last N user turns.
|
|
|
|
|
|
ThreadRolledBack(ThreadRolledBackEvent),
|
|
|
|
|
|
|
2026-01-09 17:31:17 +00:00
|
|
|
|
/// Agent has started a turn.
|
|
|
|
|
|
/// v1 wire format uses `task_started`; accept `turn_started` for v2 interop.
|
|
|
|
|
|
#[serde(rename = "task_started", alias = "turn_started")]
|
|
|
|
|
|
TurnStarted(TurnStartedEvent),
|
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-09 17:31:17 +00:00
|
|
|
|
/// Agent has completed all actions.
|
|
|
|
|
|
/// v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.
|
|
|
|
|
|
#[serde(rename = "task_complete", alias = "turn_complete")]
|
|
|
|
|
|
TurnComplete(TurnCompleteEvent),
|
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-06 08:19:23 -07:00
|
|
|
|
/// Usage update for the current session, including totals and last turn.
|
|
|
|
|
|
/// Optional means unknown — UIs should not display when `None`.
|
|
|
|
|
|
TokenCount(TokenCountEvent),
|
feat: show number of tokens remaining in UI (#1388)
When using the OpenAI Responses API, we now record the `usage` field for
a `"response.completed"` event, which includes metrics about the number
of tokens consumed. We also introduce `openai_model_info.rs`, which
includes current data about the most common OpenAI models available via
the API (specifically `context_window` and `max_output_tokens`). If
Codex does not recognize the model, you can set `model_context_window`
and `model_max_output_tokens` explicitly in `config.toml`.
When then introduce a new event type to `protocol.rs`, `TokenCount`,
which includes the `TokenUsage` for the most recent turn.
Finally, we update the TUI to record the running sum of tokens used so
the percentage of available context window remaining can be reported via
the placeholder text for the composer:

We could certainly get much fancier with this (such as reporting the
estimated cost of the conversation), but for now, we are just trying to
achieve feature parity with the TypeScript CLI.
Though arguably this improves upon the TypeScript CLI, as the TypeScript
CLI uses heuristics to estimate the number of tokens used rather than
using the `usage` information directly:
https://github.com/openai/codex/blob/296996d74e345b1b05d8c3451a06ace21c5ada96/codex-cli/src/utils/approximate-tokens-used.ts#L3-L16
Fixes https://github.com/openai/codex/issues/1242
2025-06-25 23:31:11 -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
|
|
|
|
/// Agent text output message
|
2025-05-13 20:44:42 -07:00
|
|
|
|
AgentMessage(AgentMessageEvent),
|
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
|
|
|
|
/// User/system input message (what was sent to the model)
|
|
|
|
|
|
UserMessage(UserMessageEvent),
|
|
|
|
|
|
|
2025-07-16 15:11:18 -07:00
|
|
|
|
/// Agent text output delta message
|
|
|
|
|
|
AgentMessageDelta(AgentMessageDeltaEvent),
|
|
|
|
|
|
|
2025-05-10 21:43:27 -07:00
|
|
|
|
/// Reasoning event from agent.
|
2025-05-13 20:44:42 -07:00
|
|
|
|
AgentReasoning(AgentReasoningEvent),
|
2025-05-10 21:43:27 -07:00
|
|
|
|
|
2025-07-16 15:11:18 -07:00
|
|
|
|
/// Agent reasoning delta event from agent.
|
|
|
|
|
|
AgentReasoningDelta(AgentReasoningDeltaEvent),
|
|
|
|
|
|
|
2025-08-05 01:56:13 -07:00
|
|
|
|
/// Raw chain-of-thought from agent.
|
|
|
|
|
|
AgentReasoningRawContent(AgentReasoningRawContentEvent),
|
|
|
|
|
|
|
|
|
|
|
|
/// Agent reasoning content delta event from agent.
|
|
|
|
|
|
AgentReasoningRawContentDelta(AgentReasoningRawContentDeltaEvent),
|
2025-08-12 17:37:28 -07:00
|
|
|
|
/// Signaled when the model begins a new reasoning summary section (e.g., a new titled block).
|
|
|
|
|
|
AgentReasoningSectionBreak(AgentReasoningSectionBreakEvent),
|
2025-08-05 01:56:13 -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
|
|
|
|
/// Ack the client's configure message.
|
2025-05-13 19:22:16 -07:00
|
|
|
|
SessionConfigured(SessionConfiguredEvent),
|
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-11-17 11:26:11 -08:00
|
|
|
|
/// Incremental MCP startup progress updates.
|
|
|
|
|
|
McpStartupUpdate(McpStartupUpdateEvent),
|
|
|
|
|
|
|
|
|
|
|
|
/// Aggregate MCP startup completion summary.
|
|
|
|
|
|
McpStartupComplete(McpStartupCompleteEvent),
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
McpToolCallBegin(McpToolCallBeginEvent),
|
feat: support mcp_servers in config.toml (#829)
This adds initial support for MCP servers in the style of Claude Desktop
and Cursor. Note this PR is the bare minimum to get things working end
to end: all configured MCP servers are launched every time Codex is run,
there is no recovery for MCP servers that crash, etc.
(Also, I took some shortcuts to change some fields of `Session` to be
`pub(crate)`, which also means there are circular deps between
`codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
subsequent PR.)
`codex-rs/README.md` is updated as part of this PR to explain how to use
this feature. There is a bit of plumbing to route the new settings from
`Config` to the business logic in `codex.rs`. The most significant
chunks for new code are in `mcp_connection_manager.rs` (which defines
the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
responsible for tool calls.
This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
event types to the protocol, but does not add any handlers for them.
(See https://github.com/openai/codex/pull/836 for initial usage.)
To test, I added the following to my `~/.codex/config.toml`:
```toml
# Local build of https://github.com/hideya/mcp-server-weather-js
[mcp_servers.weather]
command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
args = []
```
And then I ran the following:
```
codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
[2025-05-06T22:40:05] Task started: 1
[2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
This Afternoon (Tue):
• Sunny, high near 69 °F
• West-southwest wind around 12 mph
Tonight:
• Partly cloudy, low around 52 °F
• SW wind 7–10 mph
...
```
Note that Codex itself is not able to make network calls, so it would
not normally be able to get live weather information like this. However,
the weather MCP is [currently] not run under the Codex sandbox, so it is
able to hit `api.weather.gov` and fetch current weather information.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
* #836
* __->__ #829
2025-05-06 15:47:59 -07:00
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
McpToolCallEnd(McpToolCallEndEvent),
|
feat: support mcp_servers in config.toml (#829)
This adds initial support for MCP servers in the style of Claude Desktop
and Cursor. Note this PR is the bare minimum to get things working end
to end: all configured MCP servers are launched every time Codex is run,
there is no recovery for MCP servers that crash, etc.
(Also, I took some shortcuts to change some fields of `Session` to be
`pub(crate)`, which also means there are circular deps between
`codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
subsequent PR.)
`codex-rs/README.md` is updated as part of this PR to explain how to use
this feature. There is a bit of plumbing to route the new settings from
`Config` to the business logic in `codex.rs`. The most significant
chunks for new code are in `mcp_connection_manager.rs` (which defines
the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
responsible for tool calls.
This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
event types to the protocol, but does not add any handlers for them.
(See https://github.com/openai/codex/pull/836 for initial usage.)
To test, I added the following to my `~/.codex/config.toml`:
```toml
# Local build of https://github.com/hideya/mcp-server-weather-js
[mcp_servers.weather]
command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
args = []
```
And then I ran the following:
```
codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
[2025-05-06T22:40:05] Task started: 1
[2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
This Afternoon (Tue):
• Sunny, high near 69 °F
• West-southwest wind around 12 mph
Tonight:
• Partly cloudy, low around 52 °F
• SW wind 7–10 mph
...
```
Note that Codex itself is not able to make network calls, so it would
not normally be able to get live weather information like this. However,
the weather MCP is [currently] not run under the Codex sandbox, so it is
able to hit `api.weather.gov` and fetch current weather information.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
* #836
* __->__ #829
2025-05-06 15:47:59 -07:00
|
|
|
|
|
2025-08-23 22:58:56 -07:00
|
|
|
|
WebSearchBegin(WebSearchBeginEvent),
|
|
|
|
|
|
|
2025-08-28 19:24:38 -07:00
|
|
|
|
WebSearchEnd(WebSearchEndEvent),
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// Notification that the server is about to execute a command.
|
|
|
|
|
|
ExecCommandBegin(ExecCommandBeginEvent),
|
feat: support mcp_servers in config.toml (#829)
This adds initial support for MCP servers in the style of Claude Desktop
and Cursor. Note this PR is the bare minimum to get things working end
to end: all configured MCP servers are launched every time Codex is run,
there is no recovery for MCP servers that crash, etc.
(Also, I took some shortcuts to change some fields of `Session` to be
`pub(crate)`, which also means there are circular deps between
`codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
subsequent PR.)
`codex-rs/README.md` is updated as part of this PR to explain how to use
this feature. There is a bit of plumbing to route the new settings from
`Config` to the business logic in `codex.rs`. The most significant
chunks for new code are in `mcp_connection_manager.rs` (which defines
the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
responsible for tool calls.
This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
event types to the protocol, but does not add any handlers for them.
(See https://github.com/openai/codex/pull/836 for initial usage.)
To test, I added the following to my `~/.codex/config.toml`:
```toml
# Local build of https://github.com/hideya/mcp-server-weather-js
[mcp_servers.weather]
command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
args = []
```
And then I ran the following:
```
codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
[2025-05-06T22:40:05] Task started: 1
[2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
This Afternoon (Tue):
• Sunny, high near 69 °F
• West-southwest wind around 12 mph
Tonight:
• Partly cloudy, low around 52 °F
• SW wind 7–10 mph
...
```
Note that Codex itself is not able to make network calls, so it would
not normally be able to get live weather information like this. However,
the weather MCP is [currently] not run under the Codex sandbox, so it is
able to hit `api.weather.gov` and fetch current weather information.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
* #836
* __->__ #829
2025-05-06 15:47:59 -07:00
|
|
|
|
|
2025-08-01 14:00:19 -07:00
|
|
|
|
/// Incremental chunk of output from a running command.
|
|
|
|
|
|
ExecCommandOutputDelta(ExecCommandOutputDeltaEvent),
|
2025-08-01 13:04:34 -07:00
|
|
|
|
|
2025-12-10 10:30:38 +00:00
|
|
|
|
/// Terminal interaction for an in-progress command (stdin sent and stdout observed).
|
|
|
|
|
|
TerminalInteraction(TerminalInteractionEvent),
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
ExecCommandEnd(ExecCommandEndEvent),
|
feat: support mcp_servers in config.toml (#829)
This adds initial support for MCP servers in the style of Claude Desktop
and Cursor. Note this PR is the bare minimum to get things working end
to end: all configured MCP servers are launched every time Codex is run,
there is no recovery for MCP servers that crash, etc.
(Also, I took some shortcuts to change some fields of `Session` to be
`pub(crate)`, which also means there are circular deps between
`codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
subsequent PR.)
`codex-rs/README.md` is updated as part of this PR to explain how to use
this feature. There is a bit of plumbing to route the new settings from
`Config` to the business logic in `codex.rs`. The most significant
chunks for new code are in `mcp_connection_manager.rs` (which defines
the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
responsible for tool calls.
This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
event types to the protocol, but does not add any handlers for them.
(See https://github.com/openai/codex/pull/836 for initial usage.)
To test, I added the following to my `~/.codex/config.toml`:
```toml
# Local build of https://github.com/hideya/mcp-server-weather-js
[mcp_servers.weather]
command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
args = []
```
And then I ran the following:
```
codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
[2025-05-06T22:40:05] Task started: 1
[2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
This Afternoon (Tue):
• Sunny, high near 69 °F
• West-southwest wind around 12 mph
Tonight:
• Partly cloudy, low around 52 °F
• SW wind 7–10 mph
...
```
Note that Codex itself is not able to make network calls, so it would
not normally be able to get live weather information like this. However,
the weather MCP is [currently] not run under the Codex sandbox, so it is
able to hit `api.weather.gov` and fetch current weather information.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
* #836
* __->__ #829
2025-05-06 15:47:59 -07:00
|
|
|
|
|
2025-10-02 11:36:03 -07:00
|
|
|
|
/// Notification that the agent attached a local image via the view_image tool.
|
|
|
|
|
|
ViewImageToolCall(ViewImageToolCallEvent),
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
ExecApprovalRequest(ExecApprovalRequestEvent),
|
feat: support mcp_servers in config.toml (#829)
This adds initial support for MCP servers in the style of Claude Desktop
and Cursor. Note this PR is the bare minimum to get things working end
to end: all configured MCP servers are launched every time Codex is run,
there is no recovery for MCP servers that crash, etc.
(Also, I took some shortcuts to change some fields of `Session` to be
`pub(crate)`, which also means there are circular deps between
`codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
subsequent PR.)
`codex-rs/README.md` is updated as part of this PR to explain how to use
this feature. There is a bit of plumbing to route the new settings from
`Config` to the business logic in `codex.rs`. The most significant
chunks for new code are in `mcp_connection_manager.rs` (which defines
the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
responsible for tool calls.
This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
event types to the protocol, but does not add any handlers for them.
(See https://github.com/openai/codex/pull/836 for initial usage.)
To test, I added the following to my `~/.codex/config.toml`:
```toml
# Local build of https://github.com/hideya/mcp-server-weather-js
[mcp_servers.weather]
command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
args = []
```
And then I ran the following:
```
codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
[2025-05-06T22:40:05] Task started: 1
[2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
This Afternoon (Tue):
• Sunny, high near 69 °F
• West-southwest wind around 12 mph
Tonight:
• Partly cloudy, low around 52 °F
• SW wind 7–10 mph
...
```
Note that Codex itself is not able to make network calls, so it would
not normally be able to get live weather information like this. However,
the weather MCP is [currently] not run under the Codex sandbox, so it is
able to hit `api.weather.gov` and fetch current weather information.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
* #836
* __->__ #829
2025-05-06 15:47:59 -07:00
|
|
|
|
|
Feat: request user input tool (#9472)
### Summary
* Add `requestUserInput` tool that the model can use for gather
feedback/asking question mid turn.
### Tool input schema
```
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "requestUserInput input",
"type": "object",
"additionalProperties": false,
"required": ["questions"],
"properties": {
"questions": {
"type": "array",
"description": "Questions to show the user (1-3). Prefer 1 unless multiple independent decisions block progress.",
"minItems": 1,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "header", "question"],
"properties": {
"id": {
"type": "string",
"description": "Stable identifier for mapping answers (snake_case)."
},
"header": {
"type": "string",
"description": "Short header label shown in the UI (12 or fewer chars)."
},
"question": {
"type": "string",
"description": "Single-sentence prompt shown to the user."
},
"options": {
"type": "array",
"description": "Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with \"(Recommended)\". Only include \"Other\" option if we want to include a free form option. If the question is free form in nature, do not include any option.",
"minItems": 2,
"maxItems": 3,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["value", "label", "description"],
"properties": {
"value": {
"type": "string",
"description": "Machine-readable value (snake_case)."
},
"label": {
"type": "string",
"description": "User-facing label (1-5 words)."
},
"description": {
"type": "string",
"description": "One short sentence explaining impact/tradeoff if selected."
}
}
}
}
}
}
}
}
}
```
### Tool output schema
```
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "requestUserInput output",
"type": "object",
"additionalProperties": false,
"required": ["answers"],
"properties": {
"answers": {
"type": "object",
"description": "Map of question id to user answer.",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"required": ["selected"],
"properties": {
"selected": {
"type": "array",
"items": { "type": "string" }
},
"other": {
"type": ["string", "null"]
}
}
}
}
}
}
```
2026-01-19 10:17:30 -08:00
|
|
|
|
RequestUserInput(RequestUserInputEvent),
|
|
|
|
|
|
|
2026-01-26 11:06:44 +01:00
|
|
|
|
DynamicToolCallRequest(DynamicToolCallRequest),
|
|
|
|
|
|
|
2025-11-21 14:44:53 -08:00
|
|
|
|
ElicitationRequest(ElicitationRequestEvent),
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent),
|
feat: support mcp_servers in config.toml (#829)
This adds initial support for MCP servers in the style of Claude Desktop
and Cursor. Note this PR is the bare minimum to get things working end
to end: all configured MCP servers are launched every time Codex is run,
there is no recovery for MCP servers that crash, etc.
(Also, I took some shortcuts to change some fields of `Session` to be
`pub(crate)`, which also means there are circular deps between
`codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
subsequent PR.)
`codex-rs/README.md` is updated as part of this PR to explain how to use
this feature. There is a bit of plumbing to route the new settings from
`Config` to the business logic in `codex.rs`. The most significant
chunks for new code are in `mcp_connection_manager.rs` (which defines
the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
responsible for tool calls.
This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
event types to the protocol, but does not add any handlers for them.
(See https://github.com/openai/codex/pull/836 for initial usage.)
To test, I added the following to my `~/.codex/config.toml`:
```toml
# Local build of https://github.com/hideya/mcp-server-weather-js
[mcp_servers.weather]
command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
args = []
```
And then I ran the following:
```
codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
[2025-05-06T22:40:05] Task started: 1
[2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
This Afternoon (Tue):
• Sunny, high near 69 °F
• West-southwest wind around 12 mph
Tonight:
• Partly cloudy, low around 52 °F
• SW wind 7–10 mph
...
```
Note that Codex itself is not able to make network calls, so it would
not normally be able to get live weather information like this. However,
the weather MCP is [currently] not run under the Codex sandbox, so it is
able to hit `api.weather.gov` and fetch current weather information.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
* #836
* __->__ #829
2025-05-06 15:47:59 -07:00
|
|
|
|
|
2025-10-29 12:29:28 +00:00
|
|
|
|
/// Notification advising the user that something they are using has been
|
|
|
|
|
|
/// deprecated and should be phased out.
|
|
|
|
|
|
DeprecationNotice(DeprecationNoticeEvent),
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
BackgroundEvent(BackgroundEventEvent),
|
feat: support mcp_servers in config.toml (#829)
This adds initial support for MCP servers in the style of Claude Desktop
and Cursor. Note this PR is the bare minimum to get things working end
to end: all configured MCP servers are launched every time Codex is run,
there is no recovery for MCP servers that crash, etc.
(Also, I took some shortcuts to change some fields of `Session` to be
`pub(crate)`, which also means there are circular deps between
`codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
subsequent PR.)
`codex-rs/README.md` is updated as part of this PR to explain how to use
this feature. There is a bit of plumbing to route the new settings from
`Config` to the business logic in `codex.rs`. The most significant
chunks for new code are in `mcp_connection_manager.rs` (which defines
the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
responsible for tool calls.
This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
event types to the protocol, but does not add any handlers for them.
(See https://github.com/openai/codex/pull/836 for initial usage.)
To test, I added the following to my `~/.codex/config.toml`:
```toml
# Local build of https://github.com/hideya/mcp-server-weather-js
[mcp_servers.weather]
command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
args = []
```
And then I ran the following:
```
codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
[2025-05-06T22:40:05] Task started: 1
[2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
This Afternoon (Tue):
• Sunny, high near 69 °F
• West-southwest wind around 12 mph
Tonight:
• Partly cloudy, low around 52 °F
• SW wind 7–10 mph
...
```
Note that Codex itself is not able to make network calls, so it would
not normally be able to get live weather information like this. However,
the weather MCP is [currently] not run under the Codex sandbox, so it is
able to hit `api.weather.gov` and fetch current weather information.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
* #836
* __->__ #829
2025-05-06 15:47:59 -07:00
|
|
|
|
|
2025-10-27 10:55:29 +00:00
|
|
|
|
UndoStarted(UndoStartedEvent),
|
|
|
|
|
|
|
|
|
|
|
|
UndoCompleted(UndoCompletedEvent),
|
|
|
|
|
|
|
2025-08-21 01:15:24 -07:00
|
|
|
|
/// Notification that a model stream experienced an error or disconnect
|
|
|
|
|
|
/// and the system is handling it (e.g., retrying with backoff).
|
|
|
|
|
|
StreamError(StreamErrorEvent),
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// Notification that the agent is about to apply a code patch. Mirrors
|
|
|
|
|
|
/// `ExecCommandBegin` so front‑ends can show progress indicators.
|
|
|
|
|
|
PatchApplyBegin(PatchApplyBeginEvent),
|
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-13 20:44:42 -07:00
|
|
|
|
/// Notification that a patch application has finished.
|
|
|
|
|
|
PatchApplyEnd(PatchApplyEndEvent),
|
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
|
|
|
|
|
2025-08-04 08:57:04 -07:00
|
|
|
|
TurnDiff(TurnDiffEvent),
|
|
|
|
|
|
|
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
|
|
|
|
/// Response to GetHistoryEntryRequest.
|
|
|
|
|
|
GetHistoryEntryResponse(GetHistoryEntryResponseEvent),
|
2025-07-23 15:03:26 -07:00
|
|
|
|
|
2025-08-19 09:00:31 -07:00
|
|
|
|
/// List of MCP tools available to the agent.
|
|
|
|
|
|
McpListToolsResponse(McpListToolsResponseEvent),
|
|
|
|
|
|
|
2025-08-28 19:16:39 -07:00
|
|
|
|
/// List of custom prompts available to the agent.
|
|
|
|
|
|
ListCustomPromptsResponse(ListCustomPromptsResponseEvent),
|
|
|
|
|
|
|
2025-12-14 09:58:17 -08:00
|
|
|
|
/// List of skills available to the agent.
|
|
|
|
|
|
ListSkillsResponse(ListSkillsResponseEvent),
|
|
|
|
|
|
|
2025-12-17 01:35:49 -08:00
|
|
|
|
/// Notification that skill data may have been updated and clients may want to reload.
|
|
|
|
|
|
SkillsUpdateAvailable,
|
|
|
|
|
|
|
2025-07-29 11:22:02 -07:00
|
|
|
|
PlanUpdate(UpdatePlanArgs),
|
|
|
|
|
|
|
2025-08-17 21:40:31 -07:00
|
|
|
|
TurnAborted(TurnAbortedEvent),
|
|
|
|
|
|
|
2025-07-23 15:03:26 -07:00
|
|
|
|
/// Notification that the agent is shutting down.
|
|
|
|
|
|
ShutdownComplete,
|
2025-08-22 17:06:09 -07:00
|
|
|
|
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
/// Entered review mode.
|
|
|
|
|
|
EnteredReviewMode(ReviewRequest),
|
|
|
|
|
|
|
|
|
|
|
|
/// Exited review mode with an optional final result to apply.
|
2025-09-14 17:34:33 -07:00
|
|
|
|
ExitedReviewMode(ExitedReviewModeEvent),
|
2025-10-20 13:34:44 -07:00
|
|
|
|
|
2025-10-29 13:32:40 -07:00
|
|
|
|
RawResponseItem(RawResponseItemEvent),
|
2025-10-24 15:41:52 -07:00
|
|
|
|
|
2025-10-20 13:34:44 -07:00
|
|
|
|
ItemStarted(ItemStartedEvent),
|
|
|
|
|
|
ItemCompleted(ItemCompletedEvent),
|
2025-10-29 15:33:57 -07:00
|
|
|
|
|
|
|
|
|
|
AgentMessageContentDelta(AgentMessageContentDeltaEvent),
|
|
|
|
|
|
ReasoningContentDelta(ReasoningContentDeltaEvent),
|
|
|
|
|
|
ReasoningRawContentDelta(ReasoningRawContentDeltaEvent),
|
feat: emit events around collab tools (#9095)
Emit the following events around the collab tools. On the `app-server`
this will be under `item/started` and `item/completed`
```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentSpawnBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
/// beginning.
pub prompt: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentSpawnEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the newly spawned agent, if it was created.
pub new_thread_id: Option<ThreadId>,
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
/// beginning.
pub prompt: String,
/// Last known status of the new agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentInteractionBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
/// leaking at the beginning.
pub prompt: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentInteractionEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
/// leaking at the beginning.
pub prompt: String,
/// Last known status of the receiver agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabWaitingBeginEvent {
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// ID of the waiting call.
pub call_id: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabWaitingEndEvent {
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// ID of the waiting call.
pub call_id: String,
/// Last known status of the receiver agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabCloseBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabCloseEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Last known status of the receiver agent reported to the sender agent before
/// the close.
pub status: AgentStatus,
}
```
2026-01-14 17:55:57 +00:00
|
|
|
|
|
|
|
|
|
|
/// Collab interaction: agent spawn begin.
|
|
|
|
|
|
CollabAgentSpawnBegin(CollabAgentSpawnBeginEvent),
|
|
|
|
|
|
/// Collab interaction: agent spawn end.
|
|
|
|
|
|
CollabAgentSpawnEnd(CollabAgentSpawnEndEvent),
|
|
|
|
|
|
/// Collab interaction: agent interaction begin.
|
|
|
|
|
|
CollabAgentInteractionBegin(CollabAgentInteractionBeginEvent),
|
|
|
|
|
|
/// Collab interaction: agent interaction end.
|
|
|
|
|
|
CollabAgentInteractionEnd(CollabAgentInteractionEndEvent),
|
|
|
|
|
|
/// Collab interaction: waiting begin.
|
|
|
|
|
|
CollabWaitingBegin(CollabWaitingBeginEvent),
|
|
|
|
|
|
/// Collab interaction: waiting end.
|
|
|
|
|
|
CollabWaitingEnd(CollabWaitingEndEvent),
|
|
|
|
|
|
/// Collab interaction: close begin.
|
|
|
|
|
|
CollabCloseBegin(CollabCloseBeginEvent),
|
|
|
|
|
|
/// Collab interaction: close end.
|
|
|
|
|
|
CollabCloseEnd(CollabCloseEndEvent),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<CollabAgentSpawnBeginEvent> for EventMsg {
|
|
|
|
|
|
fn from(event: CollabAgentSpawnBeginEvent) -> Self {
|
|
|
|
|
|
EventMsg::CollabAgentSpawnBegin(event)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<CollabAgentSpawnEndEvent> for EventMsg {
|
|
|
|
|
|
fn from(event: CollabAgentSpawnEndEvent) -> Self {
|
|
|
|
|
|
EventMsg::CollabAgentSpawnEnd(event)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<CollabAgentInteractionBeginEvent> for EventMsg {
|
|
|
|
|
|
fn from(event: CollabAgentInteractionBeginEvent) -> Self {
|
|
|
|
|
|
EventMsg::CollabAgentInteractionBegin(event)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<CollabAgentInteractionEndEvent> for EventMsg {
|
|
|
|
|
|
fn from(event: CollabAgentInteractionEndEvent) -> Self {
|
|
|
|
|
|
EventMsg::CollabAgentInteractionEnd(event)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<CollabWaitingBeginEvent> for EventMsg {
|
|
|
|
|
|
fn from(event: CollabWaitingBeginEvent) -> Self {
|
|
|
|
|
|
EventMsg::CollabWaitingBegin(event)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<CollabWaitingEndEvent> for EventMsg {
|
|
|
|
|
|
fn from(event: CollabWaitingEndEvent) -> Self {
|
|
|
|
|
|
EventMsg::CollabWaitingEnd(event)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<CollabCloseBeginEvent> for EventMsg {
|
|
|
|
|
|
fn from(event: CollabCloseBeginEvent) -> Self {
|
|
|
|
|
|
EventMsg::CollabCloseBegin(event)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<CollabCloseEndEvent> for EventMsg {
|
|
|
|
|
|
fn from(event: CollabCloseEndEvent) -> Self {
|
|
|
|
|
|
EventMsg::CollabCloseEnd(event)
|
|
|
|
|
|
}
|
2025-10-20 13:34:44 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-06 19:44:39 +00:00
|
|
|
|
/// Agent lifecycle status, derived from emitted events.
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS, Default)]
|
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
#[ts(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum AgentStatus {
|
|
|
|
|
|
/// Agent is waiting for initialization.
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
PendingInit,
|
|
|
|
|
|
/// Agent is currently running.
|
|
|
|
|
|
Running,
|
|
|
|
|
|
/// Agent is done. Contains the final assistant message.
|
|
|
|
|
|
Completed(Option<String>),
|
|
|
|
|
|
/// Agent encountered an error.
|
|
|
|
|
|
Errored(String),
|
2026-01-12 12:16:24 +00:00
|
|
|
|
/// Agent has been shutdown.
|
2026-01-06 19:44:39 +00:00
|
|
|
|
Shutdown,
|
|
|
|
|
|
/// Agent is not found.
|
|
|
|
|
|
NotFound,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[app-server & core] introduce new codex error code and v2 app-server error events (#6938)
This PR does two things:
1. populate a new `codex_error_code` protocol in error events sent from
core to client;
2. old v1 core events `codex/event/stream_error` and `codex/event/error`
will now both become `error`. We also show codex error code for
turncompleted -> error status.
new events in app server test:
```
< {
< "method": "codex/event/stream_error",
< "params": {
< "conversationId": "019aa34c-0c14-70e0-9706-98520a760d67",
< "id": "0",
< "msg": {
< "codex_error_code": {
< "response_stream_disconnected": {
< "http_status_code": 401
< }
< },
< "message": "Reconnecting... 2/5",
< "type": "stream_error"
< }
< }
< }
{
< "method": "error",
< "params": {
< "error": {
< "codexErrorCode": {
< "responseStreamDisconnected": {
< "httpStatusCode": 401
< }
< },
< "message": "Reconnecting... 2/5"
< }
< }
< }
< {
< "method": "turn/completed",
< "params": {
< "turn": {
< "error": {
< "codexErrorCode": {
< "responseTooManyFailedAttempts": {
< "httpStatusCode": 401
< }
< },
< "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a1b495a1a97ed3e-SJC"
< },
< "id": "0",
< "items": [],
< "status": "failed"
< }
< }
< }
```
2025-11-20 15:06:55 -08:00
|
|
|
|
/// Codex errors that we expose to clients.
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS)]
|
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
#[ts(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum CodexErrorInfo {
|
|
|
|
|
|
ContextWindowExceeded,
|
|
|
|
|
|
UsageLimitExceeded,
|
|
|
|
|
|
HttpConnectionFailed {
|
|
|
|
|
|
http_status_code: Option<u16>,
|
|
|
|
|
|
},
|
|
|
|
|
|
/// Failed to connect to the response SSE stream.
|
|
|
|
|
|
ResponseStreamConnectionFailed {
|
|
|
|
|
|
http_status_code: Option<u16>,
|
|
|
|
|
|
},
|
|
|
|
|
|
InternalServerError,
|
|
|
|
|
|
Unauthorized,
|
|
|
|
|
|
BadRequest,
|
|
|
|
|
|
SandboxError,
|
|
|
|
|
|
/// The response SSE stream disconnected in the middle of a turnbefore completion.
|
|
|
|
|
|
ResponseStreamDisconnected {
|
|
|
|
|
|
http_status_code: Option<u16>,
|
|
|
|
|
|
},
|
|
|
|
|
|
/// Reached the retry limit for responses.
|
|
|
|
|
|
ResponseTooManyFailedAttempts {
|
|
|
|
|
|
http_status_code: Option<u16>,
|
|
|
|
|
|
},
|
2026-01-06 13:23:48 -08:00
|
|
|
|
ThreadRollbackFailed,
|
[app-server & core] introduce new codex error code and v2 app-server error events (#6938)
This PR does two things:
1. populate a new `codex_error_code` protocol in error events sent from
core to client;
2. old v1 core events `codex/event/stream_error` and `codex/event/error`
will now both become `error`. We also show codex error code for
turncompleted -> error status.
new events in app server test:
```
< {
< "method": "codex/event/stream_error",
< "params": {
< "conversationId": "019aa34c-0c14-70e0-9706-98520a760d67",
< "id": "0",
< "msg": {
< "codex_error_code": {
< "response_stream_disconnected": {
< "http_status_code": 401
< }
< },
< "message": "Reconnecting... 2/5",
< "type": "stream_error"
< }
< }
< }
{
< "method": "error",
< "params": {
< "error": {
< "codexErrorCode": {
< "responseStreamDisconnected": {
< "httpStatusCode": 401
< }
< },
< "message": "Reconnecting... 2/5"
< }
< }
< }
< {
< "method": "turn/completed",
< "params": {
< "turn": {
< "error": {
< "codexErrorCode": {
< "responseTooManyFailedAttempts": {
< "httpStatusCode": 401
< }
< },
< "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a1b495a1a97ed3e-SJC"
< },
< "id": "0",
< "items": [],
< "status": "failed"
< }
< }
< }
```
2025-11-20 15:06:55 -08:00
|
|
|
|
Other,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 13:32:40 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
|
|
|
|
|
|
pub struct RawResponseItemEvent {
|
|
|
|
|
|
pub item: ResponseItem,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 13:34:44 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
|
|
|
|
|
|
pub struct ItemStartedEvent {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
pub thread_id: ThreadId,
|
2025-10-20 13:34:44 -07:00
|
|
|
|
pub turn_id: String,
|
|
|
|
|
|
pub item: TurnItem,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 15:33:57 -07:00
|
|
|
|
impl HasLegacyEvent for ItemStartedEvent {
|
|
|
|
|
|
fn as_legacy_events(&self, _: bool) -> Vec<EventMsg> {
|
|
|
|
|
|
match &self.item {
|
|
|
|
|
|
TurnItem::WebSearch(item) => vec![EventMsg::WebSearchBegin(WebSearchBeginEvent {
|
|
|
|
|
|
call_id: item.id.clone(),
|
|
|
|
|
|
})],
|
|
|
|
|
|
_ => Vec::new(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 13:34:44 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
|
|
|
|
|
|
pub struct ItemCompletedEvent {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
pub thread_id: ThreadId,
|
2025-10-20 13:34:44 -07:00
|
|
|
|
pub turn_id: String,
|
|
|
|
|
|
pub item: TurnItem,
|
2025-09-14 17:34:33 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 15:33:57 -07:00
|
|
|
|
pub trait HasLegacyEvent {
|
|
|
|
|
|
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl HasLegacyEvent for ItemCompletedEvent {
|
|
|
|
|
|
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg> {
|
|
|
|
|
|
self.item.as_legacy_events(show_raw_agent_reasoning)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
|
|
|
|
|
|
pub struct AgentMessageContentDeltaEvent {
|
|
|
|
|
|
pub thread_id: String,
|
|
|
|
|
|
pub turn_id: String,
|
|
|
|
|
|
pub item_id: String,
|
|
|
|
|
|
pub delta: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl HasLegacyEvent for AgentMessageContentDeltaEvent {
|
|
|
|
|
|
fn as_legacy_events(&self, _: bool) -> Vec<EventMsg> {
|
|
|
|
|
|
vec![EventMsg::AgentMessageDelta(AgentMessageDeltaEvent {
|
|
|
|
|
|
delta: self.delta.clone(),
|
|
|
|
|
|
})]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
|
|
|
|
|
|
pub struct ReasoningContentDeltaEvent {
|
|
|
|
|
|
pub thread_id: String,
|
|
|
|
|
|
pub turn_id: String,
|
|
|
|
|
|
pub item_id: String,
|
|
|
|
|
|
pub delta: String,
|
2025-11-13 16:25:01 -08:00
|
|
|
|
// load with default value so it's backward compatible with the old format.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub summary_index: i64,
|
2025-10-29 15:33:57 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl HasLegacyEvent for ReasoningContentDeltaEvent {
|
|
|
|
|
|
fn as_legacy_events(&self, _: bool) -> Vec<EventMsg> {
|
|
|
|
|
|
vec![EventMsg::AgentReasoningDelta(AgentReasoningDeltaEvent {
|
|
|
|
|
|
delta: self.delta.clone(),
|
|
|
|
|
|
})]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
|
|
|
|
|
|
pub struct ReasoningRawContentDeltaEvent {
|
|
|
|
|
|
pub thread_id: String,
|
|
|
|
|
|
pub turn_id: String,
|
|
|
|
|
|
pub item_id: String,
|
|
|
|
|
|
pub delta: String,
|
2025-11-13 16:25:01 -08:00
|
|
|
|
// load with default value so it's backward compatible with the old format.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub content_index: i64,
|
2025-10-29 15:33:57 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl HasLegacyEvent for ReasoningRawContentDeltaEvent {
|
|
|
|
|
|
fn as_legacy_events(&self, _: bool) -> Vec<EventMsg> {
|
|
|
|
|
|
vec![EventMsg::AgentReasoningRawContentDelta(
|
|
|
|
|
|
AgentReasoningRawContentDeltaEvent {
|
|
|
|
|
|
delta: self.delta.clone(),
|
|
|
|
|
|
},
|
|
|
|
|
|
)]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl HasLegacyEvent for EventMsg {
|
|
|
|
|
|
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg> {
|
|
|
|
|
|
match self {
|
2026-01-26 19:33:48 -08:00
|
|
|
|
EventMsg::ItemStarted(event) => event.as_legacy_events(show_raw_agent_reasoning),
|
2025-10-29 15:33:57 -07:00
|
|
|
|
EventMsg::ItemCompleted(event) => event.as_legacy_events(show_raw_agent_reasoning),
|
|
|
|
|
|
EventMsg::AgentMessageContentDelta(event) => {
|
|
|
|
|
|
event.as_legacy_events(show_raw_agent_reasoning)
|
|
|
|
|
|
}
|
|
|
|
|
|
EventMsg::ReasoningContentDelta(event) => {
|
|
|
|
|
|
event.as_legacy_events(show_raw_agent_reasoning)
|
|
|
|
|
|
}
|
|
|
|
|
|
EventMsg::ReasoningRawContentDelta(event) => {
|
|
|
|
|
|
event.as_legacy_events(show_raw_agent_reasoning)
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => Vec::new(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-09-14 17:34:33 -07:00
|
|
|
|
pub struct ExitedReviewModeEvent {
|
|
|
|
|
|
pub review_output: Option<ReviewOutputEvent>,
|
2025-05-13 20:44:42 -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-05-13 20:44:42 -07:00
|
|
|
|
// Individual event payload types matching each `EventMsg` variant.
|
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, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct ErrorEvent {
|
|
|
|
|
|
pub message: String,
|
[app-server & core] introduce new codex error code and v2 app-server error events (#6938)
This PR does two things:
1. populate a new `codex_error_code` protocol in error events sent from
core to client;
2. old v1 core events `codex/event/stream_error` and `codex/event/error`
will now both become `error`. We also show codex error code for
turncompleted -> error status.
new events in app server test:
```
< {
< "method": "codex/event/stream_error",
< "params": {
< "conversationId": "019aa34c-0c14-70e0-9706-98520a760d67",
< "id": "0",
< "msg": {
< "codex_error_code": {
< "response_stream_disconnected": {
< "http_status_code": 401
< }
< },
< "message": "Reconnecting... 2/5",
< "type": "stream_error"
< }
< }
< }
{
< "method": "error",
< "params": {
< "error": {
< "codexErrorCode": {
< "responseStreamDisconnected": {
< "httpStatusCode": 401
< }
< },
< "message": "Reconnecting... 2/5"
< }
< }
< }
< {
< "method": "turn/completed",
< "params": {
< "turn": {
< "error": {
< "codexErrorCode": {
< "responseTooManyFailedAttempts": {
< "httpStatusCode": 401
< }
< },
< "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a1b495a1a97ed3e-SJC"
< },
< "id": "0",
< "items": [],
< "status": "failed"
< }
< }
< }
```
2025-11-20 15:06:55 -08:00
|
|
|
|
#[serde(default)]
|
2025-11-20 17:02:37 -08:00
|
|
|
|
pub codex_error_info: Option<CodexErrorInfo>,
|
2025-05-13 20:44:42 -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-10-31 13:27:33 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct WarningEvent {
|
|
|
|
|
|
pub message: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 16:12:14 +00:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct ContextCompactedEvent;
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2026-01-09 17:31:17 +00:00
|
|
|
|
pub struct TurnCompleteEvent {
|
2025-05-19 16:08:18 -07:00
|
|
|
|
pub last_agent_message: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2026-01-09 17:31:17 +00:00
|
|
|
|
pub struct TurnStartedEvent {
|
2026-01-07 10:35:09 -08:00
|
|
|
|
// TODO(aibrahim): make this not optional
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub model_context_window: Option<i64>,
|
2025-08-27 00:04:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 15:20:20 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, TS)]
|
feat: show number of tokens remaining in UI (#1388)
When using the OpenAI Responses API, we now record the `usage` field for
a `"response.completed"` event, which includes metrics about the number
of tokens consumed. We also introduce `openai_model_info.rs`, which
includes current data about the most common OpenAI models available via
the API (specifically `context_window` and `max_output_tokens`). If
Codex does not recognize the model, you can set `model_context_window`
and `model_max_output_tokens` explicitly in `config.toml`.
When then introduce a new event type to `protocol.rs`, `TokenCount`,
which includes the `TokenUsage` for the most recent turn.
Finally, we update the TUI to record the running sum of tokens used so
the percentage of available context window remaining can be reported via
the placeholder text for the composer:

We could certainly get much fancier with this (such as reporting the
estimated cost of the conversation), but for now, we are just trying to
achieve feature parity with the TypeScript CLI.
Though arguably this improves upon the TypeScript CLI, as the TypeScript
CLI uses heuristics to estimate the number of tokens used rather than
using the `usage` information directly:
https://github.com/openai/codex/blob/296996d74e345b1b05d8c3451a06ace21c5ada96/codex-cli/src/utils/approximate-tokens-used.ts#L3-L16
Fixes https://github.com/openai/codex/issues/1242
2025-06-25 23:31:11 -07:00
|
|
|
|
pub struct TokenUsage {
|
2025-10-06 18:59:37 -07:00
|
|
|
|
#[ts(type = "number")]
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub input_tokens: i64,
|
2025-10-06 18:59:37 -07:00
|
|
|
|
#[ts(type = "number")]
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub cached_input_tokens: i64,
|
2025-10-06 18:59:37 -07:00
|
|
|
|
#[ts(type = "number")]
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub output_tokens: i64,
|
2025-10-06 18:59:37 -07:00
|
|
|
|
#[ts(type = "number")]
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub reasoning_output_tokens: i64,
|
2025-10-06 18:59:37 -07:00
|
|
|
|
#[ts(type = "number")]
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub total_tokens: i64,
|
feat: show number of tokens remaining in UI (#1388)
When using the OpenAI Responses API, we now record the `usage` field for
a `"response.completed"` event, which includes metrics about the number
of tokens consumed. We also introduce `openai_model_info.rs`, which
includes current data about the most common OpenAI models available via
the API (specifically `context_window` and `max_output_tokens`). If
Codex does not recognize the model, you can set `model_context_window`
and `model_max_output_tokens` explicitly in `config.toml`.
When then introduce a new event type to `protocol.rs`, `TokenCount`,
which includes the `TokenUsage` for the most recent turn.
Finally, we update the TUI to record the running sum of tokens used so
the percentage of available context window remaining can be reported via
the placeholder text for the composer:

We could certainly get much fancier with this (such as reporting the
estimated cost of the conversation), but for now, we are just trying to
achieve feature parity with the TypeScript CLI.
Though arguably this improves upon the TypeScript CLI, as the TypeScript
CLI uses heuristics to estimate the number of tokens used rather than
using the `usage` information directly:
https://github.com/openai/codex/blob/296996d74e345b1b05d8c3451a06ace21c5ada96/codex-cli/src/utils/approximate-tokens-used.ts#L3-L16
Fixes https://github.com/openai/codex/issues/1242
2025-06-25 23:31:11 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 15:20:20 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
2025-09-06 08:19:23 -07:00
|
|
|
|
pub struct TokenUsageInfo {
|
|
|
|
|
|
pub total_token_usage: TokenUsage,
|
|
|
|
|
|
pub last_token_usage: TokenUsage,
|
2026-01-07 10:35:09 -08:00
|
|
|
|
// TODO(aibrahim): make this not optional
|
2025-10-06 18:59:37 -07:00
|
|
|
|
#[ts(type = "number | null")]
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub model_context_window: Option<i64>,
|
2025-09-06 08:19:23 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl TokenUsageInfo {
|
|
|
|
|
|
pub fn new_or_append(
|
|
|
|
|
|
info: &Option<TokenUsageInfo>,
|
|
|
|
|
|
last: &Option<TokenUsage>,
|
2025-10-20 11:29:49 -07:00
|
|
|
|
model_context_window: Option<i64>,
|
2025-09-06 08:19:23 -07:00
|
|
|
|
) -> Option<Self> {
|
|
|
|
|
|
if info.is_none() && last.is_none() {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut info = match info {
|
|
|
|
|
|
Some(info) => info.clone(),
|
|
|
|
|
|
None => Self {
|
|
|
|
|
|
total_token_usage: TokenUsage::default(),
|
|
|
|
|
|
last_token_usage: TokenUsage::default(),
|
|
|
|
|
|
model_context_window,
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
if let Some(last) = last {
|
|
|
|
|
|
info.append_last_usage(last);
|
|
|
|
|
|
}
|
|
|
|
|
|
Some(info)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn append_last_usage(&mut self, last: &TokenUsage) {
|
|
|
|
|
|
self.total_token_usage.add_assign(last);
|
|
|
|
|
|
self.last_token_usage = last.clone();
|
|
|
|
|
|
}
|
2025-10-04 18:40:06 -07:00
|
|
|
|
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub fn fill_to_context_window(&mut self, context_window: i64) {
|
2025-10-04 18:40:06 -07:00
|
|
|
|
let previous_total = self.total_token_usage.total_tokens;
|
2025-10-20 11:29:49 -07:00
|
|
|
|
let delta = (context_window - previous_total).max(0);
|
2025-10-04 18:40:06 -07:00
|
|
|
|
|
|
|
|
|
|
self.model_context_window = Some(context_window);
|
|
|
|
|
|
self.total_token_usage = TokenUsage {
|
|
|
|
|
|
total_tokens: context_window,
|
|
|
|
|
|
..TokenUsage::default()
|
|
|
|
|
|
};
|
|
|
|
|
|
self.last_token_usage = TokenUsage {
|
|
|
|
|
|
total_tokens: delta,
|
|
|
|
|
|
..TokenUsage::default()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub fn full_context_window(context_window: i64) -> Self {
|
2025-10-04 18:40:06 -07:00
|
|
|
|
let mut info = Self {
|
|
|
|
|
|
total_token_usage: TokenUsage::default(),
|
|
|
|
|
|
last_token_usage: TokenUsage::default(),
|
|
|
|
|
|
model_context_window: Some(context_window),
|
|
|
|
|
|
};
|
|
|
|
|
|
info.fill_to_context_window(context_window);
|
|
|
|
|
|
info
|
|
|
|
|
|
}
|
2025-09-06 08:19:23 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-09-06 08:19:23 -07:00
|
|
|
|
pub struct TokenCountEvent {
|
|
|
|
|
|
pub info: Option<TokenUsageInfo>,
|
2025-09-23 15:56:34 -07:00
|
|
|
|
pub rate_limits: Option<RateLimitSnapshot>,
|
2025-09-20 21:26:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 14:11:54 -07:00
|
|
|
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
2025-09-23 15:56:34 -07:00
|
|
|
|
pub struct RateLimitSnapshot {
|
2025-09-24 08:31:08 -07:00
|
|
|
|
pub primary: Option<RateLimitWindow>,
|
|
|
|
|
|
pub secondary: Option<RateLimitWindow>,
|
2025-11-19 10:49:35 -08:00
|
|
|
|
pub credits: Option<CreditsSnapshot>,
|
2025-12-04 23:34:13 -08:00
|
|
|
|
pub plan_type: Option<crate::account::PlanType>,
|
2025-09-24 08:31:08 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 14:11:54 -07:00
|
|
|
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
2025-09-24 08:31:08 -07:00
|
|
|
|
pub struct RateLimitWindow {
|
|
|
|
|
|
/// Percentage (0-100) of the window that has been consumed.
|
|
|
|
|
|
pub used_percent: f64,
|
|
|
|
|
|
/// Rolling window duration, in minutes.
|
2025-10-06 18:59:37 -07:00
|
|
|
|
#[ts(type = "number | null")]
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub window_minutes: Option<i64>,
|
2025-10-20 12:26:46 -07:00
|
|
|
|
/// Unix timestamp (seconds since epoch) when the window resets.
|
|
|
|
|
|
#[ts(type = "number | null")]
|
|
|
|
|
|
pub resets_at: Option<i64>,
|
2025-09-06 08:19:23 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-19 10:49:35 -08:00
|
|
|
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CreditsSnapshot {
|
|
|
|
|
|
pub has_credits: bool,
|
|
|
|
|
|
pub unlimited: bool,
|
|
|
|
|
|
pub balance: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 16:34:14 -07:00
|
|
|
|
// Includes prompts, tools and space to call compact.
|
2025-10-20 11:29:49 -07:00
|
|
|
|
const BASELINE_TOKENS: i64 = 12000;
|
2025-09-04 16:34:14 -07:00
|
|
|
|
|
2025-08-06 15:22:14 -07:00
|
|
|
|
impl TokenUsage {
|
|
|
|
|
|
pub fn is_zero(&self) -> bool {
|
|
|
|
|
|
self.total_tokens == 0
|
|
|
|
|
|
}
|
2025-08-07 01:13:36 -07:00
|
|
|
|
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub fn cached_input(&self) -> i64 {
|
|
|
|
|
|
self.cached_input_tokens.max(0)
|
2025-08-07 01:13:36 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub fn non_cached_input(&self) -> i64 {
|
|
|
|
|
|
(self.input_tokens - self.cached_input()).max(0)
|
2025-08-07 01:13:36 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Primary count for display as a single absolute value: non-cached input + output.
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub fn blended_total(&self) -> i64 {
|
|
|
|
|
|
(self.non_cached_input() + self.output_tokens.max(0)).max(0)
|
2025-08-07 01:13:36 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub fn tokens_in_context_window(&self) -> i64 {
|
2025-11-03 10:02:23 -08:00
|
|
|
|
self.total_tokens
|
2025-08-07 01:13:36 -07:00
|
|
|
|
}
|
2025-08-19 08:20:32 -07:00
|
|
|
|
|
|
|
|
|
|
/// Estimate the remaining user-controllable percentage of the model's context window.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// `context_window` is the total size of the model's context window.
|
2025-09-04 16:34:14 -07:00
|
|
|
|
/// `BASELINE_TOKENS` should capture tokens that are always present in
|
2025-08-19 08:20:32 -07:00
|
|
|
|
/// the context (e.g., system prompt and fixed tool instructions) so that
|
|
|
|
|
|
/// the percentage reflects the portion the user can influence.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This normalizes both the numerator and denominator by subtracting the
|
|
|
|
|
|
/// baseline, so immediately after the first prompt the UI shows 100% left
|
|
|
|
|
|
/// and trends toward 0% as the user fills the effective window.
|
2025-10-20 11:29:49 -07:00
|
|
|
|
pub fn percent_of_context_window_remaining(&self, context_window: i64) -> i64 {
|
2025-09-04 16:34:14 -07:00
|
|
|
|
if context_window <= BASELINE_TOKENS {
|
2025-08-19 08:20:32 -07:00
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 16:34:14 -07:00
|
|
|
|
let effective_window = context_window - BASELINE_TOKENS;
|
2025-10-20 11:29:49 -07:00
|
|
|
|
let used = (self.tokens_in_context_window() - BASELINE_TOKENS).max(0);
|
|
|
|
|
|
let remaining = (effective_window - used).max(0);
|
|
|
|
|
|
((remaining as f64 / effective_window as f64) * 100.0)
|
|
|
|
|
|
.clamp(0.0, 100.0)
|
|
|
|
|
|
.round() as i64
|
2025-08-19 08:20:32 -07:00
|
|
|
|
}
|
2025-09-06 08:19:23 -07:00
|
|
|
|
|
|
|
|
|
|
/// In-place element-wise sum of token counts.
|
|
|
|
|
|
pub fn add_assign(&mut self, other: &TokenUsage) {
|
|
|
|
|
|
self.input_tokens += other.input_tokens;
|
|
|
|
|
|
self.cached_input_tokens += other.cached_input_tokens;
|
|
|
|
|
|
self.output_tokens += other.output_tokens;
|
|
|
|
|
|
self.reasoning_output_tokens += other.reasoning_output_tokens;
|
|
|
|
|
|
self.total_tokens += other.total_tokens;
|
|
|
|
|
|
}
|
2025-08-06 15:22:14 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
|
2025-07-25 01:56:40 -07:00
|
|
|
|
pub struct FinalOutput {
|
|
|
|
|
|
pub token_usage: TokenUsage,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<TokenUsage> for FinalOutput {
|
|
|
|
|
|
fn from(token_usage: TokenUsage) -> Self {
|
|
|
|
|
|
Self { token_usage }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for FinalOutput {
|
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2025-08-07 01:13:36 -07:00
|
|
|
|
let token_usage = &self.token_usage;
|
2025-09-08 14:48:48 -07:00
|
|
|
|
|
2025-07-25 01:56:40 -07:00
|
|
|
|
write!(
|
|
|
|
|
|
f,
|
|
|
|
|
|
"Token usage: total={} input={}{} output={}{}",
|
2025-09-08 14:48:48 -07:00
|
|
|
|
format_with_separators(token_usage.blended_total()),
|
|
|
|
|
|
format_with_separators(token_usage.non_cached_input()),
|
2025-08-07 01:13:36 -07:00
|
|
|
|
if token_usage.cached_input() > 0 {
|
2025-09-08 14:48:48 -07:00
|
|
|
|
format!(
|
|
|
|
|
|
" (+ {} cached)",
|
|
|
|
|
|
format_with_separators(token_usage.cached_input())
|
|
|
|
|
|
)
|
2025-08-07 01:13:36 -07:00
|
|
|
|
} else {
|
|
|
|
|
|
String::new()
|
|
|
|
|
|
},
|
2025-09-08 14:48:48 -07:00
|
|
|
|
format_with_separators(token_usage.output_tokens),
|
2025-09-06 08:19:23 -07:00
|
|
|
|
if token_usage.reasoning_output_tokens > 0 {
|
2025-09-08 14:48:48 -07:00
|
|
|
|
format!(
|
|
|
|
|
|
" (reasoning {})",
|
|
|
|
|
|
format_with_separators(token_usage.reasoning_output_tokens)
|
|
|
|
|
|
)
|
2025-09-06 08:19:23 -07:00
|
|
|
|
} else {
|
|
|
|
|
|
String::new()
|
|
|
|
|
|
}
|
2025-07-25 01:56:40 -07:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct AgentMessageEvent {
|
|
|
|
|
|
pub message: 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, Deserialize, Serialize, JsonSchema, TS)]
|
2025-09-03 22:34:50 -07:00
|
|
|
|
pub struct UserMessageEvent {
|
|
|
|
|
|
pub message: String,
|
2026-01-14 16:41:50 -08:00
|
|
|
|
/// Image URLs sourced from `UserInput::Image`. These are safe
|
|
|
|
|
|
/// to replay in legacy UI history events and correspond to images sent to
|
|
|
|
|
|
/// the model.
|
2025-09-03 22:34:50 -07:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2025-09-10 10:18:43 -07:00
|
|
|
|
pub images: Option<Vec<String>>,
|
2026-01-14 16:41:50 -08:00
|
|
|
|
/// Local file paths sourced from `UserInput::LocalImage`. These are kept so
|
|
|
|
|
|
/// the UI can reattach images when editing history, and should not be sent
|
|
|
|
|
|
/// to the model or treated as API-ready URLs.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub local_images: Vec<std::path::PathBuf>,
|
|
|
|
|
|
/// UI-defined spans within `message` used to render or persist special elements.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub text_elements: Vec<crate::user_input::TextElement>,
|
2025-09-03 22:34:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-07-16 15:11:18 -07:00
|
|
|
|
pub struct AgentMessageDeltaEvent {
|
|
|
|
|
|
pub delta: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct AgentReasoningEvent {
|
|
|
|
|
|
pub text: 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, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-05 01:56:13 -07:00
|
|
|
|
pub struct AgentReasoningRawContentEvent {
|
|
|
|
|
|
pub text: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-05 01:56:13 -07:00
|
|
|
|
pub struct AgentReasoningRawContentDeltaEvent {
|
|
|
|
|
|
pub delta: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-11-13 16:25:01 -08:00
|
|
|
|
pub struct AgentReasoningSectionBreakEvent {
|
|
|
|
|
|
// load with default value so it's backward compatible with the old format.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub item_id: String,
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub summary_index: i64,
|
|
|
|
|
|
}
|
2025-08-12 17:37:28 -07:00
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-07-16 15:11:18 -07:00
|
|
|
|
pub struct AgentReasoningDeltaEvent {
|
|
|
|
|
|
pub delta: 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
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS, PartialEq)]
|
2025-07-30 10:05:40 -07:00
|
|
|
|
pub struct McpInvocation {
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// Name of the MCP server as defined in the config.
|
|
|
|
|
|
pub server: String,
|
|
|
|
|
|
/// Name of the tool as given by the MCP server.
|
|
|
|
|
|
pub tool: String,
|
|
|
|
|
|
/// Arguments to the tool call.
|
|
|
|
|
|
pub arguments: Option<serde_json::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
|
|
|
|
|
[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, Clone, Deserialize, Serialize, JsonSchema, TS, PartialEq)]
|
2025-07-30 10:05:40 -07:00
|
|
|
|
pub struct McpToolCallBeginEvent {
|
|
|
|
|
|
/// Identifier so this can be paired with the McpToolCallEnd event.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
pub invocation: McpInvocation,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[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, Clone, Deserialize, Serialize, JsonSchema, TS, PartialEq)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct McpToolCallEndEvent {
|
|
|
|
|
|
/// Identifier for the corresponding McpToolCallBegin that finished.
|
|
|
|
|
|
pub call_id: String,
|
2025-07-30 10:05:40 -07:00
|
|
|
|
pub invocation: McpInvocation,
|
2025-09-08 14:54:47 -07:00
|
|
|
|
#[ts(type = "string")]
|
2025-07-30 10:05:40 -07:00
|
|
|
|
pub duration: Duration,
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// Result of the tool call. Note this could be an error.
|
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
|
|
|
|
pub result: Result<CallToolResult, String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl McpToolCallEndEvent {
|
|
|
|
|
|
pub fn is_success(&self) -> bool {
|
|
|
|
|
|
match &self.result {
|
|
|
|
|
|
Ok(result) => !result.is_error.unwrap_or(false),
|
|
|
|
|
|
Err(_) => false,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-13 20:44:42 -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-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-23 22:58:56 -07:00
|
|
|
|
pub struct WebSearchBeginEvent {
|
|
|
|
|
|
pub call_id: String,
|
2025-08-28 19:24:38 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-28 19:24:38 -07:00
|
|
|
|
pub struct WebSearchEndEvent {
|
|
|
|
|
|
pub call_id: String,
|
2025-08-23 22:58:56 -07:00
|
|
|
|
pub query: String,
|
2026-01-26 19:33:48 -08:00
|
|
|
|
pub action: WebSearchAction,
|
2025-08-23 22:58:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-07 17:04:53 +00:00
|
|
|
|
// Conversation kept for backward compatibility.
|
2025-08-22 17:06:09 -07:00
|
|
|
|
/// Response payload for `Op::GetHistory` containing the current session's
|
|
|
|
|
|
/// in-memory transcript.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-09-10 17:42:54 -07:00
|
|
|
|
pub struct ConversationPathResponseEvent {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
pub conversation_id: ThreadId,
|
2025-09-10 17:42:54 -07:00
|
|
|
|
pub path: PathBuf,
|
2025-08-22 17:06:09 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub struct ResumedHistory {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
pub conversation_id: ThreadId,
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub history: Vec<RolloutItem>,
|
|
|
|
|
|
pub rollout_path: PathBuf,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub enum InitialHistory {
|
|
|
|
|
|
New,
|
|
|
|
|
|
Resumed(ResumedHistory),
|
|
|
|
|
|
Forked(Vec<RolloutItem>),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl InitialHistory {
|
2026-01-16 13:41:46 -08:00
|
|
|
|
pub fn forked_from_id(&self) -> Option<ThreadId> {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
InitialHistory::New => None,
|
|
|
|
|
|
InitialHistory::Resumed(resumed) => {
|
|
|
|
|
|
resumed.history.iter().find_map(|item| match item {
|
|
|
|
|
|
RolloutItem::SessionMeta(meta_line) => meta_line.meta.forked_from_id,
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
InitialHistory::Forked(items) => items.iter().find_map(|item| match item {
|
|
|
|
|
|
RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.id),
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
}),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 14:21:48 -08:00
|
|
|
|
pub fn session_cwd(&self) -> Option<PathBuf> {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
InitialHistory::New => None,
|
|
|
|
|
|
InitialHistory::Resumed(resumed) => session_cwd_from_items(&resumed.history),
|
|
|
|
|
|
InitialHistory::Forked(items) => session_cwd_from_items(items),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub fn get_rollout_items(&self) -> Vec<RolloutItem> {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
InitialHistory::New => Vec::new(),
|
|
|
|
|
|
InitialHistory::Resumed(resumed) => resumed.history.clone(),
|
|
|
|
|
|
InitialHistory::Forked(items) => items.clone(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-14 09:23:31 -04:00
|
|
|
|
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub fn get_event_msgs(&self) -> Option<Vec<EventMsg>> {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
InitialHistory::New => None,
|
|
|
|
|
|
InitialHistory::Resumed(resumed) => Some(
|
|
|
|
|
|
resumed
|
|
|
|
|
|
.history
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|ri| match ri {
|
|
|
|
|
|
RolloutItem::EventMsg(ev) => Some(ev.clone()),
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect(),
|
|
|
|
|
|
),
|
|
|
|
|
|
InitialHistory::Forked(items) => Some(
|
|
|
|
|
|
items
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|ri| match ri {
|
|
|
|
|
|
RolloutItem::EventMsg(ev) => Some(ev.clone()),
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect(),
|
|
|
|
|
|
),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-19 21:59:36 -08:00
|
|
|
|
|
|
|
|
|
|
pub fn get_base_instructions(&self) -> Option<BaseInstructions> {
|
|
|
|
|
|
// TODO: SessionMeta should (in theory) always be first in the history, so we can probably only check the first item?
|
|
|
|
|
|
match self {
|
|
|
|
|
|
InitialHistory::New => None,
|
|
|
|
|
|
InitialHistory::Resumed(resumed) => {
|
|
|
|
|
|
resumed.history.iter().find_map(|item| match item {
|
|
|
|
|
|
RolloutItem::SessionMeta(meta_line) => meta_line.meta.base_instructions.clone(),
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
InitialHistory::Forked(items) => items.iter().find_map(|item| match item {
|
|
|
|
|
|
RolloutItem::SessionMeta(meta_line) => meta_line.meta.base_instructions.clone(),
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
}),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-10 10:17:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 14:21:48 -08:00
|
|
|
|
fn session_cwd_from_items(items: &[RolloutItem]) -> Option<PathBuf> {
|
|
|
|
|
|
items.iter().find_map(|item| match item {
|
|
|
|
|
|
RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.cwd.clone()),
|
|
|
|
|
|
_ => None,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 14:04:25 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
|
2025-10-02 13:06:21 -07:00
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
|
|
#[ts(rename_all = "lowercase")]
|
|
|
|
|
|
pub enum SessionSource {
|
|
|
|
|
|
Cli,
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
VSCode,
|
|
|
|
|
|
Exec,
|
|
|
|
|
|
Mcp,
|
2025-10-29 14:04:25 -07:00
|
|
|
|
SubAgent(SubAgentSource),
|
2025-10-02 13:06:21 -07:00
|
|
|
|
#[serde(other)]
|
|
|
|
|
|
Unknown,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 14:04:25 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS)]
|
2025-10-30 02:49:40 -07:00
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
#[ts(rename_all = "snake_case")]
|
2025-10-29 14:04:25 -07:00
|
|
|
|
pub enum SubAgentSource {
|
|
|
|
|
|
Review,
|
|
|
|
|
|
Compact,
|
2026-01-25 15:57:22 +01:00
|
|
|
|
ThreadSpawn {
|
|
|
|
|
|
parent_thread_id: ThreadId,
|
|
|
|
|
|
depth: i32,
|
|
|
|
|
|
},
|
2025-10-29 14:04:25 -07:00
|
|
|
|
Other(String),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 11:05:37 -08:00
|
|
|
|
impl fmt::Display for SessionSource {
|
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
SessionSource::Cli => f.write_str("cli"),
|
|
|
|
|
|
SessionSource::VSCode => f.write_str("vscode"),
|
|
|
|
|
|
SessionSource::Exec => f.write_str("exec"),
|
|
|
|
|
|
SessionSource::Mcp => f.write_str("mcp"),
|
|
|
|
|
|
SessionSource::SubAgent(sub_source) => write!(f, "subagent_{sub_source}"),
|
|
|
|
|
|
SessionSource::Unknown => f.write_str("unknown"),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for SubAgentSource {
|
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
SubAgentSource::Review => f.write_str("review"),
|
|
|
|
|
|
SubAgentSource::Compact => f.write_str("compact"),
|
2026-01-25 15:57:22 +01:00
|
|
|
|
SubAgentSource::ThreadSpawn {
|
|
|
|
|
|
parent_thread_id,
|
|
|
|
|
|
depth,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
write!(f, "thread_spawn_{parent_thread_id}_d{depth}")
|
2026-01-24 15:21:34 +01:00
|
|
|
|
}
|
2025-11-24 11:05:37 -08:00
|
|
|
|
SubAgentSource::Other(other) => f.write_str(other),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-19 21:59:36 -08:00
|
|
|
|
/// SessionMeta contains session-level data that doesn't correspond to a specific turn.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// NOTE: There used to be an `instructions` field here, which stored user_instructions, but we
|
|
|
|
|
|
/// now save that on TurnContext. base_instructions stores the base instructions for the session,
|
|
|
|
|
|
/// and should be used when there is no config override.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub struct SessionMeta {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
pub id: ThreadId,
|
2026-01-16 13:41:46 -08:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub forked_from_id: Option<ThreadId>,
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub timestamp: String,
|
|
|
|
|
|
pub cwd: PathBuf,
|
|
|
|
|
|
pub originator: String,
|
|
|
|
|
|
pub cli_version: String,
|
2025-10-02 13:06:21 -07:00
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub source: SessionSource,
|
2025-10-27 02:03:30 -07:00
|
|
|
|
pub model_provider: Option<String>,
|
2026-01-19 21:59:36 -08:00
|
|
|
|
/// base_instructions for the session. This *should* always be present when creating a new session,
|
|
|
|
|
|
/// but may be missing for older sessions. If not present, fall back to rendering the base_instructions
|
|
|
|
|
|
/// from ModelsManager.
|
|
|
|
|
|
pub base_instructions: Option<BaseInstructions>,
|
2025-10-02 13:06:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for SessionMeta {
|
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
|
SessionMeta {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
id: ThreadId::default(),
|
2026-01-16 13:41:46 -08:00
|
|
|
|
forked_from_id: None,
|
2025-10-02 13:06:21 -07:00
|
|
|
|
timestamp: String::new(),
|
|
|
|
|
|
cwd: PathBuf::new(),
|
|
|
|
|
|
originator: String::new(),
|
|
|
|
|
|
cli_version: String::new(),
|
|
|
|
|
|
source: SessionSource::default(),
|
2025-10-27 02:03:30 -07:00
|
|
|
|
model_provider: None,
|
2026-01-19 21:59:36 -08:00
|
|
|
|
base_instructions: None,
|
2025-10-02 13:06:21 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-10 10:17:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub struct SessionMetaLine {
|
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
|
pub meta: SessionMeta,
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub git: Option<GitInfo>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
|
2025-09-10 10:17:24 -07:00
|
|
|
|
#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
|
|
|
|
|
|
pub enum RolloutItem {
|
|
|
|
|
|
SessionMeta(SessionMetaLine),
|
|
|
|
|
|
ResponseItem(ResponseItem),
|
2025-09-11 11:08:51 -07:00
|
|
|
|
Compacted(CompactedItem),
|
|
|
|
|
|
TurnContext(TurnContextItem),
|
2025-09-10 10:17:24 -07:00
|
|
|
|
EventMsg(EventMsg),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
2025-09-11 11:08:51 -07:00
|
|
|
|
pub struct CompactedItem {
|
|
|
|
|
|
pub message: String,
|
2025-11-18 16:51:16 +00:00
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub replacement_history: Option<Vec<ResponseItem>>,
|
2025-09-11 11:08:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-14 09:23:31 -04:00
|
|
|
|
impl From<CompactedItem> for ResponseItem {
|
|
|
|
|
|
fn from(value: CompactedItem) -> Self {
|
|
|
|
|
|
ResponseItem::Message {
|
|
|
|
|
|
id: None,
|
|
|
|
|
|
role: "assistant".to_string(),
|
|
|
|
|
|
content: vec![ContentItem::OutputText {
|
|
|
|
|
|
text: value.message,
|
|
|
|
|
|
}],
|
2026-01-22 09:27:48 -08:00
|
|
|
|
end_turn: None,
|
2025-09-14 09:23:31 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
2025-09-11 11:08:51 -07:00
|
|
|
|
pub struct TurnContextItem {
|
|
|
|
|
|
pub cwd: PathBuf,
|
|
|
|
|
|
pub approval_policy: AskForApproval,
|
|
|
|
|
|
pub sandbox_policy: SandboxPolicy,
|
|
|
|
|
|
pub model: String,
|
2026-01-22 12:04:23 -08:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub personality: Option<Personality>,
|
2026-01-21 14:14:21 -08:00
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub collaboration_mode: Option<CollaborationMode>,
|
2025-09-12 12:06:33 -07:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub effort: Option<ReasoningEffortConfig>,
|
2025-09-11 11:08:51 -07:00
|
|
|
|
pub summary: ReasoningSummaryConfig,
|
2025-12-22 19:51:07 -08:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub user_instructions: Option<String>,
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub developer_instructions: Option<String>,
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub final_output_json_schema: Option<Value>,
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub truncation_policy: Option<TruncationPolicy>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
|
|
|
|
|
#[serde(tag = "mode", content = "limit", rename_all = "snake_case")]
|
|
|
|
|
|
pub enum TruncationPolicy {
|
|
|
|
|
|
Bytes(usize),
|
|
|
|
|
|
Tokens(usize),
|
2025-09-11 11:08:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Clone, JsonSchema)]
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub struct RolloutLine {
|
|
|
|
|
|
pub timestamp: String,
|
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
|
pub item: RolloutItem,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
2025-09-10 10:17:24 -07:00
|
|
|
|
pub struct GitInfo {
|
|
|
|
|
|
/// Current commit hash (SHA)
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub commit_hash: Option<String>,
|
|
|
|
|
|
/// Current branch name
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub branch: Option<String>,
|
|
|
|
|
|
/// Repository URL (if available from remote)
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub repository_url: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 11:34:57 +00:00
|
|
|
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum ReviewDelivery {
|
|
|
|
|
|
Inline,
|
|
|
|
|
|
Detached,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 11:26:27 +00:00
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
|
#[ts(tag = "type")]
|
|
|
|
|
|
pub enum ReviewTarget {
|
|
|
|
|
|
/// Review the working tree: staged, unstaged, and untracked files.
|
|
|
|
|
|
UncommittedChanges,
|
|
|
|
|
|
|
|
|
|
|
|
/// Review changes between the current branch and the given base branch.
|
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
|
#[ts(rename_all = "camelCase")]
|
|
|
|
|
|
BaseBranch { branch: String },
|
|
|
|
|
|
|
|
|
|
|
|
/// Review the changes introduced by a specific commit.
|
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
|
#[ts(rename_all = "camelCase")]
|
|
|
|
|
|
Commit {
|
|
|
|
|
|
sha: String,
|
|
|
|
|
|
/// Optional human-readable label (e.g., commit subject) for UIs.
|
|
|
|
|
|
title: Option<String>,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/// Arbitrary instructions provided by the user.
|
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
|
#[ts(rename_all = "camelCase")]
|
|
|
|
|
|
Custom { instructions: String },
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
2025-11-18 21:58:54 +00:00
|
|
|
|
/// Review request sent to the review session.
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
pub struct ReviewRequest {
|
2025-12-02 11:26:27 +00:00
|
|
|
|
pub target: ReviewTarget,
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub user_facing_hint: Option<String>,
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Structured review result produced by a child review session.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
pub struct ReviewOutputEvent {
|
|
|
|
|
|
pub findings: Vec<ReviewFinding>,
|
|
|
|
|
|
pub overall_correctness: String,
|
|
|
|
|
|
pub overall_explanation: String,
|
|
|
|
|
|
pub overall_confidence_score: f32,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for ReviewOutputEvent {
|
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
|
Self {
|
|
|
|
|
|
findings: Vec::new(),
|
|
|
|
|
|
overall_correctness: String::default(),
|
|
|
|
|
|
overall_explanation: String::default(),
|
|
|
|
|
|
overall_confidence_score: 0.0,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A single review finding describing an observed issue or recommendation.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
pub struct ReviewFinding {
|
|
|
|
|
|
pub title: String,
|
|
|
|
|
|
pub body: String,
|
|
|
|
|
|
pub confidence_score: f32,
|
|
|
|
|
|
pub priority: i32,
|
|
|
|
|
|
pub code_location: ReviewCodeLocation,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Location of the code related to a review finding.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
pub struct ReviewCodeLocation {
|
|
|
|
|
|
pub absolute_file_path: PathBuf,
|
|
|
|
|
|
pub line_range: ReviewLineRange,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Inclusive line range in a file associated with the finding.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
Review Mode (Core) (#3401)
## 📝 Review Mode -- Core
This PR introduces the Core implementation for Review mode:
- New op `Op::Review { prompt: String }:` spawns a child review task
with isolated context, a review‑specific system prompt, and a
`Config.review_model`.
- `EnteredReviewMode`: emitted when the child review session starts.
Every event from this point onwards reflects the review session.
- `ExitedReviewMode(Option<ReviewOutputEvent>)`: emitted when the review
finishes or is interrupted, with optional structured findings:
```json
{
"findings": [
{
"title": "<≤ 80 chars, imperative>",
"body": "<valid Markdown explaining *why* this is a problem; cite files/lines/functions>",
"confidence_score": <float 0.0-1.0>,
"priority": <int 0-3>,
"code_location": {
"absolute_file_path": "<file path>",
"line_range": {"start": <int>, "end": <int>}
}
}
],
"overall_correctness": "patch is correct" | "patch is incorrect",
"overall_explanation": "<1-3 sentence explanation justifying the overall_correctness verdict>",
"overall_confidence_score": <float 0.0-1.0>
}
```
## Questions
### Why separate out its own message history?
We want the review thread to match the training of our review models as
much as possible -- that means using a custom prompt, removing user
instructions, and starting a clean chat history.
We also want to make sure the review thread doesn't leak into the parent
thread.
### Why do this as a mode, vs. sub-agents?
1. We want review to be a synchronous task, so it's fine for now to do a
bespoke implementation.
2. We're still unclear about the final structure for sub-agents. We'd
prefer to land this quickly and then refactor into sub-agents without
rushing that implementation.
2025-09-12 16:25:10 -07:00
|
|
|
|
pub struct ReviewLineRange {
|
|
|
|
|
|
pub start: u32,
|
|
|
|
|
|
pub end: u32,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 11:12:52 -08:00
|
|
|
|
#[derive(
|
|
|
|
|
|
Debug, Clone, Copy, Display, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS, Default,
|
|
|
|
|
|
)]
|
2025-11-14 16:31:12 +01:00
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum ExecCommandSource {
|
2026-01-16 11:12:52 -08:00
|
|
|
|
#[default]
|
2025-11-14 16:31:12 +01:00
|
|
|
|
Agent,
|
|
|
|
|
|
UserShell,
|
|
|
|
|
|
UnifiedExecStartup,
|
|
|
|
|
|
UnifiedExecInteraction,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct ExecCommandBeginEvent {
|
|
|
|
|
|
/// Identifier so this can be paired with the ExecCommandEnd event.
|
|
|
|
|
|
pub call_id: String,
|
2025-11-25 22:21:05 +00:00
|
|
|
|
/// Identifier for the underlying PTY process (when available).
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub process_id: Option<String>,
|
[app-server] feat: add v2 command execution approval flow (#6758)
This PR adds the API V2 version of the command‑execution approval flow
for the shell tool.
This PR wires the new RPC (`item/commandExecution/requestApproval`, V2
only) and related events (`item/started`, `item/completed`, and
`item/commandExecution/delta`, which are emitted in both V1 and V2)
through the app-server
protocol. The new approval RPC is only sent when the user initiates a
turn with the new `turn/start` API so we don't break backwards
compatibility with VSCE.
The approach I took was to make as few changes to the Codex core as
possible, leveraging existing `EventMsg` core events, and translating
those in app-server. I did have to add additional fields to
`EventMsg::ExecCommandEndEvent` to capture the command's input so that
app-server can statelessly transform these events to a
`ThreadItem::CommandExecution` item for the `item/completed` event.
Once we stabilize the API and it's complete enough for our partners, we
can work on migrating the core to be aware of command execution items as
a first-class concept.
**Note**: We'll need followup work to make sure these APIs work for the
unified exec tool, but will wait til that's stable and landed before
doing a pass on app-server.
Example payloads below:
```
{
"method": "item/started",
"params": {
"item": {
"aggregatedOutput": null,
"command": "/bin/zsh -lc 'touch /tmp/should-trigger-approval'",
"cwd": "/Users/owen/repos/codex/codex-rs",
"durationMs": null,
"exitCode": null,
"id": "call_lNWWsbXl1e47qNaYjFRs0dyU",
"parsedCmd": [
{
"cmd": "touch /tmp/should-trigger-approval",
"type": "unknown"
}
],
"status": "inProgress",
"type": "commandExecution"
}
}
}
```
```
{
"id": 0,
"method": "item/commandExecution/requestApproval",
"params": {
"itemId": "call_lNWWsbXl1e47qNaYjFRs0dyU",
"parsedCmd": [
{
"cmd": "touch /tmp/should-trigger-approval",
"type": "unknown"
}
],
"reason": "Need to create file in /tmp which is outside workspace sandbox",
"risk": null,
"threadId": "019a93e8-0a52-7fe3-9808-b6bc40c0989a",
"turnId": "1"
}
}
```
```
{
"id": 0,
"result": {
"acceptSettings": {
"forSession": false
},
"decision": "accept"
}
}
```
```
{
"params": {
"item": {
"aggregatedOutput": null,
"command": "/bin/zsh -lc 'touch /tmp/should-trigger-approval'",
"cwd": "/Users/owen/repos/codex/codex-rs",
"durationMs": 224,
"exitCode": 0,
"id": "call_lNWWsbXl1e47qNaYjFRs0dyU",
"parsedCmd": [
{
"cmd": "touch /tmp/should-trigger-approval",
"type": "unknown"
}
],
"status": "completed",
"type": "commandExecution"
}
}
}
```
2025-11-17 16:23:54 -08:00
|
|
|
|
/// Turn ID that this command belongs to.
|
|
|
|
|
|
pub turn_id: String,
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// The command to be executed.
|
|
|
|
|
|
pub command: Vec<String>,
|
|
|
|
|
|
/// The command's working directory if not the default cwd for the agent.
|
|
|
|
|
|
pub cwd: PathBuf,
|
2025-08-11 11:26:15 -07:00
|
|
|
|
pub parsed_cmd: Vec<ParsedCommand>,
|
2025-11-14 16:31:12 +01:00
|
|
|
|
/// Where the command originated. Defaults to Agent for backward compatibility.
|
feature: Add "!cmd" user shell execution (#2471)
feature: Add "!cmd" user shell execution
This change lets users run local shell commands directly from the TUI by
prefixing their input with ! (e.g. !ls). Output is truncated to keep the
exec cell usable, and Ctrl-C cleanly
interrupts long-running commands (e.g. !sleep 10000).
**Summary of changes**
- Route Op::RunUserShellCommand through a dedicated UserShellCommandTask
(core/src/tasks/user_shell.rs), keeping the task logic out of codex.rs.
- Reuse the existing tool router: the task constructs a ToolCall for the
local_shell tool and relies on ShellHandler, so no manual MCP tool
lookup is required.
- Emit exec lifecycle events (ExecCommandBegin/ExecCommandEnd) so the
TUI can show command metadata, live output, and exit status.
**End-to-end flow**
**TUI handling**
1. ChatWidget::submit_user_message (TUI) intercepts messages starting
with !.
2. Non-empty commands dispatch Op::RunUserShellCommand { command };
empty commands surface a help hint.
3. No UserInput items are created, so nothing is enqueued for the model.
**Core submission loop**
4. The submission loop routes the op to handlers::run_user_shell_command
(core/src/codex.rs).
5. A fresh TurnContext is created and Session::spawn_user_shell_command
enqueues UserShellCommandTask.
**Task execution**
6. UserShellCommandTask::run emits TaskStartedEvent, formats the
command, and prepares a ToolCall targeting local_shell.
7. ToolCallRuntime::handle_tool_call dispatches to ShellHandler.
**Shell tool runtime**
8. ShellHandler::run_exec_like launches the process via the unified exec
runtime, honoring sandbox and shell policies, and emits
ExecCommandBegin/End.
9. Stdout/stderr are captured for the UI, but the task does not turn the
resulting ToolOutput into a model response.
**Completion**
10. After ExecCommandEnd, the task finishes without an assistant
message; the session marks it complete and the exec cell displays the
final output.
**Conversation context**
- The command and its output never enter the conversation history or the
model prompt; the flow is local-only.
- Only exec/task events are emitted for UI rendering.
**Demo video**
https://github.com/user-attachments/assets/fcd114b0-4304-4448-a367-a04c43e0b996
2025-10-29 00:31:20 -07:00
|
|
|
|
#[serde(default)]
|
2025-11-14 16:31:12 +01:00
|
|
|
|
pub source: ExecCommandSource,
|
|
|
|
|
|
/// Raw input sent to a unified exec session (if this is an interaction event).
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub interaction_input: Option<String>,
|
2025-05-13 20:44:42 -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-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct ExecCommandEndEvent {
|
|
|
|
|
|
/// Identifier for the ExecCommandBegin that finished.
|
|
|
|
|
|
pub call_id: String,
|
2025-11-25 22:21:05 +00:00
|
|
|
|
/// Identifier for the underlying PTY process (when available).
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub process_id: Option<String>,
|
[app-server] feat: add v2 command execution approval flow (#6758)
This PR adds the API V2 version of the command‑execution approval flow
for the shell tool.
This PR wires the new RPC (`item/commandExecution/requestApproval`, V2
only) and related events (`item/started`, `item/completed`, and
`item/commandExecution/delta`, which are emitted in both V1 and V2)
through the app-server
protocol. The new approval RPC is only sent when the user initiates a
turn with the new `turn/start` API so we don't break backwards
compatibility with VSCE.
The approach I took was to make as few changes to the Codex core as
possible, leveraging existing `EventMsg` core events, and translating
those in app-server. I did have to add additional fields to
`EventMsg::ExecCommandEndEvent` to capture the command's input so that
app-server can statelessly transform these events to a
`ThreadItem::CommandExecution` item for the `item/completed` event.
Once we stabilize the API and it's complete enough for our partners, we
can work on migrating the core to be aware of command execution items as
a first-class concept.
**Note**: We'll need followup work to make sure these APIs work for the
unified exec tool, but will wait til that's stable and landed before
doing a pass on app-server.
Example payloads below:
```
{
"method": "item/started",
"params": {
"item": {
"aggregatedOutput": null,
"command": "/bin/zsh -lc 'touch /tmp/should-trigger-approval'",
"cwd": "/Users/owen/repos/codex/codex-rs",
"durationMs": null,
"exitCode": null,
"id": "call_lNWWsbXl1e47qNaYjFRs0dyU",
"parsedCmd": [
{
"cmd": "touch /tmp/should-trigger-approval",
"type": "unknown"
}
],
"status": "inProgress",
"type": "commandExecution"
}
}
}
```
```
{
"id": 0,
"method": "item/commandExecution/requestApproval",
"params": {
"itemId": "call_lNWWsbXl1e47qNaYjFRs0dyU",
"parsedCmd": [
{
"cmd": "touch /tmp/should-trigger-approval",
"type": "unknown"
}
],
"reason": "Need to create file in /tmp which is outside workspace sandbox",
"risk": null,
"threadId": "019a93e8-0a52-7fe3-9808-b6bc40c0989a",
"turnId": "1"
}
}
```
```
{
"id": 0,
"result": {
"acceptSettings": {
"forSession": false
},
"decision": "accept"
}
}
```
```
{
"params": {
"item": {
"aggregatedOutput": null,
"command": "/bin/zsh -lc 'touch /tmp/should-trigger-approval'",
"cwd": "/Users/owen/repos/codex/codex-rs",
"durationMs": 224,
"exitCode": 0,
"id": "call_lNWWsbXl1e47qNaYjFRs0dyU",
"parsedCmd": [
{
"cmd": "touch /tmp/should-trigger-approval",
"type": "unknown"
}
],
"status": "completed",
"type": "commandExecution"
}
}
}
```
2025-11-17 16:23:54 -08:00
|
|
|
|
/// Turn ID that this command belongs to.
|
|
|
|
|
|
pub turn_id: String,
|
|
|
|
|
|
/// The command that was executed.
|
|
|
|
|
|
pub command: Vec<String>,
|
|
|
|
|
|
/// The command's working directory if not the default cwd for the agent.
|
|
|
|
|
|
pub cwd: PathBuf,
|
|
|
|
|
|
pub parsed_cmd: Vec<ParsedCommand>,
|
|
|
|
|
|
/// Where the command originated. Defaults to Agent for backward compatibility.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub source: ExecCommandSource,
|
|
|
|
|
|
/// Raw input sent to a unified exec session (if this is an interaction event).
|
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub interaction_input: Option<String>,
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// Captured stdout
|
|
|
|
|
|
pub stdout: String,
|
|
|
|
|
|
/// Captured stderr
|
|
|
|
|
|
pub stderr: String,
|
2025-08-23 09:54:31 -07:00
|
|
|
|
/// Captured aggregated output
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub aggregated_output: String,
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// The command's exit code.
|
|
|
|
|
|
pub exit_code: i32,
|
2025-08-03 11:33:44 -07:00
|
|
|
|
/// The duration of the command execution.
|
2025-09-08 14:54:47 -07:00
|
|
|
|
#[ts(type = "string")]
|
2025-08-03 11:33:44 -07:00
|
|
|
|
pub duration: Duration,
|
2025-08-22 16:32:31 -07:00
|
|
|
|
/// Formatted output from the command, as seen by the model.
|
|
|
|
|
|
pub formatted_output: String,
|
2025-05-13 20:44:42 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-10-02 11:36:03 -07:00
|
|
|
|
pub struct ViewImageToolCallEvent {
|
|
|
|
|
|
/// Identifier for the originating tool call.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
/// Local filesystem path provided to the tool.
|
|
|
|
|
|
pub path: PathBuf,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
2025-08-01 14:00:19 -07:00
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum ExecOutputStream {
|
|
|
|
|
|
Stdout,
|
|
|
|
|
|
Stderr,
|
2025-08-01 13:04:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 08:21:58 -07:00
|
|
|
|
#[serde_as]
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
2025-08-01 14:00:19 -07:00
|
|
|
|
pub struct ExecCommandOutputDeltaEvent {
|
2025-08-01 13:04:34 -07:00
|
|
|
|
/// Identifier for the ExecCommandBegin that produced this chunk.
|
|
|
|
|
|
pub call_id: String,
|
2025-08-01 14:00:19 -07:00
|
|
|
|
/// Which stream produced this chunk.
|
|
|
|
|
|
pub stream: ExecOutputStream,
|
|
|
|
|
|
/// Raw bytes from the stream (may not be valid UTF-8).
|
2025-09-04 10:38:00 -07:00
|
|
|
|
#[serde_as(as = "serde_with::base64::Base64")]
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[schemars(with = "String")]
|
2025-09-08 14:54:47 -07:00
|
|
|
|
#[ts(type = "string")]
|
2025-09-04 08:21:58 -07:00
|
|
|
|
pub chunk: Vec<u8>,
|
2025-08-01 13:04:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 10:30:38 +00:00
|
|
|
|
#[serde_as]
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct TerminalInteractionEvent {
|
|
|
|
|
|
/// Identifier for the ExecCommandBegin that produced this chunk.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
/// Process id associated with the running command.
|
|
|
|
|
|
pub process_id: String,
|
|
|
|
|
|
/// Stdin sent to the running session.
|
|
|
|
|
|
pub stdin: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct BackgroundEventEvent {
|
|
|
|
|
|
pub message: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 12:29:28 +00:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct DeprecationNoticeEvent {
|
|
|
|
|
|
/// Concise summary of what is deprecated.
|
|
|
|
|
|
pub summary: String,
|
|
|
|
|
|
/// Optional extra guidance, such as migration steps or rationale.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub details: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-27 10:55:29 +00:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct UndoStartedEvent {
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub message: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct UndoCompletedEvent {
|
|
|
|
|
|
pub success: bool,
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub message: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-06 13:23:48 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct ThreadRolledBackEvent {
|
|
|
|
|
|
/// Number of user turns that were removed from context.
|
|
|
|
|
|
pub num_turns: u32,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-21 01:15:24 -07:00
|
|
|
|
pub struct StreamErrorEvent {
|
|
|
|
|
|
pub message: String,
|
[app-server & core] introduce new codex error code and v2 app-server error events (#6938)
This PR does two things:
1. populate a new `codex_error_code` protocol in error events sent from
core to client;
2. old v1 core events `codex/event/stream_error` and `codex/event/error`
will now both become `error`. We also show codex error code for
turncompleted -> error status.
new events in app server test:
```
< {
< "method": "codex/event/stream_error",
< "params": {
< "conversationId": "019aa34c-0c14-70e0-9706-98520a760d67",
< "id": "0",
< "msg": {
< "codex_error_code": {
< "response_stream_disconnected": {
< "http_status_code": 401
< }
< },
< "message": "Reconnecting... 2/5",
< "type": "stream_error"
< }
< }
< }
{
< "method": "error",
< "params": {
< "error": {
< "codexErrorCode": {
< "responseStreamDisconnected": {
< "httpStatusCode": 401
< }
< },
< "message": "Reconnecting... 2/5"
< }
< }
< }
< {
< "method": "turn/completed",
< "params": {
< "turn": {
< "error": {
< "codexErrorCode": {
< "responseTooManyFailedAttempts": {
< "httpStatusCode": 401
< }
< },
< "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a1b495a1a97ed3e-SJC"
< },
< "id": "0",
< "items": [],
< "status": "failed"
< }
< }
< }
```
2025-11-20 15:06:55 -08:00
|
|
|
|
#[serde(default)]
|
2025-11-20 17:02:37 -08:00
|
|
|
|
pub codex_error_info: Option<CodexErrorInfo>,
|
2025-12-24 10:07:38 -08:00
|
|
|
|
/// Optional details about the underlying stream failure (often the same
|
|
|
|
|
|
/// human-readable message that is surfaced as the terminal error if retries
|
|
|
|
|
|
/// are exhausted).
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub additional_details: Option<String>,
|
2025-08-21 01:15:24 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-10-10 10:07:14 +01:00
|
|
|
|
pub struct StreamInfoEvent {
|
|
|
|
|
|
pub message: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct PatchApplyBeginEvent {
|
|
|
|
|
|
/// Identifier so this can be paired with the PatchApplyEnd event.
|
|
|
|
|
|
pub call_id: String,
|
[app-server] feat: v2 apply_patch approval flow (#6760)
This PR adds the API V2 version of the apply_patch approval flow, which
centers around `ThreadItem::FileChange`.
This PR wires the new RPC (`item/fileChange/requestApproval`, V2 only)
and related events (`item/started`, `item/completed` for
`ThreadItem::FileChange`, which are emitted in both V1 and V2) through
the app-server
protocol. The new approval RPC is only sent when the user initiates a
turn with the new `turn/start` API so we don't break backwards
compatibility with VSCE.
Similar to https://github.com/openai/codex/pull/6758, the approach I
took was to make as few changes to the Codex core as possible,
leveraging existing `EventMsg` core events, and translating those in
app-server. I did have to add a few additional fields to
`EventMsg::PatchApplyBegin` and `EventMsg::PatchApplyEnd`, but those
were fairly lightweight.
However, the `EventMsg`s emitted by core are the following:
```
1) Auto-approved (no request for approval)
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
2) Approved by user
- EventMsg::ApplyPatchApprovalRequest
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
3) Declined by user
- EventMsg::ApplyPatchApprovalRequest
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
```
For a request triggering an approval, this would result in:
```
item/fileChange/requestApproval
item/started
item/completed
```
which is different from the `ThreadItem::CommandExecution` flow
introduced in https://github.com/openai/codex/pull/6758, which does the
below and is preferable:
```
item/started
item/commandExecution/requestApproval
item/completed
```
To fix this, we leverage `TurnSummaryStore` on codex_message_processor
to store a little bit of state, allowing us to fire `item/started` and
`item/fileChange/requestApproval` whenever we receive the underlying
`EventMsg::ApplyPatchApprovalRequest`, and no-oping when we receive the
`EventMsg::PatchApplyBegin` later.
This is much less invasive than modifying the order of EventMsg within
core (I tried).
The resulting payloads:
```
{
"method": "item/started",
"params": {
"item": {
"changes": [
{
"diff": "Hello from Codex!\n",
"kind": "add",
"path": "/Users/owen/repos/codex/codex-rs/APPROVAL_DEMO.txt"
}
],
"id": "call_Nxnwj7B3YXigfV6Mwh03d686",
"status": "inProgress",
"type": "fileChange"
}
}
}
```
```
{
"id": 0,
"method": "item/fileChange/requestApproval",
"params": {
"grantRoot": null,
"itemId": "call_Nxnwj7B3YXigfV6Mwh03d686",
"reason": null,
"threadId": "019a9e11-8295-7883-a283-779e06502c6f",
"turnId": "1"
}
}
```
```
{
"id": 0,
"result": {
"decision": "accept"
}
}
```
```
{
"method": "item/completed",
"params": {
"item": {
"changes": [
{
"diff": "Hello from Codex!\n",
"kind": "add",
"path": "/Users/owen/repos/codex/codex-rs/APPROVAL_DEMO.txt"
}
],
"id": "call_Nxnwj7B3YXigfV6Mwh03d686",
"status": "completed",
"type": "fileChange"
}
}
}
```
2025-11-19 20:13:31 -08:00
|
|
|
|
/// Turn ID that this patch belongs to.
|
|
|
|
|
|
/// Uses `#[serde(default)]` for backwards compatibility.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub turn_id: String,
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// If true, there was no ApplyPatchApprovalRequest for this patch.
|
|
|
|
|
|
pub auto_approved: bool,
|
|
|
|
|
|
/// The changes to be applied.
|
|
|
|
|
|
pub changes: HashMap<PathBuf, FileChange>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 20:44:42 -07:00
|
|
|
|
pub struct PatchApplyEndEvent {
|
|
|
|
|
|
/// Identifier for the PatchApplyBegin that finished.
|
|
|
|
|
|
pub call_id: String,
|
[app-server] feat: v2 apply_patch approval flow (#6760)
This PR adds the API V2 version of the apply_patch approval flow, which
centers around `ThreadItem::FileChange`.
This PR wires the new RPC (`item/fileChange/requestApproval`, V2 only)
and related events (`item/started`, `item/completed` for
`ThreadItem::FileChange`, which are emitted in both V1 and V2) through
the app-server
protocol. The new approval RPC is only sent when the user initiates a
turn with the new `turn/start` API so we don't break backwards
compatibility with VSCE.
Similar to https://github.com/openai/codex/pull/6758, the approach I
took was to make as few changes to the Codex core as possible,
leveraging existing `EventMsg` core events, and translating those in
app-server. I did have to add a few additional fields to
`EventMsg::PatchApplyBegin` and `EventMsg::PatchApplyEnd`, but those
were fairly lightweight.
However, the `EventMsg`s emitted by core are the following:
```
1) Auto-approved (no request for approval)
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
2) Approved by user
- EventMsg::ApplyPatchApprovalRequest
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
3) Declined by user
- EventMsg::ApplyPatchApprovalRequest
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
```
For a request triggering an approval, this would result in:
```
item/fileChange/requestApproval
item/started
item/completed
```
which is different from the `ThreadItem::CommandExecution` flow
introduced in https://github.com/openai/codex/pull/6758, which does the
below and is preferable:
```
item/started
item/commandExecution/requestApproval
item/completed
```
To fix this, we leverage `TurnSummaryStore` on codex_message_processor
to store a little bit of state, allowing us to fire `item/started` and
`item/fileChange/requestApproval` whenever we receive the underlying
`EventMsg::ApplyPatchApprovalRequest`, and no-oping when we receive the
`EventMsg::PatchApplyBegin` later.
This is much less invasive than modifying the order of EventMsg within
core (I tried).
The resulting payloads:
```
{
"method": "item/started",
"params": {
"item": {
"changes": [
{
"diff": "Hello from Codex!\n",
"kind": "add",
"path": "/Users/owen/repos/codex/codex-rs/APPROVAL_DEMO.txt"
}
],
"id": "call_Nxnwj7B3YXigfV6Mwh03d686",
"status": "inProgress",
"type": "fileChange"
}
}
}
```
```
{
"id": 0,
"method": "item/fileChange/requestApproval",
"params": {
"grantRoot": null,
"itemId": "call_Nxnwj7B3YXigfV6Mwh03d686",
"reason": null,
"threadId": "019a9e11-8295-7883-a283-779e06502c6f",
"turnId": "1"
}
}
```
```
{
"id": 0,
"result": {
"decision": "accept"
}
}
```
```
{
"method": "item/completed",
"params": {
"item": {
"changes": [
{
"diff": "Hello from Codex!\n",
"kind": "add",
"path": "/Users/owen/repos/codex/codex-rs/APPROVAL_DEMO.txt"
}
],
"id": "call_Nxnwj7B3YXigfV6Mwh03d686",
"status": "completed",
"type": "fileChange"
}
}
}
```
2025-11-19 20:13:31 -08:00
|
|
|
|
/// Turn ID that this patch belongs to.
|
|
|
|
|
|
/// Uses `#[serde(default)]` for backwards compatibility.
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub turn_id: String,
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// Captured stdout (summary printed by apply_patch).
|
|
|
|
|
|
pub stdout: String,
|
|
|
|
|
|
/// Captured stderr (parser errors, IO failures, etc.).
|
|
|
|
|
|
pub stderr: String,
|
|
|
|
|
|
/// Whether the patch was applied successfully.
|
|
|
|
|
|
pub success: bool,
|
[app-server] feat: v2 apply_patch approval flow (#6760)
This PR adds the API V2 version of the apply_patch approval flow, which
centers around `ThreadItem::FileChange`.
This PR wires the new RPC (`item/fileChange/requestApproval`, V2 only)
and related events (`item/started`, `item/completed` for
`ThreadItem::FileChange`, which are emitted in both V1 and V2) through
the app-server
protocol. The new approval RPC is only sent when the user initiates a
turn with the new `turn/start` API so we don't break backwards
compatibility with VSCE.
Similar to https://github.com/openai/codex/pull/6758, the approach I
took was to make as few changes to the Codex core as possible,
leveraging existing `EventMsg` core events, and translating those in
app-server. I did have to add a few additional fields to
`EventMsg::PatchApplyBegin` and `EventMsg::PatchApplyEnd`, but those
were fairly lightweight.
However, the `EventMsg`s emitted by core are the following:
```
1) Auto-approved (no request for approval)
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
2) Approved by user
- EventMsg::ApplyPatchApprovalRequest
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
3) Declined by user
- EventMsg::ApplyPatchApprovalRequest
- EventMsg::PatchApplyBegin
- EventMsg::PatchApplyEnd
```
For a request triggering an approval, this would result in:
```
item/fileChange/requestApproval
item/started
item/completed
```
which is different from the `ThreadItem::CommandExecution` flow
introduced in https://github.com/openai/codex/pull/6758, which does the
below and is preferable:
```
item/started
item/commandExecution/requestApproval
item/completed
```
To fix this, we leverage `TurnSummaryStore` on codex_message_processor
to store a little bit of state, allowing us to fire `item/started` and
`item/fileChange/requestApproval` whenever we receive the underlying
`EventMsg::ApplyPatchApprovalRequest`, and no-oping when we receive the
`EventMsg::PatchApplyBegin` later.
This is much less invasive than modifying the order of EventMsg within
core (I tried).
The resulting payloads:
```
{
"method": "item/started",
"params": {
"item": {
"changes": [
{
"diff": "Hello from Codex!\n",
"kind": "add",
"path": "/Users/owen/repos/codex/codex-rs/APPROVAL_DEMO.txt"
}
],
"id": "call_Nxnwj7B3YXigfV6Mwh03d686",
"status": "inProgress",
"type": "fileChange"
}
}
}
```
```
{
"id": 0,
"method": "item/fileChange/requestApproval",
"params": {
"grantRoot": null,
"itemId": "call_Nxnwj7B3YXigfV6Mwh03d686",
"reason": null,
"threadId": "019a9e11-8295-7883-a283-779e06502c6f",
"turnId": "1"
}
}
```
```
{
"id": 0,
"result": {
"decision": "accept"
}
}
```
```
{
"method": "item/completed",
"params": {
"item": {
"changes": [
{
"diff": "Hello from Codex!\n",
"kind": "add",
"path": "/Users/owen/repos/codex/codex-rs/APPROVAL_DEMO.txt"
}
],
"id": "call_Nxnwj7B3YXigfV6Mwh03d686",
"status": "completed",
"type": "fileChange"
}
}
}
```
2025-11-19 20:13:31 -08:00
|
|
|
|
/// The changes that were applied (mirrors PatchApplyBeginEvent::changes).
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
|
pub changes: HashMap<PathBuf, FileChange>,
|
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, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-04 08:57:04 -07:00
|
|
|
|
pub struct TurnDiffEvent {
|
|
|
|
|
|
pub unified_diff: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
|
|
|
|
pub struct GetHistoryEntryResponseEvent {
|
|
|
|
|
|
pub offset: usize,
|
|
|
|
|
|
pub log_id: u64,
|
|
|
|
|
|
/// The entry at the requested offset, if available and parseable.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub entry: Option<HistoryEntry>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-19 09:00:31 -07:00
|
|
|
|
pub struct McpListToolsResponseEvent {
|
|
|
|
|
|
/// Fully qualified tool name -> tool definition.
|
|
|
|
|
|
pub tools: std::collections::HashMap<String, McpTool>,
|
2025-10-16 22:05:15 -07:00
|
|
|
|
/// Known resources grouped by server name.
|
|
|
|
|
|
pub resources: std::collections::HashMap<String, Vec<McpResource>>,
|
|
|
|
|
|
/// Known resource templates grouped by server name.
|
|
|
|
|
|
pub resource_templates: std::collections::HashMap<String, Vec<McpResourceTemplate>>,
|
2025-10-08 14:37:57 -07:00
|
|
|
|
/// Authentication status for each configured MCP server.
|
|
|
|
|
|
pub auth_statuses: std::collections::HashMap<String, McpAuthStatus>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 11:26:11 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct McpStartupUpdateEvent {
|
|
|
|
|
|
/// Server name being started.
|
|
|
|
|
|
pub server: String,
|
|
|
|
|
|
/// Current startup status.
|
|
|
|
|
|
pub status: McpStartupStatus,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
#[serde(rename_all = "snake_case", tag = "state")]
|
|
|
|
|
|
#[ts(rename_all = "snake_case", tag = "state")]
|
|
|
|
|
|
pub enum McpStartupStatus {
|
|
|
|
|
|
Starting,
|
|
|
|
|
|
Ready,
|
|
|
|
|
|
Failed { error: String },
|
|
|
|
|
|
Cancelled,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS, Default)]
|
|
|
|
|
|
pub struct McpStartupCompleteEvent {
|
|
|
|
|
|
pub ready: Vec<String>,
|
|
|
|
|
|
pub failed: Vec<McpStartupFailure>,
|
|
|
|
|
|
pub cancelled: Vec<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct McpStartupFailure {
|
|
|
|
|
|
pub server: String,
|
|
|
|
|
|
pub error: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
|
2025-10-08 14:37:57 -07:00
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
#[ts(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum McpAuthStatus {
|
|
|
|
|
|
Unsupported,
|
|
|
|
|
|
NotLoggedIn,
|
|
|
|
|
|
BearerToken,
|
|
|
|
|
|
OAuth,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for McpAuthStatus {
|
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
|
let text = match self {
|
|
|
|
|
|
McpAuthStatus::Unsupported => "Unsupported",
|
|
|
|
|
|
McpAuthStatus::NotLoggedIn => "Not logged in",
|
|
|
|
|
|
McpAuthStatus::BearerToken => "Bearer token",
|
|
|
|
|
|
McpAuthStatus::OAuth => "OAuth",
|
|
|
|
|
|
};
|
|
|
|
|
|
f.write_str(text)
|
|
|
|
|
|
}
|
2025-08-19 09:00:31 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-28 19:16:39 -07:00
|
|
|
|
/// Response payload for `Op::ListCustomPrompts`.
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-28 19:16:39 -07:00
|
|
|
|
pub struct ListCustomPromptsResponseEvent {
|
|
|
|
|
|
pub custom_prompts: Vec<CustomPrompt>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 09:58:17 -08:00
|
|
|
|
/// Response payload for `Op::ListSkills`.
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct ListSkillsResponseEvent {
|
|
|
|
|
|
pub skills: Vec<SkillsListEntry>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
|
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
#[ts(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum SkillScope {
|
|
|
|
|
|
User,
|
|
|
|
|
|
Repo,
|
2025-12-17 18:48:28 -08:00
|
|
|
|
System,
|
2025-12-18 18:28:56 -08:00
|
|
|
|
Admin,
|
2025-12-14 09:58:17 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 13:59:17 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-12-14 09:58:17 -08:00
|
|
|
|
pub struct SkillMetadata {
|
2025-12-10 13:59:17 -08:00
|
|
|
|
pub name: String,
|
|
|
|
|
|
pub description: String,
|
2025-12-18 15:13:18 -08:00
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
2026-01-15 11:20:04 -08:00
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
/// Legacy short_description from SKILL.md. Prefer SKILL.toml interface.short_description.
|
2025-12-18 15:13:18 -08:00
|
|
|
|
pub short_description: Option<String>,
|
2026-01-15 11:20:04 -08:00
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub interface: Option<SkillInterface>,
|
2025-12-10 13:59:17 -08:00
|
|
|
|
pub path: PathBuf,
|
2025-12-14 09:58:17 -08:00
|
|
|
|
pub scope: SkillScope,
|
2026-01-21 18:21:12 -08:00
|
|
|
|
pub enabled: bool,
|
2025-12-10 13:59:17 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 11:20:04 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS, PartialEq, Eq)]
|
|
|
|
|
|
pub struct SkillInterface {
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub display_name: Option<String>,
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub short_description: Option<String>,
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub icon_small: Option<PathBuf>,
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub icon_large: Option<PathBuf>,
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub brand_color: Option<String>,
|
|
|
|
|
|
#[ts(optional)]
|
|
|
|
|
|
pub default_prompt: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 13:59:17 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct SkillErrorInfo {
|
|
|
|
|
|
pub path: PathBuf,
|
|
|
|
|
|
pub message: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 09:58:17 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
|
|
|
|
|
pub struct SkillsListEntry {
|
|
|
|
|
|
pub cwd: PathBuf,
|
|
|
|
|
|
pub skills: Vec<SkillMetadata>,
|
2025-12-10 13:59:17 -08:00
|
|
|
|
pub errors: Vec<SkillErrorInfo>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix: add more fields to ThreadStartResponse and ThreadResumeResponse (#6847)
This adds the following fields to `ThreadStartResponse` and
`ThreadResumeResponse`:
```rust
pub model: String,
pub model_provider: String,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox: SandboxPolicy,
pub reasoning_effort: Option<ReasoningEffort>,
```
This is important because these fields are optional in
`ThreadStartParams` and `ThreadResumeParams`, so the caller needs to be
able to determine what values were ultimately used to start/resume the
conversation. (Though note that any of these could be changed later
between turns in the conversation.)
Though to get this information reliably, it must be read from the
internal `SessionConfiguredEvent` that is created in response to the
start of a conversation. Because `SessionConfiguredEvent` (as defined in
`codex-rs/protocol/src/protocol.rs`) did not have all of these fields, a
number of them had to be added as part of this PR.
Because `SessionConfiguredEvent` is referenced in many tests, test
instances of `SessionConfiguredEvent` had to be updated, as well, which
is why this PR touches so many files.
2025-11-18 21:18:43 -08:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-05-13 19:22:16 -07:00
|
|
|
|
pub struct SessionConfiguredEvent {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
/// Name left as session_id instead of thread_id for backwards compatibility.
|
|
|
|
|
|
pub session_id: ThreadId,
|
2026-01-16 13:41:46 -08:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub forked_from_id: Option<ThreadId>,
|
2025-05-13 19:22:16 -07:00
|
|
|
|
|
|
|
|
|
|
/// Tell the client what model is being queried.
|
|
|
|
|
|
pub model: String,
|
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
|
|
|
|
|
fix: add more fields to ThreadStartResponse and ThreadResumeResponse (#6847)
This adds the following fields to `ThreadStartResponse` and
`ThreadResumeResponse`:
```rust
pub model: String,
pub model_provider: String,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox: SandboxPolicy,
pub reasoning_effort: Option<ReasoningEffort>,
```
This is important because these fields are optional in
`ThreadStartParams` and `ThreadResumeParams`, so the caller needs to be
able to determine what values were ultimately used to start/resume the
conversation. (Though note that any of these could be changed later
between turns in the conversation.)
Though to get this information reliably, it must be read from the
internal `SessionConfiguredEvent` that is created in response to the
start of a conversation. Because `SessionConfiguredEvent` (as defined in
`codex-rs/protocol/src/protocol.rs`) did not have all of these fields, a
number of them had to be added as part of this PR.
Because `SessionConfiguredEvent` is referenced in many tests, test
instances of `SessionConfiguredEvent` had to be updated, as well, which
is why this PR touches so many files.
2025-11-18 21:18:43 -08:00
|
|
|
|
pub model_provider_id: String,
|
|
|
|
|
|
|
|
|
|
|
|
/// When to escalate for approval for execution
|
|
|
|
|
|
pub approval_policy: AskForApproval,
|
|
|
|
|
|
|
|
|
|
|
|
/// How to sandbox commands executed in the system
|
|
|
|
|
|
pub sandbox_policy: SandboxPolicy,
|
|
|
|
|
|
|
|
|
|
|
|
/// Working directory that should be treated as the *root* of the
|
|
|
|
|
|
/// session.
|
|
|
|
|
|
pub cwd: PathBuf,
|
|
|
|
|
|
|
2025-09-11 21:04:40 -07:00
|
|
|
|
/// The effort the model is putting into reasoning about the user's request.
|
2025-09-12 12:06:33 -07:00
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub reasoning_effort: Option<ReasoningEffortConfig>,
|
2025-09-11 21:04:40 -07:00
|
|
|
|
|
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
|
|
|
|
/// Identifier of the history log file (inode on Unix, 0 otherwise).
|
|
|
|
|
|
pub history_log_id: u64,
|
|
|
|
|
|
|
|
|
|
|
|
/// Current number of entries in the history log.
|
|
|
|
|
|
pub history_entry_count: usize,
|
2025-09-03 21:47:00 -07:00
|
|
|
|
|
|
|
|
|
|
/// Optional initial messages (as events) for resumed sessions.
|
|
|
|
|
|
/// When present, UIs can use these to seed the history.
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub initial_messages: Option<Vec<EventMsg>>,
|
2025-09-09 00:11:48 -07:00
|
|
|
|
|
2026-01-24 15:57:40 +01:00
|
|
|
|
/// Path in which the rollout is stored. Can be `None` for ephemeral threads
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
|
pub rollout_path: Option<PathBuf>,
|
2025-05-13 19:22:16 -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
|
|
|
|
/// User's decision in response to an ExecApprovalRequest.
|
2025-12-04 02:17:02 -05:00
|
|
|
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, Display, 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(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum ReviewDecision {
|
|
|
|
|
|
/// User has approved this command and the agent should execute it.
|
|
|
|
|
|
Approved,
|
|
|
|
|
|
|
2025-12-04 02:39:48 -05:00
|
|
|
|
/// User has approved this command and wants to apply the proposed execpolicy
|
|
|
|
|
|
/// amendment so future matching commands are permitted.
|
|
|
|
|
|
ApprovedExecpolicyAmendment {
|
|
|
|
|
|
proposed_execpolicy_amendment: ExecPolicyAmendment,
|
|
|
|
|
|
},
|
2025-12-04 02:17:02 -05: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
|
|
|
|
/// User has approved this command and wants to automatically approve any
|
|
|
|
|
|
/// future identical instances (`command` and `cwd` match exactly) for the
|
|
|
|
|
|
/// remainder of the session.
|
|
|
|
|
|
ApprovedForSession,
|
|
|
|
|
|
|
|
|
|
|
|
/// User has denied this command and the agent should not execute it, but
|
|
|
|
|
|
/// it should continue the session and try something else.
|
|
|
|
|
|
#[default]
|
|
|
|
|
|
Denied,
|
|
|
|
|
|
|
|
|
|
|
|
/// User has denied this command and the agent should not do anything until
|
|
|
|
|
|
/// the user's next command.
|
|
|
|
|
|
Abort,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-09 13:10:31 +00:00
|
|
|
|
impl ReviewDecision {
|
|
|
|
|
|
/// Returns an opaque version of the decision without PII. We can't use an ignored flag
|
|
|
|
|
|
/// on `serde` because the serialization is required by some surfaces.
|
|
|
|
|
|
pub fn to_opaque_string(&self) -> &'static str {
|
|
|
|
|
|
match self {
|
|
|
|
|
|
ReviewDecision::Approved => "approved",
|
|
|
|
|
|
ReviewDecision::ApprovedExecpolicyAmendment { .. } => "approved_with_amendment",
|
|
|
|
|
|
ReviewDecision::ApprovedForSession => "approved_for_session",
|
|
|
|
|
|
ReviewDecision::Denied => "denied",
|
|
|
|
|
|
ReviewDecision::Abort => "abort",
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
2025-11-13 16:25:17 -08:00
|
|
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
|
|
|
|
#[ts(tag = "type")]
|
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 enum FileChange {
|
|
|
|
|
|
Add {
|
|
|
|
|
|
content: String,
|
|
|
|
|
|
},
|
2025-09-02 10:29:58 -07:00
|
|
|
|
Delete {
|
|
|
|
|
|
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
|
|
|
|
Update {
|
|
|
|
|
|
unified_diff: String,
|
|
|
|
|
|
move_path: Option<PathBuf>,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, 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 Chunk {
|
|
|
|
|
|
/// 1-based line index of the first line in the original file
|
|
|
|
|
|
pub orig_index: u32,
|
|
|
|
|
|
pub deleted_lines: Vec<String>,
|
|
|
|
|
|
pub inserted_lines: Vec<String>,
|
|
|
|
|
|
}
|
2025-05-13 20:44:42 -07:00
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
2025-08-17 21:40:31 -07:00
|
|
|
|
pub struct TurnAbortedEvent {
|
|
|
|
|
|
pub reason: TurnAbortReason,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 11:45:11 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
2025-08-17 21:40:31 -07:00
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
|
pub enum TurnAbortReason {
|
|
|
|
|
|
Interrupted,
|
|
|
|
|
|
Replaced,
|
2025-09-18 14:14:16 -07:00
|
|
|
|
ReviewEnded,
|
2025-08-17 21:40:31 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
feat: emit events around collab tools (#9095)
Emit the following events around the collab tools. On the `app-server`
this will be under `item/started` and `item/completed`
```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentSpawnBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
/// beginning.
pub prompt: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentSpawnEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the newly spawned agent, if it was created.
pub new_thread_id: Option<ThreadId>,
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
/// beginning.
pub prompt: String,
/// Last known status of the new agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentInteractionBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
/// leaking at the beginning.
pub prompt: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentInteractionEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
/// leaking at the beginning.
pub prompt: String,
/// Last known status of the receiver agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabWaitingBeginEvent {
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// ID of the waiting call.
pub call_id: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabWaitingEndEvent {
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// ID of the waiting call.
pub call_id: String,
/// Last known status of the receiver agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabCloseBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabCloseEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Last known status of the receiver agent reported to the sender agent before
/// the close.
pub status: AgentStatus,
}
```
2026-01-14 17:55:57 +00:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CollabAgentSpawnBeginEvent {
|
|
|
|
|
|
/// Identifier for the collab tool call.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
/// Thread ID of the sender.
|
|
|
|
|
|
pub sender_thread_id: ThreadId,
|
|
|
|
|
|
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
|
|
|
|
|
|
/// beginning.
|
|
|
|
|
|
pub prompt: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CollabAgentSpawnEndEvent {
|
|
|
|
|
|
/// Identifier for the collab tool call.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
/// Thread ID of the sender.
|
|
|
|
|
|
pub sender_thread_id: ThreadId,
|
|
|
|
|
|
/// Thread ID of the newly spawned agent, if it was created.
|
|
|
|
|
|
pub new_thread_id: Option<ThreadId>,
|
|
|
|
|
|
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
|
|
|
|
|
|
/// beginning.
|
|
|
|
|
|
pub prompt: String,
|
|
|
|
|
|
/// Last known status of the new agent reported to the sender agent.
|
|
|
|
|
|
pub status: AgentStatus,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CollabAgentInteractionBeginEvent {
|
|
|
|
|
|
/// Identifier for the collab tool call.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
/// Thread ID of the sender.
|
|
|
|
|
|
pub sender_thread_id: ThreadId,
|
|
|
|
|
|
/// Thread ID of the receiver.
|
|
|
|
|
|
pub receiver_thread_id: ThreadId,
|
|
|
|
|
|
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
|
|
|
|
|
|
/// leaking at the beginning.
|
|
|
|
|
|
pub prompt: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CollabAgentInteractionEndEvent {
|
|
|
|
|
|
/// Identifier for the collab tool call.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
/// Thread ID of the sender.
|
|
|
|
|
|
pub sender_thread_id: ThreadId,
|
|
|
|
|
|
/// Thread ID of the receiver.
|
|
|
|
|
|
pub receiver_thread_id: ThreadId,
|
|
|
|
|
|
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
|
|
|
|
|
|
/// leaking at the beginning.
|
|
|
|
|
|
pub prompt: String,
|
|
|
|
|
|
/// Last known status of the receiver agent reported to the sender agent.
|
|
|
|
|
|
pub status: AgentStatus,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CollabWaitingBeginEvent {
|
|
|
|
|
|
/// Thread ID of the sender.
|
|
|
|
|
|
pub sender_thread_id: ThreadId,
|
2026-01-16 12:05:04 +01:00
|
|
|
|
/// Thread ID of the receivers.
|
|
|
|
|
|
pub receiver_thread_ids: Vec<ThreadId>,
|
feat: emit events around collab tools (#9095)
Emit the following events around the collab tools. On the `app-server`
this will be under `item/started` and `item/completed`
```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentSpawnBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
/// beginning.
pub prompt: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentSpawnEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the newly spawned agent, if it was created.
pub new_thread_id: Option<ThreadId>,
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
/// beginning.
pub prompt: String,
/// Last known status of the new agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentInteractionBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
/// leaking at the beginning.
pub prompt: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentInteractionEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
/// leaking at the beginning.
pub prompt: String,
/// Last known status of the receiver agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabWaitingBeginEvent {
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// ID of the waiting call.
pub call_id: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabWaitingEndEvent {
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// ID of the waiting call.
pub call_id: String,
/// Last known status of the receiver agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabCloseBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabCloseEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Last known status of the receiver agent reported to the sender agent before
/// the close.
pub status: AgentStatus,
}
```
2026-01-14 17:55:57 +00:00
|
|
|
|
/// ID of the waiting call.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CollabWaitingEndEvent {
|
|
|
|
|
|
/// Thread ID of the sender.
|
|
|
|
|
|
pub sender_thread_id: ThreadId,
|
|
|
|
|
|
/// ID of the waiting call.
|
|
|
|
|
|
pub call_id: String,
|
2026-01-16 12:05:04 +01:00
|
|
|
|
/// Last known status of the receiver agents reported to the sender agent.
|
|
|
|
|
|
pub statuses: HashMap<ThreadId, AgentStatus>,
|
feat: emit events around collab tools (#9095)
Emit the following events around the collab tools. On the `app-server`
this will be under `item/started` and `item/completed`
```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentSpawnBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
/// beginning.
pub prompt: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentSpawnEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the newly spawned agent, if it was created.
pub new_thread_id: Option<ThreadId>,
/// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
/// beginning.
pub prompt: String,
/// Last known status of the new agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentInteractionBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
/// leaking at the beginning.
pub prompt: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabAgentInteractionEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
/// leaking at the beginning.
pub prompt: String,
/// Last known status of the receiver agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabWaitingBeginEvent {
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// ID of the waiting call.
pub call_id: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabWaitingEndEvent {
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// ID of the waiting call.
pub call_id: String,
/// Last known status of the receiver agent reported to the sender agent.
pub status: AgentStatus,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabCloseBeginEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
pub struct CollabCloseEndEvent {
/// Identifier for the collab tool call.
pub call_id: String,
/// Thread ID of the sender.
pub sender_thread_id: ThreadId,
/// Thread ID of the receiver.
pub receiver_thread_id: ThreadId,
/// Last known status of the receiver agent reported to the sender agent before
/// the close.
pub status: AgentStatus,
}
```
2026-01-14 17:55:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CollabCloseBeginEvent {
|
|
|
|
|
|
/// Identifier for the collab tool call.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
/// Thread ID of the sender.
|
|
|
|
|
|
pub sender_thread_id: ThreadId,
|
|
|
|
|
|
/// Thread ID of the receiver.
|
|
|
|
|
|
pub receiver_thread_id: ThreadId,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
|
|
|
|
|
|
pub struct CollabCloseEndEvent {
|
|
|
|
|
|
/// Identifier for the collab tool call.
|
|
|
|
|
|
pub call_id: String,
|
|
|
|
|
|
/// Thread ID of the sender.
|
|
|
|
|
|
pub sender_thread_id: ThreadId,
|
|
|
|
|
|
/// Thread ID of the receiver.
|
|
|
|
|
|
pub receiver_thread_id: ThreadId,
|
|
|
|
|
|
/// Last known status of the receiver agent reported to the sender agent before
|
|
|
|
|
|
/// the close.
|
|
|
|
|
|
pub status: AgentStatus,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod tests {
|
|
|
|
|
|
use super::*;
|
2025-10-29 15:33:57 -07:00
|
|
|
|
use crate::items::UserMessageItem;
|
|
|
|
|
|
use crate::items::WebSearchItem;
|
2025-09-23 13:31:36 -07:00
|
|
|
|
use anyhow::Result;
|
fix: add more fields to ThreadStartResponse and ThreadResumeResponse (#6847)
This adds the following fields to `ThreadStartResponse` and
`ThreadResumeResponse`:
```rust
pub model: String,
pub model_provider: String,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox: SandboxPolicy,
pub reasoning_effort: Option<ReasoningEffort>,
```
This is important because these fields are optional in
`ThreadStartParams` and `ThreadResumeParams`, so the caller needs to be
able to determine what values were ultimately used to start/resume the
conversation. (Though note that any of these could be changed later
between turns in the conversation.)
Though to get this information reliably, it must be read from the
internal `SessionConfiguredEvent` that is created in response to the
start of a conversation. Because `SessionConfiguredEvent` (as defined in
`codex-rs/protocol/src/protocol.rs`) did not have all of these fields, a
number of them had to be added as part of this PR.
Because `SessionConfiguredEvent` is referenced in many tests, test
instances of `SessionConfiguredEvent` had to be updated, as well, which
is why this PR touches so many files.
2025-11-18 21:18:43 -08:00
|
|
|
|
use pretty_assertions::assert_eq;
|
2025-09-09 00:11:48 -07:00
|
|
|
|
use serde_json::json;
|
|
|
|
|
|
use tempfile::NamedTempFile;
|
2025-05-13 20:44:42 -07:00
|
|
|
|
|
2025-12-18 17:02:03 -08:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn external_sandbox_reports_full_access_flags() {
|
|
|
|
|
|
let restricted = SandboxPolicy::ExternalSandbox {
|
|
|
|
|
|
network_access: NetworkAccess::Restricted,
|
|
|
|
|
|
};
|
|
|
|
|
|
assert!(restricted.has_full_disk_write_access());
|
|
|
|
|
|
assert!(!restricted.has_full_network_access());
|
|
|
|
|
|
|
|
|
|
|
|
let enabled = SandboxPolicy::ExternalSandbox {
|
|
|
|
|
|
network_access: NetworkAccess::Enabled,
|
|
|
|
|
|
};
|
|
|
|
|
|
assert!(enabled.has_full_disk_write_access());
|
|
|
|
|
|
assert!(enabled.has_full_network_access());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 15:33:57 -07:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn item_started_event_from_web_search_emits_begin_event() {
|
|
|
|
|
|
let event = ItemStartedEvent {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
thread_id: ThreadId::new(),
|
2025-10-29 15:33:57 -07:00
|
|
|
|
turn_id: "turn-1".into(),
|
|
|
|
|
|
item: TurnItem::WebSearch(WebSearchItem {
|
|
|
|
|
|
id: "search-1".into(),
|
|
|
|
|
|
query: "find docs".into(),
|
2026-01-26 19:33:48 -08:00
|
|
|
|
action: WebSearchAction::Search {
|
|
|
|
|
|
query: Some("find docs".into()),
|
|
|
|
|
|
},
|
2025-10-29 15:33:57 -07:00
|
|
|
|
}),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let legacy_events = event.as_legacy_events(false);
|
|
|
|
|
|
assert_eq!(legacy_events.len(), 1);
|
|
|
|
|
|
match &legacy_events[0] {
|
|
|
|
|
|
EventMsg::WebSearchBegin(event) => assert_eq!(event.call_id, "search-1"),
|
|
|
|
|
|
_ => panic!("expected WebSearchBegin event"),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn item_started_event_from_non_web_search_emits_no_legacy_events() {
|
|
|
|
|
|
let event = ItemStartedEvent {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
thread_id: ThreadId::new(),
|
2025-10-29 15:33:57 -07:00
|
|
|
|
turn_id: "turn-1".into(),
|
|
|
|
|
|
item: TurnItem::UserMessage(UserMessageItem::new(&[])),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
assert!(event.as_legacy_events(false).is_empty());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-05 10:27:00 -08:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn user_input_serialization_omits_final_output_json_schema_when_none() -> Result<()> {
|
|
|
|
|
|
let op = Op::UserInput {
|
|
|
|
|
|
items: Vec::new(),
|
|
|
|
|
|
final_output_json_schema: None,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let json_op = serde_json::to_value(op)?;
|
|
|
|
|
|
assert_eq!(json_op, json!({ "type": "user_input", "items": [] }));
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn user_input_deserializes_without_final_output_json_schema_field() -> Result<()> {
|
|
|
|
|
|
let op: Op = serde_json::from_value(json!({ "type": "user_input", "items": [] }))?;
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
op,
|
|
|
|
|
|
Op::UserInput {
|
|
|
|
|
|
items: Vec::new(),
|
|
|
|
|
|
final_output_json_schema: None,
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn user_input_serialization_includes_final_output_json_schema_when_some() -> Result<()> {
|
|
|
|
|
|
let schema = json!({
|
|
|
|
|
|
"type": "object",
|
|
|
|
|
|
"properties": {
|
|
|
|
|
|
"answer": { "type": "string" }
|
|
|
|
|
|
},
|
|
|
|
|
|
"required": ["answer"],
|
|
|
|
|
|
"additionalProperties": false
|
|
|
|
|
|
});
|
|
|
|
|
|
let op = Op::UserInput {
|
|
|
|
|
|
items: Vec::new(),
|
|
|
|
|
|
final_output_json_schema: Some(schema.clone()),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let json_op = serde_json::to_value(op)?;
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
json_op,
|
|
|
|
|
|
json!({
|
|
|
|
|
|
"type": "user_input",
|
|
|
|
|
|
"items": [],
|
|
|
|
|
|
"final_output_json_schema": schema,
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 16:41:50 -08:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn user_input_text_serializes_empty_text_elements() -> Result<()> {
|
|
|
|
|
|
let input = UserInput::Text {
|
|
|
|
|
|
text: "hello".to_string(),
|
|
|
|
|
|
text_elements: Vec::new(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let json_input = serde_json::to_value(input)?;
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
json_input,
|
|
|
|
|
|
json!({
|
|
|
|
|
|
"type": "text",
|
|
|
|
|
|
"text": "hello",
|
|
|
|
|
|
"text_elements": [],
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn user_message_event_serializes_empty_metadata_vectors() -> Result<()> {
|
|
|
|
|
|
let event = UserMessageEvent {
|
|
|
|
|
|
message: "hello".to_string(),
|
|
|
|
|
|
images: None,
|
|
|
|
|
|
local_images: Vec::new(),
|
|
|
|
|
|
text_elements: Vec::new(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let json_event = serde_json::to_value(event)?;
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
json_event,
|
|
|
|
|
|
json!({
|
|
|
|
|
|
"message": "hello",
|
|
|
|
|
|
"local_images": [],
|
|
|
|
|
|
"text_elements": [],
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-13 20:44:42 -07:00
|
|
|
|
/// Serialize Event to verify that its JSON representation has the expected
|
|
|
|
|
|
/// amount of nesting.
|
|
|
|
|
|
#[test]
|
2025-09-23 13:31:36 -07:00
|
|
|
|
fn serialize_event() -> Result<()> {
|
2026-01-07 17:04:53 +00:00
|
|
|
|
let conversation_id = ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
|
2025-09-23 13:31:36 -07:00
|
|
|
|
let rollout_file = NamedTempFile::new()?;
|
2025-05-13 20:44:42 -07:00
|
|
|
|
let event = Event {
|
|
|
|
|
|
id: "1234".to_string(),
|
|
|
|
|
|
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
|
2025-09-07 20:22:25 -07:00
|
|
|
|
session_id: conversation_id,
|
2026-01-16 13:41:46 -08:00
|
|
|
|
forked_from_id: None,
|
2025-05-29 16:55:19 -07:00
|
|
|
|
model: "codex-mini-latest".to_string(),
|
fix: add more fields to ThreadStartResponse and ThreadResumeResponse (#6847)
This adds the following fields to `ThreadStartResponse` and
`ThreadResumeResponse`:
```rust
pub model: String,
pub model_provider: String,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox: SandboxPolicy,
pub reasoning_effort: Option<ReasoningEffort>,
```
This is important because these fields are optional in
`ThreadStartParams` and `ThreadResumeParams`, so the caller needs to be
able to determine what values were ultimately used to start/resume the
conversation. (Though note that any of these could be changed later
between turns in the conversation.)
Though to get this information reliably, it must be read from the
internal `SessionConfiguredEvent` that is created in response to the
start of a conversation. Because `SessionConfiguredEvent` (as defined in
`codex-rs/protocol/src/protocol.rs`) did not have all of these fields, a
number of them had to be added as part of this PR.
Because `SessionConfiguredEvent` is referenced in many tests, test
instances of `SessionConfiguredEvent` had to be updated, as well, which
is why this PR touches so many files.
2025-11-18 21:18:43 -08:00
|
|
|
|
model_provider_id: "openai".to_string(),
|
|
|
|
|
|
approval_policy: AskForApproval::Never,
|
|
|
|
|
|
sandbox_policy: SandboxPolicy::ReadOnly,
|
|
|
|
|
|
cwd: PathBuf::from("/home/user/project"),
|
2025-09-12 12:06:33 -07:00
|
|
|
|
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
|
|
|
|
history_log_id: 0,
|
|
|
|
|
|
history_entry_count: 0,
|
2025-09-03 21:47:00 -07:00
|
|
|
|
initial_messages: None,
|
2026-01-24 15:57:40 +01:00
|
|
|
|
rollout_path: Some(rollout_file.path().to_path_buf()),
|
2025-05-13 20:44:42 -07:00
|
|
|
|
}),
|
|
|
|
|
|
};
|
2025-09-09 00:11:48 -07:00
|
|
|
|
|
|
|
|
|
|
let expected = json!({
|
|
|
|
|
|
"id": "1234",
|
|
|
|
|
|
"msg": {
|
|
|
|
|
|
"type": "session_configured",
|
|
|
|
|
|
"session_id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
|
|
|
|
|
|
"model": "codex-mini-latest",
|
fix: add more fields to ThreadStartResponse and ThreadResumeResponse (#6847)
This adds the following fields to `ThreadStartResponse` and
`ThreadResumeResponse`:
```rust
pub model: String,
pub model_provider: String,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox: SandboxPolicy,
pub reasoning_effort: Option<ReasoningEffort>,
```
This is important because these fields are optional in
`ThreadStartParams` and `ThreadResumeParams`, so the caller needs to be
able to determine what values were ultimately used to start/resume the
conversation. (Though note that any of these could be changed later
between turns in the conversation.)
Though to get this information reliably, it must be read from the
internal `SessionConfiguredEvent` that is created in response to the
start of a conversation. Because `SessionConfiguredEvent` (as defined in
`codex-rs/protocol/src/protocol.rs`) did not have all of these fields, a
number of them had to be added as part of this PR.
Because `SessionConfiguredEvent` is referenced in many tests, test
instances of `SessionConfiguredEvent` had to be updated, as well, which
is why this PR touches so many files.
2025-11-18 21:18:43 -08:00
|
|
|
|
"model_provider_id": "openai",
|
|
|
|
|
|
"approval_policy": "never",
|
|
|
|
|
|
"sandbox_policy": {
|
|
|
|
|
|
"type": "read-only"
|
|
|
|
|
|
},
|
|
|
|
|
|
"cwd": "/home/user/project",
|
2025-09-11 21:04:40 -07:00
|
|
|
|
"reasoning_effort": "medium",
|
2025-09-09 00:11:48 -07:00
|
|
|
|
"history_log_id": 0,
|
|
|
|
|
|
"history_entry_count": 0,
|
|
|
|
|
|
"rollout_path": format!("{}", rollout_file.path().display()),
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-09-23 13:31:36 -07:00
|
|
|
|
assert_eq!(expected, serde_json::to_value(&event)?);
|
|
|
|
|
|
Ok(())
|
2025-05-13 20:44:42 -07:00
|
|
|
|
}
|
2025-09-04 10:38:00 -07:00
|
|
|
|
|
|
|
|
|
|
#[test]
|
2025-09-23 13:31:36 -07:00
|
|
|
|
fn vec_u8_as_base64_serialization_and_deserialization() -> Result<()> {
|
2025-09-04 10:38:00 -07:00
|
|
|
|
let event = ExecCommandOutputDeltaEvent {
|
|
|
|
|
|
call_id: "call21".to_string(),
|
|
|
|
|
|
stream: ExecOutputStream::Stdout,
|
|
|
|
|
|
chunk: vec![1, 2, 3, 4, 5],
|
|
|
|
|
|
};
|
2025-09-23 13:31:36 -07:00
|
|
|
|
let serialized = serde_json::to_string(&event)?;
|
2025-09-04 10:38:00 -07:00
|
|
|
|
assert_eq!(
|
|
|
|
|
|
r#"{"call_id":"call21","stream":"stdout","chunk":"AQIDBAU="}"#,
|
|
|
|
|
|
serialized,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-09-23 13:31:36 -07:00
|
|
|
|
let deserialized: ExecCommandOutputDeltaEvent = serde_json::from_str(&serialized)?;
|
2025-09-04 10:38:00 -07:00
|
|
|
|
assert_eq!(deserialized, event);
|
2025-09-23 13:31:36 -07:00
|
|
|
|
Ok(())
|
2025-09-04 10:38:00 -07:00
|
|
|
|
}
|
2025-11-17 11:26:11 -08:00
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn serialize_mcp_startup_update_event() -> Result<()> {
|
|
|
|
|
|
let event = Event {
|
|
|
|
|
|
id: "init".to_string(),
|
|
|
|
|
|
msg: EventMsg::McpStartupUpdate(McpStartupUpdateEvent {
|
|
|
|
|
|
server: "srv".to_string(),
|
|
|
|
|
|
status: McpStartupStatus::Failed {
|
|
|
|
|
|
error: "boom".to_string(),
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let value = serde_json::to_value(&event)?;
|
|
|
|
|
|
assert_eq!(value["msg"]["type"], "mcp_startup_update");
|
|
|
|
|
|
assert_eq!(value["msg"]["server"], "srv");
|
|
|
|
|
|
assert_eq!(value["msg"]["status"]["state"], "failed");
|
|
|
|
|
|
assert_eq!(value["msg"]["status"]["error"], "boom");
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn serialize_mcp_startup_complete_event() -> Result<()> {
|
|
|
|
|
|
let event = Event {
|
|
|
|
|
|
id: "init".to_string(),
|
|
|
|
|
|
msg: EventMsg::McpStartupComplete(McpStartupCompleteEvent {
|
|
|
|
|
|
ready: vec!["a".to_string()],
|
|
|
|
|
|
failed: vec![McpStartupFailure {
|
|
|
|
|
|
server: "b".to_string(),
|
|
|
|
|
|
error: "bad".to_string(),
|
|
|
|
|
|
}],
|
|
|
|
|
|
cancelled: vec!["c".to_string()],
|
|
|
|
|
|
}),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let value = serde_json::to_value(&event)?;
|
|
|
|
|
|
assert_eq!(value["msg"]["type"], "mcp_startup_complete");
|
|
|
|
|
|
assert_eq!(value["msg"]["ready"][0], "a");
|
|
|
|
|
|
assert_eq!(value["msg"]["failed"][0]["server"], "b");
|
|
|
|
|
|
assert_eq!(value["msg"]["failed"][0]["error"], "bad");
|
|
|
|
|
|
assert_eq!(value["msg"]["cancelled"][0], "c");
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
2025-05-13 20:44:42 -07:00
|
|
|
|
}
|