//! Types used to define loaded and effective Codex configuration values. // Note this file should generally be restricted to simple struct/enum // definitions that do not contain business logic. pub use crate::mcp_ema::McpEmaAuthScope; pub use crate::mcp_ema::McpEnterpriseManagedAuthConfig; pub use crate::mcp_ema::McpServerIdpOAuthConfig; pub use crate::mcp_types::AppToolApproval; pub use crate::mcp_types::McpServerAuth; pub use crate::mcp_types::McpServerConfig; pub use crate::mcp_types::McpServerDisabledReason; pub use crate::mcp_types::McpServerEnvVar; pub use crate::mcp_types::McpServerOAuthConfig; pub use crate::mcp_types::McpServerToolConfig; pub use crate::mcp_types::McpServerTransportConfig; pub use crate::mcp_types::McpStartupReadiness; pub use crate::mcp_types::RawMcpServerConfig; pub use crate::shell_environment_policy::ShellEnvironmentPolicyToml; pub use codex_protocol::config_types::AltScreenMode; pub use codex_protocol::config_types::ApprovalsReviewer; pub use codex_protocol::config_types::ModeKind; pub use codex_protocol::config_types::Personality; pub use codex_protocol::config_types::ServiceTier; use codex_protocol::config_types::ToolExposureSurface; pub use codex_protocol::config_types::WebSearchMode; use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; pub use crate::tui_effects::TuiEffects; pub use crate::tui_keymap::KeybindingSpec; pub use crate::tui_keymap::KeybindingsSpec; pub use crate::tui_keymap::MAX_FUNCTION_KEY; pub use crate::tui_keymap::TuiAgentsKeymap; pub use crate::tui_keymap::TuiApprovalKeymap; pub use crate::tui_keymap::TuiChatKeymap; pub use crate::tui_keymap::TuiComposerKeymap; pub use crate::tui_keymap::TuiEditorKeymap; pub use crate::tui_keymap::TuiGlobalKeymap; pub use crate::tui_keymap::TuiKeymap; pub use crate::tui_keymap::TuiListKeymap; pub use crate::tui_keymap::TuiPagerKeymap; pub use crate::tui_keymap::TuiVimNormalKeymap; pub use crate::tui_keymap::TuiVimOperatorKeymap; pub use crate::tui_keymap::TuiVimSearchKeymap; pub use crate::tui_rendering::TuiRendering; pub const DEFAULT_OTEL_ENVIRONMENT: &str = "dev"; pub const DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 2; pub const DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS: i64 = 10; pub const DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS: i64 = 6; pub const DEFAULT_MEMORIES_MIN_RATE_LIMIT_REMAINING_PERCENT: i64 = 25; pub const DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION: usize = 256; pub const DEFAULT_MEMORIES_MAX_UNUSED_DAYS: i64 = 30; const MIN_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION: usize = 1; const MAX_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION: usize = 4096; const MIN_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 1; const MAX_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 128; const fn default_enabled() -> bool { true } /// Last selected grouping in Agent Command Center. #[derive(Serialize, Deserialize, Debug, Default, Copy, Clone, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum AgentsOverviewGrouping { #[default] Project, Status, Model, } impl AgentsOverviewGrouping { pub const fn as_str(self) -> &'static str { match self { Self::Project => "project", Self::Status => "status", Self::Model => "model", } } } /// Preferred layout for the resume/fork session picker. #[derive(Serialize, Deserialize, Debug, Default, Copy, Clone, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum SessionPickerViewMode { Comfortable, #[default] Dense, } impl SessionPickerViewMode { pub const fn as_str(self) -> &'static str { match self { Self::Comfortable => "comfortable", Self::Dense => "dense", } } } impl fmt::Display for SessionPickerViewMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } /// Working directory to use when resuming or forking a session. #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum ResumeCwdMode { /// Use the directory where Codex was launched. Current, /// Use the latest working directory recorded in the selected session. Session, } impl ResumeCwdMode { pub const fn as_str(self) -> &'static str { match self { Self::Current => "current", Self::Session => "session", } } } /// Determine where Codex should store CLI auth credentials. #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum AuthCredentialsStoreMode { #[default] /// Persist credentials in CODEX_HOME/auth.json. File, /// Persist credentials in the keyring. Fail if unavailable. Keyring, /// Use keyring when available; otherwise, fall back to a file in CODEX_HOME. Auto, /// Store credentials in memory only for the current process. Ephemeral, } /// Determine where Codex should store and read MCP credentials. #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum OAuthCredentialsStoreMode { /// Prefer `Keyring` and use `File` when keyring storage is unavailable. /// Once an MCP client loads credentials from one store, that client keeps the resolved store /// for its lifetime so refreshes cannot switch to a possibly stale credential source. /// Credentials stored in the keyring will only be readable by Codex unless the user explicitly grants access via OS-level keyring access. #[default] Auto, /// CODEX_HOME/.credentials.json /// This file will be readable to Codex and other applications running as the same user. File, /// Keyring when available, otherwise fail. Keyring, } /// Determine how auth credentials should use keyring-backed storage. #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum AuthKeyringBackendKind { /// Store the serialized auth payload directly in the OS keyring. Direct, /// Store auth payloads in the local encrypted secrets file, with the file key in the OS keyring. Secrets, } impl Default for AuthKeyringBackendKind { fn default() -> Self { if cfg!(windows) { Self::Secrets } else { Self::Direct } } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum WindowsSandboxModeToml { Elevated, Unelevated, Mxc, } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct WindowsToml { pub sandbox: Option, /// False blocks both explicit MXC configuration and automatic selection. pub allow_mxc: Option, } #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, JsonSchema)] pub enum UriBasedFileOpener { #[serde(rename = "vscode")] VsCode, #[serde(rename = "vscode-insiders")] VsCodeInsiders, #[serde(rename = "windsurf")] Windsurf, #[serde(rename = "cursor")] Cursor, /// Option to disable the URI-based file opener. #[serde(rename = "none")] None, } /// Settings that govern if and what will be written to `~/.codex/history.jsonl`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[serde(default)] #[schemars(deny_unknown_fields)] pub struct History { /// If true, history entries will not be written to disk. pub persistence: HistoryPersistence, /// If set, the maximum size of the history file in bytes. The oldest entries /// are dropped once the file exceeds this limit. pub max_bytes: Option, } #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Default, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum HistoryPersistence { /// Save all history entries to disk. #[default] SaveAll, /// Do not write history to disk. None, } // ===== Analytics configuration ===== /// Analytics settings loaded from config.toml. Fields are optional so we can apply defaults. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct AnalyticsConfigToml { /// When `false`, disables OpenAI analytics across Codex product surfaces in this profile. /// Custom OTLP metrics exporters remain enabled; set `otel.metrics_exporter = "none"` to disable them. pub enabled: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct FeedbackConfigToml { /// When `false`, disables feedback uploads and background Guardian review retention. Explicit local exports remain available. pub enabled: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ToolSuggestDiscoverableType { Connector, Plugin, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct ToolSuggestDiscoverable { #[serde(rename = "type")] pub kind: ToolSuggestDiscoverableType, pub id: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct ToolSuggestDisabledTool { #[serde(rename = "type")] pub kind: ToolSuggestDiscoverableType, pub id: String, } impl ToolSuggestDisabledTool { pub fn plugin(id: impl Into) -> Self { Self { kind: ToolSuggestDiscoverableType::Plugin, id: id.into(), } } pub fn connector(id: impl Into) -> Self { Self { kind: ToolSuggestDiscoverableType::Connector, id: id.into(), } } pub fn normalized(&self) -> Option { let id = self.id.trim(); (!id.is_empty()).then(|| Self { kind: self.kind, id: id.to_string(), }) } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct ToolSuggestConfig { #[serde(default)] pub discoverables: Vec, #[serde(default)] pub disabled_tools: Vec, } pub use codex_protocol::MemoryVersion; /// Memories settings loaded from config.toml. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct MemoriesToml { /// Selects the memory pipeline. Fresh profiles default to v2 when memories are enabled, /// unless dual-write is explicitly disabled; existing v1 profiles default to v1. pub version: Option, /// Generate both versions while v1 is selected; v2 runs alone after cutover. pub dual_write: Option, /// When `true`, external context sources mark the thread `memory_mode` as `"polluted"`. #[serde(alias = "no_memories_if_mcp_or_web_search")] pub disable_on_external_context: Option, /// When `false`, newly created threads are stored with `memory_mode = "disabled"` in the state DB. pub generate_memories: Option, /// When `false`, skip injecting memory usage instructions into developer prompts. pub use_memories: Option, /// When `true`, expose dedicated memory tools through the extension tool surface. pub dedicated_tools: Option, /// Maximum number of recent raw memories retained for global consolidation. #[schemars(range(min = 1, max = 4096))] pub max_raw_memories_for_consolidation: Option, /// Maximum number of days since a memory was last used before it becomes ineligible for phase 2 selection. pub max_unused_days: Option, /// Maximum age of the threads used for memories. pub max_rollout_age_days: Option, /// Maximum number of rollout candidates processed per pass. #[schemars(range(min = 1, max = 128))] pub max_rollouts_per_startup: Option, /// Minimum idle time between last thread activity and memory creation (hours). > 12h recommended. pub min_rollout_idle_hours: Option, /// Minimum remaining percentage required in Codex rate-limit windows before memory startup runs. #[schemars(range(min = 0, max = 100))] pub min_rate_limit_remaining_percent: Option, /// Model used for thread summarisation. pub extract_model: Option, /// Model used for memory consolidation. pub consolidation_model: Option, } /// Effective memories settings after defaults are applied. #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct MemoriesConfig { pub version: MemoryVersion, pub dual_write: bool, pub disable_on_external_context: bool, pub generate_memories: bool, pub use_memories: bool, pub dedicated_tools: bool, pub max_raw_memories_for_consolidation: usize, pub max_unused_days: i64, pub max_rollout_age_days: i64, pub max_rollouts_per_startup: usize, pub min_rollout_idle_hours: i64, pub min_rate_limit_remaining_percent: i64, pub extract_model: Option, pub consolidation_model: Option, } impl Default for MemoriesConfig { fn default() -> Self { Self { version: MemoryVersion::V1, dual_write: false, disable_on_external_context: false, generate_memories: true, use_memories: true, dedicated_tools: false, max_raw_memories_for_consolidation: DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION, max_unused_days: DEFAULT_MEMORIES_MAX_UNUSED_DAYS, max_rollout_age_days: DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS, max_rollouts_per_startup: DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP, min_rollout_idle_hours: DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS, min_rate_limit_remaining_percent: DEFAULT_MEMORIES_MIN_RATE_LIMIT_REMAINING_PERCENT, extract_model: None, consolidation_model: None, } } } impl From for MemoriesConfig { fn from(toml: MemoriesToml) -> Self { let defaults = Self::default(); Self { version: toml.version.unwrap_or(defaults.version), dual_write: toml.dual_write.unwrap_or(defaults.dual_write), disable_on_external_context: toml .disable_on_external_context .unwrap_or(defaults.disable_on_external_context), generate_memories: toml.generate_memories.unwrap_or(defaults.generate_memories), use_memories: toml.use_memories.unwrap_or(defaults.use_memories), dedicated_tools: toml.dedicated_tools.unwrap_or(defaults.dedicated_tools), max_raw_memories_for_consolidation: toml .max_raw_memories_for_consolidation .unwrap_or(defaults.max_raw_memories_for_consolidation) .clamp( MIN_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION, MAX_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION, ), max_unused_days: toml .max_unused_days .unwrap_or(defaults.max_unused_days) .clamp(0, 365), max_rollout_age_days: toml .max_rollout_age_days .unwrap_or(defaults.max_rollout_age_days) .clamp(0, 90), max_rollouts_per_startup: toml .max_rollouts_per_startup .unwrap_or(defaults.max_rollouts_per_startup) .clamp( MIN_MEMORIES_MAX_ROLLOUTS_PER_STARTUP, MAX_MEMORIES_MAX_ROLLOUTS_PER_STARTUP, ), min_rollout_idle_hours: toml .min_rollout_idle_hours .unwrap_or(defaults.min_rollout_idle_hours) .clamp(1, 48), min_rate_limit_remaining_percent: toml .min_rate_limit_remaining_percent .unwrap_or(defaults.min_rate_limit_remaining_percent) .clamp(0, 100), extract_model: toml.extract_model, consolidation_model: toml.consolidation_model, } } } /// Default settings that apply to all apps. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct AppsDefaultConfig { /// When `false`, apps are disabled unless overridden by per-app settings. #[serde(default = "default_enabled")] pub enabled: bool, /// Reviewer for approval prompts unless overridden by per-app settings. #[serde(default, skip_serializing_if = "Option::is_none")] pub approvals_reviewer: Option, /// Whether tools with `destructive_hint = true` are allowed by default. #[serde( default = "default_enabled", skip_serializing_if = "std::clone::Clone::clone" )] pub destructive_enabled: bool, /// Whether tools with `open_world_hint = true` are allowed by default. #[serde( default = "default_enabled", skip_serializing_if = "std::clone::Clone::clone" )] pub open_world_enabled: bool, /// Approval mode for tools unless overridden by per-app or per-tool settings. #[serde(default, skip_serializing_if = "Option::is_none")] pub default_tools_approval_mode: Option, } /// Per-tool settings for a single app tool. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct AppToolConfig { /// Whether this tool is enabled. `Some(true)` explicitly allows this tool. #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, /// Approval mode for this tool. #[serde(default, skip_serializing_if = "Option::is_none")] pub approval_mode: Option, } /// Tool settings for a single app. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct AppToolsConfig { /// Per-tool overrides keyed by tool name (for example `repos/list`). #[serde(default, flatten)] pub tools: HashMap, } /// Approval settings for a connected account within an app. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct AppLinkConfig { /// Reviewer for approval prompts from this account, overriding the app default. #[serde(default, skip_serializing_if = "Option::is_none")] pub approvals_reviewer: Option, /// Approval mode for this account unless a tool override exists. #[serde(default, skip_serializing_if = "Option::is_none")] pub default_tools_approval_mode: Option, } /// Account settings for a single app. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct AppLinksConfig { /// Per-account approval settings keyed by link ID. #[serde(default, flatten)] pub links: HashMap, } /// Config values for a single app/connector. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct AppConfig { /// When `false`, Codex does not surface this app. #[serde(default = "default_enabled")] pub enabled: bool, /// Model-facing surfaces from which this connector's tools must be omitted, /// in addition to any server-level omissions. `None` leaves lower-priority /// configuration unchanged; an empty list clears connector-level omissions. #[serde(default, skip_serializing_if = "Option::is_none")] pub omit_tools_from: Option>, /// Reviewer for approval prompts from this app, overriding the thread default. #[serde(default, skip_serializing_if = "Option::is_none")] pub approvals_reviewer: Option, /// Whether tools with `destructive_hint = true` are allowed for this app. #[serde(default, skip_serializing_if = "Option::is_none")] pub destructive_enabled: Option, /// Whether tools with `open_world_hint = true` are allowed for this app. #[serde(default, skip_serializing_if = "Option::is_none")] pub open_world_enabled: Option, /// Approval mode for tools in this app unless a tool override exists. #[serde(default, skip_serializing_if = "Option::is_none")] pub default_tools_approval_mode: Option, /// Whether tools are enabled by default for this app. #[serde(default, skip_serializing_if = "Option::is_none")] pub default_tools_enabled: Option, /// Per-tool settings for this app. #[serde(default, skip_serializing_if = "Option::is_none")] pub tools: Option, /// Per-account approval settings keyed by link ID. #[serde(default, skip_serializing_if = "Option::is_none")] pub links: Option, } /// App/connector settings loaded from `config.toml`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct AppsConfigToml { /// Default settings for all apps. #[serde(default, rename = "_default", skip_serializing_if = "Option::is_none")] pub default: Option, /// Per-app settings keyed by app ID (for example `[apps.google_drive]`). #[serde(default, flatten)] pub apps: HashMap, } // ===== OTEL configuration ===== #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] #[serde(rename_all = "kebab-case")] pub enum OtelHttpProtocol { /// Binary payload Binary, /// JSON payload Json, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] #[serde(rename_all = "kebab-case")] pub struct OtelTlsConfig { pub ca_certificate: Option, pub client_certificate: Option, pub client_private_key: Option, } /// Which OTEL exporter to use. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)] #[schemars(deny_unknown_fields)] #[serde(rename_all = "kebab-case")] pub enum OtelExporterKind { None, Statsig, OtlpHttp { endpoint: String, #[serde(default)] headers: HashMap, protocol: OtelHttpProtocol, #[serde(default)] tls: Option, }, OtlpGrpc { endpoint: String, #[serde(default)] headers: HashMap, #[serde(default)] tls: Option, }, } /// OTEL settings loaded from config.toml. Fields are optional so we can apply defaults. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct OtelConfigToml { /// Byte limit for tool-result log output; independent of model-visible output. #[serde(default)] pub tool_result: codex_protocol::config_types::ToolResultLogConfig, /// Log user prompt in traces pub log_user_prompt: Option, /// Opt in to logging final main-agent and spawned-subagent responses to an OTLP log exporter. /// Defaults to false. Response text can be sensitive and is capped at 64 KiB. pub log_agent_responses: Option, /// Opt in to logging completed Guardian assessments to an OTLP log exporter. /// Defaults to false. Rationales can be sensitive and are capped at 64 KiB. pub log_guardian_assessments: Option, /// Mark traces with environment (dev, staging, prod, test). Defaults to dev. pub environment: Option, /// Optional log exporter pub exporter: Option, /// Optional trace exporter pub trace_exporter: Option, /// Metrics exporter. Defaults to `statsig`, which follows `analytics.enabled`. /// Custom OTLP exporters are independent of `analytics.enabled`; `none` disables metrics export. pub metrics_exporter: Option, /// Attributes to add to every exported trace span. pub span_attributes: Option>, /// Semicolon-separated `key:value` fields to upsert into W3C tracestate members. pub tracestate: Option>>, } /// Effective OTEL settings after defaults are applied. #[derive(Debug, Clone, PartialEq)] pub struct OtelConfig { pub tool_result: codex_protocol::config_types::ToolResultLogConfig, pub log_user_prompt: bool, pub log_agent_responses: bool, pub log_guardian_assessments: bool, pub environment: String, pub exporter: OtelExporterKind, pub trace_exporter: OtelExporterKind, pub metrics_exporter: OtelExporterKind, pub span_attributes: BTreeMap, pub tracestate: BTreeMap>, } impl Default for OtelConfig { fn default() -> Self { OtelConfig { tool_result: Default::default(), log_user_prompt: false, log_agent_responses: false, log_guardian_assessments: false, environment: DEFAULT_OTEL_ENVIRONMENT.to_owned(), exporter: OtelExporterKind::None, trace_exporter: OtelExporterKind::None, metrics_exporter: OtelExporterKind::Statsig, span_attributes: BTreeMap::new(), tracestate: BTreeMap::new(), } } } impl OtelConfig { /// Response text requires a separate opt-in and an explicit OTLP log destination. pub fn agent_response_logging_enabled(&self) -> bool { self.log_agent_responses && matches!( self.exporter, OtelExporterKind::OtlpHttp { .. } | OtelExporterKind::OtlpGrpc { .. } ) } /// Assessment text requires an explicit opt-in and an OTLP log destination. pub fn guardian_assessment_logging_enabled(&self) -> bool { self.log_guardian_assessments && matches!( self.exporter, OtelExporterKind::OtlpHttp { .. } | OtelExporterKind::OtlpGrpc { .. } ) } } #[derive(Serialize, Debug, Clone, PartialEq, Eq, Deserialize, JsonSchema)] #[serde(untagged)] pub enum Notifications { Enabled(bool), Custom(Vec), } impl Default for Notifications { fn default() -> Self { Self::Enabled(true) } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)] #[serde(rename_all = "lowercase")] pub enum NotificationMethod { #[default] Auto, Osc9, Bel, } impl fmt::Display for NotificationMethod { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { NotificationMethod::Auto => write!(f, "auto"), NotificationMethod::Osc9 => write!(f, "osc9"), NotificationMethod::Bel => write!(f, "bel"), } } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)] #[serde(rename_all = "lowercase")] pub enum NotificationCondition { /// Emit TUI notifications only while the terminal is unfocused. #[default] Unfocused, /// Emit TUI notifications regardless of terminal focus. Always, } impl fmt::Display for NotificationCondition { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { NotificationCondition::Unfocused => write!(f, "unfocused"), NotificationCondition::Always => write!(f, "always"), } } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)] #[serde(rename_all = "kebab-case")] pub enum TuiPetAnchor { /// Anchor the pet to the bottom of the current TUI composer viewport. #[default] Composer, /// Anchor the pet to the physical bottom of the terminal screen. ScreenBottom, } /// Right-click text paste when the fullscreen TUI has no selection. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)] #[serde(rename_all = "lowercase")] pub enum RightClickPaste { /// Enable on Windows/WSL/Linux, except recognized terminal-owned paste paths. #[default] Auto, /// Enable on supported local platforms; selection and terminal-owned paste still win. On, /// Leave right-click paste to the terminal. Off, } /// When mouse selections are copied on release. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)] #[serde(rename_all = "lowercase")] pub enum CopyOnSelect { /// Use the terminal-specific default. #[default] Auto, /// Copy every nonempty transcript, footer, or composer mouse selection on release. Always, /// Require an explicit copy action. Never, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct TuiNotificationSettings { /// Enable desktop notifications from the TUI. /// Defaults to `true`. #[serde(default, rename = "notifications")] pub notifications: Notifications, /// Notification method to use for terminal notifications. /// Defaults to `auto`. #[serde(default, rename = "notification_method")] pub method: NotificationMethod, /// Controls whether TUI notifications are delivered only when the terminal is unfocused or /// regardless of focus. Defaults to `unfocused`. #[serde(default, rename = "notification_condition")] pub condition: NotificationCondition, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct ModelAvailabilityNuxConfig { /// Number of times a startup availability NUX has been shown per model slug. #[serde(default, flatten)] pub shown_count: HashMap, } /// Fallback resize-reflow row cap when Codex cannot identify a terminal-specific scrollback size. pub const DEFAULT_TERMINAL_RESIZE_REFLOW_FALLBACK_MAX_ROWS: usize = 1_000; /// One reusable prompt or slash command submitted through the TUI composer. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)] #[schemars(deny_unknown_fields, rename = "Preset")] pub struct QuickDial { /// Plain-text input, limited to 8,000 UTF-8 bytes. pub input: String, /// Optional short name for the picker and shortcut hints. pub label: Option, } impl QuickDial { /// Keep stored inputs nonempty and below the context-item limit even with byte tokenization. pub fn validate(&self) -> Result<(), &'static str> { if self.input.trim().is_empty() { return Err("Preset input cannot be empty."); } if self.input.trim_start().starts_with('!') { return Err("Presets support prompts and slash commands, not shell commands."); } if self.input.len() > 8_000 { return Err("Preset input cannot exceed 8,000 UTF-8 bytes."); } Ok(()) } } /// Collection of settings that are specific to the TUI. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct Tui { #[serde(default, flatten)] pub notification_settings: TuiNotificationSettings, /// Enable animations (welcome screen, shimmer effects, spinners). /// Defaults to `true`. #[serde(default = "default_true")] pub animations: bool, /// Records the one-time screen-reader detection attempt. Either value skips detection. pub screen_reader_detection_done: Option, /// Individual visual effects. Each also requires animations to be enabled. #[serde(default)] pub effects: TuiEffects, /// Rich content rendering. Independent of animations and visual effects. #[serde(default)] pub rendering: TuiRendering, /// Show startup tooltips in the TUI welcome screen. /// Defaults to `true`. #[serde(default = "default_true")] pub show_tooltips: bool, /// Show informational notices about connected app server version differences. /// Defaults to `true`; this does not control compatibility errors or version status. #[serde(default = "default_true")] pub show_server_version_notice: bool, /// Generate automatic conversation recaps when the terminal is unfocused. /// Defaults to `true`. Disabling this leaves `/recap` available on demand. #[serde(default = "default_true")] pub auto_recap: bool, /// When true, disables burst-paste detection for typed input entirely. /// All characters are inserted as they are received, and no buffering /// or placeholder replacement will occur for fast keypress bursts. /// Overrides the legacy top-level `disable_paste_burst` setting. Defaults to `false`. pub disable_paste_burst: Option, /// Start the composer in Vim mode (`Normal`) by default. /// Defaults to `false`. #[serde(default)] pub vim_mode_default: bool, /// Escape returns from async questions to the composer, preserving the answer draft. #[serde(default = "default_true")] pub question_esc_back: bool, /// Start the TUI in raw scrollback mode for copy-friendly transcript output. /// Defaults to `false`. #[serde(default)] pub raw_output_mode: bool, /// Own the fullscreen transcript, including scrolling, selection, and search. /// Defaults to `true`; alternate-screen restrictions take precedence. #[serde(default = "default_true")] pub fullscreen_transcript: bool, /// Mouse wheel speed multiplier for transcript scrolling, based on one row per event. /// Defaults to `1.0`. Positive fractional values slow scrolling; values above `1.0` speed it up. #[serde(default, deserialize_with = "crate::tui_mouse_scroll::deserialize")] #[schemars(schema_with = "crate::tui_mouse_scroll::schema")] pub mouse_scroll_speed: Option, /// Copy selected transcript, footer, or composer text when the mouse button is released. /// Defaults to `auto`: enabled except in direct terminals known to forward their native /// copy shortcut (Ghostty 1.2+, Kitty on macOS, Windows Terminal, and VS Code on Windows). /// Unknown terminals, Ghostty without a recognized version, and tmux/Zellij default to copying. #[serde(default)] pub copy_on_select: CopyOnSelect, /// Deselect read-only transcript or footer text after Copy on select confirms delivery. /// Editable composer selections remain visible. Defaults to `false` and has no effect when /// Copy on select is off. #[serde(default)] pub copy_on_select_clear_selection: bool, /// Right-click text paste fallback. Defaults to `auto` (Windows/WSL/Linux). /// `on` also enables macOS; neither mode reads over SSH or in recognized VS Code terminals. /// This controls the fullscreen fallback, not the terminal's own paste binding. #[serde(default)] pub right_click_paste: RightClickPaste, /// Controls whether the TUI uses the terminal's alternate screen buffer. /// /// - `auto` (default): Use alternate screen. /// - `always`: Always use alternate screen. /// - `never`: Never use alternate screen (inline mode only, preserves scrollback). #[serde(default)] pub alternate_screen: AltScreenMode, /// Ordered list of status line item identifiers. /// /// When set, the TUI renders the selected items as the status line. /// When unset, the TUI defaults to: `model-with-reasoning`, `current-dir`, and `thread-name`. #[serde(default)] pub status_line: Option>, /// Color status line items with colors derived from the active syntax theme. /// Defaults to `true`. #[serde(default = "default_true")] pub status_line_use_colors: bool, /// Ordered list of terminal title item identifiers. /// /// When set, the TUI renders the selected items into the terminal window/tab title. /// When unset, the TUI defaults to: `activity`, `thread-name`, and `project-name`. /// The `activity` item spins while working and shows an action-required /// message when blocked on the user. #[serde(default)] pub terminal_title: Option>, /// Syntax highlighting theme name (kebab-case). /// /// When set, overrides automatic light/dark theme detection. /// Use `/theme` in the TUI or see `$CODEX_HOME/themes` for custom themes. #[serde(default)] pub theme: Option, /// Pet id to preselect in the terminal pet picker. /// /// Custom pet ids resolve against CODEX_HOME/pets//pet.json. #[serde(default)] pub pet: Option, /// Where the terminal pet should anchor vertically. /// /// Defaults to `composer`, which follows the current TUI composer viewport. #[serde(default)] pub pet_anchor: TuiPetAnchor, /// Preferred layout for resume/fork session picker results. #[serde(default)] pub session_picker_view: Option, /// Last selected grouping in Agent Command Center. #[serde(default)] pub agents_overview_grouping: AgentsOverviewGrouping, /// Working directory to use when resuming or forking a session. /// When unset, prompt if the current and session directories differ. #[serde(default)] pub resume_cwd: Option, /// Keybinding overrides for the TUI. /// /// This supports rebinding selected actions globally and by context. /// Context bindings take precedence over `global` bindings. #[serde(default)] pub keymap: TuiKeymap, /// Saved inputs keyed by a single digit (`0` through `9`). #[serde(default, rename = "presets")] pub quick_dials: BTreeMap, /// Startup tooltip availability NUX state persisted by the TUI. #[serde(default)] pub model_availability_nux: ModelAvailabilityNuxConfig, /// Trim terminal resize-reflow replay to the most recent rendered terminal rows when the /// transcript exceeds this cap. Omit to use Codex's terminal-specific default. Set to `0` to /// keep all rendered rows. #[serde(default)] #[schemars(range(min = 0))] pub terminal_resize_reflow_max_rows: Option, } const fn default_true() -> bool { true } /// Settings for notices we display to users via the tui and app-server clients /// (primarily the Codex IDE extension). NOTE: these are different from /// notifications - notices are warnings, NUX screens, acknowledgements, etc. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct ExternalConfigMigrationPrompts { /// Tracks whether home-level external config migration prompts are hidden. pub home: Option, /// Tracks the last time the home-level external config migration prompt was shown. pub home_last_prompted_at: Option, /// Tracks which project paths have opted out of external config migration prompts. #[serde(default)] pub projects: BTreeMap, /// Tracks the last time a project-level external config migration prompt was shown. #[serde(default)] pub project_last_prompted_at: BTreeMap, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct Notice { /// Tracks whether the user has acknowledged the AWS GovCloud guidance. pub hide_gov_cloud_guidance: Option, /// Tracks whether the user has acknowledged the full access warning prompt. pub hide_full_access_warning: Option, /// Tracks whether the user has acknowledged the Windows world-writable directories warning. pub hide_world_writable_warning: Option, /// Tracks whether the user opted out of Codex-managed fast defaults. pub fast_default_opt_out: Option, /// Tracks whether the user opted out of the rate limit model switch reminder. pub hide_rate_limit_model_nudge: Option, /// Tracks whether the user has seen the model migration prompt pub hide_gpt5_1_migration_prompt: Option, /// Tracks whether the user has seen the gpt-5.1-codex-max migration prompt #[serde(rename = "hide_gpt-5.1-codex-max_migration_prompt")] pub hide_gpt_5_1_codex_max_migration_prompt: Option, /// Tracks acknowledged model migrations as old->new model slug mappings. #[serde(default)] pub model_migrations: BTreeMap, /// Tracks scopes where external config migration prompts should be suppressed. #[serde(default)] pub external_config_migration_prompts: ExternalConfigMigrationPrompts, } pub use crate::skills_config::BundledSkillsConfig; pub use crate::skills_config::SkillConfig; pub use crate::skills_config::SkillsConfig; /// Default activation settings for installed plugins. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct PluginsDefaultConfig { #[serde(default = "default_enabled")] pub enabled: bool, } /// Plugin activation and capability settings loaded from configuration layers. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct PluginsConfigToml { /// Defaults for installed plugin activation. Omission preserves source enablement. /// Explicit plugin enablement settings override this default, but cannot enable /// an installation disabled by its source. #[serde(default, rename = "_default", skip_serializing_if = "Option::is_none")] pub default: Option, #[serde(default, flatten)] pub plugins: HashMap, } impl PluginsConfigToml { /// Whether configuration permits a source-enabled plugin to remain active. /// Without `_default`, preserve legacy source and account enablement. /// Otherwise, an explicit enablement setting overrides the configured default. pub fn allows_plugin(&self, plugin_id: &str) -> bool { let Some(default) = &self.default else { return true; }; self.plugins .get(plugin_id) .and_then(|plugin| plugin.enabled) .unwrap_or(default.enabled) } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct PluginConfig { /// Explicit activation override. Omission inherits the installed-plugin default. #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, /// Stable, unique identifier of a ChatGPT-hosted remote plugin. /// Availability to the requesting user is determined by Plugin Service at /// provisioning time. #[serde(default, skip_serializing_if = "Option::is_none")] pub remote_plugin_id: Option, /// Release identifier used to pin the remote plugin to a specific release. /// Omission leaves release selection unchanged. /// Core stores this metadata without selecting or fetching a release. #[serde(default, skip_serializing_if = "Option::is_none")] pub release_id: Option, /// Provisioning metadata. Omission does not request provisioning. /// Only applies with `remote_plugin_id` and Plugin Service availability to the /// requesting user at provisioning time. Effective enablement, trusted /// provisioning policy, and authentication and authorization restrictions still apply. /// This value is not trusted authorization and does not trigger provisioning. #[serde(default, skip_serializing_if = "Option::is_none")] pub auto_provision: Option, /// Per-MCP-server policy overlays for MCP servers contributed by this plugin. #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub mcp_servers: HashMap, } /// Policy settings for a plugin-provided MCP server. /// /// This intentionally excludes transport settings: plugin manifests own how the /// MCP server is launched, while host config owns enablement, auth, and tool policy. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct PluginMcpServerConfig { /// When `false`, Codex skips initializing this plugin MCP server. #[serde(default = "default_enabled")] pub enabled: bool, /// Retired EMA overlays must disable the server instead of losing their auth policy. #[serde( default, rename = "ema_auth", deserialize_with = "unsupported_plugin_ema_auth", skip_serializing_if = "std::ops::Not::not" )] #[schemars(skip)] pub has_unsupported_ema_auth: bool, /// Approval mode for tools in this server unless a tool override exists. #[serde(default, skip_serializing_if = "Option::is_none")] pub default_tools_approval_mode: Option, /// Explicit allow-list of tools exposed from this server. #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled_tools: Option>, /// Explicit deny-list of tools. These tools are removed after applying `enabled_tools`. #[serde(default, skip_serializing_if = "Option::is_none")] pub disabled_tools: Option>, /// Per-tool policy settings keyed by tool name. #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub tools: HashMap, } impl Default for PluginMcpServerConfig { fn default() -> Self { Self { enabled: true, has_unsupported_ema_auth: false, default_tools_approval_mode: None, enabled_tools: None, disabled_tools: None, tools: HashMap::new(), } } } fn unsupported_plugin_ema_auth<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { serde::de::IgnoredAny::deserialize(deserializer).map(|_| true) } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] pub struct MarketplaceConfig { /// Last time Codex successfully added or refreshed this marketplace. #[serde(default)] pub last_updated: Option, /// Git revision Codex last successfully activated for this marketplace. #[serde(default)] pub last_revision: Option, /// Source kind used to install this marketplace. #[serde(default)] pub source_type: Option, /// Source location used when the marketplace was added. #[serde(default)] pub source: Option, /// Git ref to check out when `source_type` is `git`. #[serde(default, rename = "ref")] pub ref_name: Option, /// Sparse checkout paths used when `source_type` is `git`. #[serde(default)] pub sparse_paths: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum MarketplaceSourceType { Git, Local, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] #[schemars(deny_unknown_fields)] 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, } #[cfg(test)] #[path = "types_tests.rs"] mod tests;