fix: introduce codex-protocol crate (#2355)

This commit is contained in:
Michael Bolin
2025-08-15 12:44:40 -07:00
committed by GitHub
parent 7c26c8e091
commit d262244725
23 changed files with 244 additions and 43 deletions

15
codex-rs/Cargo.lock generated
View File

@@ -695,6 +695,7 @@ dependencies = [
"codex-apply-patch",
"codex-login",
"codex-mcp-client",
"codex-protocol",
"core_test_support",
"dirs",
"env-flags",
@@ -889,6 +890,19 @@ dependencies = [
"wiremock",
]
[[package]]
name = "codex-protocol"
version = "0.0.0"
dependencies = [
"mcp-types",
"serde",
"serde_bytes",
"serde_json",
"strum 0.27.2",
"strum_macros 0.27.2",
"uuid",
]
[[package]]
name = "codex-tui"
version = "0.0.0"
@@ -904,6 +918,7 @@ dependencies = [
"codex-file-search",
"codex-login",
"codex-ollama",
"codex-protocol",
"color-eyre",
"crossterm",
"diffy",

View File

@@ -15,6 +15,7 @@ members = [
"mcp-server",
"mcp-types",
"ollama",
"protocol",
"tui",
]
resolver = "2"

View File

@@ -19,6 +19,7 @@ chrono = { version = "0.4", features = ["serde"] }
codex-apply-patch = { path = "../apply-patch" }
codex-login = { path = "../login" }
codex-mcp-client = { path = "../mcp-client" }
codex-protocol = { path = "../protocol" }
dirs = "6"
env-flags = "0.1.1"
eventsource-stream = "0.2.3"

View File

@@ -677,7 +677,10 @@ impl Session {
call_id,
command: command_for_display.clone(),
cwd,
parsed_cmd: parse_command(&command_for_display),
parsed_cmd: parse_command(&command_for_display)
.into_iter()
.map(Into::into)
.collect(),
}),
};
let event = Event {
@@ -1031,8 +1034,8 @@ async fn submission_loop(
Arc::new(per_turn_config),
None,
provider,
effort,
summary,
effort.into(),
summary.into(),
sess.session_id,
);
@@ -1102,7 +1105,13 @@ async fn submission_loop(
crate::protocol::GetHistoryEntryResponseEvent {
offset,
log_id,
entry: entry_opt,
entry: entry_opt.map(|e| {
codex_protocol::message_history::HistoryEntry {
session_id: e.session_id,
ts: e.ts,
text: e.text,
}
}),
},
),
};
@@ -1160,6 +1169,9 @@ async fn submission_loop(
}
break;
}
_ => {
// Ignore unknown ops; enum is non_exhaustive to allow extensions.
}
}
}
debug!("Agent loop exited");

View File

@@ -228,3 +228,27 @@ pub enum ReasoningSummary {
/// Option to disable reasoning summaries.
None,
}
// Conversions from protocol enums to core config enums used where protocol
// values are supplied by clients and core needs its internal representations.
impl From<codex_protocol::config_types::ReasoningEffort> for ReasoningEffort {
fn from(v: codex_protocol::config_types::ReasoningEffort) -> Self {
match v {
codex_protocol::config_types::ReasoningEffort::Low => ReasoningEffort::Low,
codex_protocol::config_types::ReasoningEffort::Medium => ReasoningEffort::Medium,
codex_protocol::config_types::ReasoningEffort::High => ReasoningEffort::High,
codex_protocol::config_types::ReasoningEffort::None => ReasoningEffort::None,
}
}
}
impl From<codex_protocol::config_types::ReasoningSummary> for ReasoningSummary {
fn from(v: codex_protocol::config_types::ReasoningSummary) -> Self {
match v {
codex_protocol::config_types::ReasoningSummary::Auto => ReasoningSummary::Auto,
codex_protocol::config_types::ReasoningSummary::Concise => ReasoningSummary::Concise,
codex_protocol::config_types::ReasoningSummary::Detailed => ReasoningSummary::Detailed,
codex_protocol::config_types::ReasoningSummary::None => ReasoningSummary::None,
}
}
}

View File

@@ -44,7 +44,6 @@ mod openai_model_info;
mod openai_tools;
pub mod plan_tool;
mod project_doc;
pub mod protocol;
mod rollout;
pub(crate) mod safety;
pub mod seatbelt;
@@ -56,3 +55,9 @@ mod user_notification;
pub mod util;
pub use apply_patch::CODEX_APPLY_PATCH_ARG1;
pub use safety::get_platform_sandbox;
// Re-export the protocol types from the standalone `codex-protocol` crate so existing
// `codex_core::protocol::...` references continue to work across the workspace.
pub use codex_protocol::protocol;
// Re-export protocol config enums to ensure call sites can use the same types
// as those in the protocol crate when constructing protocol messages.
pub use codex_protocol::config_types as protocol_config_types;

View File

@@ -183,6 +183,7 @@ impl From<Vec<InputItem>> for ResponseInputItem {
None
}
},
_ => None,
})
.collect::<Vec<ContentItem>>(),
}

View File

@@ -41,6 +41,24 @@ pub enum ParsedCommand {
},
}
// Convert core's parsed command enum into the protocol's simplified type so
// events can carry the canonical representation across process boundaries.
impl From<ParsedCommand> for codex_protocol::parse_command::ParsedCommand {
fn from(v: ParsedCommand) -> Self {
use codex_protocol::parse_command::ParsedCommand as P;
match v {
ParsedCommand::Read { cmd, name } => P::Read { cmd, name },
ParsedCommand::ListFiles { cmd, path } => P::ListFiles { cmd, path },
ParsedCommand::Search { cmd, query, path } => P::Search { cmd, query, path },
ParsedCommand::Format { cmd, tool, targets } => P::Format { cmd, tool, targets },
ParsedCommand::Test { cmd } => P::Test { cmd },
ParsedCommand::Lint { cmd, tool, targets } => P::Lint { cmd, tool, targets },
ParsedCommand::Noop { cmd } => P::Noop { cmd },
ParsedCommand::Unknown { cmd } => P::Unknown { cmd },
}
}
}
fn shlex_join(tokens: &[String]) -> String {
shlex_try_join(tokens.iter().map(|s| s.as_str()))
.unwrap_or_else(|_| "<command included NUL byte>".to_string())

View File

@@ -1,9 +1,6 @@
use std::collections::BTreeMap;
use std::sync::LazyLock;
use serde::Deserialize;
use serde::Serialize;
use crate::codex::Session;
use crate::models::FunctionCallOutputPayload;
use crate::models::ResponseInputItem;
@@ -13,29 +10,13 @@ use crate::openai_tools::ResponsesApiTool;
use crate::protocol::Event;
use crate::protocol::EventMsg;
// Use the canonical plan tool types from the protocol crate to ensure
// type-identity matches events transported via `codex_protocol`.
pub use codex_protocol::plan_tool::PlanItemArg;
pub use codex_protocol::plan_tool::StepStatus;
pub use codex_protocol::plan_tool::UpdatePlanArgs;
// Types for the TODO tool arguments matching codex-vscode/todo-mcp/src/main.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StepStatus {
Pending,
InProgress,
Completed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PlanItemArg {
pub step: String,
pub status: StepStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UpdatePlanArgs {
#[serde(default)]
pub explanation: Option<String>,
pub plan: Vec<PlanItemArg>,
}
pub(crate) static PLAN_TOOL: LazyLock<OpenAiTool> = LazyLock::new(|| {
let mut plan_item_props = BTreeMap::new();

View File

@@ -2,12 +2,12 @@ use std::collections::HashMap;
use std::fmt::Display;
use std::path::PathBuf;
use codex_core::config_types::ReasoningEffort;
use codex_core::config_types::ReasoningSummary;
use codex_core::protocol::AskForApproval;
use codex_core::protocol::FileChange;
use codex_core::protocol::ReviewDecision;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol_config_types::ReasoningEffort;
use codex_core::protocol_config_types::ReasoningSummary;
use mcp_types::RequestId;
use serde::Deserialize;
use serde::Serialize;

View File

@@ -1,9 +1,9 @@
use std::path::Path;
use codex_core::config_types::ReasoningEffort;
use codex_core::config_types::ReasoningSummary;
use codex_core::protocol::AskForApproval;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol_config_types::ReasoningEffort;
use codex_core::protocol_config_types::ReasoningSummary;
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
use codex_mcp_server::wire_format::AddConversationListenerParams;
use codex_mcp_server::wire_format::AddConversationSubscriptionResponse;

View File

@@ -0,0 +1,20 @@
[package]
edition = "2024"
name = "codex-protocol"
version = { workspace = true }
[lib]
name = "codex_protocol"
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
mcp-types = { path = "../mcp-types" }
serde = { version = "1", features = ["derive"] }
serde_bytes = "0.11"
serde_json = "1"
strum = "0.27.2"
strum_macros = "0.27.2"
uuid = { version = "1", features = ["serde", "v4"] }

View File

@@ -0,0 +1,7 @@
# codex-protocol
This crate defines the "types" for the protocol used by Codex CLI, which includes both "internal types" for communication between `codex-core` and `codex-tui`, as well as "external types" used with `codex mcp`.
This crate should have minimal dependencies.
Ideally, we should avoid "material business logic" in this crate, as we can always introduce `Ext`-style traits to add functionality to types in other crates.

View File

@@ -0,0 +1,31 @@
use serde::Deserialize;
use serde::Serialize;
use strum_macros::Display;
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ReasoningEffort {
Low,
#[default]
Medium,
High,
/// Option to disable reasoning.
None,
}
/// A summary of the reasoning performed by the model. This can be useful for
/// debugging and understanding the model's reasoning process.
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ReasoningSummary {
#[default]
Auto,
Concise,
Detailed,
/// Option to disable reasoning summaries.
None,
}

View File

@@ -0,0 +1,5 @@
pub mod config_types;
pub mod message_history;
pub mod parse_command;
pub mod plan_tool;
pub mod protocol;

View File

@@ -0,0 +1,9 @@
use serde::Deserialize;
use serde::Serialize;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct HistoryEntry {
pub session_id: String,
pub ts: u64,
pub text: String,
}

View File

@@ -0,0 +1,38 @@
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum ParsedCommand {
Read {
cmd: String,
name: String,
},
ListFiles {
cmd: String,
path: Option<String>,
},
Search {
cmd: String,
query: Option<String>,
path: Option<String>,
},
Format {
cmd: String,
tool: Option<String>,
targets: Option<Vec<String>>,
},
Test {
cmd: String,
},
Lint {
cmd: String,
tool: Option<String>,
targets: Option<Vec<String>>,
},
Noop {
cmd: String,
},
Unknown {
cmd: String,
},
}

View File

@@ -0,0 +1,26 @@
use serde::Deserialize;
use serde::Serialize;
// Types for the TODO tool arguments matching codex-vscode/todo-mcp/src/main.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StepStatus {
Pending,
InProgress,
Completed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PlanItemArg {
pub step: String,
pub status: StepStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UpdatePlanArgs {
#[serde(default)]
pub explanation: Option<String>,
pub plan: Vec<PlanItemArg>,
}

View File

@@ -193,7 +193,7 @@ pub struct WritableRoot {
}
impl WritableRoot {
pub(crate) fn is_path_writable(&self, path: &Path) -> bool {
pub fn is_path_writable(&self, path: &Path) -> bool {
// Check if the path is under the root.
if !path.starts_with(&self.root) {
return false;

View File

@@ -33,6 +33,7 @@ codex-common = { path = "../common", features = [
"sandbox_summary",
] }
codex-core = { path = "../core" }
codex-protocol = { path = "../protocol" }
codex-file-search = { path = "../file-search" }
codex-login = { path = "../login" }
codex-ollama = { path = "../ollama" }

View File

@@ -3,7 +3,6 @@ use std::path::PathBuf;
use std::sync::Arc;
use codex_core::config::Config;
use codex_core::parse_command::ParsedCommand;
use codex_core::protocol::AgentMessageDeltaEvent;
use codex_core::protocol::AgentMessageEvent;
use codex_core::protocol::AgentReasoningDeltaEvent;
@@ -26,6 +25,7 @@ use codex_core::protocol::PatchApplyBeginEvent;
use codex_core::protocol::TaskCompleteEvent;
use codex_core::protocol::TokenUsage;
use codex_core::protocol::TurnDiffEvent;
use codex_protocol::parse_command::ParsedCommand;
use crossterm::event::KeyEvent;
use crossterm::event::KeyEventKind;
use ratatui::buffer::Buffer;

View File

@@ -204,9 +204,12 @@ fn exec_history_cell_shows_working_then_completed() {
call_id: "call-1".into(),
command: vec!["bash".into(), "-lc".into(), "echo done".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
parsed_cmd: vec![codex_core::parse_command::ParsedCommand::Unknown {
cmd: "echo done".into(),
}],
parsed_cmd: vec![
codex_core::parse_command::ParsedCommand::Unknown {
cmd: "echo done".into(),
}
.into(),
],
}),
});
@@ -246,9 +249,12 @@ fn exec_history_cell_shows_working_then_failed() {
call_id: "call-2".into(),
command: vec!["bash".into(), "-lc".into(), "false".into()],
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
parsed_cmd: vec![codex_core::parse_command::ParsedCommand::Unknown {
cmd: "false".into(),
}],
parsed_cmd: vec![
codex_core::parse_command::ParsedCommand::Unknown {
cmd: "false".into(),
}
.into(),
],
}),
});

View File

@@ -9,7 +9,6 @@ use codex_ansi_escape::ansi_escape_line;
use codex_common::create_config_summary_entries;
use codex_common::elapsed::format_duration;
use codex_core::config::Config;
use codex_core::parse_command::ParsedCommand;
use codex_core::plan_tool::PlanItemArg;
use codex_core::plan_tool::StepStatus;
use codex_core::plan_tool::UpdatePlanArgs;
@@ -20,6 +19,7 @@ use codex_core::protocol::SessionConfiguredEvent;
use codex_core::protocol::TokenUsage;
use codex_login::get_auth_file;
use codex_login::try_read_auth_json;
use codex_protocol::parse_command::ParsedCommand;
use image::DynamicImage;
use image::ImageReader;
use mcp_types::EmbeddedResourceResource;