Skip to main content

codex_config/
types.rs

1//! Types used to define loaded and effective Codex configuration values.
2
3// Note this file should generally be restricted to simple struct/enum
4// definitions that do not contain business logic.
5
6pub use crate::mcp_types::AppToolApproval;
7pub use crate::mcp_types::McpServerAuth;
8pub use crate::mcp_types::McpServerConfig;
9pub use crate::mcp_types::McpServerDisabledReason;
10pub use crate::mcp_types::McpServerEnvVar;
11pub use crate::mcp_types::McpServerOAuthConfig;
12pub use crate::mcp_types::McpServerToolConfig;
13pub use crate::mcp_types::McpServerTransportConfig;
14pub use crate::mcp_types::RawMcpServerConfig;
15pub use crate::shell_environment_policy::ShellEnvironmentPolicyToml;
16pub use codex_protocol::config_types::AltScreenMode;
17pub use codex_protocol::config_types::ApprovalsReviewer;
18pub use codex_protocol::config_types::ModeKind;
19pub use codex_protocol::config_types::Personality;
20pub use codex_protocol::config_types::ServiceTier;
21pub use codex_protocol::config_types::WebSearchMode;
22use codex_utils_absolute_path::AbsolutePathBuf;
23use std::collections::BTreeMap;
24use std::collections::HashMap;
25use std::fmt;
26
27use schemars::JsonSchema;
28use serde::Deserialize;
29use serde::Serialize;
30
31pub use crate::tui_keymap::KeybindingSpec;
32pub use crate::tui_keymap::KeybindingsSpec;
33pub use crate::tui_keymap::MAX_FUNCTION_KEY;
34pub use crate::tui_keymap::TuiApprovalKeymap;
35pub use crate::tui_keymap::TuiChatKeymap;
36pub use crate::tui_keymap::TuiComposerKeymap;
37pub use crate::tui_keymap::TuiEditorKeymap;
38pub use crate::tui_keymap::TuiGlobalKeymap;
39pub use crate::tui_keymap::TuiKeymap;
40pub use crate::tui_keymap::TuiListKeymap;
41pub use crate::tui_keymap::TuiPagerKeymap;
42pub use crate::tui_keymap::TuiVimNormalKeymap;
43pub use crate::tui_keymap::TuiVimOperatorKeymap;
44
45pub const DEFAULT_OTEL_ENVIRONMENT: &str = "dev";
46pub const DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 2;
47pub const DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS: i64 = 10;
48pub const DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS: i64 = 6;
49pub const DEFAULT_MEMORIES_MIN_RATE_LIMIT_REMAINING_PERCENT: i64 = 25;
50pub const DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION: usize = 256;
51pub const DEFAULT_MEMORIES_MAX_UNUSED_DAYS: i64 = 30;
52const MIN_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION: usize = 1;
53const MAX_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION: usize = 4096;
54const MIN_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 1;
55const MAX_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 128;
56
57const fn default_enabled() -> bool {
58    true
59}
60
61/// Preferred layout for the resume/fork session picker.
62#[derive(Serialize, Deserialize, Debug, Default, Copy, Clone, PartialEq, Eq, JsonSchema)]
63#[serde(rename_all = "kebab-case")]
64pub enum SessionPickerViewMode {
65    Comfortable,
66    #[default]
67    Dense,
68}
69
70impl SessionPickerViewMode {
71    pub const fn as_str(self) -> &'static str {
72        match self {
73            Self::Comfortable => "comfortable",
74            Self::Dense => "dense",
75        }
76    }
77}
78
79impl fmt::Display for SessionPickerViewMode {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.write_str(self.as_str())
82    }
83}
84
85/// Working directory to use when resuming or forking a session.
86#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, JsonSchema)]
87#[serde(rename_all = "kebab-case")]
88pub enum ResumeCwdMode {
89    /// Use the directory where Codex was launched.
90    Current,
91    /// Use the latest working directory recorded in the selected session.
92    Session,
93}
94
95impl ResumeCwdMode {
96    pub const fn as_str(self) -> &'static str {
97        match self {
98            Self::Current => "current",
99            Self::Session => "session",
100        }
101    }
102}
103
104/// Determine where Codex should store CLI auth credentials.
105#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
106#[serde(rename_all = "lowercase")]
107pub enum AuthCredentialsStoreMode {
108    #[default]
109    /// Persist credentials in CODEX_HOME/auth.json.
110    File,
111    /// Persist credentials in the keyring. Fail if unavailable.
112    Keyring,
113    /// Use keyring when available; otherwise, fall back to a file in CODEX_HOME.
114    Auto,
115    /// Store credentials in memory only for the current process.
116    Ephemeral,
117}
118
119/// Determine where Codex should store and read MCP credentials.
120#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
121#[serde(rename_all = "lowercase")]
122pub enum OAuthCredentialsStoreMode {
123    /// Prefer `Keyring` and use `File` when keyring storage is unavailable.
124    /// Once an MCP client loads credentials from one store, that client keeps the resolved store
125    /// for its lifetime so refreshes cannot switch to a possibly stale credential source.
126    /// Credentials stored in the keyring will only be readable by Codex unless the user explicitly grants access via OS-level keyring access.
127    #[default]
128    Auto,
129    /// CODEX_HOME/.credentials.json
130    /// This file will be readable to Codex and other applications running as the same user.
131    File,
132    /// Keyring when available, otherwise fail.
133    Keyring,
134}
135
136/// Determine how auth credentials should use keyring-backed storage.
137#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
138#[serde(rename_all = "lowercase")]
139pub enum AuthKeyringBackendKind {
140    /// Store the serialized auth payload directly in the OS keyring.
141    Direct,
142    /// Store auth payloads in the local encrypted secrets file, with the file key in the OS keyring.
143    Secrets,
144}
145
146impl Default for AuthKeyringBackendKind {
147    fn default() -> Self {
148        if cfg!(windows) {
149            Self::Secrets
150        } else {
151            Self::Direct
152        }
153    }
154}
155
156#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
157#[serde(rename_all = "kebab-case")]
158pub enum WindowsSandboxModeToml {
159    Elevated,
160    Unelevated,
161}
162
163#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
164#[schemars(deny_unknown_fields)]
165pub struct WindowsToml {
166    pub sandbox: Option<WindowsSandboxModeToml>,
167    /// Defaults to `true`. Set to `false` to launch the final sandboxed child
168    /// process on `Winsta0\\Default` instead of a private desktop.
169    pub sandbox_private_desktop: Option<bool>,
170}
171
172#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, JsonSchema)]
173pub enum UriBasedFileOpener {
174    #[serde(rename = "vscode")]
175    VsCode,
176
177    #[serde(rename = "vscode-insiders")]
178    VsCodeInsiders,
179
180    #[serde(rename = "windsurf")]
181    Windsurf,
182
183    #[serde(rename = "cursor")]
184    Cursor,
185
186    /// Option to disable the URI-based file opener.
187    #[serde(rename = "none")]
188    None,
189}
190
191/// Settings that govern if and what will be written to `~/.codex/history.jsonl`.
192#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
193#[serde(default)]
194#[schemars(deny_unknown_fields)]
195pub struct History {
196    /// If true, history entries will not be written to disk.
197    pub persistence: HistoryPersistence,
198
199    /// If set, the maximum size of the history file in bytes. The oldest entries
200    /// are dropped once the file exceeds this limit.
201    pub max_bytes: Option<usize>,
202}
203
204#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Default, JsonSchema)]
205#[serde(rename_all = "kebab-case")]
206pub enum HistoryPersistence {
207    /// Save all history entries to disk.
208    #[default]
209    SaveAll,
210    /// Do not write history to disk.
211    None,
212}
213
214// ===== Analytics configuration =====
215
216/// Analytics settings loaded from config.toml. Fields are optional so we can apply defaults.
217#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
218#[schemars(deny_unknown_fields)]
219pub struct AnalyticsConfigToml {
220    /// When `false`, disables analytics across Codex product surfaces in this profile.
221    pub enabled: Option<bool>,
222}
223
224#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
225#[schemars(deny_unknown_fields)]
226pub struct FeedbackConfigToml {
227    /// When `false`, disables the feedback flow across Codex product surfaces.
228    pub enabled: Option<bool>,
229}
230
231#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema)]
232#[serde(rename_all = "snake_case")]
233pub enum ToolSuggestDiscoverableType {
234    Connector,
235    Plugin,
236}
237
238#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, JsonSchema)]
239#[schemars(deny_unknown_fields)]
240pub struct ToolSuggestDiscoverable {
241    #[serde(rename = "type")]
242    pub kind: ToolSuggestDiscoverableType,
243    pub id: String,
244}
245
246#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, JsonSchema)]
247#[schemars(deny_unknown_fields)]
248pub struct ToolSuggestDisabledTool {
249    #[serde(rename = "type")]
250    pub kind: ToolSuggestDiscoverableType,
251    pub id: String,
252}
253
254impl ToolSuggestDisabledTool {
255    pub fn plugin(id: impl Into<String>) -> Self {
256        Self {
257            kind: ToolSuggestDiscoverableType::Plugin,
258            id: id.into(),
259        }
260    }
261
262    pub fn connector(id: impl Into<String>) -> Self {
263        Self {
264            kind: ToolSuggestDiscoverableType::Connector,
265            id: id.into(),
266        }
267    }
268
269    pub fn normalized(&self) -> Option<Self> {
270        let id = self.id.trim();
271        (!id.is_empty()).then(|| Self {
272            kind: self.kind,
273            id: id.to_string(),
274        })
275    }
276}
277
278#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
279#[schemars(deny_unknown_fields)]
280pub struct ToolSuggestConfig {
281    #[serde(default)]
282    pub discoverables: Vec<ToolSuggestDiscoverable>,
283    #[serde(default)]
284    pub disabled_tools: Vec<ToolSuggestDisabledTool>,
285}
286
287/// Memories settings loaded from config.toml.
288#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
289#[schemars(deny_unknown_fields)]
290pub struct MemoriesToml {
291    /// When `true`, external context sources mark the thread `memory_mode` as `"polluted"`.
292    #[serde(alias = "no_memories_if_mcp_or_web_search")]
293    pub disable_on_external_context: Option<bool>,
294    /// When `false`, newly created threads are stored with `memory_mode = "disabled"` in the state DB.
295    pub generate_memories: Option<bool>,
296    /// When `false`, skip injecting memory usage instructions into developer prompts.
297    pub use_memories: Option<bool>,
298    /// When `true`, expose dedicated memory tools through the extension tool surface.
299    pub dedicated_tools: Option<bool>,
300    /// Maximum number of recent raw memories retained for global consolidation.
301    #[schemars(range(min = 1, max = 4096))]
302    pub max_raw_memories_for_consolidation: Option<usize>,
303    /// Maximum number of days since a memory was last used before it becomes ineligible for phase 2 selection.
304    pub max_unused_days: Option<i64>,
305    /// Maximum age of the threads used for memories.
306    pub max_rollout_age_days: Option<i64>,
307    /// Maximum number of rollout candidates processed per pass.
308    #[schemars(range(min = 1, max = 128))]
309    pub max_rollouts_per_startup: Option<usize>,
310    /// Minimum idle time between last thread activity and memory creation (hours). > 12h recommended.
311    pub min_rollout_idle_hours: Option<i64>,
312    /// Minimum remaining percentage required in Codex rate-limit windows before memory startup runs.
313    #[schemars(range(min = 0, max = 100))]
314    pub min_rate_limit_remaining_percent: Option<i64>,
315    /// Model used for thread summarisation.
316    pub extract_model: Option<String>,
317    /// Model used for memory consolidation.
318    pub consolidation_model: Option<String>,
319}
320
321/// Effective memories settings after defaults are applied.
322#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
323pub struct MemoriesConfig {
324    pub disable_on_external_context: bool,
325    pub generate_memories: bool,
326    pub use_memories: bool,
327    pub dedicated_tools: bool,
328    pub max_raw_memories_for_consolidation: usize,
329    pub max_unused_days: i64,
330    pub max_rollout_age_days: i64,
331    pub max_rollouts_per_startup: usize,
332    pub min_rollout_idle_hours: i64,
333    pub min_rate_limit_remaining_percent: i64,
334    pub extract_model: Option<String>,
335    pub consolidation_model: Option<String>,
336}
337
338impl Default for MemoriesConfig {
339    fn default() -> Self {
340        Self {
341            disable_on_external_context: false,
342            generate_memories: true,
343            use_memories: true,
344            dedicated_tools: false,
345            max_raw_memories_for_consolidation: DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION,
346            max_unused_days: DEFAULT_MEMORIES_MAX_UNUSED_DAYS,
347            max_rollout_age_days: DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS,
348            max_rollouts_per_startup: DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP,
349            min_rollout_idle_hours: DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS,
350            min_rate_limit_remaining_percent: DEFAULT_MEMORIES_MIN_RATE_LIMIT_REMAINING_PERCENT,
351            extract_model: None,
352            consolidation_model: None,
353        }
354    }
355}
356
357impl From<MemoriesToml> for MemoriesConfig {
358    fn from(toml: MemoriesToml) -> Self {
359        let defaults = Self::default();
360        Self {
361            disable_on_external_context: toml
362                .disable_on_external_context
363                .unwrap_or(defaults.disable_on_external_context),
364            generate_memories: toml.generate_memories.unwrap_or(defaults.generate_memories),
365            use_memories: toml.use_memories.unwrap_or(defaults.use_memories),
366            dedicated_tools: toml.dedicated_tools.unwrap_or(defaults.dedicated_tools),
367            max_raw_memories_for_consolidation: toml
368                .max_raw_memories_for_consolidation
369                .unwrap_or(defaults.max_raw_memories_for_consolidation)
370                .clamp(
371                    MIN_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION,
372                    MAX_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION,
373                ),
374            max_unused_days: toml
375                .max_unused_days
376                .unwrap_or(defaults.max_unused_days)
377                .clamp(0, 365),
378            max_rollout_age_days: toml
379                .max_rollout_age_days
380                .unwrap_or(defaults.max_rollout_age_days)
381                .clamp(0, 90),
382            max_rollouts_per_startup: toml
383                .max_rollouts_per_startup
384                .unwrap_or(defaults.max_rollouts_per_startup)
385                .clamp(
386                    MIN_MEMORIES_MAX_ROLLOUTS_PER_STARTUP,
387                    MAX_MEMORIES_MAX_ROLLOUTS_PER_STARTUP,
388                ),
389            min_rollout_idle_hours: toml
390                .min_rollout_idle_hours
391                .unwrap_or(defaults.min_rollout_idle_hours)
392                .clamp(1, 48),
393            min_rate_limit_remaining_percent: toml
394                .min_rate_limit_remaining_percent
395                .unwrap_or(defaults.min_rate_limit_remaining_percent)
396                .clamp(0, 100),
397            extract_model: toml.extract_model,
398            consolidation_model: toml.consolidation_model,
399        }
400    }
401}
402
403/// Default settings that apply to all apps.
404#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
405#[schemars(deny_unknown_fields)]
406pub struct AppsDefaultConfig {
407    /// When `false`, apps are disabled unless overridden by per-app settings.
408    #[serde(default = "default_enabled")]
409    pub enabled: bool,
410
411    /// Reviewer for approval prompts unless overridden by per-app settings.
412    #[serde(default, skip_serializing_if = "Option::is_none")]
413    pub approvals_reviewer: Option<ApprovalsReviewer>,
414
415    /// Whether tools with `destructive_hint = true` are allowed by default.
416    #[serde(
417        default = "default_enabled",
418        skip_serializing_if = "std::clone::Clone::clone"
419    )]
420    pub destructive_enabled: bool,
421
422    /// Whether tools with `open_world_hint = true` are allowed by default.
423    #[serde(
424        default = "default_enabled",
425        skip_serializing_if = "std::clone::Clone::clone"
426    )]
427    pub open_world_enabled: bool,
428
429    /// Approval mode for tools unless overridden by per-app or per-tool settings.
430    #[serde(default, skip_serializing_if = "Option::is_none")]
431    pub default_tools_approval_mode: Option<AppToolApproval>,
432}
433
434/// Per-tool settings for a single app tool.
435#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
436#[schemars(deny_unknown_fields)]
437pub struct AppToolConfig {
438    /// Whether this tool is enabled. `Some(true)` explicitly allows this tool.
439    #[serde(default, skip_serializing_if = "Option::is_none")]
440    pub enabled: Option<bool>,
441
442    /// Approval mode for this tool.
443    #[serde(default, skip_serializing_if = "Option::is_none")]
444    pub approval_mode: Option<AppToolApproval>,
445}
446
447/// Tool settings for a single app.
448#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
449#[schemars(deny_unknown_fields)]
450pub struct AppToolsConfig {
451    /// Per-tool overrides keyed by tool name (for example `repos/list`).
452    #[serde(default, flatten)]
453    pub tools: HashMap<String, AppToolConfig>,
454}
455
456/// Config values for a single app/connector.
457#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
458#[schemars(deny_unknown_fields)]
459pub struct AppConfig {
460    /// When `false`, Codex does not surface this app.
461    #[serde(default = "default_enabled")]
462    pub enabled: bool,
463
464    /// Reviewer for approval prompts from this app, overriding the thread default.
465    #[serde(default, skip_serializing_if = "Option::is_none")]
466    pub approvals_reviewer: Option<ApprovalsReviewer>,
467
468    /// Whether tools with `destructive_hint = true` are allowed for this app.
469    #[serde(default, skip_serializing_if = "Option::is_none")]
470    pub destructive_enabled: Option<bool>,
471
472    /// Whether tools with `open_world_hint = true` are allowed for this app.
473    #[serde(default, skip_serializing_if = "Option::is_none")]
474    pub open_world_enabled: Option<bool>,
475
476    /// Approval mode for tools in this app unless a tool override exists.
477    #[serde(default, skip_serializing_if = "Option::is_none")]
478    pub default_tools_approval_mode: Option<AppToolApproval>,
479
480    /// Whether tools are enabled by default for this app.
481    #[serde(default, skip_serializing_if = "Option::is_none")]
482    pub default_tools_enabled: Option<bool>,
483
484    /// Per-tool settings for this app.
485    #[serde(default, skip_serializing_if = "Option::is_none")]
486    pub tools: Option<AppToolsConfig>,
487}
488
489/// App/connector settings loaded from `config.toml`.
490#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
491#[schemars(deny_unknown_fields)]
492pub struct AppsConfigToml {
493    /// Default settings for all apps.
494    #[serde(default, rename = "_default", skip_serializing_if = "Option::is_none")]
495    pub default: Option<AppsDefaultConfig>,
496
497    /// Per-app settings keyed by app ID (for example `[apps.google_drive]`).
498    #[serde(default, flatten)]
499    pub apps: HashMap<String, AppConfig>,
500}
501
502// ===== OTEL configuration =====
503
504#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
505#[serde(rename_all = "kebab-case")]
506pub enum OtelHttpProtocol {
507    /// Binary payload
508    Binary,
509    /// JSON payload
510    Json,
511}
512
513#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
514#[schemars(deny_unknown_fields)]
515#[serde(rename_all = "kebab-case")]
516pub struct OtelTlsConfig {
517    pub ca_certificate: Option<AbsolutePathBuf>,
518    pub client_certificate: Option<AbsolutePathBuf>,
519    pub client_private_key: Option<AbsolutePathBuf>,
520}
521
522/// Which OTEL exporter to use.
523#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
524#[schemars(deny_unknown_fields)]
525#[serde(rename_all = "kebab-case")]
526pub enum OtelExporterKind {
527    None,
528    Statsig,
529    OtlpHttp {
530        endpoint: String,
531        #[serde(default)]
532        headers: HashMap<String, String>,
533        protocol: OtelHttpProtocol,
534        #[serde(default)]
535        tls: Option<OtelTlsConfig>,
536    },
537    OtlpGrpc {
538        endpoint: String,
539        #[serde(default)]
540        headers: HashMap<String, String>,
541        #[serde(default)]
542        tls: Option<OtelTlsConfig>,
543    },
544}
545
546/// OTEL settings loaded from config.toml. Fields are optional so we can apply defaults.
547#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
548#[schemars(deny_unknown_fields)]
549pub struct OtelConfigToml {
550    /// Log user prompt in traces
551    pub log_user_prompt: Option<bool>,
552
553    /// Mark traces with environment (dev, staging, prod, test). Defaults to dev.
554    pub environment: Option<String>,
555
556    /// Optional log exporter
557    pub exporter: Option<OtelExporterKind>,
558
559    /// Optional trace exporter
560    pub trace_exporter: Option<OtelExporterKind>,
561
562    /// Optional metrics exporter
563    pub metrics_exporter: Option<OtelExporterKind>,
564
565    /// Attributes to add to every exported trace span.
566    pub span_attributes: Option<BTreeMap<String, String>>,
567
568    /// Semicolon-separated `key:value` fields to upsert into W3C tracestate members.
569    pub tracestate: Option<BTreeMap<String, BTreeMap<String, String>>>,
570}
571
572/// Effective OTEL settings after defaults are applied.
573#[derive(Debug, Clone, PartialEq)]
574pub struct OtelConfig {
575    pub log_user_prompt: bool,
576    pub environment: String,
577    pub exporter: OtelExporterKind,
578    pub trace_exporter: OtelExporterKind,
579    pub metrics_exporter: OtelExporterKind,
580    pub span_attributes: BTreeMap<String, String>,
581    pub tracestate: BTreeMap<String, BTreeMap<String, String>>,
582}
583
584impl Default for OtelConfig {
585    fn default() -> Self {
586        OtelConfig {
587            log_user_prompt: false,
588            environment: DEFAULT_OTEL_ENVIRONMENT.to_owned(),
589            exporter: OtelExporterKind::None,
590            trace_exporter: OtelExporterKind::None,
591            metrics_exporter: OtelExporterKind::Statsig,
592            span_attributes: BTreeMap::new(),
593            tracestate: BTreeMap::new(),
594        }
595    }
596}
597
598#[derive(Serialize, Debug, Clone, PartialEq, Eq, Deserialize, JsonSchema)]
599#[serde(untagged)]
600pub enum Notifications {
601    Enabled(bool),
602    Custom(Vec<String>),
603}
604
605impl Default for Notifications {
606    fn default() -> Self {
607        Self::Enabled(true)
608    }
609}
610
611#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)]
612#[serde(rename_all = "lowercase")]
613pub enum NotificationMethod {
614    #[default]
615    Auto,
616    Osc9,
617    Bel,
618}
619
620impl fmt::Display for NotificationMethod {
621    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
622        match self {
623            NotificationMethod::Auto => write!(f, "auto"),
624            NotificationMethod::Osc9 => write!(f, "osc9"),
625            NotificationMethod::Bel => write!(f, "bel"),
626        }
627    }
628}
629
630#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)]
631#[serde(rename_all = "lowercase")]
632pub enum NotificationCondition {
633    /// Emit TUI notifications only while the terminal is unfocused.
634    #[default]
635    Unfocused,
636    /// Emit TUI notifications regardless of terminal focus.
637    Always,
638}
639
640impl fmt::Display for NotificationCondition {
641    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642        match self {
643            NotificationCondition::Unfocused => write!(f, "unfocused"),
644            NotificationCondition::Always => write!(f, "always"),
645        }
646    }
647}
648
649#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)]
650#[serde(rename_all = "kebab-case")]
651pub enum TuiPetAnchor {
652    /// Anchor the pet to the bottom of the current TUI composer viewport.
653    #[default]
654    Composer,
655    /// Anchor the pet to the physical bottom of the terminal screen.
656    ScreenBottom,
657}
658
659#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
660#[schemars(deny_unknown_fields)]
661pub struct TuiNotificationSettings {
662    /// Enable desktop notifications from the TUI.
663    /// Defaults to `true`.
664    #[serde(default, rename = "notifications")]
665    pub notifications: Notifications,
666
667    /// Notification method to use for terminal notifications.
668    /// Defaults to `auto`.
669    #[serde(default, rename = "notification_method")]
670    pub method: NotificationMethod,
671
672    /// Controls whether TUI notifications are delivered only when the terminal is unfocused or
673    /// regardless of focus. Defaults to `unfocused`.
674    #[serde(default, rename = "notification_condition")]
675    pub condition: NotificationCondition,
676}
677
678#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
679#[schemars(deny_unknown_fields)]
680pub struct ModelAvailabilityNuxConfig {
681    /// Number of times a startup availability NUX has been shown per model slug.
682    #[serde(default, flatten)]
683    pub shown_count: HashMap<String, u32>,
684}
685
686/// Fallback resize-reflow row cap when Codex cannot identify a terminal-specific scrollback size.
687pub const DEFAULT_TERMINAL_RESIZE_REFLOW_FALLBACK_MAX_ROWS: usize = 1_000;
688
689/// Collection of settings that are specific to the TUI.
690#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
691#[schemars(deny_unknown_fields)]
692pub struct Tui {
693    #[serde(default, flatten)]
694    pub notification_settings: TuiNotificationSettings,
695
696    /// Enable animations (welcome screen, shimmer effects, spinners).
697    /// Defaults to `true`.
698    #[serde(default = "default_true")]
699    pub animations: bool,
700
701    /// Show startup tooltips in the TUI welcome screen.
702    /// Defaults to `true`.
703    #[serde(default = "default_true")]
704    pub show_tooltips: bool,
705
706    /// Start the composer in Vim mode (`Normal`) by default.
707    /// Defaults to `false`.
708    #[serde(default)]
709    pub vim_mode_default: bool,
710
711    /// Start the TUI in raw scrollback mode for copy-friendly transcript output.
712    /// Defaults to `false`.
713    #[serde(default)]
714    pub raw_output_mode: bool,
715
716    /// Controls whether the TUI uses the terminal's alternate screen buffer.
717    ///
718    /// - `auto` (default): Use alternate screen.
719    /// - `always`: Always use alternate screen.
720    /// - `never`: Never use alternate screen (inline mode only, preserves scrollback).
721    #[serde(default)]
722    pub alternate_screen: AltScreenMode,
723
724    /// Ordered list of status line item identifiers.
725    ///
726    /// When set, the TUI renders the selected items as the status line.
727    /// When unset, the TUI defaults to: `model-with-reasoning` and `current-dir`.
728    #[serde(default)]
729    pub status_line: Option<Vec<String>>,
730
731    /// Color status line items with colors derived from the active syntax theme.
732    /// Defaults to `true`.
733    #[serde(default = "default_true")]
734    pub status_line_use_colors: bool,
735
736    /// Ordered list of terminal title item identifiers.
737    ///
738    /// When set, the TUI renders the selected items into the terminal window/tab title.
739    /// When unset, the TUI defaults to: `activity` and `project`.
740    /// The `activity` item spins while working and shows an action-required
741    /// message when blocked on the user.
742    #[serde(default)]
743    pub terminal_title: Option<Vec<String>>,
744
745    /// Syntax highlighting theme name (kebab-case).
746    ///
747    /// When set, overrides automatic light/dark theme detection.
748    /// Use `/theme` in the TUI or see `$CODEX_HOME/themes` for custom themes.
749    #[serde(default)]
750    pub theme: Option<String>,
751
752    /// Pet id to preselect in the terminal pet picker.
753    ///
754    /// Custom pet ids resolve against CODEX_HOME/pets/<pet-id>/pet.json.
755    #[serde(default)]
756    pub pet: Option<String>,
757
758    /// Where the terminal pet should anchor vertically.
759    ///
760    /// Defaults to `composer`, which follows the current TUI composer viewport.
761    #[serde(default)]
762    pub pet_anchor: TuiPetAnchor,
763
764    /// Preferred layout for resume/fork session picker results.
765    #[serde(default)]
766    pub session_picker_view: Option<SessionPickerViewMode>,
767
768    /// Working directory to use when resuming or forking a session.
769    /// When unset, prompt if the current and session directories differ.
770    #[serde(default)]
771    pub resume_cwd: Option<ResumeCwdMode>,
772
773    /// Keybinding overrides for the TUI.
774    ///
775    /// This supports rebinding selected actions globally and by context.
776    /// Context bindings take precedence over `global` bindings.
777    #[serde(default)]
778    pub keymap: TuiKeymap,
779
780    /// Startup tooltip availability NUX state persisted by the TUI.
781    #[serde(default)]
782    pub model_availability_nux: ModelAvailabilityNuxConfig,
783
784    /// Trim terminal resize-reflow replay to the most recent rendered terminal rows when the
785    /// transcript exceeds this cap. Omit to use Codex's terminal-specific default. Set to `0` to
786    /// keep all rendered rows.
787    #[serde(default)]
788    #[schemars(range(min = 0))]
789    pub terminal_resize_reflow_max_rows: Option<usize>,
790}
791
792const fn default_true() -> bool {
793    true
794}
795
796/// Settings for notices we display to users via the tui and app-server clients
797/// (primarily the Codex IDE extension). NOTE: these are different from
798/// notifications - notices are warnings, NUX screens, acknowledgements, etc.
799#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
800#[schemars(deny_unknown_fields)]
801pub struct ExternalConfigMigrationPrompts {
802    /// Tracks whether home-level external config migration prompts are hidden.
803    pub home: Option<bool>,
804    /// Tracks the last time the home-level external config migration prompt was shown.
805    pub home_last_prompted_at: Option<i64>,
806    /// Tracks which project paths have opted out of external config migration prompts.
807    #[serde(default)]
808    pub projects: BTreeMap<String, bool>,
809    /// Tracks the last time a project-level external config migration prompt was shown.
810    #[serde(default)]
811    pub project_last_prompted_at: BTreeMap<String, i64>,
812}
813
814#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
815#[schemars(deny_unknown_fields)]
816pub struct Notice {
817    /// Tracks whether the user has acknowledged the full access warning prompt.
818    pub hide_full_access_warning: Option<bool>,
819    /// Tracks whether the user has acknowledged the Windows world-writable directories warning.
820    pub hide_world_writable_warning: Option<bool>,
821    /// Tracks whether the user opted out of Codex-managed fast defaults.
822    pub fast_default_opt_out: Option<bool>,
823    /// Tracks whether the user opted out of the rate limit model switch reminder.
824    pub hide_rate_limit_model_nudge: Option<bool>,
825    /// Tracks whether the user has seen the model migration prompt
826    pub hide_gpt5_1_migration_prompt: Option<bool>,
827    /// Tracks whether the user has seen the gpt-5.1-codex-max migration prompt
828    #[serde(rename = "hide_gpt-5.1-codex-max_migration_prompt")]
829    pub hide_gpt_5_1_codex_max_migration_prompt: Option<bool>,
830    /// Tracks acknowledged model migrations as old->new model slug mappings.
831    #[serde(default)]
832    pub model_migrations: BTreeMap<String, String>,
833    /// Tracks scopes where external config migration prompts should be suppressed.
834    #[serde(default)]
835    pub external_config_migration_prompts: ExternalConfigMigrationPrompts,
836}
837
838pub use crate::skills_config::BundledSkillsConfig;
839pub use crate::skills_config::SkillConfig;
840pub use crate::skills_config::SkillsConfig;
841
842#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
843#[schemars(deny_unknown_fields)]
844pub struct PluginConfig {
845    #[serde(default = "default_enabled")]
846    pub enabled: bool,
847
848    /// Per-MCP-server policy overlays for MCP servers contributed by this plugin.
849    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
850    pub mcp_servers: HashMap<String, PluginMcpServerConfig>,
851}
852
853/// Policy settings for a plugin-provided MCP server.
854///
855/// This intentionally excludes transport settings: plugin manifests own how the
856/// MCP server is launched, while user config owns enablement and tool policy.
857#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
858#[schemars(deny_unknown_fields)]
859pub struct PluginMcpServerConfig {
860    /// When `false`, Codex skips initializing this plugin MCP server.
861    #[serde(default = "default_enabled")]
862    pub enabled: bool,
863
864    /// Approval mode for tools in this server unless a tool override exists.
865    #[serde(default, skip_serializing_if = "Option::is_none")]
866    pub default_tools_approval_mode: Option<AppToolApproval>,
867
868    /// Explicit allow-list of tools exposed from this server.
869    #[serde(default, skip_serializing_if = "Option::is_none")]
870    pub enabled_tools: Option<Vec<String>>,
871
872    /// Explicit deny-list of tools. These tools are removed after applying `enabled_tools`.
873    #[serde(default, skip_serializing_if = "Option::is_none")]
874    pub disabled_tools: Option<Vec<String>>,
875
876    /// Per-tool approval settings keyed by tool name.
877    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
878    pub tools: HashMap<String, McpServerToolConfig>,
879}
880
881impl Default for PluginMcpServerConfig {
882    fn default() -> Self {
883        Self {
884            enabled: true,
885            default_tools_approval_mode: None,
886            enabled_tools: None,
887            disabled_tools: None,
888            tools: HashMap::new(),
889        }
890    }
891}
892
893#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
894#[schemars(deny_unknown_fields)]
895pub struct MarketplaceConfig {
896    /// Last time Codex successfully added or refreshed this marketplace.
897    #[serde(default)]
898    pub last_updated: Option<String>,
899    /// Git revision Codex last successfully activated for this marketplace.
900    #[serde(default)]
901    pub last_revision: Option<String>,
902    /// Source kind used to install this marketplace.
903    #[serde(default)]
904    pub source_type: Option<MarketplaceSourceType>,
905    /// Source location used when the marketplace was added.
906    #[serde(default)]
907    pub source: Option<String>,
908    /// Git ref to check out when `source_type` is `git`.
909    #[serde(default, rename = "ref")]
910    pub ref_name: Option<String>,
911    /// Sparse checkout paths used when `source_type` is `git`.
912    #[serde(default)]
913    pub sparse_paths: Option<Vec<String>>,
914}
915
916#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
917#[serde(rename_all = "snake_case")]
918pub enum MarketplaceSourceType {
919    Git,
920    Local,
921}
922
923#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
924#[schemars(deny_unknown_fields)]
925pub struct SandboxWorkspaceWrite {
926    #[serde(default)]
927    pub writable_roots: Vec<AbsolutePathBuf>,
928    #[serde(default)]
929    pub network_access: bool,
930    #[serde(default)]
931    pub exclude_tmpdir_env_var: bool,
932    #[serde(default)]
933    pub exclude_slash_tmp: bool,
934}
935
936#[cfg(test)]
937#[path = "types_tests.rs"]
938mod tests;