diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index d481de92c3..6bf4f09f1e 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -349,6 +349,8 @@ pub struct Config { /// Optional base URL for storing shared session rollouts in an object store. pub session_object_storage_url: Option, + /// Optional command to produce the base URL for storing shared session rollouts. + pub session_object_storage_url_cmd: Option>, /// When set, restricts ChatGPT login to a specific workspace identifier. pub forced_chatgpt_workspace_id: Option, @@ -1518,7 +1520,15 @@ impl Config { Some(WindowsSandboxModeToml::Unelevated) => WindowsSandboxLevel::RestrictedToken, None => WindowsSandboxLevel::from_features(&features), }; - let session_object_storage_url = Self::resolve_session_object_storage_url(&cfg)?; + let session_object_storage_url = + cfg.session_object_storage_url.as_ref().and_then(|value| { + let trimmed = value.trim(); + if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + } + }); let mut sandbox_policy = cfg.derive_sandbox_policy( sandbox_mode, config_profile.sandbox_mode, @@ -1827,6 +1837,7 @@ impl Config { .or(cfg.chatgpt_base_url) .unwrap_or("https://chatgpt.com/backend-api/".to_string()), session_object_storage_url, + session_object_storage_url_cmd: cfg.session_object_storage_url_cmd, forced_chatgpt_workspace_id, forced_login_method, include_apply_patch_tool: include_apply_patch_tool_flag, @@ -1893,6 +1904,17 @@ impl Config { Ok(config) } + pub fn resolve_session_object_storage_url(&self) -> std::io::Result> { + if let Some(url) = self.session_object_storage_url.as_ref() { + return Ok(Some(url.clone())); + } + + Self::try_run_command_for_value( + self.session_object_storage_url_cmd.as_deref(), + "session_object_storage_url_cmd", + ) + } + fn load_instructions(codex_dir: Option<&Path>) -> Option { let base = codex_dir?; for candidate in [LOCAL_PROJECT_DOC_FILENAME, DEFAULT_PROJECT_DOC_FILENAME] { @@ -1937,27 +1959,6 @@ impl Config { } } - fn resolve_session_object_storage_url(cfg: &ConfigToml) -> std::io::Result> { - let session_object_storage_url = - cfg.session_object_storage_url.as_ref().and_then(|value| { - let trimmed = value.trim(); - if trimmed.is_empty() { - None - } else { - Some(trimmed.to_string()) - } - }); - - if session_object_storage_url.is_some() { - return Ok(session_object_storage_url); - } - - Self::try_run_command_for_value( - cfg.session_object_storage_url_cmd.as_deref(), - "session_object_storage_url_cmd", - ) - } - fn try_run_command_for_value( command: Option<&[String]>, context: &str, @@ -4220,6 +4221,7 @@ model_verbosity = "high" personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), session_object_storage_url: None, + session_object_storage_url_cmd: None, base_instructions: None, developer_instructions: None, compact_prompt: None, @@ -4332,6 +4334,7 @@ model_verbosity = "high" personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), session_object_storage_url: None, + session_object_storage_url_cmd: None, base_instructions: None, developer_instructions: None, compact_prompt: None, @@ -4442,6 +4445,7 @@ model_verbosity = "high" personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), session_object_storage_url: None, + session_object_storage_url_cmd: None, base_instructions: None, developer_instructions: None, compact_prompt: None, @@ -4538,6 +4542,7 @@ model_verbosity = "high" personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), session_object_storage_url: None, + session_object_storage_url_cmd: None, base_instructions: None, developer_instructions: None, compact_prompt: None, diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index d10c646a14..89d08eaf1c 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -3746,9 +3746,12 @@ impl ChatWidget { self.add_error_message("Current session is not ready to share yet.".to_string()); return; } - if self.config.session_object_storage_url.is_none() { + if self.config.session_object_storage_url.is_none() + && self.config.session_object_storage_url_cmd.is_none() + { self.add_error_message( - "Sharing requires `session_object_storage_url` in config.toml.".to_string(), + "Sharing requires `session_object_storage_url` or `session_object_storage_url_cmd` in config.toml." + .to_string(), ); return; } @@ -3801,11 +3804,21 @@ impl ChatWidget { // TODO: Enforce email-based access once we have a server-side auth check. - let Some(storage_url) = self.config.session_object_storage_url.clone() else { - self.add_error_message( - "Sharing requires `session_object_storage_url` in config.toml.".to_string(), - ); - return; + let storage_url = match self.config.resolve_session_object_storage_url() { + Ok(Some(url)) => url, + Ok(None) => { + self.add_error_message( + "Sharing requires `session_object_storage_url` or `session_object_storage_url_cmd` in config.toml." + .to_string(), + ); + return; + } + Err(err) => { + self.add_error_message(format!( + "Failed to resolve session object storage URL: {err}" + )); + return; + } }; let owner = self .auth_manager diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 98b630a302..0f3f50eb02 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -582,14 +582,25 @@ async fn run_ratatui_app( match path { Some(path) => resume_picker::SessionSelection::Fork(path), None => { - let Some(storage_url) = config.session_object_storage_url.as_deref() else { - return missing_session_exit(&mut tui, id_str, "fork"); + let storage_url = match config.resolve_session_object_storage_url() { + Ok(Some(url)) => url, + Ok(None) => return missing_session_exit(&mut tui, id_str, "fork"), + Err(err) => { + return fatal_exit( + &mut tui, + format!("Failed to resolve session object storage URL: {err}"), + ); + } }; let Ok(session_id) = ThreadId::from_string(id_str) else { return missing_session_exit(&mut tui, id_str, "fork"); }; - match download_rollout_if_available(storage_url, session_id, &config.codex_home) - .await + match download_rollout_if_available( + &storage_url, + session_id, + &config.codex_home, + ) + .await { Ok(Some(path)) => { remote_fork_downloaded = true;