Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions codex-rs/config/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
);
continue;
}
if feature.id == codex_features::Feature::CurrentTimeReminder {
validation.properties.insert(
feature.key.to_string(),
schema_gen.subschema_for::<codex_features::FeatureToml<
codex_features::CurrentTimeReminderConfigToml,
>>(),
);
continue;
}
if feature.id == codex_features::Feature::AppsMcpPathOverride {
validation.properties.insert(
feature.key.to_string(),
Expand Down
40 changes: 40 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,9 @@
"connectors": {
"type": "boolean"
},
"current_time_reminder": {
"$ref": "#/definitions/FeatureToml_for_CurrentTimeReminderConfigToml"
},
"default_mode_request_user_input": {
"type": "boolean"
},
Expand Down Expand Up @@ -799,6 +802,30 @@
},
"type": "object"
},
"CurrentTimeReminderConfigToml": {
"additionalProperties": false,
"properties": {
"clock_source": {
"$ref": "#/definitions/CurrentTimeSource"
},
"enabled": {
"type": "boolean"
},
"reminder_interval_model_requests": {
"format": "uint64",
"minimum": 1.0,
"type": "integer"
}
},
"type": "object"
},
"CurrentTimeSource": {
"enum": [
"system",
"external"
],
"type": "string"
},
"DebugConfigLockToml": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -891,6 +918,16 @@
}
]
},
"FeatureToml_for_CurrentTimeReminderConfigToml": {
"anyOf": [
{
"type": "boolean"
},
{
"$ref": "#/definitions/CurrentTimeReminderConfigToml"
}
]
},
"FeatureToml_for_MultiAgentV2ConfigToml": {
"anyOf": [
{
Expand Down Expand Up @@ -4679,6 +4716,9 @@
"connectors": {
"type": "boolean"
},
"current_time_reminder": {
"$ref": "#/definitions/FeatureToml_for_CurrentTimeReminderConfigToml"
},
"default_mode_request_user_input": {
"type": "boolean"
},
Expand Down
61 changes: 61 additions & 0 deletions codex-rs/core/src/config/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,67 @@ async fn load_config_rejects_enabled_rollout_budget_without_limit() -> std::io::
Ok(())
}

#[tokio::test]
async fn load_config_resolves_current_time_reminder() -> std::io::Result<()> {
for (config_toml, expected) in [
(
r#"
[features]
current_time_reminder = true
"#,
CurrentTimeReminderConfig::default(),
),
(
r#"
[features.current_time_reminder]
enabled = true
reminder_interval_model_requests = 4
clock_source = "external"
"#,
CurrentTimeReminderConfig {
reminder_interval_model_requests: 4,
clock_source: CurrentTimeSource::External,
},
),
] {
let config = load_current_time_reminder_config(config_toml).await?;
assert!(config.features.enabled(Feature::CurrentTimeReminder));
assert_eq!(config.current_time_reminder, Some(expected));
}
Ok(())
}

#[tokio::test]
async fn load_config_rejects_zero_current_time_reminder_interval() -> std::io::Result<()> {
let error = load_current_time_reminder_config(
r#"
[features.current_time_reminder]
enabled = true
reminder_interval_model_requests = 0
"#,
)
.await
.expect_err("zero reminder interval should be rejected");

assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
error.to_string(),
"features.current_time_reminder.reminder_interval_model_requests must be positive"
);
Ok(())
}

async fn load_current_time_reminder_config(config_toml: &str) -> std::io::Result<Config> {
let codex_home = tempdir()?;
let config_toml = toml::from_str(config_toml).expect("TOML should deserialize");
Config::load_from_base_config_with_overrides(
config_toml,
ConfigOverrides::default(),
codex_home.abs(),
)
.await
}

#[test]
fn rejects_provider_auth_with_env_key() {
let err = toml::from_str::<ConfigToml>(
Expand Down
58 changes: 58 additions & 0 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ use codex_core_plugins::PluginsConfigInput;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::LOCAL_FS;
use codex_features::CodeModeConfigToml;
use codex_features::CurrentTimeReminderConfigToml;
use codex_features::CurrentTimeSource;
use codex_features::Feature;
use codex_features::FeatureConfigSource;
use codex_features::FeatureOverrides;
Expand Down Expand Up @@ -1022,6 +1024,8 @@ pub struct Config {

/// Shared token budget for the root thread and its sub-agents.
pub rollout_budget: Option<RolloutBudgetConfig>,
/// Current-time reminder configuration, when enabled.
pub current_time_reminder: Option<CurrentTimeReminderConfig>,

/// Centralized feature flags; source of truth for feature gating.
pub features: ManagedFeatures,
Expand Down Expand Up @@ -1075,6 +1079,21 @@ pub struct RolloutBudgetConfig {
pub prefill_token_weight: f64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct CurrentTimeReminderConfig {
pub reminder_interval_model_requests: u64,
pub clock_source: CurrentTimeSource,
}

impl Default for CurrentTimeReminderConfig {
fn default() -> Self {
Self {
reminder_interval_model_requests: 1,
clock_source: CurrentTimeSource::System,
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct MultiAgentV2Config {
pub max_concurrent_threads_per_session: usize,
Expand Down Expand Up @@ -2539,6 +2558,34 @@ fn resolve_rollout_budget_config(
}))
}

fn resolve_current_time_reminder_config(
config_toml: &ConfigToml,
features: &ManagedFeatures,
) -> std::io::Result<Option<CurrentTimeReminderConfig>> {
if !features.enabled(Feature::CurrentTimeReminder) {
return Ok(None);
}

let base = current_time_reminder_toml_config(config_toml.features.as_ref());
let default = CurrentTimeReminderConfig::default();
let reminder_interval_model_requests = base
.and_then(|config| config.reminder_interval_model_requests)
.unwrap_or(default.reminder_interval_model_requests);
if reminder_interval_model_requests == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"features.current_time_reminder.reminder_interval_model_requests must be positive",
));
}

Ok(Some(CurrentTimeReminderConfig {
reminder_interval_model_requests,
clock_source: base
.and_then(|config| config.clock_source)
.unwrap_or(default.clock_source),
}))
}

fn resolve_terminal_resize_reflow_config(config_toml: &ConfigToml) -> TerminalResizeReflowConfig {
let Some(tui) = config_toml.tui.as_ref() else {
return TerminalResizeReflowConfig::default();
Expand Down Expand Up @@ -2578,6 +2625,15 @@ fn multi_agent_v2_toml_config(features: Option<&FeaturesToml>) -> Option<&MultiA
}
}

fn current_time_reminder_toml_config(
features: Option<&FeaturesToml>,
) -> Option<&CurrentTimeReminderConfigToml> {
match features?.current_time_reminder.as_ref()? {
FeatureToml::Enabled(_) => None,
FeatureToml::Config(config) => Some(config),
}
}

fn network_proxy_toml_config(features: Option<&FeaturesToml>) -> Option<&NetworkProxyConfigToml> {
match features?.network_proxy.as_ref()? {
FeatureToml::Enabled(_) => None,
Expand Down Expand Up @@ -3217,6 +3273,7 @@ impl Config {
let code_mode = resolve_code_mode_config(&cfg);
let multi_agent_v2 = resolve_multi_agent_v2_config(&cfg);
let rollout_budget = resolve_rollout_budget_config(&cfg, &features)?;
let current_time_reminder = resolve_current_time_reminder_config(&cfg, &features)?;
let terminal_resize_reflow = resolve_terminal_resize_reflow_config(&cfg);

let agent_roles =
Expand Down Expand Up @@ -3758,6 +3815,7 @@ impl Config {
ghost_snapshot,
multi_agent_v2,
rollout_budget,
current_time_reminder,
features,
suppress_unstable_features_warning: cfg
.suppress_unstable_features_warning
Expand Down
20 changes: 20 additions & 0 deletions codex-rs/core/src/session/config_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::Context;
use codex_config::config_toml::ConfigLockfileToml;
use codex_config::config_toml::ConfigToml;
use codex_config::types::MemoriesToml;
use codex_features::CurrentTimeReminderConfigToml;
use codex_features::Feature;
use codex_features::FeatureToml;
use codex_features::FeaturesToml;
Expand Down Expand Up @@ -155,6 +156,12 @@ fn save_config_resolved_fields(
rollout_budget.enabled = Some(config.features.enabled(Feature::RolloutBudget));
features.rollout_budget = Some(FeatureToml::Config(rollout_budget));
}
if let Some(current_time_reminder) = config.current_time_reminder.as_ref() {
let mut current_time_reminder: CurrentTimeReminderConfigToml =
resolved_config_to_toml(current_time_reminder, "features.current_time_reminder")?;
current_time_reminder.enabled = Some(config.features.enabled(Feature::CurrentTimeReminder));
features.current_time_reminder = Some(FeatureToml::Config(current_time_reminder));
}
lock_config.memories = Some(resolved_config_to_toml::<MemoriesToml>(
&config.memories,
"memories",
Expand Down Expand Up @@ -227,6 +234,11 @@ mod tests {
.features
.enable(Feature::RolloutBudget)
.expect("rollout_budget should be enableable in tests");
config.current_time_reminder = Some(crate::config::CurrentTimeReminderConfig::default());
config
.features
.enable(Feature::CurrentTimeReminder)
.expect("current_time_reminder should be enableable in tests");
sc.original_config_do_not_use = Arc::new(config);
sc.base_instructions = "resolved instructions".to_string();
sc.developer_instructions = Some("resolved developer instructions".to_string());
Expand Down Expand Up @@ -302,6 +314,14 @@ mod tests {
prefill_token_weight: Some(0.25),
}))
);
assert_eq!(
features.current_time_reminder,
Some(FeatureToml::Config(CurrentTimeReminderConfigToml {
enabled: Some(true),
reminder_interval_model_requests: Some(1),
clock_source: Some(codex_features::CurrentTimeSource::System),
}))
);

assert_eq!(lockfile.version, crate::config_lock::CONFIG_LOCK_VERSION);
}
Expand Down
30 changes: 30 additions & 0 deletions codex-rs/features/src/feature_configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,36 @@ impl FeatureConfig for RolloutBudgetConfigToml {
}
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CurrentTimeSource {
#[default]
System,
External,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CurrentTimeReminderConfigToml {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1))]
pub reminder_interval_model_requests: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub clock_source: Option<CurrentTimeSource>,
}

impl FeatureConfig for CurrentTimeReminderConfigToml {
fn enabled(&self) -> Option<bool> {
self.enabled
}

fn set_enabled(&mut self, enabled: bool) {
self.enabled = Some(enabled);
}
}

#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(crate) struct RemovedAppsMcpPathOverrideConfigToml {
Expand Down
Loading
Loading