use std::collections::BTreeMap; use std::collections::HashMap; use std::path::PathBuf; use crate::RequestId; use crate::protocol::common::AuthMode; use codex_experimental_api_macros::ExperimentalApi; use codex_protocol::account::PlanType; use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest; use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment; use codex_protocol::approvals::NetworkApprovalContext as CoreNetworkApprovalContext; use codex_protocol::approvals::NetworkApprovalProtocol as CoreNetworkApprovalProtocol; use codex_protocol::approvals::NetworkPolicyAmendment as CoreNetworkPolicyAmendment; use codex_protocol::approvals::NetworkPolicyRuleAction as CoreNetworkPolicyRuleAction; use codex_protocol::config_types::ApprovalsReviewer as CoreApprovalsReviewer; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::CollaborationModeMask as CoreCollaborationModeMask; use codex_protocol::config_types::ForcedLoginMethod; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Personality; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode as CoreSandboxMode; use codex_protocol::config_types::ServiceTier; use codex_protocol::config_types::Verbosity; use codex_protocol::config_types::WebSearchMode; use codex_protocol::config_types::WebSearchToolConfig; use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent; use codex_protocol::items::TurnItem as CoreTurnItem; use codex_protocol::mcp::Resource as McpResource; use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate; use codex_protocol::mcp::Tool as McpTool; use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation; use codex_protocol::memory_citation::MemoryCitationEntry as CoreMemoryCitationEntry; use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions; use codex_protocol::models::MessagePhase; use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions; use codex_protocol::models::PermissionProfile as CorePermissionProfile; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::InputModality; use codex_protocol::openai_models::ModelAvailabilityNux as CoreModelAvailabilityNux; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::default_input_modalities; use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand; use codex_protocol::plan_tool::PlanItemArg as CorePlanItemArg; use codex_protocol::plan_tool::StepStatus as CorePlanStepStatus; use codex_protocol::protocol::AgentStatus as CoreAgentStatus; use codex_protocol::protocol::AskForApproval as CoreAskForApproval; use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo; use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot; use codex_protocol::protocol::ExecCommandSource as CoreExecCommandSource; use codex_protocol::protocol::ExecCommandStatus as CoreExecCommandStatus; use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig; use codex_protocol::protocol::GuardianRiskLevel as CoreGuardianRiskLevel; use codex_protocol::protocol::HookEventName as CoreHookEventName; use codex_protocol::protocol::HookExecutionMode as CoreHookExecutionMode; use codex_protocol::protocol::HookHandlerType as CoreHookHandlerType; use codex_protocol::protocol::HookOutputEntry as CoreHookOutputEntry; use codex_protocol::protocol::HookOutputEntryKind as CoreHookOutputEntryKind; use codex_protocol::protocol::HookRunStatus as CoreHookRunStatus; use codex_protocol::protocol::HookRunSummary as CoreHookRunSummary; use codex_protocol::protocol::HookScope as CoreHookScope; use codex_protocol::protocol::ModelRerouteReason as CoreModelRerouteReason; use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess; use codex_protocol::protocol::NonSteerableTurnKind as CoreNonSteerableTurnKind; use codex_protocol::protocol::PatchApplyStatus as CorePatchApplyStatus; use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot; use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow; use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess; use codex_protocol::protocol::RealtimeAudioFrame as CoreRealtimeAudioFrame; use codex_protocol::protocol::RealtimeConversationVersion; use codex_protocol::protocol::ReviewDecision as CoreReviewDecision; use codex_protocol::protocol::SessionSource as CoreSessionSource; use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies; use codex_protocol::protocol::SkillErrorInfo as CoreSkillErrorInfo; use codex_protocol::protocol::SkillInterface as CoreSkillInterface; use codex_protocol::protocol::SkillMetadata as CoreSkillMetadata; use codex_protocol::protocol::SkillScope as CoreSkillScope; use codex_protocol::protocol::SkillToolDependency as CoreSkillToolDependency; use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource; use codex_protocol::protocol::TokenUsage as CoreTokenUsage; use codex_protocol::protocol::TokenUsageInfo as CoreTokenUsageInfo; use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope; use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile; use codex_protocol::user_input::ByteRange as CoreByteRange; use codex_protocol::user_input::TextElement as CoreTextElement; use codex_protocol::user_input::UserInput as CoreUserInput; use codex_utils_absolute_path::AbsolutePathBuf; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use serde_json::Value as JsonValue; use serde_with::serde_as; use thiserror::Error; 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 { ( $(#[$enum_meta:meta])* pub enum $Name:ident from $Src:path { $( $(#[$variant_meta:meta])* $Variant:ident ),+ $(,)? } ) => { #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] $(#[$enum_meta])* #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum $Name { $( $(#[$variant_meta])* $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 ),+ } } } }; } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum NonSteerableTurnKind { Review, Compact, } /// This translation layer make sure that we expose codex error code in camel case. /// /// When an upstream HTTP status is available (for example, from the Responses API or a provider), /// it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CodexErrorInfo { ContextWindowExceeded, UsageLimitExceeded, ServerOverloaded, HttpConnectionFailed { #[serde(rename = "httpStatusCode")] #[ts(rename = "httpStatusCode")] http_status_code: Option, }, /// Failed to connect to the response SSE stream. ResponseStreamConnectionFailed { #[serde(rename = "httpStatusCode")] #[ts(rename = "httpStatusCode")] http_status_code: Option, }, InternalServerError, Unauthorized, BadRequest, ThreadRollbackFailed, SandboxError, /// The response SSE stream disconnected in the middle of a turn before completion. ResponseStreamDisconnected { #[serde(rename = "httpStatusCode")] #[ts(rename = "httpStatusCode")] http_status_code: Option, }, /// Reached the retry limit for responses. ResponseTooManyFailedAttempts { #[serde(rename = "httpStatusCode")] #[ts(rename = "httpStatusCode")] http_status_code: Option, }, /// Returned when `turn/start` or `turn/steer` is submitted while the current active turn /// cannot accept same-turn steering, for example `/review` or manual `/compact`. ActiveTurnNotSteerable { #[serde(rename = "turnKind")] #[ts(rename = "turnKind")] turn_kind: NonSteerableTurnKind, }, Other, } impl From for CodexErrorInfo { fn from(value: CoreCodexErrorInfo) -> Self { match value { CoreCodexErrorInfo::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded, CoreCodexErrorInfo::UsageLimitExceeded => CodexErrorInfo::UsageLimitExceeded, CoreCodexErrorInfo::ServerOverloaded => CodexErrorInfo::ServerOverloaded, CoreCodexErrorInfo::HttpConnectionFailed { http_status_code } => { CodexErrorInfo::HttpConnectionFailed { http_status_code } } CoreCodexErrorInfo::ResponseStreamConnectionFailed { http_status_code } => { CodexErrorInfo::ResponseStreamConnectionFailed { http_status_code } } CoreCodexErrorInfo::InternalServerError => CodexErrorInfo::InternalServerError, CoreCodexErrorInfo::Unauthorized => CodexErrorInfo::Unauthorized, CoreCodexErrorInfo::BadRequest => CodexErrorInfo::BadRequest, CoreCodexErrorInfo::ThreadRollbackFailed => CodexErrorInfo::ThreadRollbackFailed, CoreCodexErrorInfo::SandboxError => CodexErrorInfo::SandboxError, CoreCodexErrorInfo::ResponseStreamDisconnected { http_status_code } => { CodexErrorInfo::ResponseStreamDisconnected { http_status_code } } CoreCodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code } => { CodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code } } CoreCodexErrorInfo::ActiveTurnNotSteerable { turn_kind } => { CodexErrorInfo::ActiveTurnNotSteerable { turn_kind: turn_kind.into(), } } CoreCodexErrorInfo::Other => CodexErrorInfo::Other, } } } impl From for NonSteerableTurnKind { fn from(value: CoreNonSteerableTurnKind) -> Self { match value { CoreNonSteerableTurnKind::Review => Self::Review, CoreNonSteerableTurnKind::Compact => Self::Compact, } } } #[derive( Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, ExperimentalApi, )] #[serde(rename_all = "kebab-case")] #[ts(rename_all = "kebab-case", export_to = "v2/")] pub enum AskForApproval { #[serde(rename = "untrusted")] #[ts(rename = "untrusted")] UnlessTrusted, OnFailure, OnRequest, #[experimental("askForApproval.granular")] Granular { sandbox_approval: bool, rules: bool, #[serde(default)] skill_approval: bool, #[serde(default)] request_permissions: bool, mcp_elicitations: bool, }, Never, } impl AskForApproval { pub fn to_core(self) -> CoreAskForApproval { match self { AskForApproval::UnlessTrusted => CoreAskForApproval::UnlessTrusted, AskForApproval::OnFailure => CoreAskForApproval::OnFailure, AskForApproval::OnRequest => CoreAskForApproval::OnRequest, AskForApproval::Granular { sandbox_approval, rules, skill_approval, request_permissions, mcp_elicitations, } => CoreAskForApproval::Granular(CoreGranularApprovalConfig { sandbox_approval, rules, skill_approval, request_permissions, mcp_elicitations, }), AskForApproval::Never => CoreAskForApproval::Never, } } } impl From for AskForApproval { fn from(value: CoreAskForApproval) -> Self { match value { CoreAskForApproval::UnlessTrusted => AskForApproval::UnlessTrusted, CoreAskForApproval::OnFailure => AskForApproval::OnFailure, CoreAskForApproval::OnRequest => AskForApproval::OnRequest, CoreAskForApproval::Granular(granular_config) => AskForApproval::Granular { sandbox_approval: granular_config.sandbox_approval, rules: granular_config.rules, skill_approval: granular_config.skill_approval, request_permissions: granular_config.request_permissions, mcp_elicitations: granular_config.mcp_elicitations, }, CoreAskForApproval::Never => AskForApproval::Never, } } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(rename_all = "snake_case", export_to = "v2/")] /// Configures who approval requests are routed to for review. Examples /// include sandbox escapes, blocked network access, MCP approval prompts, and /// ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully /// prompted subagent to gather relevant context and apply a risk-based /// decision framework before approving or denying the request. pub enum ApprovalsReviewer { User, GuardianSubagent, } impl ApprovalsReviewer { pub fn to_core(self) -> CoreApprovalsReviewer { match self { ApprovalsReviewer::User => CoreApprovalsReviewer::User, ApprovalsReviewer::GuardianSubagent => CoreApprovalsReviewer::GuardianSubagent, } } } impl From for ApprovalsReviewer { fn from(value: CoreApprovalsReviewer) -> Self { match value { CoreApprovalsReviewer::User => ApprovalsReviewer::User, CoreApprovalsReviewer::GuardianSubagent => ApprovalsReviewer::GuardianSubagent, } } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "kebab-case")] #[ts(rename_all = "kebab-case", export_to = "v2/")] pub enum SandboxMode { ReadOnly, WorkspaceWrite, DangerFullAccess, } impl SandboxMode { pub fn to_core(self) -> CoreSandboxMode { match self { SandboxMode::ReadOnly => CoreSandboxMode::ReadOnly, SandboxMode::WorkspaceWrite => CoreSandboxMode::WorkspaceWrite, SandboxMode::DangerFullAccess => CoreSandboxMode::DangerFullAccess, } } } impl From for SandboxMode { fn from(value: CoreSandboxMode) -> Self { match value { CoreSandboxMode::ReadOnly => SandboxMode::ReadOnly, CoreSandboxMode::WorkspaceWrite => SandboxMode::WorkspaceWrite, CoreSandboxMode::DangerFullAccess => SandboxMode::DangerFullAccess, } } } v2_enum_from_core!( pub enum ReviewDelivery from codex_protocol::protocol::ReviewDelivery { Inline, Detached } ); v2_enum_from_core!( pub enum McpAuthStatus from codex_protocol::protocol::McpAuthStatus { Unsupported, NotLoggedIn, BearerToken, OAuth } ); v2_enum_from_core!( pub enum ModelRerouteReason from CoreModelRerouteReason { HighRiskCyberActivity } ); v2_enum_from_core!( pub enum HookEventName from CoreHookEventName { PreToolUse, PostToolUse, SessionStart, UserPromptSubmit, Stop } ); v2_enum_from_core!( pub enum HookHandlerType from CoreHookHandlerType { Command, Prompt, Agent } ); v2_enum_from_core!( pub enum HookExecutionMode from CoreHookExecutionMode { Sync, Async } ); v2_enum_from_core!( pub enum HookScope from CoreHookScope { Thread, Turn } ); v2_enum_from_core!( pub enum HookRunStatus from CoreHookRunStatus { Running, Completed, Failed, Blocked, Stopped } ); v2_enum_from_core!( pub enum HookOutputEntryKind from CoreHookOutputEntryKind { Warning, Stop, Feedback, Context, Error } ); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct HookOutputEntry { pub kind: HookOutputEntryKind, pub text: String, } impl From for HookOutputEntry { fn from(value: CoreHookOutputEntry) -> Self { Self { kind: value.kind.into(), text: value.text, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct HookRunSummary { pub id: String, pub event_name: HookEventName, pub handler_type: HookHandlerType, pub execution_mode: HookExecutionMode, pub scope: HookScope, pub source_path: PathBuf, pub display_order: i64, pub status: HookRunStatus, pub status_message: Option, pub started_at: i64, pub completed_at: Option, pub duration_ms: Option, pub entries: Vec, } impl From for HookRunSummary { fn from(value: CoreHookRunSummary) -> Self { Self { id: value.id, event_name: value.event_name.into(), handler_type: value.handler_type.into(), execution_mode: value.execution_mode.into(), scope: value.scope.into(), source_path: value.source_path, display_order: value.display_order, status: value.status.into(), status_message: value.status_message, started_at: value.started_at, completed_at: value.completed_at, duration_ms: value.duration_ms, entries: value.entries.into_iter().map(Into::into).collect(), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum ConfigLayerSource { /// Managed preferences layer delivered by MDM (macOS only). #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Mdm { domain: String, key: String, }, /// Managed config layer from a file (usually `managed_config.toml`). #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] System { /// This is the path to the system config.toml file, though it is not /// guaranteed to exist. file: AbsolutePathBuf, }, /// User config layer from $CODEX_HOME/config.toml. This layer is special /// in that it is expected to be: /// - writable by the user /// - generally outside the workspace directory #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] User { /// This is the path to the user's config.toml file, though it is not /// guaranteed to exist. file: AbsolutePathBuf, }, /// Path to a .codex/ folder within a project. There could be multiple of /// these between `cwd` and the project/repo root. #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Project { dot_codex_folder: AbsolutePathBuf, }, /// Session-layer overrides supplied via `-c`/`--config`. SessionFlags, /// `managed_config.toml` was designed to be a config that was loaded /// as the last layer on top of everything else. This scheme did not quite /// work out as intended, but we keep this variant as a "best effort" while /// we phase out `managed_config.toml` in favor of `requirements.toml`. #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] LegacyManagedConfigTomlFromFile { file: AbsolutePathBuf, }, LegacyManagedConfigTomlFromMdm, } impl ConfigLayerSource { /// A settings from a layer with a higher precedence will override a setting /// from a layer with a lower precedence. pub fn precedence(&self) -> i16 { match self { ConfigLayerSource::Mdm { .. } => 0, ConfigLayerSource::System { .. } => 10, ConfigLayerSource::User { .. } => 20, ConfigLayerSource::Project { .. } => 25, ConfigLayerSource::SessionFlags => 30, ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => 40, ConfigLayerSource::LegacyManagedConfigTomlFromMdm => 50, } } } /// Compares [ConfigLayerSource] by precedence, so `A < B` means settings from /// layer `A` will be overridden by settings from layer `B`. impl PartialOrd for ConfigLayerSource { fn partial_cmp(&self, other: &Self) -> Option { Some(self.precedence().cmp(&other.precedence())) } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct SandboxWorkspaceWrite { #[serde(default)] pub writable_roots: Vec, #[serde(default)] pub network_access: bool, #[serde(default)] pub exclude_tmpdir_env_var: bool, #[serde(default)] pub exclude_slash_tmp: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct ToolsV2 { pub web_search: Option, pub view_image: Option, } #[derive(Serialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct DynamicToolSpec { pub name: String, pub description: String, pub input_schema: JsonValue, #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub defer_loading: bool, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct DynamicToolSpecDe { name: String, description: String, input_schema: JsonValue, defer_loading: Option, expose_to_context: Option, } impl<'de> Deserialize<'de> for DynamicToolSpec { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let DynamicToolSpecDe { name, description, input_schema, defer_loading, expose_to_context, } = DynamicToolSpecDe::deserialize(deserializer)?; Ok(Self { name, description, input_schema, defer_loading: defer_loading .unwrap_or_else(|| expose_to_context.map(|visible| !visible).unwrap_or(false)), }) } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct ProfileV2 { pub model: Option, pub model_provider: Option, #[experimental(nested)] pub approval_policy: Option, /// [UNSTABLE] Optional profile-level override for where approval requests /// are routed for review. If omitted, the enclosing config default is /// used. #[experimental("config/read.approvalsReviewer")] pub approvals_reviewer: Option, pub service_tier: Option, pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, pub web_search: Option, pub tools: Option, pub chatgpt_base_url: Option, #[serde(default, flatten)] pub additional: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct AnalyticsConfig { pub enabled: Option, #[serde(default, flatten)] pub additional: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub enum AppToolApproval { Auto, Prompt, Approve, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct AppsDefaultConfig { #[serde(default = "default_enabled")] pub enabled: bool, #[serde(default = "default_enabled")] pub destructive_enabled: bool, #[serde(default = "default_enabled")] pub open_world_enabled: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct AppToolConfig { pub enabled: Option, pub approval_mode: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct AppToolsConfig { #[serde(default, flatten)] pub tools: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct AppConfig { #[serde(default = "default_enabled")] pub enabled: bool, pub destructive_enabled: Option, pub open_world_enabled: Option, pub default_tools_approval_mode: Option, pub default_tools_enabled: Option, pub tools: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct AppsConfig { #[serde(default, rename = "_default")] pub default: Option, #[serde(default, flatten)] pub apps: HashMap, } const fn default_enabled() -> bool { true } const fn default_include_platform_defaults() -> bool { true } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct Config { pub model: Option, pub review_model: Option, pub model_context_window: Option, pub model_auto_compact_token_limit: Option, pub model_provider: Option, #[experimental(nested)] pub approval_policy: Option, /// [UNSTABLE] Optional default for where approval requests are routed for /// review. #[experimental("config/read.approvalsReviewer")] pub approvals_reviewer: Option, pub sandbox_mode: Option, pub sandbox_workspace_write: Option, pub forced_chatgpt_workspace_id: Option, pub forced_login_method: Option, pub web_search: Option, pub tools: Option, pub profile: Option, #[experimental(nested)] #[serde(default)] pub profiles: HashMap, pub instructions: Option, pub developer_instructions: Option, pub compact_prompt: Option, pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, pub service_tier: Option, pub analytics: Option, #[experimental("config/read.apps")] #[serde(default)] pub apps: Option, #[serde(default, flatten)] pub additional: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigLayerMetadata { pub name: ConfigLayerSource, pub version: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigLayer { pub name: ConfigLayerSource, pub version: String, pub config: JsonValue, #[serde(skip_serializing_if = "Option::is_none")] pub disabled_reason: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum MergeStrategy { Replace, Upsert, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum WriteStatus { Ok, OkOverridden, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct OverriddenMetadata { pub message: String, pub overriding_layer: ConfigLayerMetadata, pub effective_value: JsonValue, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigWriteResponse { pub status: WriteStatus, pub version: String, /// Canonical path to the config file that was written. pub file_path: AbsolutePathBuf, pub overridden_metadata: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum ConfigWriteErrorCode { ConfigLayerReadonly, ConfigVersionConflict, ConfigValidationError, ConfigPathNotFound, ConfigSchemaUnknownKey, UserLayerNotFound, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigReadParams { #[serde(default)] pub include_layers: bool, /// Optional working directory to resolve project config layers. If specified, /// return the effective config as seen from that directory (i.e., including any /// project layers between `cwd` and the project/repo root). #[ts(optional = nullable)] pub cwd: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigReadResponse { #[experimental(nested)] pub config: Config, pub origins: HashMap, #[serde(skip_serializing_if = "Option::is_none")] pub layers: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigRequirements { #[experimental(nested)] pub allowed_approval_policies: Option>, pub allowed_sandbox_modes: Option>, pub allowed_web_search_modes: Option>, pub feature_requirements: Option>, pub enforce_residency: Option, #[experimental("configRequirements/read.network")] pub network: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct NetworkRequirements { pub enabled: Option, pub http_port: Option, pub socks_port: Option, pub allow_upstream_proxy: Option, pub dangerously_allow_non_loopback_proxy: Option, pub dangerously_allow_all_unix_sockets: Option, /// Canonical network permission map for `experimental_network`. pub domains: Option>, /// When true, only managed allowlist entries are respected while managed /// network enforcement is active. pub managed_allowed_domains_only: Option, /// Legacy compatibility view derived from `domains`. pub allowed_domains: Option>, /// Legacy compatibility view derived from `domains`. pub denied_domains: Option>, /// Canonical unix socket permission map for `experimental_network`. pub unix_sockets: Option>, /// Legacy compatibility view derived from `unix_sockets`. pub allow_unix_sockets: Option>, pub allow_local_binding: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum NetworkDomainPermission { Allow, Deny, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum NetworkUnixSocketPermission { Allow, None, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum ResidencyRequirement { Us, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigRequirementsReadResponse { /// Null if no requirements are configured (e.g. no requirements.toml/MDM entries). #[experimental(nested)] pub requirements: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, TS)] #[ts(export_to = "v2/")] pub enum ExternalAgentConfigMigrationItemType { #[serde(rename = "AGENTS_MD")] #[ts(rename = "AGENTS_MD")] AgentsMd, #[serde(rename = "CONFIG")] #[ts(rename = "CONFIG")] Config, #[serde(rename = "SKILLS")] #[ts(rename = "SKILLS")] Skills, #[serde(rename = "MCP_SERVER_CONFIG")] #[ts(rename = "MCP_SERVER_CONFIG")] McpServerConfig, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExternalAgentConfigMigrationItem { pub item_type: ExternalAgentConfigMigrationItemType, pub description: String, /// Null or empty means home-scoped migration; non-empty means repo-scoped migration. pub cwd: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExternalAgentConfigDetectResponse { pub items: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExternalAgentConfigDetectParams { /// If true, include detection under the user's home (~/.claude, ~/.codex, etc.). #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub include_home: bool, /// Zero or more working directories to include for repo-scoped detection. #[ts(optional = nullable)] pub cwds: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExternalAgentConfigImportParams { pub migration_items: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExternalAgentConfigImportResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigValueWriteParams { pub key_path: String, pub value: JsonValue, pub merge_strategy: MergeStrategy, /// Path to the config file to write; defaults to the user's `config.toml` when omitted. #[ts(optional = nullable)] pub file_path: Option, #[ts(optional = nullable)] pub expected_version: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigBatchWriteParams { pub edits: Vec, /// Path to the config file to write; defaults to the user's `config.toml` when omitted. #[ts(optional = nullable)] pub file_path: Option, #[ts(optional = nullable)] pub expected_version: Option, /// When true, hot-reload the updated user config into all loaded threads after writing. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub reload_user_config: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigEdit { pub key_path: String, pub value: JsonValue, pub merge_strategy: MergeStrategy, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CommandExecutionApprovalDecision { /// User approved the command. Accept, /// User approved the command and future prompts in the same session-scoped /// approval cache should run without prompting. AcceptForSession, /// User approved the command, and wants to apply the proposed execpolicy amendment so future /// matching commands can run without prompting. AcceptWithExecpolicyAmendment { execpolicy_amendment: ExecPolicyAmendment, }, /// User chose a persistent network policy rule (allow/deny) for this host. ApplyNetworkPolicyAmendment { network_policy_amendment: NetworkPolicyAmendment, }, /// User denied the command. The agent will continue the turn. Decline, /// User denied the command. The turn will also be immediately interrupted. Cancel, } impl From for CommandExecutionApprovalDecision { fn from(value: CoreReviewDecision) -> Self { match value { CoreReviewDecision::Approved => Self::Accept, CoreReviewDecision::ApprovedExecpolicyAmendment { proposed_execpolicy_amendment, } => Self::AcceptWithExecpolicyAmendment { execpolicy_amendment: proposed_execpolicy_amendment.into(), }, CoreReviewDecision::ApprovedForSession => Self::AcceptForSession, CoreReviewDecision::NetworkPolicyAmendment { network_policy_amendment, } => Self::ApplyNetworkPolicyAmendment { network_policy_amendment: network_policy_amendment.into(), }, CoreReviewDecision::Abort => Self::Cancel, CoreReviewDecision::Denied => Self::Decline, } } } v2_enum_from_core! { pub enum NetworkApprovalProtocol from CoreNetworkApprovalProtocol { Http, Https, Socks5Tcp, Socks5Udp, } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct NetworkApprovalContext { pub host: String, pub protocol: NetworkApprovalProtocol, } impl From for NetworkApprovalContext { fn from(value: CoreNetworkApprovalContext) -> Self { Self { host: value.host, protocol: value.protocol.into(), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AdditionalFileSystemPermissions { pub read: Option>, pub write: Option>, } impl From for AdditionalFileSystemPermissions { fn from(value: CoreFileSystemPermissions) -> Self { Self { read: value.read, write: value.write, } } } impl From for CoreFileSystemPermissions { fn from(value: AdditionalFileSystemPermissions) -> Self { Self { read: value.read, write: value.write, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AdditionalNetworkPermissions { pub enabled: Option, } impl From for AdditionalNetworkPermissions { fn from(value: CoreNetworkPermissions) -> Self { Self { enabled: value.enabled, } } } impl From for CoreNetworkPermissions { fn from(value: AdditionalNetworkPermissions) -> Self { Self { enabled: value.enabled, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct RequestPermissionProfile { pub network: Option, pub file_system: Option, } impl From for RequestPermissionProfile { fn from(value: CoreRequestPermissionProfile) -> Self { Self { network: value.network.map(AdditionalNetworkPermissions::from), file_system: value.file_system.map(AdditionalFileSystemPermissions::from), } } } impl From for CoreRequestPermissionProfile { fn from(value: RequestPermissionProfile) -> Self { Self { network: value.network.map(CoreNetworkPermissions::from), file_system: value.file_system.map(CoreFileSystemPermissions::from), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AdditionalPermissionProfile { pub network: Option, pub file_system: Option, } impl From for AdditionalPermissionProfile { fn from(value: CorePermissionProfile) -> Self { Self { network: value.network.map(AdditionalNetworkPermissions::from), file_system: value.file_system.map(AdditionalFileSystemPermissions::from), } } } impl From for CorePermissionProfile { fn from(value: AdditionalPermissionProfile) -> Self { Self { network: value.network.map(CoreNetworkPermissions::from), file_system: value.file_system.map(CoreFileSystemPermissions::from), } } } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GrantedPermissionProfile { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub network: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub file_system: Option, } impl From for CorePermissionProfile { fn from(value: GrantedPermissionProfile) -> Self { Self { network: value.network.map(CoreNetworkPermissions::from), file_system: value.file_system.map(CoreFileSystemPermissions::from), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum FileChangeApprovalDecision { /// User approved the file changes. Accept, /// User approved the file changes and future changes to the same files should run without prompting. AcceptForSession, /// User denied the file changes. The agent will continue the turn. Decline, /// User denied the file changes. The turn will also be immediately interrupted. Cancel, } #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum NetworkAccess { #[default] Restricted, Enabled, } #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum ReadOnlyAccess { #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Restricted { #[serde(default = "default_include_platform_defaults")] include_platform_defaults: bool, #[serde(default)] readable_roots: Vec, }, #[default] FullAccess, } impl ReadOnlyAccess { pub fn to_core(&self) -> CoreReadOnlyAccess { match self { ReadOnlyAccess::Restricted { include_platform_defaults, readable_roots, } => CoreReadOnlyAccess::Restricted { include_platform_defaults: *include_platform_defaults, readable_roots: readable_roots.clone(), }, ReadOnlyAccess::FullAccess => CoreReadOnlyAccess::FullAccess, } } } impl From for ReadOnlyAccess { fn from(value: CoreReadOnlyAccess) -> Self { match value { CoreReadOnlyAccess::Restricted { include_platform_defaults, readable_roots, } => ReadOnlyAccess::Restricted { include_platform_defaults, readable_roots, }, CoreReadOnlyAccess::FullAccess => ReadOnlyAccess::FullAccess, } } } #[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, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] ReadOnly { #[serde(default)] access: ReadOnlyAccess, #[serde(default)] network_access: bool, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] ExternalSandbox { #[serde(default)] network_access: NetworkAccess, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] WorkspaceWrite { #[serde(default)] writable_roots: Vec, #[serde(default)] read_only_access: ReadOnlyAccess, #[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 { access, network_access, } => codex_protocol::protocol::SandboxPolicy::ReadOnly { access: access.to_core(), network_access: *network_access, }, SandboxPolicy::ExternalSandbox { network_access } => { codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access: match network_access { NetworkAccess::Restricted => CoreNetworkAccess::Restricted, NetworkAccess::Enabled => CoreNetworkAccess::Enabled, }, } } SandboxPolicy::WorkspaceWrite { writable_roots, read_only_access, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, } => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { writable_roots: writable_roots.clone(), read_only_access: read_only_access.to_core(), 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 { access, network_access, } => SandboxPolicy::ReadOnly { access: ReadOnlyAccess::from(access), network_access, }, codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access } => { SandboxPolicy::ExternalSandbox { network_access: match network_access { CoreNetworkAccess::Restricted => NetworkAccess::Restricted, CoreNetworkAccess::Enabled => NetworkAccess::Enabled, }, } } codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { writable_roots, read_only_access, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, } => SandboxPolicy::WorkspaceWrite { writable_roots, read_only_access: ReadOnlyAccess::from(read_only_access), network_access, exclude_tmpdir_env_var, exclude_slash_tmp, }, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(transparent)] #[ts(type = "Array", export_to = "v2/")] pub struct ExecPolicyAmendment { pub command: Vec, } impl ExecPolicyAmendment { pub fn into_core(self) -> CoreExecPolicyAmendment { CoreExecPolicyAmendment::new(self.command) } } impl From for ExecPolicyAmendment { fn from(value: CoreExecPolicyAmendment) -> Self { Self { command: value.command().to_vec(), } } } v2_enum_from_core!( pub enum NetworkPolicyRuleAction from CoreNetworkPolicyRuleAction { Allow, Deny } ); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct NetworkPolicyAmendment { pub host: String, pub action: NetworkPolicyRuleAction, } impl NetworkPolicyAmendment { pub fn into_core(self) -> CoreNetworkPolicyAmendment { CoreNetworkPolicyAmendment { host: self.host, action: self.action.to_core(), } } } impl From for NetworkPolicyAmendment { fn from(value: CoreNetworkPolicyAmendment) -> Self { Self { host: value.host, action: NetworkPolicyRuleAction::from(value.action), } } } #[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, }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase", export_to = "v2/")] #[derive(Default)] pub enum SessionSource { Cli, #[serde(rename = "vscode")] #[ts(rename = "vscode")] #[default] VsCode, Exec, AppServer, Custom(String), SubAgent(CoreSubAgentSource), #[serde(other)] Unknown, } impl From for SessionSource { fn from(value: CoreSessionSource) -> Self { match value { CoreSessionSource::Cli => SessionSource::Cli, CoreSessionSource::VSCode => SessionSource::VsCode, CoreSessionSource::Exec => SessionSource::Exec, CoreSessionSource::Mcp => SessionSource::AppServer, CoreSessionSource::Custom(source) => SessionSource::Custom(source), CoreSessionSource::SubAgent(sub) => SessionSource::SubAgent(sub), CoreSessionSource::Unknown => SessionSource::Unknown, } } } impl From for CoreSessionSource { fn from(value: SessionSource) -> Self { match value { SessionSource::Cli => CoreSessionSource::Cli, SessionSource::VsCode => CoreSessionSource::VSCode, SessionSource::Exec => CoreSessionSource::Exec, SessionSource::AppServer => CoreSessionSource::Mcp, SessionSource::Custom(source) => CoreSessionSource::Custom(source), SessionSource::SubAgent(sub) => CoreSessionSource::SubAgent(sub), SessionSource::Unknown => CoreSessionSource::Unknown, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GitInfo { pub sha: Option, pub branch: Option, pub origin_url: Option, } 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, ExperimentalApi)] #[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, #[serde(rename = "chatgptDeviceCode")] #[ts(rename = "chatgptDeviceCode")] ChatgptDeviceCode, /// [UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. /// The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have. #[experimental("account/login/start.chatgptAuthTokens")] #[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")] #[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")] ChatgptAuthTokens { /// Access token (JWT) supplied by the client. /// This token is used for backend API requests and email extraction. access_token: String, /// Workspace/account identifier supplied by the client. chatgpt_account_id: String, /// Optional plan type supplied by the client. /// /// When `null`, Codex attempts to derive the plan type from access-token /// claims. If unavailable, the plan defaults to `unknown`. #[ts(optional = nullable)] chatgpt_plan_type: Option, }, } #[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, }, #[serde(rename = "chatgptDeviceCode", rename_all = "camelCase")] #[ts(rename = "chatgptDeviceCode", rename_all = "camelCase")] ChatgptDeviceCode { // 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 complete device code authorization. verification_url: String, /// One-time code the user must enter after signing in. user_code: String, }, #[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")] #[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")] ChatgptAuthTokens {}, } #[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, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CancelLoginAccountStatus { Canceled, NotFound, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CancelLoginAccountResponse { pub status: CancelLoginAccountStatus, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct LogoutAccountResponse {} #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum ChatgptAuthTokensRefreshReason { /// Codex attempted a backend request and received `401 Unauthorized`. Unauthorized, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ChatgptAuthTokensRefreshParams { pub reason: ChatgptAuthTokensRefreshReason, /// Workspace/account identifier that Codex was previously using. /// /// Clients that manage multiple accounts/workspaces can use this as a hint /// to refresh the token for the correct workspace. /// /// This may be `null` when the prior auth state did not include a workspace /// identifier (`chatgpt_account_id`). #[ts(optional = nullable)] pub previous_account_id: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ChatgptAuthTokensRefreshResponse { pub access_token: String, pub chatgpt_account_id: String, pub chatgpt_plan_type: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GetAccountRateLimitsResponse { /// Backward-compatible single-bucket view; mirrors the historical payload. pub rate_limits: RateLimitSnapshot, /// Multi-bucket view keyed by metered `limit_id` (for example, `codex`). pub rate_limits_by_limit_id: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GetAccountParams { /// When `true`, requests a proactive token refresh before returning. /// /// In managed auth mode this triggers the normal refresh-token flow. In /// external auth mode this flag is ignored. Clients should refresh tokens /// themselves and call `account/login/start` with `chatgptAuthTokens`. #[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. #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. #[ts(optional = nullable)] pub limit: Option, /// When true, include models that are hidden from the default picker list. #[ts(optional = nullable)] pub include_hidden: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ModelAvailabilityNux { pub message: String, } impl From for ModelAvailabilityNux { fn from(value: CoreModelAvailabilityNux) -> Self { Self { message: value.message, } } } #[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 upgrade: Option, pub upgrade_info: Option, pub availability_nux: Option, pub display_name: String, pub description: String, pub hidden: bool, pub supported_reasoning_efforts: Vec, pub default_reasoning_effort: ReasoningEffort, #[serde(default = "default_input_modalities")] pub input_modalities: Vec, #[serde(default)] pub supports_personality: bool, // 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 ModelUpgradeInfo { pub model: String, pub upgrade_copy: Option, pub model_link: Option, pub migration_markdown: Option, } #[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, } /// EXPERIMENTAL - list collaboration mode presets. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CollaborationModeListParams {} /// EXPERIMENTAL - collaboration mode preset metadata for clients. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CollaborationModeMask { pub name: String, pub mode: Option, pub model: Option, #[serde(rename = "reasoning_effort")] #[ts(rename = "reasoning_effort")] pub reasoning_effort: Option>, } impl From for CollaborationModeMask { fn from(value: CoreCollaborationModeMask) -> Self { Self { name: value.name, mode: value.mode, model: value.model, reasoning_effort: value.reasoning_effort, } } } /// EXPERIMENTAL - collaboration mode presets response. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CollaborationModeListResponse { pub data: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExperimentalFeatureListParams { /// Opaque pagination cursor returned by a previous call. #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. #[ts(optional = nullable)] pub limit: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum ExperimentalFeatureStage { /// Feature is available for user testing and feedback. Beta, /// Feature is still being built and not ready for broad use. UnderDevelopment, /// Feature is production-ready. Stable, /// Feature is deprecated and should be avoided. Deprecated, /// Feature flag is retained only for backwards compatibility. Removed, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExperimentalFeature { /// Stable key used in config.toml and CLI flag toggles. pub name: String, /// Lifecycle stage of this feature flag. pub stage: ExperimentalFeatureStage, /// User-facing display name shown in the experimental features UI. /// Null when this feature is not in beta. pub display_name: Option, /// Short summary describing what the feature does. /// Null when this feature is not in beta. pub description: Option, /// Announcement copy shown to users when the feature is introduced. /// Null when this feature is not in beta. pub announcement: Option, /// Whether this feature is currently enabled in the loaded config. pub enabled: bool, /// Whether this feature is enabled by default. pub default_enabled: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExperimentalFeatureListResponse { 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, Eq, Default, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExperimentalFeatureEnablementSetParams { /// Process-wide runtime feature enablement keyed by canonical feature name. /// /// Only named features are updated. Omitted features are left unchanged. /// Send an empty map for a no-op. pub enablement: std::collections::BTreeMap, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExperimentalFeatureEnablementSetResponse { /// Feature enablement entries updated by this request. pub enablement: std::collections::BTreeMap, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ListMcpServerStatusParams { /// Opaque pagination cursor returned by a previous call. #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a server-defined value. #[ts(optional = nullable)] pub limit: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpServerStatus { pub name: String, pub tools: std::collections::HashMap, pub resources: Vec, pub resource_templates: Vec, pub auth_status: McpAuthStatus, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ListMcpServerStatusResponse { 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, Default, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL - list available apps/connectors. pub struct AppsListParams { /// Opaque pagination cursor returned by a previous call. #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. #[ts(optional = nullable)] pub limit: Option, /// Optional thread id used to evaluate app feature gating from that thread's config. #[ts(optional = nullable)] pub thread_id: Option, /// When true, bypass app caches and fetch the latest data from sources. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub force_refetch: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL - app metadata returned by app-list APIs. pub struct AppBranding { pub category: Option, pub developer: Option, pub website: Option, pub privacy_policy: Option, pub terms_of_service: Option, pub is_discoverable_app: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AppReview { pub status: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AppScreenshot { pub url: Option, #[serde(alias = "file_id")] pub file_id: Option, #[serde(alias = "user_prompt")] pub user_prompt: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AppMetadata { pub review: Option, pub categories: Option>, pub sub_categories: Option>, pub seo_description: Option, pub screenshots: Option>, pub developer: Option, pub version: Option, pub version_id: Option, pub version_notes: Option, pub first_party_type: Option, pub first_party_requires_install: Option, pub show_in_composer_when_unlinked: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL - app metadata returned by app-list APIs. pub struct AppInfo { pub id: String, pub name: String, pub description: Option, pub logo_url: Option, pub logo_url_dark: Option, pub distribution_channel: Option, pub branding: Option, pub app_metadata: Option, pub labels: Option>, pub install_url: Option, #[serde(default)] pub is_accessible: bool, /// Whether this app is enabled in config.toml. /// Example: /// ```toml /// [apps.bad_app] /// enabled = false /// ``` #[serde(default = "default_enabled")] pub is_enabled: bool, #[serde(default)] pub plugin_display_names: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL - app metadata summary for plugin responses. pub struct AppSummary { pub id: String, pub name: String, pub description: Option, pub install_url: Option, pub needs_auth: bool, } impl From for AppSummary { fn from(value: AppInfo) -> Self { Self { id: value.id, name: value.name, description: value.description, install_url: value.install_url, needs_auth: false, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL - app list response. pub struct AppsListResponse { 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/")] /// EXPERIMENTAL - notification emitted when the app list changes. pub struct AppListUpdatedNotification { pub data: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpServerRefreshParams {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpServerRefreshResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpServerOauthLoginParams { pub name: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub scopes: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub timeout_secs: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpServerOauthLoginResponse { pub authorization_url: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FeedbackUploadParams { pub classification: String, #[ts(optional = nullable)] pub reason: Option, #[ts(optional = nullable)] pub thread_id: Option, pub include_logs: bool, #[ts(optional = nullable)] pub extra_log_files: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FeedbackUploadResponse { pub thread_id: String, } /// Read a file from the host filesystem. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsReadFileParams { /// Absolute path to read. pub path: AbsolutePathBuf, } /// Base64-encoded file contents returned by `fs/readFile`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsReadFileResponse { /// File contents encoded as base64. pub data_base64: String, } /// Write a file on the host filesystem. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsWriteFileParams { /// Absolute path to write. pub path: AbsolutePathBuf, /// File contents encoded as base64. pub data_base64: String, } /// Successful response for `fs/writeFile`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsWriteFileResponse {} /// Create a directory on the host filesystem. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsCreateDirectoryParams { /// Absolute directory path to create. pub path: AbsolutePathBuf, /// Whether parent directories should also be created. Defaults to `true`. #[ts(optional = nullable)] pub recursive: Option, } /// Successful response for `fs/createDirectory`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsCreateDirectoryResponse {} /// Request metadata for an absolute path. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsGetMetadataParams { /// Absolute path to inspect. pub path: AbsolutePathBuf, } /// Metadata returned by `fs/getMetadata`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsGetMetadataResponse { /// Whether the path currently resolves to a directory. pub is_directory: bool, /// Whether the path currently resolves to a regular file. pub is_file: bool, /// File creation time in Unix milliseconds when available, otherwise `0`. #[ts(type = "number")] pub created_at_ms: i64, /// File modification time in Unix milliseconds when available, otherwise `0`. #[ts(type = "number")] pub modified_at_ms: i64, } /// List direct child names for a directory. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsReadDirectoryParams { /// Absolute directory path to read. pub path: AbsolutePathBuf, } /// A directory entry returned by `fs/readDirectory`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsReadDirectoryEntry { /// Direct child entry name only, not an absolute or relative path. pub file_name: String, /// Whether this entry resolves to a directory. pub is_directory: bool, /// Whether this entry resolves to a regular file. pub is_file: bool, } /// Directory entries returned by `fs/readDirectory`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsReadDirectoryResponse { /// Direct child entries in the requested directory. pub entries: Vec, } /// Remove a file or directory tree from the host filesystem. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsRemoveParams { /// Absolute path to remove. pub path: AbsolutePathBuf, /// Whether directory removal should recurse. Defaults to `true`. #[ts(optional = nullable)] pub recursive: Option, /// Whether missing paths should be ignored. Defaults to `true`. #[ts(optional = nullable)] pub force: Option, } /// Successful response for `fs/remove`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsRemoveResponse {} /// Copy a file or directory tree on the host filesystem. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsCopyParams { /// Absolute source path. pub source_path: AbsolutePathBuf, /// Absolute destination path. pub destination_path: AbsolutePathBuf, /// Required for directory copies; ignored for file copies. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub recursive: bool, } /// Successful response for `fs/copy`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsCopyResponse {} /// Start filesystem watch notifications for an absolute path. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsWatchParams { /// Absolute file or directory path to watch. pub path: AbsolutePathBuf, } /// Created watch handle returned by `fs/watch`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsWatchResponse { /// Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`. pub watch_id: String, /// Canonicalized path associated with the watch. pub path: AbsolutePathBuf, } /// Stop filesystem watch notifications for a prior `fs/watch`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsUnwatchParams { /// Watch identifier returned by `fs/watch`. pub watch_id: String, } /// Successful response for `fs/unwatch`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsUnwatchResponse {} /// Filesystem watch notification emitted for `fs/watch` subscribers. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FsChangedNotification { /// Watch identifier returned by `fs/watch`. pub watch_id: String, /// File or directory paths associated with this event. pub changed_paths: Vec, } /// PTY size in character cells for `command/exec` PTY sessions. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecTerminalSize { /// Terminal height in character cells. pub rows: u16, /// Terminal width in character cells. pub cols: u16, } /// Run a standalone command (argv vector) in the server sandbox without /// creating a thread or turn. /// /// The final `command/exec` response is deferred until the process exits and is /// sent only after all `command/exec/outputDelta` notifications for that /// connection have been emitted. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecParams { /// Command argv vector. Empty arrays are rejected. pub command: Vec, /// Optional client-supplied, connection-scoped process id. /// /// Required for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up /// `command/exec/write`, `command/exec/resize`, and /// `command/exec/terminate` calls. When omitted, buffered execution gets an /// internal id that is not exposed to the client. #[ts(optional = nullable)] pub process_id: Option, /// Enable PTY mode. /// /// This implies `streamStdin` and `streamStdoutStderr`. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub tty: bool, /// Allow follow-up `command/exec/write` requests to write stdin bytes. /// /// Requires a client-supplied `processId`. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub stream_stdin: bool, /// Stream stdout/stderr via `command/exec/outputDelta` notifications. /// /// Streamed bytes are not duplicated into the final response and require a /// client-supplied `processId`. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub stream_stdout_stderr: bool, /// Optional per-stream stdout/stderr capture cap in bytes. /// /// When omitted, the server default applies. Cannot be combined with /// `disableOutputCap`. #[ts(type = "number | null")] #[ts(optional = nullable)] pub output_bytes_cap: Option, /// Disable stdout/stderr capture truncation for this request. /// /// Cannot be combined with `outputBytesCap`. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub disable_output_cap: bool, /// Disable the timeout entirely for this request. /// /// Cannot be combined with `timeoutMs`. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub disable_timeout: bool, /// Optional timeout in milliseconds. /// /// When omitted, the server default applies. Cannot be combined with /// `disableTimeout`. #[ts(type = "number | null")] #[ts(optional = nullable)] pub timeout_ms: Option, /// Optional working directory. Defaults to the server cwd. #[ts(optional = nullable)] pub cwd: Option, /// Optional environment overrides merged into the server-computed /// environment. /// /// Matching names override inherited values. Set a key to `null` to unset /// an inherited variable. #[ts(optional = nullable)] pub env: Option>>, /// Optional initial PTY size in character cells. Only valid when `tty` is /// true. #[ts(optional = nullable)] pub size: Option, /// Optional sandbox policy for this command. /// /// Uses the same shape as thread/turn execution sandbox configuration and /// defaults to the user's configured policy when omitted. #[ts(optional = nullable)] pub sandbox_policy: Option, } /// Final buffered result for `command/exec`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecResponse { /// Process exit code. pub exit_code: i32, /// Buffered stdout capture. /// /// Empty when stdout was streamed via `command/exec/outputDelta`. pub stdout: String, /// Buffered stderr capture. /// /// Empty when stderr was streamed via `command/exec/outputDelta`. pub stderr: String, } /// Write stdin bytes to a running `command/exec` session, close stdin, or /// both. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecWriteParams { /// Client-supplied, connection-scoped `processId` from the original /// `command/exec` request. pub process_id: String, /// Optional base64-encoded stdin bytes to write. #[ts(optional = nullable)] pub delta_base64: Option, /// Close stdin after writing `deltaBase64`, if present. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub close_stdin: bool, } /// Empty success response for `command/exec/write`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecWriteResponse {} /// Terminate a running `command/exec` session. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecTerminateParams { /// Client-supplied, connection-scoped `processId` from the original /// `command/exec` request. pub process_id: String, } /// Empty success response for `command/exec/terminate`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecTerminateResponse {} /// Resize a running PTY-backed `command/exec` session. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecResizeParams { /// Client-supplied, connection-scoped `processId` from the original /// `command/exec` request. pub process_id: String, /// New PTY size in character cells. pub size: CommandExecTerminalSize, } /// Empty success response for `command/exec/resize`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecResizeResponse {} /// Stream label for `command/exec/outputDelta` notifications. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CommandExecOutputStream { /// stdout stream. PTY mode multiplexes terminal output here. Stdout, /// stderr stream. Stderr, } // === Threads, Turns, and Items === // Thread APIs #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS, ExperimentalApi, )] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartParams { #[ts(optional = nullable)] pub model: Option, #[ts(optional = nullable)] pub model_provider: Option, #[serde( default, deserialize_with = "super::serde_helpers::deserialize_double_option", serialize_with = "super::serde_helpers::serialize_double_option", skip_serializing_if = "Option::is_none" )] #[ts(optional = nullable)] pub service_tier: Option>, #[ts(optional = nullable)] pub cwd: Option, #[experimental(nested)] #[ts(optional = nullable)] pub approval_policy: Option, /// Override where approval requests are routed for review on this thread /// and subsequent turns. #[ts(optional = nullable)] pub approvals_reviewer: Option, #[ts(optional = nullable)] pub sandbox: Option, #[ts(optional = nullable)] pub config: Option>, #[ts(optional = nullable)] pub service_name: Option, #[ts(optional = nullable)] pub base_instructions: Option, #[ts(optional = nullable)] pub developer_instructions: Option, #[ts(optional = nullable)] pub personality: Option, #[ts(optional = nullable)] pub ephemeral: Option, #[experimental("thread/start.dynamicTools")] #[ts(optional = nullable)] pub dynamic_tools: Option>, /// Test-only experimental field used to validate experimental gating and /// schema filtering behavior in a stable way. #[experimental("thread/start.mockExperimentalField")] #[ts(optional = nullable)] pub mock_experimental_field: Option, /// If true, opt into emitting raw Responses API items on the event stream. /// This is for internal use only (e.g. Codex Cloud). #[experimental("thread/start.experimentalRawEvents")] #[serde(default)] pub experimental_raw_events: bool, /// If true, persist additional rollout EventMsg variants required to /// reconstruct a richer thread history on resume/fork/read. #[experimental("thread/start.persistFullHistory")] #[serde(default)] pub persist_extended_history: bool, } #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct MockExperimentalMethodParams { /// Test-only payload field. #[ts(optional = nullable)] pub value: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct MockExperimentalMethodResponse { /// Echoes the input `value`. pub echoed: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartResponse { pub thread: Thread, pub model: String, pub model_provider: String, pub service_tier: Option, pub cwd: PathBuf, #[experimental(nested)] pub approval_policy: AskForApproval, /// Reviewer currently used for approval requests on this thread. pub approvals_reviewer: ApprovalsReviewer, pub sandbox: SandboxPolicy, pub reasoning_effort: Option, } #[derive( Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi, )] #[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. #[experimental("thread/resume.history")] #[ts(optional = nullable)] pub history: Option>, /// [UNSTABLE] Specify the rollout path to resume from. /// If specified, the thread_id param will be ignored. #[experimental("thread/resume.path")] #[ts(optional = nullable)] pub path: Option, /// Configuration overrides for the resumed thread, if any. #[ts(optional = nullable)] pub model: Option, #[ts(optional = nullable)] pub model_provider: Option, #[serde( default, deserialize_with = "super::serde_helpers::deserialize_double_option", serialize_with = "super::serde_helpers::serialize_double_option", skip_serializing_if = "Option::is_none" )] #[ts(optional = nullable)] pub service_tier: Option>, #[ts(optional = nullable)] pub cwd: Option, #[experimental(nested)] #[ts(optional = nullable)] pub approval_policy: Option, /// Override where approval requests are routed for review on this thread /// and subsequent turns. #[ts(optional = nullable)] pub approvals_reviewer: Option, #[ts(optional = nullable)] pub sandbox: Option, #[ts(optional = nullable)] pub config: Option>, #[ts(optional = nullable)] pub base_instructions: Option, #[ts(optional = nullable)] pub developer_instructions: Option, #[ts(optional = nullable)] pub personality: Option, /// If true, persist additional rollout EventMsg variants required to /// reconstruct a richer thread history on subsequent resume/fork/read. #[experimental("thread/resume.persistFullHistory")] #[serde(default)] pub persist_extended_history: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadResumeResponse { pub thread: Thread, pub model: String, pub model_provider: String, pub service_tier: Option, pub cwd: PathBuf, #[experimental(nested)] pub approval_policy: AskForApproval, /// Reviewer currently used for approval requests on this thread. pub approvals_reviewer: ApprovalsReviewer, pub sandbox: SandboxPolicy, pub reasoning_effort: Option, } #[derive( Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi, )] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// There are two ways to fork a thread: /// 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. /// 2. By path: load the thread from disk by path and fork it into a new thread. /// /// If using path, the thread_id param will be ignored. /// /// Prefer using thread_id whenever possible. pub struct ThreadForkParams { pub thread_id: String, /// [UNSTABLE] Specify the rollout path to fork from. /// If specified, the thread_id param will be ignored. #[experimental("thread/fork.path")] #[ts(optional = nullable)] pub path: Option, /// Configuration overrides for the forked thread, if any. #[ts(optional = nullable)] pub model: Option, #[ts(optional = nullable)] pub model_provider: Option, #[serde( default, deserialize_with = "super::serde_helpers::deserialize_double_option", serialize_with = "super::serde_helpers::serialize_double_option", skip_serializing_if = "Option::is_none" )] #[ts(optional = nullable)] pub service_tier: Option>, #[ts(optional = nullable)] pub cwd: Option, #[experimental(nested)] #[ts(optional = nullable)] pub approval_policy: Option, /// Override where approval requests are routed for review on this thread /// and subsequent turns. #[ts(optional = nullable)] pub approvals_reviewer: Option, #[ts(optional = nullable)] pub sandbox: Option, #[ts(optional = nullable)] pub config: Option>, #[ts(optional = nullable)] pub base_instructions: Option, #[ts(optional = nullable)] pub developer_instructions: Option, #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub ephemeral: bool, /// If true, persist additional rollout EventMsg variants required to /// reconstruct a richer thread history on subsequent resume/fork/read. #[experimental("thread/fork.persistFullHistory")] #[serde(default)] pub persist_extended_history: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadForkResponse { pub thread: Thread, pub model: String, pub model_provider: String, pub service_tier: Option, pub cwd: PathBuf, #[experimental(nested)] pub approval_policy: AskForApproval, /// Reviewer currently used for approval requests on this thread. pub approvals_reviewer: ApprovalsReviewer, 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 ThreadUnsubscribeParams { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadUnsubscribeResponse { pub status: ThreadUnsubscribeStatus, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum ThreadUnsubscribeStatus { NotLoaded, NotSubscribed, Unsubscribed, } /// Parameters for `thread/increment_elicitation`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadIncrementElicitationParams { /// Thread whose out-of-band elicitation counter should be incremented. pub thread_id: String, } /// Response for `thread/increment_elicitation`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadIncrementElicitationResponse { /// Current out-of-band elicitation count after the increment. pub count: u64, /// Whether timeout accounting is paused after applying the increment. pub paused: bool, } /// Parameters for `thread/decrement_elicitation`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadDecrementElicitationParams { /// Thread whose out-of-band elicitation counter should be decremented. pub thread_id: String, } /// Response for `thread/decrement_elicitation`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadDecrementElicitationResponse { /// Current out-of-band elicitation count after the decrement. pub count: u64, /// Whether timeout accounting remains paused after applying the decrement. pub paused: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadSetNameParams { pub thread_id: String, pub name: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadUnarchiveParams { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadSetNameResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadMetadataUpdateParams { pub thread_id: String, /// Patch the stored Git metadata for this thread. /// Omit a field to leave it unchanged, set it to `null` to clear it, or /// provide a string to replace the stored value. #[ts(optional = nullable)] pub git_info: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadMetadataGitInfoUpdateParams { /// Omit to leave the stored commit unchanged, set to `null` to clear it, /// or provide a non-empty string to replace it. #[serde( default, skip_serializing_if = "Option::is_none", serialize_with = "super::serde_helpers::serialize_double_option", deserialize_with = "super::serde_helpers::deserialize_double_option" )] #[ts(optional = nullable, type = "string | null")] pub sha: Option>, /// Omit to leave the stored branch unchanged, set to `null` to clear it, /// or provide a non-empty string to replace it. #[serde( default, skip_serializing_if = "Option::is_none", serialize_with = "super::serde_helpers::serialize_double_option", deserialize_with = "super::serde_helpers::deserialize_double_option" )] #[ts(optional = nullable, type = "string | null")] pub branch: Option>, /// Omit to leave the stored origin URL unchanged, set to `null` to clear it, /// or provide a non-empty string to replace it. #[serde( default, skip_serializing_if = "Option::is_none", serialize_with = "super::serde_helpers::serialize_double_option", deserialize_with = "super::serde_helpers::deserialize_double_option" )] #[ts(optional = nullable, type = "string | null")] pub origin_url: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadMetadataUpdateResponse { pub thread: Thread, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadUnarchiveResponse { pub thread: Thread, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadCompactStartParams { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadCompactStartResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadShellCommandParams { pub thread_id: String, /// Shell command string evaluated by the thread's configured shell. /// Unlike `command/exec`, this intentionally preserves shell syntax /// such as pipes, redirects, and quoting. This runs unsandboxed with full /// access rather than inheriting the thread sandbox policy. pub command: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadShellCommandResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadBackgroundTerminalsCleanParams { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadBackgroundTerminalsCleanResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRollbackParams { pub thread_id: String, /// The number of turns to drop from the end of the thread. Must be >= 1. /// /// This only modifies the thread's history and does not revert local file changes /// that have been made by the agent. Clients are responsible for reverting these changes. pub num_turns: u32, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRollbackResponse { /// The updated thread after applying the rollback, with `turns` populated. /// /// The ThreadItems stored in each Turn are lossy since we explicitly do not /// persist all agent interactions, such as command executions. This is the same /// behavior as `thread/resume`. pub thread: Thread, } #[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. #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to a reasonable server-side value. #[ts(optional = nullable)] pub limit: Option, /// Optional sort key; defaults to created_at. #[ts(optional = nullable)] pub sort_key: Option, /// Optional provider filter; when set, only sessions recorded under these /// providers are returned. When present but empty, includes all providers. #[ts(optional = nullable)] pub model_providers: Option>, /// Optional source filter; when set, only sessions from these source kinds /// are returned. When omitted or empty, defaults to interactive sources. #[ts(optional = nullable)] pub source_kinds: Option>, /// Optional archived filter; when set to true, only archived threads are returned. /// If false or null, only non-archived threads are returned. #[ts(optional = nullable)] pub archived: Option, /// Optional cwd filter; when set, only threads whose session cwd exactly /// matches this path are returned. #[ts(optional = nullable)] pub cwd: Option, /// Optional substring filter for the extracted thread title. #[ts(optional = nullable)] pub search_term: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase", export_to = "v2/")] pub enum ThreadSourceKind { Cli, #[serde(rename = "vscode")] #[ts(rename = "vscode")] VsCode, Exec, AppServer, SubAgent, SubAgentReview, SubAgentCompact, SubAgentThreadSpawn, SubAgentOther, Unknown, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub enum ThreadSortKey { CreatedAt, UpdatedAt, } #[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, Default, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadLoadedListParams { /// Opaque pagination cursor returned by a previous call. #[ts(optional = nullable)] pub cursor: Option, /// Optional page size; defaults to no limit. #[ts(optional = nullable)] pub limit: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadLoadedListResponse { /// Thread ids for sessions currently loaded in memory. 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(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum ThreadStatus { NotLoaded, Idle, SystemError, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Active { active_flags: Vec, }, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum ThreadActiveFlag { WaitingOnApproval, WaitingOnUserInput, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadReadParams { pub thread_id: String, /// When true, include turns and their items from rollout history. #[serde(default)] pub include_turns: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadReadResponse { pub thread: Thread, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsListParams { /// When empty, defaults to the current session working directory. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub cwds: Vec, /// When true, bypass the skills cache and re-scan skills from disk. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub force_reload: bool, /// Optional per-cwd extra roots to scan as user-scoped skills. #[serde(default)] #[ts(optional = nullable)] pub per_cwd_extra_user_roots: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsListExtraRootsForCwd { pub cwd: PathBuf, pub extra_user_roots: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsListResponse { pub data: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginListParams { /// Optional working directories used to discover repo marketplaces. When omitted, /// only home-scoped marketplaces and the official curated marketplace are considered. #[ts(optional = nullable)] pub cwds: Option>, /// When true, reconcile the official curated marketplace against the remote plugin state /// before listing marketplaces. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub force_remote_sync: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginListResponse { pub marketplaces: Vec, #[serde(default)] pub marketplace_load_errors: Vec, pub remote_sync_error: Option, #[serde(default)] pub featured_plugin_ids: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct MarketplaceLoadErrorInfo { pub marketplace_path: AbsolutePathBuf, pub message: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginReadParams { pub marketplace_path: AbsolutePathBuf, pub plugin_name: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginReadResponse { pub plugin: PluginDetail, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub enum SkillScope { User, Repo, System, Admin, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillMetadata { pub name: String, pub description: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] /// Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description. pub short_description: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub interface: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub dependencies: Option, pub path: PathBuf, pub scope: SkillScope, pub enabled: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillInterface { #[ts(optional)] pub display_name: Option, #[ts(optional)] pub short_description: Option, #[ts(optional)] pub icon_small: Option, #[ts(optional)] pub icon_large: Option, #[ts(optional)] pub brand_color: Option, #[ts(optional)] pub default_prompt: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillDependencies { pub tools: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillToolDependency { #[serde(rename = "type")] #[ts(rename = "type")] pub r#type: String, pub value: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub transport: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub command: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub url: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillErrorInfo { pub path: PathBuf, pub message: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsListEntry { pub cwd: PathBuf, pub skills: Vec, pub errors: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginMarketplaceEntry { pub name: String, pub path: AbsolutePathBuf, pub interface: Option, pub plugins: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct MarketplaceInterface { pub display_name: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[ts(export_to = "v2/")] pub enum PluginInstallPolicy { #[serde(rename = "NOT_AVAILABLE")] #[ts(rename = "NOT_AVAILABLE")] NotAvailable, #[serde(rename = "AVAILABLE")] #[ts(rename = "AVAILABLE")] Available, #[serde(rename = "INSTALLED_BY_DEFAULT")] #[ts(rename = "INSTALLED_BY_DEFAULT")] InstalledByDefault, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[ts(export_to = "v2/")] pub enum PluginAuthPolicy { #[serde(rename = "ON_INSTALL")] #[ts(rename = "ON_INSTALL")] OnInstall, #[serde(rename = "ON_USE")] #[ts(rename = "ON_USE")] OnUse, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginSummary { pub id: String, pub name: String, pub source: PluginSource, pub installed: bool, pub enabled: bool, pub install_policy: PluginInstallPolicy, pub auth_policy: PluginAuthPolicy, pub interface: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginDetail { pub marketplace_name: String, pub marketplace_path: AbsolutePathBuf, pub summary: PluginSummary, pub description: Option, pub skills: Vec, pub apps: Vec, pub mcp_servers: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillSummary { pub name: String, pub description: String, pub short_description: Option, pub interface: Option, pub path: PathBuf, pub enabled: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginInterface { pub display_name: Option, pub short_description: Option, pub long_description: Option, pub developer_name: Option, pub category: Option, pub capabilities: Vec, pub website_url: Option, pub privacy_policy_url: Option, pub terms_of_service_url: Option, /// Starter prompts for the plugin. Capped at 3 entries with a maximum of /// 128 characters per entry. pub default_prompt: Option>, pub brand_color: Option, pub composer_icon: Option, pub logo: Option, pub screenshots: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum PluginSource { #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Local { path: AbsolutePathBuf }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsConfigWriteParams { /// Path-based selector. #[ts(optional = nullable)] pub path: Option, /// Name-based selector. #[ts(optional = nullable)] pub name: Option, pub enabled: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsConfigWriteResponse { pub effective_enabled: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginInstallParams { pub marketplace_path: AbsolutePathBuf, pub plugin_name: String, /// When true, apply the remote plugin change before the local install flow. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub force_remote_sync: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginInstallResponse { pub auth_policy: PluginAuthPolicy, pub apps_needing_auth: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginUninstallParams { pub plugin_id: String, /// When true, apply the remote plugin change before the local uninstall flow. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub force_remote_sync: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PluginUninstallResponse {} impl From for SkillMetadata { fn from(value: CoreSkillMetadata) -> Self { Self { name: value.name, description: value.description, short_description: value.short_description, interface: value.interface.map(SkillInterface::from), dependencies: value.dependencies.map(SkillDependencies::from), path: value.path, scope: value.scope.into(), enabled: true, } } } impl From for SkillInterface { fn from(value: CoreSkillInterface) -> Self { Self { display_name: value.display_name, short_description: value.short_description, brand_color: value.brand_color, default_prompt: value.default_prompt, icon_small: value.icon_small, icon_large: value.icon_large, } } } impl From for SkillDependencies { fn from(value: CoreSkillDependencies) -> Self { Self { tools: value .tools .into_iter() .map(SkillToolDependency::from) .collect(), } } } impl From for SkillToolDependency { fn from(value: CoreSkillToolDependency) -> Self { Self { r#type: value.r#type, value: value.value, description: value.description, transport: value.transport, command: value.command, url: value.url, } } } impl From for SkillScope { fn from(value: CoreSkillScope) -> Self { match value { CoreSkillScope::User => Self::User, CoreSkillScope::Repo => Self::Repo, CoreSkillScope::System => Self::System, CoreSkillScope::Admin => Self::Admin, } } } impl From for SkillErrorInfo { fn from(value: CoreSkillErrorInfo) -> Self { Self { path: value.path, message: value.message, } } } #[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, /// Whether the thread is ephemeral and should not be materialized on disk. pub ephemeral: bool, /// Model provider used for this thread (for example, 'openai'). pub model_provider: String, /// Unix timestamp (in seconds) when the thread was created. #[ts(type = "number")] pub created_at: i64, /// Unix timestamp (in seconds) when the thread was last updated. #[ts(type = "number")] pub updated_at: i64, /// Current runtime status for the thread. pub status: ThreadStatus, /// [UNSTABLE] Path to the thread on disk. pub path: Option, /// Working directory captured for the thread. pub cwd: PathBuf, /// Version of the CLI that created the thread. pub cli_version: String, /// Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.). pub source: SessionSource, /// Optional random unique nickname assigned to an AgentControl-spawned sub-agent. pub agent_nickname: Option, /// Optional role (agent_role) assigned to an AgentControl-spawned sub-agent. pub agent_role: Option, /// Optional Git metadata captured when the thread was created. pub git_info: Option, /// Optional user-facing thread title. pub name: Option, /// Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` /// (when `includeTurns` is true) responses. /// 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, pub plan_type: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadTokenUsageUpdatedNotification { pub thread_id: String, pub turn_id: String, pub token_usage: ThreadTokenUsage, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadTokenUsage { pub total: TokenUsageBreakdown, pub last: TokenUsageBreakdown, // TODO(aibrahim): make this not optional #[ts(type = "number | null")] pub model_context_window: Option, } impl From for ThreadTokenUsage { fn from(value: CoreTokenUsageInfo) -> Self { Self { total: value.total_token_usage.into(), last: value.last_token_usage.into(), model_context_window: value.model_context_window, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TokenUsageBreakdown { #[ts(type = "number")] pub total_tokens: i64, #[ts(type = "number")] pub input_tokens: i64, #[ts(type = "number")] pub cached_input_tokens: i64, #[ts(type = "number")] pub output_tokens: i64, #[ts(type = "number")] pub reasoning_output_tokens: i64, } impl From for TokenUsageBreakdown { fn from(value: CoreTokenUsage) -> Self { Self { total_tokens: value.total_tokens, input_tokens: value.input_tokens, cached_input_tokens: value.cached_input_tokens, output_tokens: value.output_tokens, reasoning_output_tokens: value.reasoning_output_tokens, } } } #[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` or `thread/fork` response. /// For all other responses and notifications returning a Turn, /// the items field will be an empty list. pub items: Vec, pub status: TurnStatus, /// Only populated when the Turn's status is failed. pub error: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct MemoryCitation { pub entries: Vec, pub thread_ids: Vec, } impl From for MemoryCitation { fn from(value: CoreMemoryCitation) -> Self { Self { entries: value.entries.into_iter().map(Into::into).collect(), thread_ids: value.rollout_ids, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct MemoryCitationEntry { pub path: String, pub line_start: u32, pub line_end: u32, pub note: String, } impl From for MemoryCitationEntry { fn from(value: CoreMemoryCitationEntry) -> Self { Self { path: value.path, line_start: value.line_start, line_end: value.line_end, note: value.note, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Error)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] #[error("{message}")] pub struct TurnError { pub message: String, pub codex_error_info: Option, #[serde(default)] pub additional_details: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ErrorNotification { pub error: TurnError, // Set to true if the error is transient and the app-server process will automatically retry. // If true, this will not interrupt a turn. pub will_retry: bool, pub thread_id: String, pub turn_id: String, } /// EXPERIMENTAL - thread realtime audio chunk. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeAudioChunk { pub data: String, pub sample_rate: u32, pub num_channels: u16, pub samples_per_channel: Option, pub item_id: Option, } impl From for ThreadRealtimeAudioChunk { fn from(value: CoreRealtimeAudioFrame) -> Self { let CoreRealtimeAudioFrame { data, sample_rate, num_channels, samples_per_channel, item_id, } = value; Self { data, sample_rate, num_channels, samples_per_channel, item_id, } } } impl From for CoreRealtimeAudioFrame { fn from(value: ThreadRealtimeAudioChunk) -> Self { let ThreadRealtimeAudioChunk { data, sample_rate, num_channels, samples_per_channel, item_id, } = value; Self { data, sample_rate, num_channels, samples_per_channel, item_id, } } } /// EXPERIMENTAL - start a thread-scoped realtime session. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeStartParams { pub thread_id: String, pub prompt: String, #[ts(optional = nullable)] pub session_id: Option, } /// EXPERIMENTAL - response for starting thread realtime. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeStartResponse {} /// EXPERIMENTAL - append audio input to thread realtime. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeAppendAudioParams { pub thread_id: String, pub audio: ThreadRealtimeAudioChunk, } /// EXPERIMENTAL - response for appending realtime audio input. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeAppendAudioResponse {} /// EXPERIMENTAL - append text input to thread realtime. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeAppendTextParams { pub thread_id: String, pub text: String, } /// EXPERIMENTAL - response for appending realtime text input. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeAppendTextResponse {} /// EXPERIMENTAL - stop thread realtime. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeStopParams { pub thread_id: String, } /// EXPERIMENTAL - response for stopping thread realtime. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeStopResponse {} /// EXPERIMENTAL - emitted when thread realtime startup is accepted. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeStartedNotification { pub thread_id: String, pub session_id: Option, pub version: RealtimeConversationVersion, } /// EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeItemAddedNotification { pub thread_id: String, pub item: JsonValue, } /// EXPERIMENTAL - flat transcript delta emitted whenever realtime /// transcript text changes. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeTranscriptUpdatedNotification { pub thread_id: String, pub role: String, pub text: String, } /// EXPERIMENTAL - streamed output audio emitted by thread realtime. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeOutputAudioDeltaNotification { pub thread_id: String, pub audio: ThreadRealtimeAudioChunk, } /// EXPERIMENTAL - emitted when thread realtime encounters an error. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeErrorNotification { pub thread_id: String, pub message: String, } /// EXPERIMENTAL - emitted when thread realtime transport closes. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadRealtimeClosedNotification { pub thread_id: String, pub reason: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum TurnStatus { Completed, Interrupted, Failed, InProgress, } // Turn APIs #[derive( Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi, )] #[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. #[ts(optional = nullable)] pub cwd: Option, /// Override the approval policy for this turn and subsequent turns. #[experimental(nested)] #[ts(optional = nullable)] pub approval_policy: Option, /// Override where approval requests are routed for review on this turn and /// subsequent turns. #[ts(optional = nullable)] pub approvals_reviewer: Option, /// Override the sandbox policy for this turn and subsequent turns. #[ts(optional = nullable)] pub sandbox_policy: Option, /// Override the model for this turn and subsequent turns. #[ts(optional = nullable)] pub model: Option, /// Override the service tier for this turn and subsequent turns. #[serde( default, deserialize_with = "super::serde_helpers::deserialize_double_option", serialize_with = "super::serde_helpers::serialize_double_option", skip_serializing_if = "Option::is_none" )] #[ts(optional = nullable)] pub service_tier: Option>, /// Override the reasoning effort for this turn and subsequent turns. #[ts(optional = nullable)] pub effort: Option, /// Override the reasoning summary for this turn and subsequent turns. #[ts(optional = nullable)] pub summary: Option, /// Override the personality for this turn and subsequent turns. #[ts(optional = nullable)] pub personality: Option, /// Optional JSON Schema used to constrain the final assistant message for /// this turn. #[ts(optional = nullable)] pub output_schema: Option, /// EXPERIMENTAL - Set a pre-set collaboration mode. /// Takes precedence over model, reasoning_effort, and developer instructions if set. /// /// For `collaboration_mode.settings.developer_instructions`, `null` means /// "use the built-in instructions for the selected mode". #[experimental("turn/start.collaborationMode")] #[ts(optional = nullable)] pub collaboration_mode: 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, /// Where to run the review: inline (default) on the current thread or /// detached on a new thread (returned in `reviewThreadId`). #[serde(default)] #[ts(optional = nullable)] pub delivery: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ReviewStartResponse { pub turn: Turn, /// Identifies the thread where the review runs. /// /// For inline reviews, this is the original thread id. /// For detached reviews, this is the id of the new review thread. pub review_thread_id: String, } #[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, Default, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnSteerParams { pub thread_id: String, pub input: Vec, /// Required active turn id precondition. The request fails when it does not /// match the currently active turn. pub expected_turn_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnSteerResponse { pub turn_id: String, } #[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, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ByteRange { pub start: usize, pub end: usize, } impl From for ByteRange { fn from(value: CoreByteRange) -> Self { Self { start: value.start, end: value.end, } } } impl From for CoreByteRange { fn from(value: ByteRange) -> Self { Self { start: value.start, end: value.end, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TextElement { /// Byte range in the parent `text` buffer that this element occupies. pub byte_range: ByteRange, /// Optional human-readable placeholder for the element, displayed in the UI. placeholder: Option, } impl TextElement { pub fn new(byte_range: ByteRange, placeholder: Option) -> Self { Self { byte_range, placeholder, } } pub fn set_placeholder(&mut self, placeholder: Option) { self.placeholder = placeholder; } pub fn placeholder(&self) -> Option<&str> { self.placeholder.as_deref() } } impl From for TextElement { fn from(value: CoreTextElement) -> Self { Self::new( value.byte_range.into(), value._placeholder_for_conversion_only().map(str::to_string), ) } } impl From for CoreTextElement { fn from(value: TextElement) -> Self { Self::new(value.byte_range.into(), value.placeholder) } } #[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, /// UI-defined spans within `text` used to render or persist special elements. #[serde(default)] text_elements: Vec, }, Image { url: String, }, LocalImage { path: PathBuf, }, Skill { name: String, path: PathBuf, }, Mention { name: String, path: String, }, } impl UserInput { pub fn into_core(self) -> CoreUserInput { match self { UserInput::Text { text, text_elements, } => CoreUserInput::Text { text, text_elements: text_elements.into_iter().map(Into::into).collect(), }, UserInput::Image { url } => CoreUserInput::Image { image_url: url }, UserInput::LocalImage { path } => CoreUserInput::LocalImage { path }, UserInput::Skill { name, path } => CoreUserInput::Skill { name, path }, UserInput::Mention { name, path } => CoreUserInput::Mention { name, path }, } } } impl From for UserInput { fn from(value: CoreUserInput) -> Self { match value { CoreUserInput::Text { text, text_elements, } => UserInput::Text { text, text_elements: text_elements.into_iter().map(Into::into).collect(), }, CoreUserInput::Image { image_url } => UserInput::Image { url: image_url }, CoreUserInput::LocalImage { path } => UserInput::LocalImage { path }, CoreUserInput::Skill { name, path } => UserInput::Skill { name, path }, CoreUserInput::Mention { name, path } => UserInput::Mention { name, path }, _ => unreachable!("unsupported user input variant"), } } } impl UserInput { pub fn text_char_count(&self) -> usize { match self { UserInput::Text { text, .. } => text.chars().count(), UserInput::Image { .. } | UserInput::LocalImage { .. } | UserInput::Skill { .. } | UserInput::Mention { .. } => 0, } } } #[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")] HookPrompt { id: String, fragments: Vec, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] AgentMessage { id: String, text: String, #[serde(default)] phase: Option, #[serde(default)] memory_citation: Option, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] /// EXPERIMENTAL - proposed plan item content. The completed plan item is /// authoritative and may not match the concatenation of `PlanDelta` text. Plan { 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, /// Identifier for the underlying PTY process (when available). process_id: Option, #[serde(default)] source: CommandExecutionSource, 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. #[ts(type = "number | null")] 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, /// The duration of the MCP tool call in milliseconds. #[ts(type = "number | null")] duration_ms: Option, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] DynamicToolCall { id: String, tool: String, arguments: JsonValue, status: DynamicToolCallStatus, content_items: Option>, success: Option, /// The duration of the dynamic tool call in milliseconds. #[ts(type = "number | null")] duration_ms: Option, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] CollabAgentToolCall { /// Unique identifier for this collab tool call. id: String, /// Name of the collab tool that was invoked. tool: CollabAgentTool, /// Current status of the collab tool call. status: CollabAgentToolCallStatus, /// Thread ID of the agent issuing the collab request. sender_thread_id: String, /// Thread ID of the receiving agent, when applicable. In case of spawn operation, /// this corresponds to the newly spawned agent. receiver_thread_ids: Vec, /// Prompt text sent as part of the collab tool call, when available. prompt: Option, /// Model requested for the spawned agent, when applicable. model: Option, /// Reasoning effort requested for the spawned agent, when applicable. reasoning_effort: Option, /// Last known status of the target agents, when available. agents_states: HashMap, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] WebSearch { id: String, query: String, action: Option, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] ImageView { id: String, path: String }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] ImageGeneration { id: String, status: String, revised_prompt: Option, result: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] saved_path: Option, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] EnteredReviewMode { id: String, review: String }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] ExitedReviewMode { id: String, review: String }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] ContextCompaction { id: String }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase", export_to = "v2/")] pub struct HookPromptFragment { pub text: String, pub hook_run_id: String, } impl ThreadItem { pub fn id(&self) -> &str { match self { ThreadItem::UserMessage { id, .. } | ThreadItem::HookPrompt { id, .. } | ThreadItem::AgentMessage { id, .. } | ThreadItem::Plan { id, .. } | ThreadItem::Reasoning { id, .. } | ThreadItem::CommandExecution { id, .. } | ThreadItem::FileChange { id, .. } | ThreadItem::McpToolCall { id, .. } | ThreadItem::DynamicToolCall { id, .. } | ThreadItem::CollabAgentToolCall { id, .. } | ThreadItem::WebSearch { id, .. } | ThreadItem::ImageView { id, .. } | ThreadItem::ImageGeneration { id, .. } | ThreadItem::EnteredReviewMode { id, .. } | ThreadItem::ExitedReviewMode { id, .. } | ThreadItem::ContextCompaction { id, .. } => id, } } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// [UNSTABLE] Lifecycle state for a guardian approval review. pub enum GuardianApprovalReviewStatus { InProgress, Approved, Denied, Aborted, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] /// [UNSTABLE] Risk level assigned by guardian approval review. pub enum GuardianRiskLevel { Low, Medium, High, } impl From for GuardianRiskLevel { fn from(value: CoreGuardianRiskLevel) -> Self { match value { CoreGuardianRiskLevel::Low => Self::Low, CoreGuardianRiskLevel::Medium => Self::Medium, CoreGuardianRiskLevel::High => Self::High, } } } /// [UNSTABLE] Temporary guardian approval review payload used by /// `item/autoApprovalReview/*` notifications. This shape is expected to change /// soon. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GuardianApprovalReview { pub status: GuardianApprovalReviewStatus, #[serde(alias = "risk_score")] #[ts(type = "number | null")] pub risk_score: Option, #[serde(alias = "risk_level")] pub risk_level: Option, pub rationale: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type", rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum WebSearchAction { Search { query: Option, queries: Option>, }, OpenPage { url: Option, }, FindInPage { url: Option, pattern: Option, }, #[serde(other)] Other, } impl From for WebSearchAction { fn from(value: codex_protocol::models::WebSearchAction) -> Self { match value { codex_protocol::models::WebSearchAction::Search { query, queries } => { WebSearchAction::Search { query, queries } } codex_protocol::models::WebSearchAction::OpenPage { url } => { WebSearchAction::OpenPage { url } } codex_protocol::models::WebSearchAction::FindInPage { url, pattern } => { WebSearchAction::FindInPage { url, pattern } } codex_protocol::models::WebSearchAction::Other => WebSearchAction::Other, } } } 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::HookPrompt(hook_prompt) => ThreadItem::HookPrompt { id: hook_prompt.id, fragments: hook_prompt .fragments .into_iter() .map(HookPromptFragment::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, phase: agent.phase, memory_citation: agent.memory_citation.map(Into::into), } } CoreTurnItem::Plan(plan) => ThreadItem::Plan { id: plan.id, text: plan.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, action: Some(WebSearchAction::from(search.action)), }, CoreTurnItem::ImageGeneration(image) => ThreadItem::ImageGeneration { id: image.id, status: image.status, revised_prompt: image.revised_prompt, result: image.result, saved_path: image.saved_path, }, CoreTurnItem::ContextCompaction(compaction) => { ThreadItem::ContextCompaction { id: compaction.id } } } } } impl From for HookPromptFragment { fn from(value: codex_protocol::items::HookPromptFragment) -> Self { Self { text: value.text, hook_run_id: value.hook_run_id, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CommandExecutionStatus { InProgress, Completed, Failed, Declined, } impl From for CommandExecutionStatus { fn from(value: CoreExecCommandStatus) -> Self { Self::from(&value) } } impl From<&CoreExecCommandStatus> for CommandExecutionStatus { fn from(value: &CoreExecCommandStatus) -> Self { match value { CoreExecCommandStatus::Completed => CommandExecutionStatus::Completed, CoreExecCommandStatus::Failed => CommandExecutionStatus::Failed, CoreExecCommandStatus::Declined => CommandExecutionStatus::Declined, } } } v2_enum_from_core! { #[derive(Default)] pub enum CommandExecutionSource from CoreExecCommandSource { #[default] Agent, UserShell, UnifiedExecStartup, UnifiedExecInteraction, } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CollabAgentTool { SpawnAgent, SendInput, ResumeAgent, Wait, CloseAgent, } #[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(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum PatchChangeKind { Add, Delete, Update { move_path: Option }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum PatchApplyStatus { InProgress, Completed, Failed, Declined, } impl From for PatchApplyStatus { fn from(value: CorePatchApplyStatus) -> Self { Self::from(&value) } } impl From<&CorePatchApplyStatus> for PatchApplyStatus { fn from(value: &CorePatchApplyStatus) -> Self { match value { CorePatchApplyStatus::Completed => PatchApplyStatus::Completed, CorePatchApplyStatus::Failed => PatchApplyStatus::Failed, CorePatchApplyStatus::Declined => PatchApplyStatus::Declined, } } } #[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 enum DynamicToolCallStatus { InProgress, Completed, Failed, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CollabAgentToolCallStatus { InProgress, Completed, Failed, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum CollabAgentStatus { PendingInit, Running, Interrupted, Completed, Errored, Shutdown, NotFound, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CollabAgentState { pub status: CollabAgentStatus, pub message: Option, } impl From for CollabAgentState { fn from(value: CoreAgentStatus) -> Self { match value { CoreAgentStatus::PendingInit => Self { status: CollabAgentStatus::PendingInit, message: None, }, CoreAgentStatus::Running => Self { status: CollabAgentStatus::Running, message: None, }, CoreAgentStatus::Interrupted => Self { status: CollabAgentStatus::Interrupted, message: None, }, CoreAgentStatus::Completed(message) => Self { status: CollabAgentStatus::Completed, message, }, CoreAgentStatus::Errored(message) => Self { status: CollabAgentStatus::Errored, message: Some(message), }, CoreAgentStatus::Shutdown => Self { status: CollabAgentStatus::Shutdown, message: None, }, CoreAgentStatus::NotFound => Self { status: CollabAgentStatus::NotFound, message: None, }, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpToolCallResult { // NOTE: `rmcp::model::Content` (and its `RawContent` variants) would be a more precise Rust // representation of MCP content blocks. We intentionally use `serde_json::Value` here because // this crate exports JSON schema + TS types (`schemars`/`ts-rs`), and the rmcp model types // aren't set up to be schema/TS friendly (and would introduce heavier coupling to rmcp's Rust // representations). Using `JsonValue` keeps the payload wire-shaped and easy to export. 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, } // === 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 ThreadStatusChangedNotification { pub thread_id: String, pub status: ThreadStatus, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadArchivedNotification { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadUnarchivedNotification { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadClosedNotification { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// Notification emitted when watched local skill files change. /// /// Treat this as an invalidation signal and re-run `skills/list` with the /// client's current parameters when refreshed skill metadata is needed. pub struct SkillsChangedNotification {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadNameUpdatedNotification { pub thread_id: String, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub thread_name: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnStartedNotification { pub thread_id: String, pub turn: Turn, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct HookStartedNotification { pub thread_id: String, pub turn_id: Option, pub run: HookRunSummary, } #[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 thread_id: String, pub turn: Turn, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct HookCompletedNotification { pub thread_id: String, pub turn_id: Option, pub run: HookRunSummary, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// Notification that the turn-level unified diff has changed. /// Contains the latest aggregated diff across all file changes in the turn. pub struct TurnDiffUpdatedNotification { pub thread_id: String, pub turn_id: String, pub diff: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnPlanUpdatedNotification { pub thread_id: String, pub turn_id: String, pub explanation: Option, pub plan: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TurnPlanStep { pub step: String, pub status: TurnPlanStepStatus, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum TurnPlanStepStatus { Pending, InProgress, Completed, } impl From for TurnPlanStep { fn from(value: CorePlanItemArg) -> Self { Self { step: value.step, status: value.status.into(), } } } impl From for TurnPlanStepStatus { fn from(value: CorePlanStepStatus) -> Self { match value { CorePlanStepStatus::Pending => Self::Pending, CorePlanStepStatus::InProgress => Self::InProgress, CorePlanStepStatus::Completed => Self::Completed, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ItemStartedNotification { pub item: ThreadItem, pub thread_id: String, pub turn_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// [UNSTABLE] Temporary notification payload for guardian automatic approval /// review. This shape is expected to change soon. /// /// TODO(ccunningham): Attach guardian review state to the reviewed tool item's /// lifecycle instead of sending separate standalone review notifications so the /// app-server API can persist and replay review state via `thread/read`. pub struct ItemGuardianApprovalReviewStartedNotification { pub thread_id: String, pub turn_id: String, pub target_item_id: String, pub review: GuardianApprovalReview, pub action: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// [UNSTABLE] Temporary notification payload for guardian automatic approval /// review. This shape is expected to change soon. /// /// TODO(ccunningham): Attach guardian review state to the reviewed tool item's /// lifecycle instead of sending separate standalone review notifications so the /// app-server API can persist and replay review state via `thread/read`. pub struct ItemGuardianApprovalReviewCompletedNotification { pub thread_id: String, pub turn_id: String, pub target_item_id: String, pub review: GuardianApprovalReview, pub action: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ItemCompletedNotification { pub item: ThreadItem, 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 RawResponseItemCompletedNotification { pub thread_id: String, pub turn_id: String, pub item: ResponseItem, } // Item-specific progress notifications #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AgentMessageDeltaNotification { pub thread_id: String, pub turn_id: String, pub item_id: String, pub delta: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should /// not assume concatenated deltas match the completed plan item content. pub struct PlanDeltaNotification { pub thread_id: String, pub turn_id: String, 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 thread_id: String, pub turn_id: String, pub item_id: String, pub delta: String, #[ts(type = "number")] pub summary_index: i64, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ReasoningSummaryPartAddedNotification { pub thread_id: String, pub turn_id: String, pub item_id: String, #[ts(type = "number")] pub summary_index: i64, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ReasoningTextDeltaNotification { pub thread_id: String, pub turn_id: String, pub item_id: String, pub delta: String, #[ts(type = "number")] pub content_index: i64, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TerminalInteractionNotification { pub thread_id: String, pub turn_id: String, pub item_id: String, pub process_id: String, pub stdin: String, } #[serde_as] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecutionOutputDeltaNotification { pub thread_id: String, pub turn_id: String, pub item_id: String, pub delta: String, } /// Base64-encoded output chunk emitted for a streaming `command/exec` request. /// /// These notifications are connection-scoped. If the originating connection /// closes, the server terminates the process. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecOutputDeltaNotification { /// Client-supplied, connection-scoped `processId` from the original /// `command/exec` request. pub process_id: String, /// Output stream for this chunk. pub stream: CommandExecOutputStream, /// Base64-encoded output bytes. pub delta_base64: String, /// `true` on the final streamed chunk for a stream when `outputBytesCap` /// truncated later output on that stream. pub cap_reached: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FileChangeOutputDeltaNotification { pub thread_id: String, pub turn_id: String, 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 ServerRequestResolvedNotification { pub thread_id: String, pub request_id: RequestId, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpToolCallProgressNotification { pub thread_id: String, pub turn_id: String, 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 McpServerOauthLoginCompletedNotification { pub name: String, pub success: bool, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub error: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum McpServerStartupState { Starting, Ready, Failed, Cancelled, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpServerStatusUpdatedNotification { pub name: String, pub status: McpServerStartupState, pub error: Option, } #[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, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum WindowsSandboxSetupMode { Elevated, Unelevated, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct WindowsSandboxSetupStartParams { pub mode: WindowsSandboxSetupMode, #[ts(optional = nullable)] pub cwd: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct WindowsSandboxSetupStartResponse { pub started: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct WindowsSandboxSetupCompletedNotification { pub mode: WindowsSandboxSetupMode, pub success: bool, pub error: Option, } /// Deprecated: Use `ContextCompaction` item type instead. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ContextCompactedNotification { pub thread_id: String, pub turn_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecutionRequestApprovalParams { pub thread_id: String, pub turn_id: String, pub item_id: String, /// Unique identifier for this specific approval callback. /// /// For regular shell/unified_exec approvals, this is null. /// /// For zsh-exec-bridge subcommand approvals, multiple callbacks can belong to /// one parent `itemId`, so `approvalId` is a distinct opaque callback id /// (a UUID) used to disambiguate routing. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub approval_id: Option, /// Optional explanatory reason (e.g. request for network access). #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub reason: Option, /// Optional context for a managed-network approval prompt. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub network_approval_context: Option, /// The command to be executed. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub command: Option, /// The command's working directory. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub cwd: Option, /// Best-effort parsed command actions for friendly display. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub command_actions: Option>, /// Optional additional permissions requested for this command. #[experimental("item/commandExecution/requestApproval.additionalPermissions")] #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub additional_permissions: Option, /// Optional proposed execpolicy amendment to allow similar commands without prompting. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub proposed_execpolicy_amendment: Option, /// Optional proposed network policy amendments (allow/deny host) for future requests. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub proposed_network_policy_amendments: Option>, /// Ordered list of decisions the client may present for this prompt. #[experimental("item/commandExecution/requestApproval.availableDecisions")] #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional = nullable)] pub available_decisions: Option>, } impl CommandExecutionRequestApprovalParams { pub fn strip_experimental_fields(&mut self) { // TODO: Avoid hardcoding individual experimental fields here. // We need a generic outbound compatibility design for stripping or // otherwise handling experimental server->client payloads. self.additional_permissions = None; } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecutionRequestApprovalResponse { pub decision: CommandExecutionApprovalDecision, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct FileChangeRequestApprovalParams { pub thread_id: String, pub turn_id: String, pub item_id: String, /// Optional explanatory reason (e.g. request for extra write access). #[ts(optional = nullable)] pub reason: Option, /// [UNSTABLE] When set, the agent is asking the user to allow writes under this root /// for the remainder of the session (unclear if this is honored today). #[ts(optional = nullable)] pub grant_root: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[ts(export_to = "v2/")] pub struct FileChangeRequestApprovalResponse { pub decision: FileChangeApprovalDecision, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum McpServerElicitationAction { Accept, Decline, Cancel, } impl McpServerElicitationAction { pub fn to_core(self) -> codex_protocol::approvals::ElicitationAction { match self { Self::Accept => codex_protocol::approvals::ElicitationAction::Accept, Self::Decline => codex_protocol::approvals::ElicitationAction::Decline, Self::Cancel => codex_protocol::approvals::ElicitationAction::Cancel, } } } impl From for rmcp::model::ElicitationAction { fn from(value: McpServerElicitationAction) -> Self { match value { McpServerElicitationAction::Accept => Self::Accept, McpServerElicitationAction::Decline => Self::Decline, McpServerElicitationAction::Cancel => Self::Cancel, } } } impl From for McpServerElicitationAction { fn from(value: rmcp::model::ElicitationAction) -> Self { match value { rmcp::model::ElicitationAction::Accept => Self::Accept, rmcp::model::ElicitationAction::Decline => Self::Decline, rmcp::model::ElicitationAction::Cancel => Self::Cancel, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpServerElicitationRequestParams { pub thread_id: String, /// Active Codex turn when this elicitation was observed, if app-server could correlate one. /// /// This is nullable because MCP models elicitation as a standalone server-to-client request /// identified by the MCP server request id. It may be triggered during a turn, but turn /// context is app-server correlation rather than part of the protocol identity of the /// elicitation itself. pub turn_id: Option, pub server_name: String, #[serde(flatten)] pub request: McpServerElicitationRequest, // TODO: When core can correlate an elicitation with an MCP tool call, expose the associated // McpToolCall item id here as an optional field. The current core event does not carry that // association. } /// Typed form schema for MCP `elicitation/create` requests. /// /// This matches the `requestedSchema` shape from the MCP 2025-11-25 /// `ElicitRequestFormParams` schema. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationSchema { #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] #[ts(optional, rename = "$schema")] pub schema_uri: Option, #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationObjectType, pub properties: BTreeMap, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub required: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum McpElicitationObjectType { Object, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(untagged)] #[ts(export_to = "v2/")] pub enum McpElicitationPrimitiveSchema { Enum(McpElicitationEnumSchema), String(McpElicitationStringSchema), Number(McpElicitationNumberSchema), Boolean(McpElicitationBooleanSchema), } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationStringSchema { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationStringType, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub min_length: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub max_length: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub format: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub default: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum McpElicitationStringType { String, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "kebab-case")] #[ts(rename_all = "kebab-case", export_to = "v2/")] pub enum McpElicitationStringFormat { Email, Uri, Date, DateTime, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationNumberSchema { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationNumberType, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub minimum: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub maximum: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub default: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum McpElicitationNumberType { Number, Integer, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationBooleanSchema { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationBooleanType, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub default: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum McpElicitationBooleanType { Boolean, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(untagged)] #[ts(export_to = "v2/")] pub enum McpElicitationEnumSchema { SingleSelect(McpElicitationSingleSelectEnumSchema), MultiSelect(McpElicitationMultiSelectEnumSchema), Legacy(McpElicitationLegacyTitledEnumSchema), } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationLegacyTitledEnumSchema { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationStringType, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(rename = "enum")] #[ts(rename = "enum")] pub enum_: Vec, #[serde(rename = "enumNames", skip_serializing_if = "Option::is_none")] #[ts(optional, rename = "enumNames")] pub enum_names: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub default: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(untagged)] #[ts(export_to = "v2/")] pub enum McpElicitationSingleSelectEnumSchema { Untitled(McpElicitationUntitledSingleSelectEnumSchema), Titled(McpElicitationTitledSingleSelectEnumSchema), } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationUntitledSingleSelectEnumSchema { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationStringType, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(rename = "enum")] #[ts(rename = "enum")] pub enum_: Vec, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub default: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationTitledSingleSelectEnumSchema { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationStringType, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(rename = "oneOf")] #[ts(rename = "oneOf")] pub one_of: Vec, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub default: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(untagged)] #[ts(export_to = "v2/")] pub enum McpElicitationMultiSelectEnumSchema { Untitled(McpElicitationUntitledMultiSelectEnumSchema), Titled(McpElicitationTitledMultiSelectEnumSchema), } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationUntitledMultiSelectEnumSchema { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationArrayType, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub min_items: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub max_items: Option, pub items: McpElicitationUntitledEnumItems, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub default: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationTitledMultiSelectEnumSchema { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationArrayType, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub min_items: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub max_items: Option, pub items: McpElicitationTitledEnumItems, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] pub default: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum McpElicitationArrayType { Array, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationUntitledEnumItems { #[serde(rename = "type")] #[ts(rename = "type")] pub type_: McpElicitationStringType, #[serde(rename = "enum")] #[ts(rename = "enum")] pub enum_: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationTitledEnumItems { #[serde(rename = "anyOf", alias = "oneOf")] #[ts(rename = "anyOf")] pub any_of: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(deny_unknown_fields)] #[ts(export_to = "v2/")] pub struct McpElicitationConstOption { #[serde(rename = "const")] #[ts(rename = "const")] pub const_: String, pub title: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "mode", rename_all = "camelCase")] #[ts(tag = "mode")] #[ts(export_to = "v2/")] pub enum McpServerElicitationRequest { #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Form { #[serde(rename = "_meta")] #[ts(rename = "_meta")] meta: Option, message: String, requested_schema: McpElicitationSchema, }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] Url { #[serde(rename = "_meta")] #[ts(rename = "_meta")] meta: Option, message: String, url: String, elicitation_id: String, }, } impl TryFrom for McpServerElicitationRequest { type Error = serde_json::Error; fn try_from(value: CoreElicitationRequest) -> Result { match value { CoreElicitationRequest::Form { meta, message, requested_schema, } => Ok(Self::Form { meta, message, requested_schema: serde_json::from_value(requested_schema)?, }), CoreElicitationRequest::Url { meta, message, url, elicitation_id, } => Ok(Self::Url { meta, message, url, elicitation_id, }), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct McpServerElicitationRequestResponse { pub action: McpServerElicitationAction, /// Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`. /// /// This is nullable because decline/cancel responses have no content. pub content: Option, /// Optional client metadata for form-mode action handling. #[serde(rename = "_meta")] #[ts(rename = "_meta")] pub meta: Option, } impl From for rmcp::model::CreateElicitationResult { fn from(value: McpServerElicitationRequestResponse) -> Self { Self { action: value.action.into(), content: value.content, } } } impl From for McpServerElicitationRequestResponse { fn from(value: rmcp::model::CreateElicitationResult) -> Self { Self { action: value.action.into(), content: value.content, meta: None, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct DynamicToolCallParams { pub thread_id: String, pub turn_id: String, pub call_id: String, pub tool: String, pub arguments: JsonValue, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PermissionsRequestApprovalParams { pub thread_id: String, pub turn_id: String, pub item_id: String, pub reason: Option, pub permissions: RequestPermissionProfile, } v2_enum_from_core!( #[derive(Default)] pub enum PermissionGrantScope from CorePermissionGrantScope { #[default] Turn, Session } ); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct PermissionsRequestApprovalResponse { pub permissions: GrantedPermissionProfile, #[serde(default)] pub scope: PermissionGrantScope, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct DynamicToolCallResponse { pub content_items: Vec, pub success: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum DynamicToolCallOutputContentItem { #[serde(rename_all = "camelCase")] InputText { text: String }, #[serde(rename_all = "camelCase")] InputImage { image_url: String }, } impl From for codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem { fn from(item: DynamicToolCallOutputContentItem) -> Self { match item { DynamicToolCallOutputContentItem::InputText { text } => Self::InputText { text }, DynamicToolCallOutputContentItem::InputImage { image_url } => { Self::InputImage { image_url } } } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL. Defines a single selectable option for request_user_input. pub struct ToolRequestUserInputOption { pub label: String, pub description: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL. Represents one request_user_input question and its required options. pub struct ToolRequestUserInputQuestion { pub id: String, pub header: String, pub question: String, #[serde(default)] pub is_other: bool, #[serde(default)] pub is_secret: bool, pub options: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL. Params sent with a request_user_input event. pub struct ToolRequestUserInputParams { pub thread_id: String, pub turn_id: String, pub item_id: String, pub questions: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL. Captures a user's answer to a request_user_input question. pub struct ToolRequestUserInputAnswer { pub answers: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] /// EXPERIMENTAL. Response payload mapping question ids to answers. pub struct ToolRequestUserInputResponse { pub answers: HashMap, } #[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 limit_id: Option, pub limit_name: Option, pub primary: Option, pub secondary: Option, pub credits: Option, pub plan_type: Option, } impl From for RateLimitSnapshot { fn from(value: CoreRateLimitSnapshot) -> Self { Self { limit_id: value.limit_id, limit_name: value.limit_name, primary: value.primary.map(RateLimitWindow::from), secondary: value.secondary.map(RateLimitWindow::from), credits: value.credits.map(CreditsSnapshot::from), plan_type: value.plan_type, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct RateLimitWindow { pub used_percent: i32, #[ts(type = "number | null")] pub window_duration_mins: Option, #[ts(type = "number | null")] 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, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ModelReroutedNotification { pub thread_id: String, pub turn_id: String, pub from_model: String, pub to_model: String, pub reason: ModelRerouteReason, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct DeprecationNoticeNotification { /// Concise summary of what is deprecated. pub summary: String, /// Optional extra guidance, such as migration steps or rationale. pub details: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TextPosition { /// 1-based line number. pub line: usize, /// 1-based column number (in Unicode scalar values). pub column: usize, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct TextRange { pub start: TextPosition, pub end: TextPosition, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigWarningNotification { /// Concise summary of the warning. pub summary: String, /// Optional extra guidance or error details. pub details: Option, /// Optional path to the config file that triggered the warning. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub path: Option, /// Optional range for the error location inside the config file. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub range: 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::models::WebSearchAction as CoreWebSearchAction; use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess; use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess; use codex_protocol::user_input::UserInput as CoreUserInput; use pretty_assertions::assert_eq; use serde_json::json; use std::path::PathBuf; fn absolute_path_string(path: &str) -> String { let trimmed = path.trim_start_matches('/'); if cfg!(windows) { format!(r"C:\{}", trimmed.replace('/', "\\")) } else { format!("/{trimmed}") } } fn absolute_path(path: &str) -> AbsolutePathBuf { AbsolutePathBuf::from_absolute_path(absolute_path_string(path)) .expect("path must be absolute") } fn test_absolute_path() -> AbsolutePathBuf { absolute_path("readable") } #[test] fn collab_agent_state_maps_interrupted_status() { assert_eq!( CollabAgentState::from(CoreAgentStatus::Interrupted), CollabAgentState { status: CollabAgentStatus::Interrupted, message: None, } ); } #[test] fn command_execution_request_approval_rejects_relative_additional_permission_paths() { let err = serde_json::from_value::(json!({ "threadId": "thr_123", "turnId": "turn_123", "itemId": "call_123", "command": "cat file", "cwd": "/tmp", "commandActions": null, "reason": null, "networkApprovalContext": null, "additionalPermissions": { "network": null, "fileSystem": { "read": ["relative/path"], "write": null } }, "proposedExecpolicyAmendment": null, "proposedNetworkPolicyAmendments": null, "availableDecisions": null })) .expect_err("relative additional permission paths should fail"); assert!( err.to_string() .contains("AbsolutePathBuf deserialized without a base path"), "unexpected error: {err}" ); } #[test] fn permissions_request_approval_uses_request_permission_profile() { let read_only_path = if cfg!(windows) { r"C:\tmp\read-only" } else { "/tmp/read-only" }; let read_write_path = if cfg!(windows) { r"C:\tmp\read-write" } else { "/tmp/read-write" }; let params = serde_json::from_value::(json!({ "threadId": "thr_123", "turnId": "turn_123", "itemId": "call_123", "reason": "Select a workspace root", "permissions": { "network": { "enabled": true, }, "fileSystem": { "read": [read_only_path], "write": [read_write_path], }, }, })) .expect("permissions request should deserialize"); assert_eq!( params.permissions, RequestPermissionProfile { network: Some(AdditionalNetworkPermissions { enabled: Some(true), }), file_system: Some(AdditionalFileSystemPermissions { read: Some(vec![ AbsolutePathBuf::try_from(PathBuf::from(read_only_path)) .expect("path must be absolute"), ]), write: Some(vec![ AbsolutePathBuf::try_from(PathBuf::from(read_write_path)) .expect("path must be absolute"), ]), }), } ); assert_eq!( CoreRequestPermissionProfile::from(params.permissions), CoreRequestPermissionProfile { network: Some(CoreNetworkPermissions { enabled: Some(true), }), file_system: Some(CoreFileSystemPermissions { read: Some(vec![ AbsolutePathBuf::try_from(PathBuf::from(read_only_path)) .expect("path must be absolute"), ]), write: Some(vec![ AbsolutePathBuf::try_from(PathBuf::from(read_write_path)) .expect("path must be absolute"), ]), }), } ); } #[test] fn permissions_request_approval_rejects_macos_permissions() { let err = serde_json::from_value::(json!({ "threadId": "thr_123", "turnId": "turn_123", "itemId": "call_123", "reason": "Select a workspace root", "permissions": { "network": null, "fileSystem": null, "macos": { "preferences": "read_only", "automations": "none", "launchServices": false, "accessibility": false, "calendar": false, "reminders": false, "contacts": "none", }, }, })) .expect_err("permissions request should reject macos permissions"); assert!( err.to_string().contains("unknown field `macos`"), "unexpected error: {err}" ); } #[test] fn permissions_request_approval_response_uses_granted_permission_profile_without_macos() { let read_only_path = if cfg!(windows) { r"C:\tmp\read-only" } else { "/tmp/read-only" }; let read_write_path = if cfg!(windows) { r"C:\tmp\read-write" } else { "/tmp/read-write" }; let response = serde_json::from_value::(json!({ "permissions": { "network": { "enabled": true, }, "fileSystem": { "read": [read_only_path], "write": [read_write_path], }, }, })) .expect("permissions response should deserialize"); assert_eq!( response.permissions, GrantedPermissionProfile { network: Some(AdditionalNetworkPermissions { enabled: Some(true), }), file_system: Some(AdditionalFileSystemPermissions { read: Some(vec![ AbsolutePathBuf::try_from(PathBuf::from(read_only_path)) .expect("path must be absolute"), ]), write: Some(vec![ AbsolutePathBuf::try_from(PathBuf::from(read_write_path)) .expect("path must be absolute"), ]), }), } ); assert_eq!( CorePermissionProfile::from(response.permissions), CorePermissionProfile { network: Some(CoreNetworkPermissions { enabled: Some(true), }), file_system: Some(CoreFileSystemPermissions { read: Some(vec![ AbsolutePathBuf::try_from(PathBuf::from(read_only_path)) .expect("path must be absolute"), ]), write: Some(vec![ AbsolutePathBuf::try_from(PathBuf::from(read_write_path)) .expect("path must be absolute"), ]), }), } ); } #[test] fn permissions_request_approval_response_defaults_scope_to_turn() { let response = serde_json::from_value::(json!({ "permissions": {}, })) .expect("response should deserialize"); assert_eq!(response.scope, PermissionGrantScope::Turn); } #[test] fn fs_get_metadata_response_round_trips_minimal_fields() { let response = FsGetMetadataResponse { is_directory: false, is_file: true, created_at_ms: 123, modified_at_ms: 456, }; let value = serde_json::to_value(&response).expect("serialize fs/getMetadata response"); assert_eq!( value, json!({ "isDirectory": false, "isFile": true, "createdAtMs": 123, "modifiedAtMs": 456, }) ); let decoded = serde_json::from_value::(value) .expect("deserialize fs/getMetadata response"); assert_eq!(decoded, response); } #[test] fn fs_read_file_response_round_trips_base64_data() { let response = FsReadFileResponse { data_base64: "aGVsbG8=".to_string(), }; let value = serde_json::to_value(&response).expect("serialize fs/readFile response"); assert_eq!( value, json!({ "dataBase64": "aGVsbG8=", }) ); let decoded = serde_json::from_value::(value) .expect("deserialize fs/readFile response"); assert_eq!(decoded, response); } #[test] fn fs_read_file_params_round_trip() { let params = FsReadFileParams { path: absolute_path("tmp/example.txt"), }; let value = serde_json::to_value(¶ms).expect("serialize fs/readFile params"); assert_eq!( value, json!({ "path": absolute_path_string("tmp/example.txt"), }) ); let decoded = serde_json::from_value::(value) .expect("deserialize fs/readFile params"); assert_eq!(decoded, params); } #[test] fn fs_create_directory_params_round_trip_with_default_recursive() { let params = FsCreateDirectoryParams { path: absolute_path("tmp/example"), recursive: None, }; let value = serde_json::to_value(¶ms).expect("serialize fs/createDirectory params"); assert_eq!( value, json!({ "path": absolute_path_string("tmp/example"), "recursive": null, }) ); let decoded = serde_json::from_value::(value) .expect("deserialize fs/createDirectory params"); assert_eq!(decoded, params); } #[test] fn fs_write_file_params_round_trip_with_base64_data() { let params = FsWriteFileParams { path: absolute_path("tmp/example.bin"), data_base64: "AAE=".to_string(), }; let value = serde_json::to_value(¶ms).expect("serialize fs/writeFile params"); assert_eq!( value, json!({ "path": absolute_path_string("tmp/example.bin"), "dataBase64": "AAE=", }) ); let decoded = serde_json::from_value::(value) .expect("deserialize fs/writeFile params"); assert_eq!(decoded, params); } #[test] fn fs_copy_params_round_trip_with_recursive_directory_copy() { let params = FsCopyParams { source_path: absolute_path("tmp/source"), destination_path: absolute_path("tmp/destination"), recursive: true, }; let value = serde_json::to_value(¶ms).expect("serialize fs/copy params"); assert_eq!( value, json!({ "sourcePath": absolute_path_string("tmp/source"), "destinationPath": absolute_path_string("tmp/destination"), "recursive": true, }) ); let decoded = serde_json::from_value::(value).expect("deserialize fs/copy params"); assert_eq!(decoded, params); } #[test] fn thread_shell_command_params_round_trip() { let params = ThreadShellCommandParams { thread_id: "thr_123".to_string(), command: "printf 'hello world\\n'".to_string(), }; let value = serde_json::to_value(¶ms).expect("serialize thread/shellCommand params"); assert_eq!( value, json!({ "threadId": "thr_123", "command": "printf 'hello world\\n'", }) ); let decoded = serde_json::from_value::(value) .expect("deserialize thread/shellCommand params"); assert_eq!(decoded, params); } #[test] fn thread_shell_command_response_round_trip() { let response = ThreadShellCommandResponse {}; let value = serde_json::to_value(&response).expect("serialize thread/shellCommand response"); assert_eq!(value, json!({})); let decoded = serde_json::from_value::(value) .expect("deserialize thread/shellCommand response"); assert_eq!(decoded, response); } #[test] fn fs_changed_notification_round_trips() { let notification = FsChangedNotification { watch_id: "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1".to_string(), changed_paths: vec![ absolute_path("tmp/repo/.git/HEAD"), absolute_path("tmp/repo/.git/FETCH_HEAD"), ], }; let value = serde_json::to_value(¬ification).expect("serialize fs/changed notification"); assert_eq!( value, json!({ "watchId": "0195ec6b-1d6f-7c2e-8c7a-56f2c4a8b9d1", "changedPaths": [ absolute_path_string("tmp/repo/.git/HEAD"), absolute_path_string("tmp/repo/.git/FETCH_HEAD"), ], }) ); let decoded = serde_json::from_value::(value) .expect("deserialize fs/changed notification"); assert_eq!(decoded, notification); } #[test] fn command_exec_params_default_optional_streaming_flags() { let params = serde_json::from_value::(json!({ "command": ["ls", "-la"], "timeoutMs": 1000, "cwd": "/tmp" })) .expect("command/exec payload should deserialize"); assert_eq!( params, CommandExecParams { command: vec!["ls".to_string(), "-la".to_string()], process_id: None, tty: false, stream_stdin: false, stream_stdout_stderr: false, output_bytes_cap: None, disable_output_cap: false, disable_timeout: false, timeout_ms: Some(1000), cwd: Some(PathBuf::from("/tmp")), env: None, size: None, sandbox_policy: None, } ); } #[test] fn command_exec_params_round_trips_disable_timeout() { let params = CommandExecParams { command: vec!["sleep".to_string(), "30".to_string()], process_id: Some("sleep-1".to_string()), tty: false, stream_stdin: false, stream_stdout_stderr: false, output_bytes_cap: None, disable_output_cap: false, disable_timeout: true, timeout_ms: None, cwd: None, env: None, size: None, sandbox_policy: None, }; let value = serde_json::to_value(¶ms).expect("serialize command/exec params"); assert_eq!( value, json!({ "command": ["sleep", "30"], "processId": "sleep-1", "disableTimeout": true, "timeoutMs": null, "cwd": null, "env": null, "size": null, "sandboxPolicy": null, "outputBytesCap": null, }) ); let decoded = serde_json::from_value::(value).expect("deserialize round-trip"); assert_eq!(decoded, params); } #[test] fn command_exec_params_round_trips_disable_output_cap() { let params = CommandExecParams { command: vec!["yes".to_string()], process_id: Some("yes-1".to_string()), tty: false, stream_stdin: false, stream_stdout_stderr: true, output_bytes_cap: None, disable_output_cap: true, disable_timeout: false, timeout_ms: None, cwd: None, env: None, size: None, sandbox_policy: None, }; let value = serde_json::to_value(¶ms).expect("serialize command/exec params"); assert_eq!( value, json!({ "command": ["yes"], "processId": "yes-1", "streamStdoutStderr": true, "outputBytesCap": null, "disableOutputCap": true, "timeoutMs": null, "cwd": null, "env": null, "size": null, "sandboxPolicy": null, }) ); let decoded = serde_json::from_value::(value).expect("deserialize round-trip"); assert_eq!(decoded, params); } #[test] fn command_exec_params_round_trips_env_overrides_and_unsets() { let params = CommandExecParams { command: vec!["printenv".to_string(), "FOO".to_string()], process_id: Some("env-1".to_string()), tty: false, stream_stdin: false, stream_stdout_stderr: false, output_bytes_cap: None, disable_output_cap: false, disable_timeout: false, timeout_ms: None, cwd: None, env: Some(HashMap::from([ ("FOO".to_string(), Some("override".to_string())), ("BAR".to_string(), Some("added".to_string())), ("BAZ".to_string(), None), ])), size: None, sandbox_policy: None, }; let value = serde_json::to_value(¶ms).expect("serialize command/exec params"); assert_eq!( value, json!({ "command": ["printenv", "FOO"], "processId": "env-1", "outputBytesCap": null, "timeoutMs": null, "cwd": null, "env": { "FOO": "override", "BAR": "added", "BAZ": null, }, "size": null, "sandboxPolicy": null, }) ); let decoded = serde_json::from_value::(value).expect("deserialize round-trip"); assert_eq!(decoded, params); } #[test] fn command_exec_write_round_trips_close_only_payload() { let params = CommandExecWriteParams { process_id: "proc-7".to_string(), delta_base64: None, close_stdin: true, }; let value = serde_json::to_value(¶ms).expect("serialize command/exec/write params"); assert_eq!( value, json!({ "processId": "proc-7", "deltaBase64": null, "closeStdin": true, }) ); let decoded = serde_json::from_value::(value) .expect("deserialize round-trip"); assert_eq!(decoded, params); } #[test] fn command_exec_terminate_round_trips() { let params = CommandExecTerminateParams { process_id: "proc-8".to_string(), }; let value = serde_json::to_value(¶ms).expect("serialize command/exec/terminate params"); assert_eq!( value, json!({ "processId": "proc-8", }) ); let decoded = serde_json::from_value::(value) .expect("deserialize round-trip"); assert_eq!(decoded, params); } #[test] fn command_exec_params_round_trip_with_size() { let params = CommandExecParams { command: vec!["top".to_string()], process_id: Some("pty-1".to_string()), tty: true, stream_stdin: false, stream_stdout_stderr: false, output_bytes_cap: None, disable_output_cap: false, disable_timeout: false, timeout_ms: None, cwd: None, env: None, size: Some(CommandExecTerminalSize { rows: 40, cols: 120, }), sandbox_policy: None, }; let value = serde_json::to_value(¶ms).expect("serialize command/exec params"); assert_eq!( value, json!({ "command": ["top"], "processId": "pty-1", "tty": true, "outputBytesCap": null, "timeoutMs": null, "cwd": null, "env": null, "size": { "rows": 40, "cols": 120, }, "sandboxPolicy": null, }) ); let decoded = serde_json::from_value::(value).expect("deserialize round-trip"); assert_eq!(decoded, params); } #[test] fn command_exec_resize_round_trips() { let params = CommandExecResizeParams { process_id: "proc-9".to_string(), size: CommandExecTerminalSize { rows: 50, cols: 160, }, }; let value = serde_json::to_value(¶ms).expect("serialize command/exec/resize params"); assert_eq!( value, json!({ "processId": "proc-9", "size": { "rows": 50, "cols": 160, }, }) ); let decoded = serde_json::from_value::(value) .expect("deserialize round-trip"); assert_eq!(decoded, params); } #[test] fn command_exec_output_delta_round_trips() { let notification = CommandExecOutputDeltaNotification { process_id: "proc-1".to_string(), stream: CommandExecOutputStream::Stdout, delta_base64: "AQI=".to_string(), cap_reached: false, }; let value = serde_json::to_value(¬ification) .expect("serialize command/exec/outputDelta notification"); assert_eq!( value, json!({ "processId": "proc-1", "stream": "stdout", "deltaBase64": "AQI=", "capReached": false, }) ); let decoded = serde_json::from_value::(value) .expect("deserialize round-trip"); assert_eq!(decoded, notification); } #[test] fn command_execution_output_delta_round_trips() { let notification = CommandExecutionOutputDeltaNotification { thread_id: "thread-1".to_string(), turn_id: "turn-1".to_string(), item_id: "item-1".to_string(), delta: "\u{fffd}a\n".to_string(), }; let value = serde_json::to_value(¬ification) .expect("serialize item/commandExecution/outputDelta notification"); assert_eq!( value, json!({ "threadId": "thread-1", "turnId": "turn-1", "itemId": "item-1", "delta": "\u{fffd}a\n", }) ); let decoded = serde_json::from_value::(value) .expect("deserialize round-trip"); assert_eq!(decoded, notification); } #[test] fn sandbox_policy_round_trips_external_sandbox_network_access() { let v2_policy = SandboxPolicy::ExternalSandbox { network_access: NetworkAccess::Enabled, }; let core_policy = v2_policy.to_core(); assert_eq!( core_policy, codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access: CoreNetworkAccess::Enabled, } ); let back_to_v2 = SandboxPolicy::from(core_policy); assert_eq!(back_to_v2, v2_policy); } #[test] fn sandbox_policy_round_trips_read_only_access() { let readable_root = test_absolute_path(); let v2_policy = SandboxPolicy::ReadOnly { access: ReadOnlyAccess::Restricted { include_platform_defaults: false, readable_roots: vec![readable_root.clone()], }, network_access: true, }; let core_policy = v2_policy.to_core(); assert_eq!( core_policy, codex_protocol::protocol::SandboxPolicy::ReadOnly { access: CoreReadOnlyAccess::Restricted { include_platform_defaults: false, readable_roots: vec![readable_root], }, network_access: true, } ); let back_to_v2 = SandboxPolicy::from(core_policy); assert_eq!(back_to_v2, v2_policy); } #[test] fn ask_for_approval_granular_round_trips_request_permissions_flag() { let v2_policy = AskForApproval::Granular { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: true, mcp_elicitations: false, }; let core_policy = v2_policy.to_core(); assert_eq!( core_policy, CoreAskForApproval::Granular(CoreGranularApprovalConfig { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: true, mcp_elicitations: false, }) ); let back_to_v2 = AskForApproval::from(core_policy); assert_eq!(back_to_v2, v2_policy); } #[test] fn ask_for_approval_granular_defaults_missing_optional_flags_to_false() { let decoded = serde_json::from_value::(serde_json::json!({ "granular": { "sandbox_approval": true, "rules": false, "mcp_elicitations": true, } })) .expect("granular approval policy should deserialize"); assert_eq!( decoded, AskForApproval::Granular { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: false, mcp_elicitations: true, } ); } #[test] fn ask_for_approval_granular_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason( &AskForApproval::Granular { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: false, mcp_elicitations: true, }, ); assert_eq!(reason, Some("askForApproval.granular")); assert_eq!( crate::experimental_api::ExperimentalApi::experimental_reason( &AskForApproval::OnRequest, ), None ); } #[test] fn profile_v2_granular_approval_policy_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&ProfileV2 { model: None, model_provider: None, approval_policy: Some(AskForApproval::Granular { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: true, mcp_elicitations: false, }), approvals_reviewer: None, service_tier: None, model_reasoning_effort: None, model_reasoning_summary: None, model_verbosity: None, web_search: None, tools: None, chatgpt_base_url: None, additional: HashMap::new(), }); assert_eq!(reason, Some("askForApproval.granular")); } #[test] fn config_granular_approval_policy_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&Config { model: None, review_model: None, model_context_window: None, model_auto_compact_token_limit: None, model_provider: None, approval_policy: Some(AskForApproval::Granular { sandbox_approval: false, rules: true, skill_approval: false, request_permissions: false, mcp_elicitations: true, }), approvals_reviewer: None, sandbox_mode: None, sandbox_workspace_write: None, forced_chatgpt_workspace_id: None, forced_login_method: None, web_search: None, tools: None, profile: None, profiles: HashMap::new(), instructions: None, developer_instructions: None, compact_prompt: None, model_reasoning_effort: None, model_reasoning_summary: None, model_verbosity: None, service_tier: None, analytics: None, apps: None, additional: HashMap::new(), }); assert_eq!(reason, Some("askForApproval.granular")); } #[test] fn config_approvals_reviewer_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&Config { model: None, review_model: None, model_context_window: None, model_auto_compact_token_limit: None, model_provider: None, approval_policy: None, approvals_reviewer: Some(ApprovalsReviewer::GuardianSubagent), sandbox_mode: None, sandbox_workspace_write: None, forced_chatgpt_workspace_id: None, forced_login_method: None, web_search: None, tools: None, profile: None, profiles: HashMap::new(), instructions: None, developer_instructions: None, compact_prompt: None, model_reasoning_effort: None, model_reasoning_summary: None, model_verbosity: None, service_tier: None, analytics: None, apps: None, additional: HashMap::new(), }); assert_eq!(reason, Some("config/read.approvalsReviewer")); } #[test] fn config_nested_profile_granular_approval_policy_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&Config { model: None, review_model: None, model_context_window: None, model_auto_compact_token_limit: None, model_provider: None, approval_policy: None, approvals_reviewer: None, sandbox_mode: None, sandbox_workspace_write: None, forced_chatgpt_workspace_id: None, forced_login_method: None, web_search: None, tools: None, profile: None, profiles: HashMap::from([( "default".to_string(), ProfileV2 { model: None, model_provider: None, approval_policy: Some(AskForApproval::Granular { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: false, mcp_elicitations: true, }), approvals_reviewer: None, service_tier: None, model_reasoning_effort: None, model_reasoning_summary: None, model_verbosity: None, web_search: None, tools: None, chatgpt_base_url: None, additional: HashMap::new(), }, )]), instructions: None, developer_instructions: None, compact_prompt: None, model_reasoning_effort: None, model_reasoning_summary: None, model_verbosity: None, service_tier: None, analytics: None, apps: None, additional: HashMap::new(), }); assert_eq!(reason, Some("askForApproval.granular")); } #[test] fn config_nested_profile_approvals_reviewer_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&Config { model: None, review_model: None, model_context_window: None, model_auto_compact_token_limit: None, model_provider: None, approval_policy: None, approvals_reviewer: None, sandbox_mode: None, sandbox_workspace_write: None, forced_chatgpt_workspace_id: None, forced_login_method: None, web_search: None, tools: None, profile: None, profiles: HashMap::from([( "default".to_string(), ProfileV2 { model: None, model_provider: None, approval_policy: None, approvals_reviewer: Some(ApprovalsReviewer::GuardianSubagent), service_tier: None, model_reasoning_effort: None, model_reasoning_summary: None, model_verbosity: None, web_search: None, tools: None, chatgpt_base_url: None, additional: HashMap::new(), }, )]), instructions: None, developer_instructions: None, compact_prompt: None, model_reasoning_effort: None, model_reasoning_summary: None, model_verbosity: None, service_tier: None, analytics: None, apps: None, additional: HashMap::new(), }); assert_eq!(reason, Some("config/read.approvalsReviewer")); } #[test] fn config_requirements_granular_allowed_approval_policy_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&ConfigRequirements { allowed_approval_policies: Some(vec![AskForApproval::Granular { sandbox_approval: true, rules: true, skill_approval: false, request_permissions: false, mcp_elicitations: false, }]), allowed_sandbox_modes: None, allowed_web_search_modes: None, feature_requirements: None, enforce_residency: None, network: None, }); assert_eq!(reason, Some("askForApproval.granular")); } #[test] fn client_request_thread_start_granular_approval_policy_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason( &crate::ClientRequest::ThreadStart { request_id: crate::RequestId::Integer(1), params: ThreadStartParams { approval_policy: Some(AskForApproval::Granular { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: true, mcp_elicitations: false, }), ..Default::default() }, }, ); assert_eq!(reason, Some("askForApproval.granular")); } #[test] fn client_request_thread_resume_granular_approval_policy_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason( &crate::ClientRequest::ThreadResume { request_id: crate::RequestId::Integer(2), params: ThreadResumeParams { thread_id: "thr_123".to_string(), approval_policy: Some(AskForApproval::Granular { sandbox_approval: false, rules: true, skill_approval: false, request_permissions: false, mcp_elicitations: true, }), ..Default::default() }, }, ); assert_eq!(reason, Some("askForApproval.granular")); } #[test] fn client_request_thread_fork_granular_approval_policy_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason( &crate::ClientRequest::ThreadFork { request_id: crate::RequestId::Integer(3), params: ThreadForkParams { thread_id: "thr_456".to_string(), approval_policy: Some(AskForApproval::Granular { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: false, mcp_elicitations: true, }), ..Default::default() }, }, ); assert_eq!(reason, Some("askForApproval.granular")); } #[test] fn client_request_turn_start_granular_approval_policy_is_marked_experimental() { let reason = crate::experimental_api::ExperimentalApi::experimental_reason( &crate::ClientRequest::TurnStart { request_id: crate::RequestId::Integer(4), params: TurnStartParams { thread_id: "thr_123".to_string(), input: Vec::new(), approval_policy: Some(AskForApproval::Granular { sandbox_approval: false, rules: true, skill_approval: false, request_permissions: false, mcp_elicitations: true, }), ..Default::default() }, }, ); assert_eq!(reason, Some("askForApproval.granular")); } #[test] fn mcp_server_elicitation_response_round_trips_rmcp_result() { let rmcp_result = rmcp::model::CreateElicitationResult { action: rmcp::model::ElicitationAction::Accept, content: Some(json!({ "confirmed": true, })), }; let v2_response = McpServerElicitationRequestResponse::from(rmcp_result.clone()); assert_eq!( v2_response, McpServerElicitationRequestResponse { action: McpServerElicitationAction::Accept, content: Some(json!({ "confirmed": true, })), meta: None, } ); assert_eq!( rmcp::model::CreateElicitationResult::from(v2_response), rmcp_result ); } #[test] fn mcp_server_elicitation_request_from_core_url_request() { let request = McpServerElicitationRequest::try_from(CoreElicitationRequest::Url { meta: None, message: "Finish sign-in".to_string(), url: "https://example.com/complete".to_string(), elicitation_id: "elicitation-123".to_string(), }) .expect("URL request should convert"); assert_eq!( request, McpServerElicitationRequest::Url { meta: None, message: "Finish sign-in".to_string(), url: "https://example.com/complete".to_string(), elicitation_id: "elicitation-123".to_string(), } ); } #[test] fn mcp_server_elicitation_request_from_core_form_request() { let request = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form { meta: None, message: "Allow this request?".to_string(), requested_schema: json!({ "type": "object", "properties": { "confirmed": { "type": "boolean", } }, "required": ["confirmed"], }), }) .expect("form request should convert"); let expected_schema: McpElicitationSchema = serde_json::from_value(json!({ "type": "object", "properties": { "confirmed": { "type": "boolean", } }, "required": ["confirmed"], })) .expect("expected schema should deserialize"); assert_eq!( request, McpServerElicitationRequest::Form { meta: None, message: "Allow this request?".to_string(), requested_schema: expected_schema, } ); } #[test] fn mcp_elicitation_schema_matches_mcp_2025_11_25_primitives() { let schema: McpElicitationSchema = serde_json::from_value(json!({ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "email": { "type": "string", "title": "Email", "description": "Work email address", "format": "email", "default": "dev@example.com", }, "count": { "type": "integer", "title": "Count", "description": "How many items to create", "minimum": 1, "maximum": 5, "default": 3, }, "confirmed": { "type": "boolean", "title": "Confirm", "description": "Approve the pending action", "default": true, }, "legacyChoice": { "type": "string", "title": "Action", "description": "Legacy titled enum form", "enum": ["allow", "deny"], "enumNames": ["Allow", "Deny"], "default": "allow", }, }, "required": ["email", "confirmed"], })) .expect("schema should deserialize"); assert_eq!( schema, McpElicitationSchema { schema_uri: Some("https://json-schema.org/draft/2020-12/schema".to_string()), type_: McpElicitationObjectType::Object, properties: BTreeMap::from([ ( "confirmed".to_string(), McpElicitationPrimitiveSchema::Boolean(McpElicitationBooleanSchema { type_: McpElicitationBooleanType::Boolean, title: Some("Confirm".to_string()), description: Some("Approve the pending action".to_string()), default: Some(true), }), ), ( "count".to_string(), McpElicitationPrimitiveSchema::Number(McpElicitationNumberSchema { type_: McpElicitationNumberType::Integer, title: Some("Count".to_string()), description: Some("How many items to create".to_string()), minimum: Some(1.0), maximum: Some(5.0), default: Some(3.0), }), ), ( "email".to_string(), McpElicitationPrimitiveSchema::String(McpElicitationStringSchema { type_: McpElicitationStringType::String, title: Some("Email".to_string()), description: Some("Work email address".to_string()), min_length: None, max_length: None, format: Some(McpElicitationStringFormat::Email), default: Some("dev@example.com".to_string()), }), ), ( "legacyChoice".to_string(), McpElicitationPrimitiveSchema::Enum(McpElicitationEnumSchema::Legacy( McpElicitationLegacyTitledEnumSchema { type_: McpElicitationStringType::String, title: Some("Action".to_string()), description: Some("Legacy titled enum form".to_string()), enum_: vec!["allow".to_string(), "deny".to_string()], enum_names: Some(vec!["Allow".to_string(), "Deny".to_string(),]), default: Some("allow".to_string()), }, )), ), ]), required: Some(vec!["email".to_string(), "confirmed".to_string()]), } ); } #[test] fn mcp_server_elicitation_request_rejects_null_core_form_schema() { let result = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form { meta: Some(json!({ "persist": "session", })), message: "Allow this request?".to_string(), requested_schema: JsonValue::Null, }); assert!(result.is_err()); } #[test] fn mcp_server_elicitation_request_rejects_invalid_core_form_schema() { let result = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form { meta: None, message: "Allow this request?".to_string(), requested_schema: json!({ "type": "object", "properties": { "confirmed": { "type": "object", } }, }), }); assert!(result.is_err()); } #[test] fn mcp_server_elicitation_response_serializes_nullable_content() { let response = McpServerElicitationRequestResponse { action: McpServerElicitationAction::Decline, content: None, meta: None, }; assert_eq!( serde_json::to_value(response).expect("response should serialize"), json!({ "action": "decline", "content": null, "_meta": null, }) ); } #[test] fn sandbox_policy_round_trips_workspace_write_read_only_access() { let readable_root = test_absolute_path(); let v2_policy = SandboxPolicy::WorkspaceWrite { writable_roots: vec![], read_only_access: ReadOnlyAccess::Restricted { include_platform_defaults: false, readable_roots: vec![readable_root.clone()], }, network_access: true, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, }; let core_policy = v2_policy.to_core(); assert_eq!( core_policy, codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { writable_roots: vec![], read_only_access: CoreReadOnlyAccess::Restricted { include_platform_defaults: false, readable_roots: vec![readable_root], }, network_access: true, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, } ); let back_to_v2 = SandboxPolicy::from(core_policy); assert_eq!(back_to_v2, v2_policy); } #[test] fn sandbox_policy_deserializes_legacy_read_only_without_access_field() { let policy: SandboxPolicy = serde_json::from_value(json!({ "type": "readOnly" })) .expect("read-only policy should deserialize"); assert_eq!( policy, SandboxPolicy::ReadOnly { access: ReadOnlyAccess::FullAccess, network_access: false, } ); } #[test] fn sandbox_policy_deserializes_legacy_workspace_write_without_read_only_access_field() { let policy: SandboxPolicy = serde_json::from_value(json!({ "type": "workspaceWrite", "writableRoots": [], "networkAccess": false, "excludeTmpdirEnvVar": false, "excludeSlashTmp": false })) .expect("workspace-write policy should deserialize"); assert_eq!( policy, SandboxPolicy::WorkspaceWrite { writable_roots: vec![], read_only_access: ReadOnlyAccess::FullAccess, network_access: false, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, } ); } #[test] fn automatic_approval_review_deserializes_legacy_snake_case_risk_fields() { let review: GuardianApprovalReview = serde_json::from_value(json!({ "status": "denied", "risk_score": 91, "risk_level": "high", "rationale": "too risky" })) .expect("legacy snake_case automatic review should deserialize"); assert_eq!( review, GuardianApprovalReview { status: GuardianApprovalReviewStatus::Denied, risk_score: Some(91), risk_level: Some(GuardianRiskLevel::High), rationale: Some("too risky".to_string()), } ); } #[test] fn automatic_approval_review_deserializes_aborted_status() { let review: GuardianApprovalReview = serde_json::from_value(json!({ "status": "aborted", "riskScore": null, "riskLevel": null, "rationale": null })) .expect("aborted automatic review should deserialize"); assert_eq!( review, GuardianApprovalReview { status: GuardianApprovalReviewStatus::Aborted, risk_score: None, risk_level: None, rationale: None, } ); } #[test] fn network_requirements_deserializes_legacy_fields() { let requirements: NetworkRequirements = serde_json::from_value(json!({ "allowedDomains": ["api.openai.com"], "deniedDomains": ["blocked.example.com"], "allowUnixSockets": ["/tmp/proxy.sock"] })) .expect("legacy network requirements should deserialize"); assert_eq!( requirements, NetworkRequirements { enabled: None, http_port: None, socks_port: None, allow_upstream_proxy: None, dangerously_allow_non_loopback_proxy: None, dangerously_allow_all_unix_sockets: None, domains: None, managed_allowed_domains_only: None, allowed_domains: Some(vec!["api.openai.com".to_string()]), denied_domains: Some(vec!["blocked.example.com".to_string()]), unix_sockets: None, allow_unix_sockets: Some(vec!["/tmp/proxy.sock".to_string()]), allow_local_binding: None, } ); } #[test] fn network_requirements_serializes_canonical_and_legacy_fields() { let requirements = NetworkRequirements { enabled: Some(true), http_port: Some(8080), socks_port: Some(1080), allow_upstream_proxy: Some(false), dangerously_allow_non_loopback_proxy: Some(false), dangerously_allow_all_unix_sockets: Some(true), domains: Some(BTreeMap::from([ ("api.openai.com".to_string(), NetworkDomainPermission::Allow), ( "blocked.example.com".to_string(), NetworkDomainPermission::Deny, ), ])), managed_allowed_domains_only: Some(true), allowed_domains: Some(vec!["api.openai.com".to_string()]), denied_domains: Some(vec!["blocked.example.com".to_string()]), unix_sockets: Some(BTreeMap::from([ ( "/tmp/proxy.sock".to_string(), NetworkUnixSocketPermission::Allow, ), ( "/tmp/ignored.sock".to_string(), NetworkUnixSocketPermission::None, ), ])), allow_unix_sockets: Some(vec!["/tmp/proxy.sock".to_string()]), allow_local_binding: Some(true), }; assert_eq!( serde_json::to_value(requirements).expect("network requirements should serialize"), json!({ "enabled": true, "httpPort": 8080, "socksPort": 1080, "allowUpstreamProxy": false, "dangerouslyAllowNonLoopbackProxy": false, "dangerouslyAllowAllUnixSockets": true, "domains": { "api.openai.com": "allow", "blocked.example.com": "deny" }, "managedAllowedDomainsOnly": true, "allowedDomains": ["api.openai.com"], "deniedDomains": ["blocked.example.com"], "unixSockets": { "/tmp/ignored.sock": "none", "/tmp/proxy.sock": "allow" }, "allowUnixSockets": ["/tmp/proxy.sock"], "allowLocalBinding": true }) ); } #[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(), text_elements: Vec::new(), }, CoreUserInput::Image { image_url: "https://example.com/image.png".to_string(), }, CoreUserInput::LocalImage { path: PathBuf::from("local/image.png"), }, CoreUserInput::Skill { name: "skill-creator".to_string(), path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"), }, CoreUserInput::Mention { name: "Demo App".to_string(), path: "app://demo-app".to_string(), }, ], }); assert_eq!( ThreadItem::from(user_item), ThreadItem::UserMessage { id: "user-1".to_string(), content: vec![ UserInput::Text { text: "hello".to_string(), text_elements: Vec::new(), }, UserInput::Image { url: "https://example.com/image.png".to_string(), }, UserInput::LocalImage { path: PathBuf::from("local/image.png"), }, UserInput::Skill { name: "skill-creator".to_string(), path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"), }, UserInput::Mention { name: "Demo App".to_string(), path: "app://demo-app".to_string(), }, ], } ); 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(), }, ], phase: None, memory_citation: None, }); assert_eq!( ThreadItem::from(agent_item), ThreadItem::AgentMessage { id: "agent-1".to_string(), text: "Hello world".to_string(), phase: None, memory_citation: None, } ); let agent_item_with_phase = TurnItem::AgentMessage(AgentMessageItem { id: "agent-2".to_string(), content: vec![AgentMessageContent::Text { text: "final".to_string(), }], phase: Some(MessagePhase::FinalAnswer), memory_citation: Some(CoreMemoryCitation { entries: vec![CoreMemoryCitationEntry { path: "MEMORY.md".to_string(), line_start: 1, line_end: 2, note: "summary".to_string(), }], rollout_ids: vec!["rollout-1".to_string()], }), }); assert_eq!( ThreadItem::from(agent_item_with_phase), ThreadItem::AgentMessage { id: "agent-2".to_string(), text: "final".to_string(), phase: Some(MessagePhase::FinalAnswer), memory_citation: Some(MemoryCitation { entries: vec![MemoryCitationEntry { path: "MEMORY.md".to_string(), line_start: 1, line_end: 2, note: "summary".to_string(), }], thread_ids: vec!["rollout-1".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(), action: CoreWebSearchAction::Search { query: Some("docs".to_string()), queries: None, }, }); assert_eq!( ThreadItem::from(search_item), ThreadItem::WebSearch { id: "search-1".to_string(), query: "docs".to_string(), action: Some(WebSearchAction::Search { query: Some("docs".to_string()), queries: None, }), } ); } #[test] fn skills_list_params_serialization_uses_force_reload() { assert_eq!( serde_json::to_value(SkillsListParams { cwds: Vec::new(), force_reload: false, per_cwd_extra_user_roots: None, }) .unwrap(), json!({ "perCwdExtraUserRoots": null, }), ); assert_eq!( serde_json::to_value(SkillsListParams { cwds: vec![PathBuf::from("/repo")], force_reload: true, per_cwd_extra_user_roots: Some(vec![SkillsListExtraRootsForCwd { cwd: PathBuf::from("/repo"), extra_user_roots: vec![ PathBuf::from("/shared/skills"), PathBuf::from("/tmp/x") ], }]), }) .unwrap(), json!({ "cwds": ["/repo"], "forceReload": true, "perCwdExtraUserRoots": [ { "cwd": "/repo", "extraUserRoots": ["/shared/skills", "/tmp/x"], } ], }), ); } #[test] fn plugin_list_params_serialization_uses_force_remote_sync() { assert_eq!( serde_json::to_value(PluginListParams { cwds: None, force_remote_sync: false, }) .unwrap(), json!({ "cwds": null, }), ); assert_eq!( serde_json::to_value(PluginListParams { cwds: None, force_remote_sync: true, }) .unwrap(), json!({ "cwds": null, "forceRemoteSync": true, }), ); } #[test] fn plugin_install_params_serialization_uses_force_remote_sync() { let marketplace_path = if cfg!(windows) { r"C:\plugins\marketplace.json" } else { "/plugins/marketplace.json" }; let marketplace_path = AbsolutePathBuf::try_from(PathBuf::from(marketplace_path)).unwrap(); let marketplace_path_json = marketplace_path.as_path().display().to_string(); assert_eq!( serde_json::to_value(PluginInstallParams { marketplace_path: marketplace_path.clone(), plugin_name: "gmail".to_string(), force_remote_sync: false, }) .unwrap(), json!({ "marketplacePath": marketplace_path_json, "pluginName": "gmail", }), ); assert_eq!( serde_json::to_value(PluginInstallParams { marketplace_path, plugin_name: "gmail".to_string(), force_remote_sync: true, }) .unwrap(), json!({ "marketplacePath": marketplace_path_json, "pluginName": "gmail", "forceRemoteSync": true, }), ); } #[test] fn plugin_uninstall_params_serialization_uses_force_remote_sync() { assert_eq!( serde_json::to_value(PluginUninstallParams { plugin_id: "gmail@openai-curated".to_string(), force_remote_sync: false, }) .unwrap(), json!({ "pluginId": "gmail@openai-curated", }), ); assert_eq!( serde_json::to_value(PluginUninstallParams { plugin_id: "gmail@openai-curated".to_string(), force_remote_sync: true, }) .unwrap(), json!({ "pluginId": "gmail@openai-curated", "forceRemoteSync": true, }), ); } #[test] fn codex_error_info_serializes_http_status_code_in_camel_case() { let value = CodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code: Some(401), }; assert_eq!( serde_json::to_value(value).unwrap(), json!({ "responseTooManyFailedAttempts": { "httpStatusCode": 401 } }) ); } #[test] fn codex_error_info_serializes_active_turn_not_steerable_turn_kind_in_camel_case() { let value = CodexErrorInfo::ActiveTurnNotSteerable { turn_kind: NonSteerableTurnKind::Review, }; assert_eq!( serde_json::to_value(value).unwrap(), json!({ "activeTurnNotSteerable": { "turnKind": "review" } }) ); } #[test] fn dynamic_tool_response_serializes_content_items() { let value = serde_json::to_value(DynamicToolCallResponse { content_items: vec![DynamicToolCallOutputContentItem::InputText { text: "dynamic-ok".to_string(), }], success: true, }) .unwrap(); assert_eq!( value, json!({ "contentItems": [ { "type": "inputText", "text": "dynamic-ok" } ], "success": true, }) ); } #[test] fn dynamic_tool_response_serializes_text_and_image_content_items() { let value = serde_json::to_value(DynamicToolCallResponse { content_items: vec![ DynamicToolCallOutputContentItem::InputText { text: "dynamic-ok".to_string(), }, DynamicToolCallOutputContentItem::InputImage { image_url: "data:image/png;base64,AAA".to_string(), }, ], success: true, }) .unwrap(); assert_eq!( value, json!({ "contentItems": [ { "type": "inputText", "text": "dynamic-ok" }, { "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" } ], "success": true, }) ); } #[test] fn dynamic_tool_spec_deserializes_defer_loading() { let value = json!({ "name": "lookup_ticket", "description": "Fetch a ticket", "inputSchema": { "type": "object", "properties": { "id": { "type": "string" } } }, "deferLoading": true, }); let actual: DynamicToolSpec = serde_json::from_value(value).expect("deserialize"); assert_eq!( actual, DynamicToolSpec { name: "lookup_ticket".to_string(), description: "Fetch a ticket".to_string(), input_schema: json!({ "type": "object", "properties": { "id": { "type": "string" } } }), defer_loading: true, } ); } #[test] fn dynamic_tool_spec_legacy_expose_to_context_inverts_to_defer_loading() { let value = json!({ "name": "lookup_ticket", "description": "Fetch a ticket", "inputSchema": { "type": "object", "properties": {} }, "exposeToContext": false, }); let actual: DynamicToolSpec = serde_json::from_value(value).expect("deserialize"); assert!(actual.defer_loading); } #[test] fn thread_start_params_preserve_explicit_null_service_tier() { let params: ThreadStartParams = serde_json::from_value(json!({ "serviceTier": null })) .expect("params should deserialize"); assert_eq!(params.service_tier, Some(None)); let serialized = serde_json::to_value(¶ms).expect("params should serialize"); assert_eq!( serialized.get("serviceTier"), Some(&serde_json::Value::Null) ); let serialized_without_override = serde_json::to_value(ThreadStartParams::default()).expect("params should serialize"); assert_eq!(serialized_without_override.get("serviceTier"), None); } #[test] fn turn_start_params_preserve_explicit_null_service_tier() { let params: TurnStartParams = serde_json::from_value(json!({ "threadId": "thread_123", "input": [], "serviceTier": null })) .expect("params should deserialize"); assert_eq!(params.service_tier, Some(None)); let serialized = serde_json::to_value(¶ms).expect("params should serialize"); assert_eq!( serialized.get("serviceTier"), Some(&serde_json::Value::Null) ); let without_override = TurnStartParams { thread_id: "thread_123".to_string(), input: vec![], cwd: None, approval_policy: None, approvals_reviewer: None, sandbox_policy: None, model: None, service_tier: None, effort: None, summary: None, output_schema: None, collaboration_mode: None, personality: None, }; let serialized_without_override = serde_json::to_value(&without_override).expect("params should serialize"); assert_eq!(serialized_without_override.get("serviceTier"), None); } }