use std::collections::HashMap; use std::path::PathBuf; use crate::protocol::common::AuthMode; use codex_experimental_api_macros::ExperimentalApi; use codex_protocol::account::PlanType; 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::CollaborationMode; use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::config_types::ForcedLoginMethod; 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::Verbosity; use codex_protocol::config_types::WebSearchMode; 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::models::FileSystemPermissions as CoreFileSystemPermissions; use codex_protocol::models::MacOsAutomationValue as CoreMacOsAutomationValue; use codex_protocol::models::MacOsPermissions as CoreMacOsPermissions; use codex_protocol::models::MacOsPreferencesValue as CoreMacOsPreferencesValue; use codex_protocol::models::MessagePhase; use codex_protocol::models::PermissionProfile as CorePermissionProfile; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::InputModality; 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::ExecCommandStatus as CoreExecCommandStatus; use codex_protocol::protocol::ModelRerouteReason as CoreModelRerouteReason; use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess; 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::RejectConfig as CoreRejectConfig; 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::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 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 { ( pub enum $Name:ident from $Src:path { $( $Variant:ident ),+ $(,)? } ) => { #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum $Name { $( $Variant ),+ } impl $Name { pub fn to_core(self) -> $Src { match self { $( $Name::$Variant => <$Src>::$Variant ),+ } } } impl From<$Src> for $Name { fn from(value: $Src) -> Self { match value { $( <$Src>::$Variant => $Name::$Variant ),+ } } } }; } /// 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, }, 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::Other => CodexErrorInfo::Other, } } } #[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 AskForApproval { #[serde(rename = "untrusted")] #[ts(rename = "untrusted")] UnlessTrusted, OnFailure, OnRequest, Reject { sandbox_approval: bool, rules: 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::Reject { sandbox_approval, rules, mcp_elicitations, } => CoreAskForApproval::Reject(CoreRejectConfig { sandbox_approval, rules, 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::Reject(reject_config) => AskForApproval::Reject { sandbox_approval: reject_config.sandbox_approval, rules: reject_config.rules, mcp_elicitations: reject_config.mcp_elicitations, }, CoreAskForApproval::Never => AskForApproval::Never, } } } #[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 } ); #[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 { #[serde(alias = "web_search_request")] pub web_search: Option, pub view_image: Option, } #[derive(Serialize, Deserialize, 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, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "snake_case")] #[ts(export_to = "v2/")] pub struct ProfileV2 { pub model: Option, pub model_provider: Option, pub approval_policy: Option, pub model_reasoning_effort: Option, pub model_reasoning_summary: Option, pub model_verbosity: Option, pub web_search: 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, pub approval_policy: 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, #[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 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)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigReadResponse { 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 { pub allowed_approval_policies: Option>, pub allowed_sandbox_modes: Option>, pub allowed_web_search_modes: 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_non_loopback_admin: Option, pub dangerously_allow_all_unix_sockets: Option, pub allowed_domains: Option>, pub denied_domains: Option>, pub allow_unix_sockets: Option>, pub allow_local_binding: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum ResidencyRequirement { Us, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ConfigRequirementsReadResponse { /// Null if no requirements are configured (e.g. no requirements.toml/MDM entries). pub requirements: Option, } #[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, } #[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 identical commands 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, } 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, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct AdditionalMacOsPermissions { pub preferences: Option, pub automations: Option, pub accessibility: Option, pub calendar: Option, } impl From for AdditionalMacOsPermissions { fn from(value: CoreMacOsPermissions) -> Self { Self { preferences: value.preferences, automations: value.automations, accessibility: value.accessibility, calendar: value.calendar, } } } #[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, pub macos: Option, } impl From for AdditionalPermissionProfile { fn from(value: CorePermissionProfile) -> Self { Self { network: value.network, file_system: value.file_system.map(AdditionalFileSystemPermissions::from), macos: value.macos.map(AdditionalMacOsPermissions::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(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 } => { codex_protocol::protocol::SandboxPolicy::ReadOnly { access: access.to_core(), } } 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 } => { SandboxPolicy::ReadOnly { access: ReadOnlyAccess::from(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, 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::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::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, /// [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 = "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 Model { pub id: String, pub model: String, pub upgrade: 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 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 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, 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, } #[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, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecParams { pub command: Vec, #[ts(type = "number | null")] #[ts(optional = nullable)] pub timeout_ms: Option, #[ts(optional = nullable)] pub cwd: Option, #[ts(optional = nullable)] pub sandbox_policy: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct CommandExecResponse { pub exit_code: i32, pub stdout: String, pub stderr: String, } // === 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, #[ts(optional = nullable)] pub cwd: Option, #[ts(optional = nullable)] pub approval_policy: 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)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadStartResponse { pub thread: Thread, pub model: String, pub model_provider: String, pub cwd: PathBuf, pub approval_policy: AskForApproval, pub sandbox: SandboxPolicy, pub reasoning_effort: Option, } #[derive( Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, 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, #[ts(optional = nullable)] pub cwd: Option, #[ts(optional = nullable)] pub approval_policy: 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)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadResumeResponse { pub thread: Thread, pub model: String, pub model_provider: String, pub cwd: PathBuf, pub approval_policy: AskForApproval, pub sandbox: SandboxPolicy, pub reasoning_effort: Option, } #[derive( Serialize, Deserialize, Debug, 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, #[ts(optional = nullable)] pub cwd: Option, #[ts(optional = nullable)] pub approval_policy: 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, /// 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)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadForkResponse { pub thread: Thread, pub model: String, pub model_provider: String, pub cwd: PathBuf, pub approval_policy: AskForApproval, pub sandbox: SandboxPolicy, pub reasoning_effort: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadArchiveParams { pub thread_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ThreadArchiveResponse {} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct 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 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 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 SkillsRemoteReadParams { #[serde(default)] pub hazelnut_scope: HazelnutScope, #[serde(default)] pub product_surface: ProductSurface, #[serde(default)] pub enabled: bool, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, Default)] #[serde(rename_all = "kebab-case")] #[ts(rename_all = "kebab-case")] #[ts(export_to = "v2/")] pub enum HazelnutScope { #[default] Example, WorkspaceShared, AllShared, Personal, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, Default)] #[serde(rename_all = "lowercase")] #[ts(rename_all = "lowercase")] #[ts(export_to = "v2/")] pub enum ProductSurface { Chatgpt, #[default] Codex, Api, Atlas, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct RemoteSkillSummary { pub id: String, pub name: String, pub description: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsRemoteReadResponse { pub data: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsRemoteWriteParams { pub hazelnut_id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillsRemoteWriteResponse { pub id: String, pub path: PathBuf, } #[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 SkillsConfigWriteParams { pub path: PathBuf, 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, } 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, /// 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, } #[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, 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, } #[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. #[ts(optional = nullable)] pub approval_policy: 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 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"), } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(tag = "type", rename_all = "camelCase")] #[ts(tag = "type")] #[ts(export_to = "v2/")] pub enum ThreadItem { #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] UserMessage { id: String, content: Vec }, #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] AgentMessage { id: String, text: String, #[serde(default)] phase: 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, 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")] 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, /// 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")] 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 }, } impl ThreadItem { pub fn id(&self) -> &str { match self { ThreadItem::UserMessage { id, .. } | ThreadItem::AgentMessage { id, .. } | ThreadItem::Plan { id, .. } | ThreadItem::Reasoning { id, .. } | ThreadItem::CommandExecution { id, .. } | ThreadItem::FileChange { id, .. } | ThreadItem::McpToolCall { id, .. } | ThreadItem::CollabAgentToolCall { id, .. } | ThreadItem::WebSearch { id, .. } | ThreadItem::ImageView { id, .. } | ThreadItem::EnteredReviewMode { id, .. } | ThreadItem::ExitedReviewMode { id, .. } | ThreadItem::ContextCompaction { id, .. } => id, } } } #[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::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, } } 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::ContextCompaction(compaction) => { ThreadItem::ContextCompaction { id: compaction.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, } } } #[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, 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, 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::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 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 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/")] /// 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/")] 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, } #[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, } #[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 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, 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, } #[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>, } 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, PartialEq, JsonSchema, TS, ExperimentalApi)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillRequestApprovalParams { pub item_id: String, pub skill_name: String, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum SkillApprovalDecision { Approve, Decline, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct SkillRequestApprovalResponse { pub decision: SkillApprovalDecision, } #[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 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 test_absolute_path() -> AbsolutePathBuf { let path = if cfg!(windows) { r"C:\readable" } else { "/readable" }; AbsolutePathBuf::from_absolute_path(path).expect("path must be absolute") } #[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()], }, }; 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], }, } ); let back_to_v2 = SandboxPolicy::from(core_policy); assert_eq!(back_to_v2, v2_policy); } #[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, } ); } #[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 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, }); assert_eq!( ThreadItem::from(agent_item), ThreadItem::AgentMessage { id: "agent-1".to_string(), text: "Hello world".to_string(), phase: 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), }); assert_eq!( ThreadItem::from(agent_item_with_phase), ThreadItem::AgentMessage { id: "agent-2".to_string(), text: "final".to_string(), phase: Some(MessagePhase::FinalAnswer), } ); 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 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 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, }) ); } }