use std::collections::HashMap; use std::path::PathBuf; use crate::protocol::common::AuthMode; use codex_protocol::ConversationId; use codex_protocol::account::PlanType; use codex_protocol::approvals::SandboxCommandAssessment as CoreSandboxCommandAssessment; use codex_protocol::config_types::ReasoningEffort; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent; use codex_protocol::items::TurnItem as CoreTurnItem; use codex_protocol::models::ResponseItem; use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand; use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot; use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot; use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow; use codex_protocol::user_input::UserInput as CoreUserInput; use mcp_types::ContentBlock as McpContentBlock; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use serde_json::Value as JsonValue; use ts_rs::TS; // Macro to declare a camelCased API v2 enum mirroring a core enum which // tends to use either snake_case or kebab-case. macro_rules! v2_enum_from_core { ( pub enum $Name:ident from $Src:path { $( $Variant:ident ),+ $(,)? } ) => { #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum $Name { $( $Variant ),+ } impl $Name { pub fn to_core(self) -> $Src { match self { $( $Name::$Variant => <$Src>::$Variant ),+ } } } impl From<$Src> for $Name { fn from(value: $Src) -> Self { match value { $( <$Src>::$Variant => $Name::$Variant ),+ } } } }; } v2_enum_from_core!( pub enum AskForApproval from codex_protocol::protocol::AskForApproval { UnlessTrusted, OnFailure, OnRequest, Never } ); v2_enum_from_core!( pub enum SandboxMode from codex_protocol::config_types::SandboxMode { ReadOnly, WorkspaceWrite, DangerFullAccess } ); v2_enum_from_core!( pub enum CommandRiskLevel from codex_protocol::approvals::SandboxRiskLevel { Low, Medium, High } ); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum ApprovalDecision { Accept, Decline, Cancel, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum SandboxPolicy { DangerFullAccess, ReadOnly, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] WorkspaceWrite { #[serde(default)] writable_roots: Vec, #[serde(default)] network_access: bool, #[serde(default)] exclude_tmpdir_env_var: bool, #[serde(default)] exclude_slash_tmp: bool, }, } impl SandboxPolicy { pub fn to_core(&self) -> codex_protocol::protocol::SandboxPolicy { match self { SandboxPolicy::DangerFullAccess => { codex_protocol::protocol::SandboxPolicy::DangerFullAccess } SandboxPolicy::ReadOnly => codex_protocol::protocol::SandboxPolicy::ReadOnly, SandboxPolicy::WorkspaceWrite { writable_roots, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, } => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { writable_roots: writable_roots.clone(), network_access: *network_access, exclude_tmpdir_env_var: *exclude_tmpdir_env_var, exclude_slash_tmp: *exclude_slash_tmp, }, } } } impl From for SandboxPolicy { fn from(value: codex_protocol::protocol::SandboxPolicy) -> Self { match value { codex_protocol::protocol::SandboxPolicy::DangerFullAccess => { SandboxPolicy::DangerFullAccess } codex_protocol::protocol::SandboxPolicy::ReadOnly => SandboxPolicy::ReadOnly, codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { writable_roots, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, } => SandboxPolicy::WorkspaceWrite { writable_roots, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, }, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SandboxCommandAssessment { pub description: String, pub risk_level: CommandRiskLevel, } impl SandboxCommandAssessment { pub fn into_core(self) -> CoreSandboxCommandAssessment { CoreSandboxCommandAssessment { description: self.description, risk_level: self.risk_level.to_core(), } } } impl From for SandboxCommandAssessment { fn from(value: CoreSandboxCommandAssessment) -> Self { Self { description: value.description, risk_level: CommandRiskLevel::from(value.risk_level), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum CommandAction { Read { command: String, name: String, path: PathBuf, }, ListFiles { command: String, path: Option, }, Search { command: String, query: Option, path: Option, }, Unknown { command: String, }, } impl CommandAction { pub fn into_core(self) -> CoreParsedCommand { match self { CommandAction::Read { command: cmd, name, path, } => CoreParsedCommand::Read { cmd, name, path }, CommandAction::ListFiles { command: cmd, path } => { CoreParsedCommand::ListFiles { cmd, path } } CommandAction::Search { command: cmd, query, path, } => CoreParsedCommand::Search { cmd, query, path }, CommandAction::Unknown { command: cmd } => CoreParsedCommand::Unknown { cmd }, } } } impl From for CommandAction { fn from(value: CoreParsedCommand) -> Self { match value { CoreParsedCommand::Read { cmd, name, path } => CommandAction::Read { command: cmd, name, path, }, CoreParsedCommand::ListFiles { cmd, path } => { CommandAction::ListFiles { command: cmd, path } } CoreParsedCommand::Search { cmd, query, path } => CommandAction::Search { command: cmd, query, path, }, CoreParsedCommand::Unknown { cmd } => CommandAction::Unknown { command: cmd }, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum Account { #[serde(rename = "apiKey", rename_all = "camelCase")] #[ts(rename = "apiKey", rename_all = "camelCase")] ApiKey {}, #[serde(rename = "chatgpt", rename_all = "camelCase")] #[ts(rename = "chatgpt", rename_all = "camelCase")] Chatgpt { email: String, plan_type: PlanType }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum LoginAccountParams { #[serde(rename = "apiKey", rename_all = "camelCase")] #[ts(rename = "apiKey", rename_all = "camelCase")] ApiKey { #[serde(rename = "apiKey")] #[ts(rename = "apiKey")] api_key: String, }, #[serde(rename = "chatgpt")] #[ts(rename = "chatgpt")] Chatgpt, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum LoginAccountResponse { #[serde(rename = "apiKey", rename_all = "camelCase")] #[ts(rename = "apiKey", rename_all = "camelCase")] ApiKey {}, #[serde(rename = "chatgpt", rename_all = "camelCase")] #[ts(rename = "chatgpt", rename_all = "camelCase")] Chatgpt { // Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types. // Convert to/from UUIDs at the application layer as needed. login_id: String, /// URL the client should open in a browser to initiate the OAuth flow. auth_url: String, }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CancelLoginAccountParams { pub login_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CancelLoginAccountResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct LogoutAccountResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GetAccountRateLimitsResponse { pub rate_limits: RateLimitSnapshot, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GetAccountParams { #[serde(default)] pub refresh_token: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GetAccountResponse { pub account: Option, pub requires_openai_auth: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ModelListParams { /// Opaque pagination cursor returned by a previous call. pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. pub limit: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct Model { pub id: String, pub model: String, pub display_name: String, pub description: String, pub supported_reasoning_efforts: Vec, pub default_reasoning_effort: ReasoningEffort, // Only one model should be marked as default. pub is_default: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ReasoningEffortOption { pub reasoning_effort: ReasoningEffort, pub description: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ModelListResponse { pub data: Vec, /// Opaque cursor to pass to the next call to continue after the last item. /// If None, there are no more items to return. pub next_cursor: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FeedbackUploadParams { pub classification: String, pub reason: Option, pub conversation_id: Option, pub include_logs: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FeedbackUploadResponse { pub thread_id: String, } // === Threads, Turns, and Items === // Thread APIs #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartParams { pub model: Option, pub model_provider: Option, pub cwd: Option, pub approval_policy: Option, pub sandbox: Option, pub config: Option>, pub base_instructions: Option, pub developer_instructions: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartResponse { pub thread: Thread, pub model: String, pub model_provider: String, pub cwd: PathBuf, pub approval_policy: AskForApproval, pub sandbox: SandboxPolicy, pub reasoning_effort: Option, } #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// There are three ways to resume a thread: /// 1. By thread_id: load the thread from disk by thread_id and resume it. /// 2. By history: instantiate the thread from memory and resume it. /// 3. By path: load the thread from disk by path and resume it. /// /// The precedence is: history > path > thread_id. /// If using history or path, the thread_id param will be ignored. /// /// Prefer using thread_id whenever possible. pub struct ThreadResumeParams { pub thread_id: String, /// [UNSTABLE] FOR CODEX CLOUD - DO NOT USE. /// If specified, the thread will be resumed with the provided history /// instead of loaded from disk. pub history: Option>, /// [UNSTABLE] Specify the rollout path to resume from. /// If specified, the thread_id param will be ignored. pub path: Option, /// Configuration overrides for the resumed thread, if any. pub model: Option, pub model_provider: Option, pub cwd: Option, pub approval_policy: Option, pub sandbox: Option, pub config: Option>, pub base_instructions: Option, pub developer_instructions: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadResumeResponse { pub thread: Thread, pub model: String, pub model_provider: String, pub cwd: PathBuf, pub approval_policy: AskForApproval, pub sandbox: SandboxPolicy, pub reasoning_effort: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadArchiveParams { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadArchiveResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadListParams { /// Opaque pagination cursor returned by a previous call. pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. pub limit: Option, /// Optional provider filter; when set, only sessions recorded under these /// providers are returned. When present but empty, includes all providers. pub model_providers: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadListResponse { pub data: Vec, /// Opaque cursor to pass to the next call to continue after the last item. /// if None, there are no more items to return. pub next_cursor: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadCompactParams { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadCompactResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct Thread { pub id: String, /// Usually the first user message in the thread, if available. pub preview: String, pub model_provider: String, /// Unix timestamp (in seconds) when the thread was created. pub created_at: i64, /// [UNSTABLE] Path to the thread on disk. pub path: PathBuf, /// Only populated on a `thread/resume` response. /// For all other responses and notifications returning a Thread, /// the turns field will be an empty list. pub turns: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AccountUpdatedNotification { pub auth_mode: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct Turn { pub id: String, /// Only populated on a `thread/resume` response. /// For all other responses and notifications returning a Turn, /// the items field will be an empty list. pub items: Vec, #[serde(flatten)] pub status: TurnStatus, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnError { pub message: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "status", rename_all = "camelCase")] #[ts(tag = "status", export_to = "v2/")] pub enum TurnStatus { Completed, Interrupted, Failed { error: TurnError }, InProgress, } // Turn APIs #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnStartParams { pub thread_id: String, pub input: Vec, /// Override the working directory for this turn and subsequent turns. pub cwd: Option, /// Override the approval policy for this turn and subsequent turns. pub approval_policy: Option, /// Override the sandbox policy for this turn and subsequent turns. pub sandbox_policy: Option, /// Override the model for this turn and subsequent turns. pub model: Option, /// Override the reasoning effort for this turn and subsequent turns. pub effort: Option, /// Override the reasoning summary for this turn and subsequent turns. pub summary: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ReviewStartParams { pub thread_id: String, pub target: ReviewTarget, /// When true, also append the final review message to the original thread. #[serde(default)] pub append_to_original_thread: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type", export_to = "v2/")] 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, }, /// Arbitrary instructions, equivalent to the old free-form prompt. #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Custom { instructions: String }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnStartResponse { pub turn: Turn, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnInterruptParams { pub thread_id: String, pub turn_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnInterruptResponse {} // User input types #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum UserInput { Text { text: String }, Image { url: String }, LocalImage { path: PathBuf }, } impl UserInput { pub fn into_core(self) -> CoreUserInput { match self { UserInput::Text { text } => CoreUserInput::Text { text }, UserInput::Image { url } => CoreUserInput::Image { image_url: url }, UserInput::LocalImage { path } => CoreUserInput::LocalImage { path }, } } } impl From for UserInput { fn from(value: CoreUserInput) -> Self { match value { CoreUserInput::Text { text } => UserInput::Text { text }, CoreUserInput::Image { image_url } => UserInput::Image { url: image_url }, CoreUserInput::LocalImage { path } => UserInput::LocalImage { path }, _ => unreachable!("unsupported user input variant"), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum ThreadItem { #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] UserMessage { id: String, content: Vec }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] AgentMessage { id: String, text: String }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Reasoning { id: String, #[serde(default)] summary: Vec, #[serde(default)] content: Vec, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] CommandExecution { id: String, /// The command to be executed. command: String, /// The command's working directory. cwd: PathBuf, status: CommandExecutionStatus, /// A best-effort parsing of the command to understand the action(s) it will perform. /// This returns a list of CommandAction objects because a single shell command may /// be composed of many commands piped together. command_actions: Vec, /// The command's output, aggregated from stdout and stderr. aggregated_output: Option, /// The command's exit code. exit_code: Option, /// The duration of the command execution in milliseconds. duration_ms: Option, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] FileChange { id: String, changes: Vec, status: PatchApplyStatus, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] McpToolCall { id: String, server: String, tool: String, status: McpToolCallStatus, arguments: JsonValue, result: Option, error: Option, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] WebSearch { id: String, query: String }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] TodoList { id: String, items: Vec }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] ImageView { id: String, path: String }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] CodeReview { id: String, review: String }, } impl From for ThreadItem { fn from(value: CoreTurnItem) -> Self { match value { CoreTurnItem::UserMessage(user) => ThreadItem::UserMessage { id: user.id, content: user.content.into_iter().map(UserInput::from).collect(), }, CoreTurnItem::AgentMessage(agent) => { let text = agent .content .into_iter() .map(|entry| match entry { CoreAgentMessageContent::Text { text } => text, }) .collect::(); ThreadItem::AgentMessage { id: agent.id, text } } CoreTurnItem::Reasoning(reasoning) => ThreadItem::Reasoning { id: reasoning.id, summary: reasoning.summary_text, content: reasoning.raw_content, }, CoreTurnItem::WebSearch(search) => ThreadItem::WebSearch { id: search.id, query: search.query, }, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CommandExecutionStatus { InProgress, Completed, Failed, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FileUpdateChange { pub path: String, pub kind: PatchChangeKind, pub diff: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum PatchChangeKind { Add, Delete, Update, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum PatchApplyStatus { Completed, Failed, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum McpToolCallStatus { InProgress, Completed, Failed, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpToolCallResult { pub content: Vec, pub structured_content: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpToolCallError { pub message: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TodoItem { pub id: String, pub text: String, pub completed: bool, } // === Server Notifications === // Thread/Turn lifecycle notifications and item progress events #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartedNotification { pub thread: Thread, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnStartedNotification { pub turn: Turn, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct Usage { pub input_tokens: i32, pub cached_input_tokens: i32, pub output_tokens: i32, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnCompletedNotification { pub turn: Turn, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ItemStartedNotification { pub item: ThreadItem, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ItemCompletedNotification { pub item: ThreadItem, } // Item-specific progress notifications #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AgentMessageDeltaNotification { pub item_id: String, pub delta: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ReasoningSummaryTextDeltaNotification { pub item_id: String, pub delta: String, pub summary_index: i64, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ReasoningSummaryPartAddedNotification { pub item_id: String, pub summary_index: i64, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ReasoningTextDeltaNotification { pub item_id: String, pub delta: String, pub content_index: i64, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecutionOutputDeltaNotification { pub item_id: String, pub delta: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpToolCallProgressNotification { pub item_id: String, pub message: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct WindowsWorldWritableWarningNotification { pub sample_paths: Vec, pub extra_count: usize, pub failed_scan: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecutionRequestApprovalParams { pub thread_id: String, pub turn_id: String, pub item_id: String, /// Optional explanatory reason (e.g. request for network access). pub reason: Option, /// Optional model-provided risk assessment describing the blocked command. pub risk: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecutionRequestAcceptSettings { /// If true, automatically approve this command for the duration of the session. #[serde(default)] pub for_session: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecutionRequestApprovalResponse { pub decision: ApprovalDecision, /// Optional approval settings for when the decision is `accept`. /// Ignored if the decision is `decline` or `cancel`. #[serde(default)] pub accept_settings: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AccountRateLimitsUpdatedNotification { pub rate_limits: RateLimitSnapshot, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct RateLimitSnapshot { pub primary: Option, pub secondary: Option, pub credits: Option, } impl From for RateLimitSnapshot { fn from(value: CoreRateLimitSnapshot) -> Self { Self { primary: value.primary.map(RateLimitWindow::from), secondary: value.secondary.map(RateLimitWindow::from), credits: value.credits.map(CreditsSnapshot::from), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct RateLimitWindow { pub used_percent: i32, pub window_duration_mins: Option, pub resets_at: Option, } impl From for RateLimitWindow { fn from(value: CoreRateLimitWindow) -> Self { Self { used_percent: value.used_percent.round() as i32, window_duration_mins: value.window_minutes, resets_at: value.resets_at, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CreditsSnapshot { pub has_credits: bool, pub unlimited: bool, pub balance: Option, } impl From for CreditsSnapshot { fn from(value: CoreCreditsSnapshot) -> Self { Self { has_credits: value.has_credits, unlimited: value.unlimited, balance: value.balance, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AccountLoginCompletedNotification { // Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types. // Convert to/from UUIDs at the application layer as needed. pub login_id: Option, pub success: bool, pub error: Option, } #[cfg(test)] mod tests { use super::*; use codex_protocol::items::AgentMessageContent; use codex_protocol::items::AgentMessageItem; use codex_protocol::items::ReasoningItem; use codex_protocol::items::TurnItem; use codex_protocol::items::UserMessageItem; use codex_protocol::items::WebSearchItem; use codex_protocol::user_input::UserInput as CoreUserInput; use pretty_assertions::assert_eq; use std::path::PathBuf; #[test] fn core_turn_item_into_thread_item_converts_supported_variants() { let user_item = TurnItem::UserMessage(UserMessageItem { id: "user-1".to_string(), content: vec![ CoreUserInput::Text { text: "hello".to_string(), }, CoreUserInput::Image { image_url: "https://example.com/image.png".to_string(), }, CoreUserInput::LocalImage { path: PathBuf::from("local/image.png"), }, ], }); assert_eq!( ThreadItem::from(user_item), ThreadItem::UserMessage { id: "user-1".to_string(), content: vec![ UserInput::Text { text: "hello".to_string(), }, UserInput::Image { url: "https://example.com/image.png".to_string(), }, UserInput::LocalImage { path: PathBuf::from("local/image.png"), }, ], } ); let agent_item = TurnItem::AgentMessage(AgentMessageItem { id: "agent-1".to_string(), content: vec![ AgentMessageContent::Text { text: "Hello ".to_string(), }, AgentMessageContent::Text { text: "world".to_string(), }, ], }); assert_eq!( ThreadItem::from(agent_item), ThreadItem::AgentMessage { id: "agent-1".to_string(), text: "Hello world".to_string(), } ); let reasoning_item = TurnItem::Reasoning(ReasoningItem { id: "reasoning-1".to_string(), summary_text: vec!["line one".to_string(), "line two".to_string()], raw_content: vec![], }); assert_eq!( ThreadItem::from(reasoning_item), ThreadItem::Reasoning { id: "reasoning-1".to_string(), summary: vec!["line one".to_string(), "line two".to_string()], content: vec![], } ); let search_item = TurnItem::WebSearch(WebSearchItem { id: "search-1".to_string(), query: "docs".to_string(), }); assert_eq!( ThreadItem::from(search_item), ThreadItem::WebSearch { id: "search-1".to_string(), query: "docs".to_string(), } ); } }