From f32a5e84bf925db613c1738052aa96bfc1f954f6 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 2 Apr 2026 00:39:20 -0700 Subject: [PATCH] [codex] Move config types into codex-config (#16523) ## Why `codex-rs/core/src/config/types.rs` is a plain config-type module with no dependency on `codex-core`. Moving it into `codex-config` shrinks the core crate and gives config-only consumers a more natural dependency boundary. ## What Changed - Added `codex_config::types` with the moved structs, enums, constants, and unit tests. - Kept `codex_core::config::types` as a compatibility re-export to avoid a broad call-site migration in this PR. - Switched notice-table writes in `core/src/config/edit.rs` to a local `NOTICE_TABLE_KEY` constant. - Added the `wildmatch` runtime dependency and `tempfile` test dependency to `codex-config`. --- codex-rs/Cargo.lock | 3 +- codex-rs/config/Cargo.toml | 2 + codex-rs/config/src/lib.rs | 1 + codex-rs/config/src/types.rs | 687 +++++++++++++++++ .../src/config => config/src}/types_tests.rs | 0 codex-rs/core/Cargo.toml | 1 - codex-rs/core/src/config/edit.rs | 13 +- codex-rs/core/src/config/types.rs | 693 +----------------- 8 files changed, 700 insertions(+), 700 deletions(-) create mode 100644 codex-rs/config/src/types.rs rename codex-rs/{core/src/config => config/src}/types_tests.rs (100%) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 4cb526430..d5bab66c6 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1803,11 +1803,13 @@ dependencies = [ "serde_json", "serde_path_to_error", "sha2", + "tempfile", "thiserror 2.0.18", "tokio", "toml 0.9.11+spec-1.1.0", "toml_edit 0.24.0+spec-1.1.0", "tracing", + "wildmatch", ] [[package]] @@ -1936,7 +1938,6 @@ dependencies = [ "uuid", "walkdir", "which 8.0.0", - "wildmatch", "windows-sys 0.52.0", "wiremock", "zip", diff --git a/codex-rs/config/Cargo.toml b/codex-rs/config/Cargo.toml index f1bd2dba1..0b440263e 100644 --- a/codex-rs/config/Cargo.toml +++ b/codex-rs/config/Cargo.toml @@ -24,8 +24,10 @@ tokio = { workspace = true, features = ["fs"] } toml = { workspace = true } toml_edit = { workspace = true } tracing = { workspace = true } +wildmatch = { workspace = true } [dev-dependencies] anyhow = { workspace = true } pretty_assertions = { workspace = true } +tempfile = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/codex-rs/config/src/lib.rs b/codex-rs/config/src/lib.rs index f769f2496..219601132 100644 --- a/codex-rs/config/src/lib.rs +++ b/codex-rs/config/src/lib.rs @@ -11,6 +11,7 @@ mod project_root_markers; mod requirements_exec_policy; mod skills_config; mod state; +pub mod types; pub const CONFIG_TOML_FILE: &str = "config.toml"; diff --git a/codex-rs/config/src/types.rs b/codex-rs/config/src/types.rs new file mode 100644 index 000000000..c52383352 --- /dev/null +++ b/codex-rs/config/src/types.rs @@ -0,0 +1,687 @@ +//! 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_types::AppToolApproval; +pub use crate::mcp_types::McpServerConfig; +pub use crate::mcp_types::McpServerDisabledReason; +pub use crate::mcp_types::McpServerToolConfig; +pub use crate::mcp_types::McpServerTransportConfig; +pub use crate::mcp_types::RawMcpServerConfig; +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; +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 wildmatch::WildMatchPattern; + +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; + +pub const DEFAULT_OTEL_ENVIRONMENT: &str = "dev"; +pub const DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 16; +pub const DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS: i64 = 30; +pub const DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS: i64 = 6; +pub const DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION: usize = 256; +pub const DEFAULT_MEMORIES_MAX_UNUSED_DAYS: i64 = 30; + +const fn default_enabled() -> bool { + true +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "kebab-case")] +pub enum WindowsSandboxModeToml { + Elevated, + Unelevated, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] +#[schemars(deny_unknown_fields)] +pub struct WindowsToml { + pub sandbox: Option, + /// Defaults to `true`. Set to `false` to launch the final sandboxed child + /// process on `Winsta0\\Default` instead of a private desktop. + pub sandbox_private_desktop: 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, +} + +impl UriBasedFileOpener { + pub fn get_scheme(&self) -> Option<&str> { + match self { + UriBasedFileOpener::VsCode => Some("vscode"), + UriBasedFileOpener::VsCodeInsiders => Some("vscode-insiders"), + UriBasedFileOpener::Windsurf => Some("windsurf"), + UriBasedFileOpener::Cursor => Some("cursor"), + UriBasedFileOpener::None => None, + } + } +} + +/// Settings that govern if and what will be written to `~/.codex/history.jsonl`. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] +#[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 analytics across Codex product surfaces in this profile. + pub enabled: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] +#[schemars(deny_unknown_fields)] +pub struct FeedbackConfigToml { + /// When `false`, disables the feedback flow across Codex product surfaces. + 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, Default, JsonSchema)] +#[schemars(deny_unknown_fields)] +pub struct ToolSuggestConfig { + #[serde(default)] + pub discoverables: Vec, +} + +/// Memories settings loaded from config.toml. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] +#[schemars(deny_unknown_fields)] +pub struct MemoriesToml { + /// When `true`, web searches and MCP tool calls mark the thread `memory_mode` as `"polluted"`. + pub no_memories_if_mcp_or_web_search: 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, + /// Maximum number of recent raw memories retained for global consolidation. + 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. + 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, + /// 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)] +pub struct MemoriesConfig { + pub no_memories_if_mcp_or_web_search: bool, + pub generate_memories: bool, + pub use_memories: 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 extract_model: Option, + pub consolidation_model: Option, +} + +impl Default for MemoriesConfig { + fn default() -> Self { + Self { + no_memories_if_mcp_or_web_search: false, + generate_memories: true, + use_memories: true, + 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, + extract_model: None, + consolidation_model: None, + } + } +} + +impl From for MemoriesConfig { + fn from(toml: MemoriesToml) -> Self { + let defaults = Self::default(); + Self { + no_memories_if_mcp_or_web_search: toml + .no_memories_if_mcp_or_web_search + .unwrap_or(defaults.no_memories_if_mcp_or_web_search), + generate_memories: toml.generate_memories.unwrap_or(defaults.generate_memories), + use_memories: toml.use_memories.unwrap_or(defaults.use_memories), + max_raw_memories_for_consolidation: toml + .max_raw_memories_for_consolidation + .unwrap_or(defaults.max_raw_memories_for_consolidation) + .min(4096), + 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) + .min(128), + min_rollout_idle_hours: toml + .min_rollout_idle_hours + .unwrap_or(defaults.min_rollout_idle_hours) + .clamp(1, 48), + 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, + + /// 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, +} + +/// 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, +} + +/// 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, + + /// 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, +} + +/// 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 { + /// Log user prompt in traces + pub log_user_prompt: 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, + + /// Optional metrics exporter + pub metrics_exporter: Option, +} + +/// Effective OTEL settings after defaults are applied. +#[derive(Debug, Clone, PartialEq)] +pub struct OtelConfig { + pub log_user_prompt: bool, + pub environment: String, + pub exporter: OtelExporterKind, + pub trace_exporter: OtelExporterKind, + pub metrics_exporter: OtelExporterKind, +} + +impl Default for OtelConfig { + fn default() -> Self { + OtelConfig { + log_user_prompt: false, + environment: DEFAULT_OTEL_ENVIRONMENT.to_owned(), + exporter: OtelExporterKind::None, + trace_exporter: OtelExporterKind::None, + metrics_exporter: OtelExporterKind::Statsig, + } + } +} + +#[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, 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, +} + +/// Collection of settings that are specific to the TUI. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] +#[schemars(deny_unknown_fields)] +pub struct Tui { + /// Enable desktop notifications from the TUI when the terminal is unfocused. + /// Defaults to `true`. + #[serde(default)] + pub notifications: Notifications, + + /// Notification method to use for unfocused terminal notifications. + /// Defaults to `auto`. + #[serde(default)] + pub notification_method: NotificationMethod, + + /// Enable animations (welcome screen, shimmer effects, spinners). + /// Defaults to `true`. + #[serde(default = "default_true")] + pub animations: bool, + + /// Show startup tooltips in the TUI welcome screen. + /// Defaults to `true`. + #[serde(default = "default_true")] + pub show_tooltips: bool, + + /// Controls whether the TUI uses the terminal's alternate screen buffer. + /// + /// - `auto` (default): Disable alternate screen in Zellij, enable elsewhere. + /// - `always`: Always use alternate screen (original behavior). + /// - `never`: Never use alternate screen (inline mode only, preserves scrollback). + /// + /// Using alternate screen provides a cleaner fullscreen experience but prevents + /// scrollback in terminal multiplexers like Zellij that follow the xterm spec. + #[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`, `context-remaining`, and + /// `current-dir`. + #[serde(default)] + pub status_line: Option>, + + /// 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: `spinner` and `project`. + #[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, + + /// Startup tooltip availability NUX state persisted by the TUI. + #[serde(default)] + pub model_availability_nux: ModelAvailabilityNuxConfig, +} + +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, Default, JsonSchema)] +pub struct Notice { + /// 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 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, +} + +pub use crate::skills_config::BundledSkillsConfig; +pub use crate::skills_config::SkillConfig; +pub use crate::skills_config::SkillsConfig; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)] +#[schemars(deny_unknown_fields)] +pub struct PluginConfig { + #[serde(default = "default_enabled")] + pub enabled: bool, +} + +#[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, +} + +impl From for codex_app_server_protocol::SandboxSettings { + fn from(sandbox_workspace_write: SandboxWorkspaceWrite) -> Self { + Self { + writable_roots: sandbox_workspace_write.writable_roots, + network_access: Some(sandbox_workspace_write.network_access), + exclude_tmpdir_env_var: Some(sandbox_workspace_write.exclude_tmpdir_env_var), + exclude_slash_tmp: Some(sandbox_workspace_write.exclude_slash_tmp), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] +#[serde(rename_all = "kebab-case")] +pub enum ShellEnvironmentPolicyInherit { + /// "Core" environment variables for the platform. On UNIX, this would + /// include HOME, LOGNAME, PATH, SHELL, and USER, among others. + Core, + + /// Inherits the full environment from the parent process. + #[default] + All, + + /// Do not inherit any environment variables from the parent process. + None, +} + +/// Policy for building the `env` when spawning a process via either the +/// `shell` or `local_shell` tool. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] +#[schemars(deny_unknown_fields)] +pub struct ShellEnvironmentPolicyToml { + pub inherit: Option, + + pub ignore_default_excludes: Option, + + /// List of regular expressions. + pub exclude: Option>, + + pub r#set: Option>, + + /// List of regular expressions. + pub include_only: Option>, + + pub experimental_use_profile: Option, +} + +pub type EnvironmentVariablePattern = WildMatchPattern<'*', '?'>; + +/// Deriving the `env` based on this policy works as follows: +/// 1. Create an initial map based on the `inherit` policy. +/// 2. If `ignore_default_excludes` is false, filter the map using the default +/// exclude pattern(s), which are: `"*KEY*"`, `"*SECRET*"`, and `"*TOKEN*"`. +/// 3. If `exclude` is not empty, filter the map using the provided patterns. +/// 4. Insert any entries from `r#set` into the map. +/// 5. If non-empty, filter the map using the `include_only` patterns. +#[derive(Debug, Clone, PartialEq)] +pub struct ShellEnvironmentPolicy { + /// Starting point when building the environment. + pub inherit: ShellEnvironmentPolicyInherit, + + /// True to skip the check to exclude default environment variables that + /// contain "KEY", "SECRET", or "TOKEN" in their name. Defaults to true. + pub ignore_default_excludes: bool, + + /// Environment variable names to exclude from the environment. + pub exclude: Vec, + + /// (key, value) pairs to insert in the environment. + pub r#set: HashMap, + + /// Environment variable names to retain in the environment. + pub include_only: Vec, + + /// If true, the shell profile will be used to run the command. + pub use_profile: bool, +} + +impl From for ShellEnvironmentPolicy { + fn from(toml: ShellEnvironmentPolicyToml) -> Self { + // Default to inheriting the full environment when not specified. + let inherit = toml.inherit.unwrap_or(ShellEnvironmentPolicyInherit::All); + let ignore_default_excludes = toml.ignore_default_excludes.unwrap_or(true); + let exclude = toml + .exclude + .unwrap_or_default() + .into_iter() + .map(|s| EnvironmentVariablePattern::new_case_insensitive(&s)) + .collect(); + let r#set = toml.r#set.unwrap_or_default(); + let include_only = toml + .include_only + .unwrap_or_default() + .into_iter() + .map(|s| EnvironmentVariablePattern::new_case_insensitive(&s)) + .collect(); + let use_profile = toml.experimental_use_profile.unwrap_or(false); + + Self { + inherit, + ignore_default_excludes, + exclude, + r#set, + include_only, + use_profile, + } + } +} + +impl Default for ShellEnvironmentPolicy { + fn default() -> Self { + Self { + inherit: ShellEnvironmentPolicyInherit::All, + ignore_default_excludes: true, + exclude: Vec::new(), + r#set: HashMap::new(), + include_only: Vec::new(), + use_profile: false, + } + } +} + +#[cfg(test)] +#[path = "types_tests.rs"] +mod tests; diff --git a/codex-rs/core/src/config/types_tests.rs b/codex-rs/config/src/types_tests.rs similarity index 100% rename from codex-rs/core/src/config/types_tests.rs rename to codex-rs/config/src/types_tests.rs diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index d576a7550..ad82b6c08 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -115,7 +115,6 @@ tracing = { workspace = true, features = ["log"] } url = { workspace = true } uuid = { workspace = true, features = ["serde", "v4", "v5"] } which = { workspace = true } -wildmatch = { workspace = true } zip = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index 370c46ce4..761491eb6 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -1,5 +1,4 @@ use crate::config::types::McpServerConfig; -use crate::config::types::Notice; use crate::path_utils::resolve_symlink_write_paths; use crate::path_utils::write_atomically; use anyhow::Context; @@ -20,6 +19,8 @@ use toml_edit::Item as TomlItem; use toml_edit::Table as TomlTable; use toml_edit::value; +const NOTICE_TABLE_KEY: &str = "notice"; + /// Discrete config mutations supported by the persistence engine. #[derive(Clone, Debug)] pub enum ConfigEdit { @@ -387,29 +388,29 @@ impl ConfigDocument { )), ConfigEdit::SetNoticeHideFullAccessWarning(acknowledged) => Ok(self.write_value( Scope::Global, - &[Notice::TABLE_KEY, "hide_full_access_warning"], + &[NOTICE_TABLE_KEY, "hide_full_access_warning"], value(*acknowledged), )), ConfigEdit::SetNoticeHideWorldWritableWarning(acknowledged) => Ok(self.write_value( Scope::Global, - &[Notice::TABLE_KEY, "hide_world_writable_warning"], + &[NOTICE_TABLE_KEY, "hide_world_writable_warning"], value(*acknowledged), )), ConfigEdit::SetNoticeHideRateLimitModelNudge(acknowledged) => Ok(self.write_value( Scope::Global, - &[Notice::TABLE_KEY, "hide_rate_limit_model_nudge"], + &[NOTICE_TABLE_KEY, "hide_rate_limit_model_nudge"], value(*acknowledged), )), ConfigEdit::SetNoticeHideModelMigrationPrompt(migration_config, acknowledged) => { Ok(self.write_value( Scope::Global, - &[Notice::TABLE_KEY, migration_config.as_str()], + &[NOTICE_TABLE_KEY, migration_config.as_str()], value(*acknowledged), )) } ConfigEdit::RecordModelMigrationSeen { from, to } => Ok(self.write_value( Scope::Global, - &[Notice::TABLE_KEY, "model_migrations", from.as_str()], + &[NOTICE_TABLE_KEY, "model_migrations", from.as_str()], value(to.clone()), )), ConfigEdit::SetWindowsWslSetupAcknowledged(acknowledged) => Ok(self.write_value( diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index b83be53e0..9debd7d79 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -1,692 +1 @@ -//! Types used to define the fields of [`crate::config::Config`]. - -// Note this file should generally be restricted to simple struct/enum -// definitions that do not contain business logic. - -pub use codex_config::AppToolApproval; -pub use codex_config::McpServerConfig; -pub use codex_config::McpServerDisabledReason; -pub use codex_config::McpServerToolConfig; -pub use codex_config::McpServerTransportConfig; -pub use codex_config::RawMcpServerConfig; -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; -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 wildmatch::WildMatchPattern; - -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; - -pub const DEFAULT_OTEL_ENVIRONMENT: &str = "dev"; -pub const DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 16; -pub const DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS: i64 = 30; -pub const DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS: i64 = 6; -pub const DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION: usize = 256; -pub const DEFAULT_MEMORIES_MAX_UNUSED_DAYS: i64 = 30; - -const fn default_enabled() -> bool { - true -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "kebab-case")] -pub enum WindowsSandboxModeToml { - Elevated, - Unelevated, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] -#[schemars(deny_unknown_fields)] -pub struct WindowsToml { - pub sandbox: Option, - /// Defaults to `true`. Set to `false` to launch the final sandboxed child - /// process on `Winsta0\\Default` instead of a private desktop. - pub sandbox_private_desktop: 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, -} - -impl UriBasedFileOpener { - pub fn get_scheme(&self) -> Option<&str> { - match self { - UriBasedFileOpener::VsCode => Some("vscode"), - UriBasedFileOpener::VsCodeInsiders => Some("vscode-insiders"), - UriBasedFileOpener::Windsurf => Some("windsurf"), - UriBasedFileOpener::Cursor => Some("cursor"), - UriBasedFileOpener::None => None, - } - } -} - -/// Settings that govern if and what will be written to `~/.codex/history.jsonl`. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] -#[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 analytics across Codex product surfaces in this profile. - pub enabled: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] -#[schemars(deny_unknown_fields)] -pub struct FeedbackConfigToml { - /// When `false`, disables the feedback flow across Codex product surfaces. - 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, Default, JsonSchema)] -#[schemars(deny_unknown_fields)] -pub struct ToolSuggestConfig { - #[serde(default)] - pub discoverables: Vec, -} - -/// Memories settings loaded from config.toml. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] -#[schemars(deny_unknown_fields)] -pub struct MemoriesToml { - /// When `true`, web searches and MCP tool calls mark the thread `memory_mode` as `"polluted"`. - pub no_memories_if_mcp_or_web_search: 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, - /// Maximum number of recent raw memories retained for global consolidation. - 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. - 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, - /// 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)] -pub struct MemoriesConfig { - pub no_memories_if_mcp_or_web_search: bool, - pub generate_memories: bool, - pub use_memories: 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 extract_model: Option, - pub consolidation_model: Option, -} - -impl Default for MemoriesConfig { - fn default() -> Self { - Self { - no_memories_if_mcp_or_web_search: false, - generate_memories: true, - use_memories: true, - 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, - extract_model: None, - consolidation_model: None, - } - } -} - -impl From for MemoriesConfig { - fn from(toml: MemoriesToml) -> Self { - let defaults = Self::default(); - Self { - no_memories_if_mcp_or_web_search: toml - .no_memories_if_mcp_or_web_search - .unwrap_or(defaults.no_memories_if_mcp_or_web_search), - generate_memories: toml.generate_memories.unwrap_or(defaults.generate_memories), - use_memories: toml.use_memories.unwrap_or(defaults.use_memories), - max_raw_memories_for_consolidation: toml - .max_raw_memories_for_consolidation - .unwrap_or(defaults.max_raw_memories_for_consolidation) - .min(4096), - 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) - .min(128), - min_rollout_idle_hours: toml - .min_rollout_idle_hours - .unwrap_or(defaults.min_rollout_idle_hours) - .clamp(1, 48), - 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, - - /// 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, -} - -/// 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, -} - -/// 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, - - /// 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, -} - -/// 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 { - /// Log user prompt in traces - pub log_user_prompt: 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, - - /// Optional metrics exporter - pub metrics_exporter: Option, -} - -/// Effective OTEL settings after defaults are applied. -#[derive(Debug, Clone, PartialEq)] -pub struct OtelConfig { - pub log_user_prompt: bool, - pub environment: String, - pub exporter: OtelExporterKind, - pub trace_exporter: OtelExporterKind, - pub metrics_exporter: OtelExporterKind, -} - -impl Default for OtelConfig { - fn default() -> Self { - OtelConfig { - log_user_prompt: false, - environment: DEFAULT_OTEL_ENVIRONMENT.to_owned(), - exporter: OtelExporterKind::None, - trace_exporter: OtelExporterKind::None, - metrics_exporter: OtelExporterKind::Statsig, - } - } -} - -#[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, 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, -} - -/// Collection of settings that are specific to the TUI. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] -#[schemars(deny_unknown_fields)] -pub struct Tui { - /// Enable desktop notifications from the TUI when the terminal is unfocused. - /// Defaults to `true`. - #[serde(default)] - pub notifications: Notifications, - - /// Notification method to use for unfocused terminal notifications. - /// Defaults to `auto`. - #[serde(default)] - pub notification_method: NotificationMethod, - - /// Enable animations (welcome screen, shimmer effects, spinners). - /// Defaults to `true`. - #[serde(default = "default_true")] - pub animations: bool, - - /// Show startup tooltips in the TUI welcome screen. - /// Defaults to `true`. - #[serde(default = "default_true")] - pub show_tooltips: bool, - - /// Controls whether the TUI uses the terminal's alternate screen buffer. - /// - /// - `auto` (default): Disable alternate screen in Zellij, enable elsewhere. - /// - `always`: Always use alternate screen (original behavior). - /// - `never`: Never use alternate screen (inline mode only, preserves scrollback). - /// - /// Using alternate screen provides a cleaner fullscreen experience but prevents - /// scrollback in terminal multiplexers like Zellij that follow the xterm spec. - #[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`, `context-remaining`, and - /// `current-dir`. - #[serde(default)] - pub status_line: Option>, - - /// 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: `spinner` and `project`. - #[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, - - /// Startup tooltip availability NUX state persisted by the TUI. - #[serde(default)] - pub model_availability_nux: ModelAvailabilityNuxConfig, -} - -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, Default, JsonSchema)] -pub struct Notice { - /// 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 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, -} - -impl Notice { - /// referenced by config_edit helpers when writing notice flags - pub(crate) const TABLE_KEY: &'static str = "notice"; -} - -pub use codex_config::BundledSkillsConfig; -pub use codex_config::SkillConfig; -pub use codex_config::SkillsConfig; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)] -#[schemars(deny_unknown_fields)] -pub struct PluginConfig { - #[serde(default = "default_enabled")] - pub enabled: bool, -} - -#[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, -} - -impl From for codex_app_server_protocol::SandboxSettings { - fn from(sandbox_workspace_write: SandboxWorkspaceWrite) -> Self { - Self { - writable_roots: sandbox_workspace_write.writable_roots, - network_access: Some(sandbox_workspace_write.network_access), - exclude_tmpdir_env_var: Some(sandbox_workspace_write.exclude_tmpdir_env_var), - exclude_slash_tmp: Some(sandbox_workspace_write.exclude_slash_tmp), - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] -#[serde(rename_all = "kebab-case")] -pub enum ShellEnvironmentPolicyInherit { - /// "Core" environment variables for the platform. On UNIX, this would - /// include HOME, LOGNAME, PATH, SHELL, and USER, among others. - Core, - - /// Inherits the full environment from the parent process. - #[default] - All, - - /// Do not inherit any environment variables from the parent process. - None, -} - -/// Policy for building the `env` when spawning a process via either the -/// `shell` or `local_shell` tool. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)] -#[schemars(deny_unknown_fields)] -pub struct ShellEnvironmentPolicyToml { - pub inherit: Option, - - pub ignore_default_excludes: Option, - - /// List of regular expressions. - pub exclude: Option>, - - pub r#set: Option>, - - /// List of regular expressions. - pub include_only: Option>, - - pub experimental_use_profile: Option, -} - -pub type EnvironmentVariablePattern = WildMatchPattern<'*', '?'>; - -/// Deriving the `env` based on this policy works as follows: -/// 1. Create an initial map based on the `inherit` policy. -/// 2. If `ignore_default_excludes` is false, filter the map using the default -/// exclude pattern(s), which are: `"*KEY*"`, `"*SECRET*"`, and `"*TOKEN*"`. -/// 3. If `exclude` is not empty, filter the map using the provided patterns. -/// 4. Insert any entries from `r#set` into the map. -/// 5. If non-empty, filter the map using the `include_only` patterns. -#[derive(Debug, Clone, PartialEq)] -pub struct ShellEnvironmentPolicy { - /// Starting point when building the environment. - pub inherit: ShellEnvironmentPolicyInherit, - - /// True to skip the check to exclude default environment variables that - /// contain "KEY", "SECRET", or "TOKEN" in their name. Defaults to true. - pub ignore_default_excludes: bool, - - /// Environment variable names to exclude from the environment. - pub exclude: Vec, - - /// (key, value) pairs to insert in the environment. - pub r#set: HashMap, - - /// Environment variable names to retain in the environment. - pub include_only: Vec, - - /// If true, the shell profile will be used to run the command. - pub use_profile: bool, -} - -impl From for ShellEnvironmentPolicy { - fn from(toml: ShellEnvironmentPolicyToml) -> Self { - // Default to inheriting the full environment when not specified. - let inherit = toml.inherit.unwrap_or(ShellEnvironmentPolicyInherit::All); - let ignore_default_excludes = toml.ignore_default_excludes.unwrap_or(true); - let exclude = toml - .exclude - .unwrap_or_default() - .into_iter() - .map(|s| EnvironmentVariablePattern::new_case_insensitive(&s)) - .collect(); - let r#set = toml.r#set.unwrap_or_default(); - let include_only = toml - .include_only - .unwrap_or_default() - .into_iter() - .map(|s| EnvironmentVariablePattern::new_case_insensitive(&s)) - .collect(); - let use_profile = toml.experimental_use_profile.unwrap_or(false); - - Self { - inherit, - ignore_default_excludes, - exclude, - r#set, - include_only, - use_profile, - } - } -} - -impl Default for ShellEnvironmentPolicy { - fn default() -> Self { - Self { - inherit: ShellEnvironmentPolicyInherit::All, - ignore_default_excludes: true, - exclude: Vec::new(), - r#set: HashMap::new(), - include_only: Vec::new(), - use_profile: false, - } - } -} - -#[cfg(test)] -#[path = "types_tests.rs"] -mod tests; +pub use codex_config::types::*;