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
2 changes: 1 addition & 1 deletion codex-rs/app-server/src/fs_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) struct FsApi {
impl Default for FsApi {
fn default() -> Self {
Self {
file_system: Arc::new(Environment.get_filesystem()),
file_system: Arc::new(Environment::default().get_filesystem()),
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1895,6 +1895,10 @@
"experimental_compact_prompt_file": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"experimental_exec_server_url": {
"description": "Experimental / do not use. Overrides the URL used when connecting to a remote exec server.",
"type": "string"
},
"experimental_realtime_start_instructions": {
"description": "Experimental / do not use. Replaces the built-in realtime start instructions inserted into developer messages when realtime becomes active.",
"type": "string"
Expand Down
4 changes: 3 additions & 1 deletion codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1827,7 +1827,9 @@ impl Session {
code_mode_service: crate::tools::code_mode::CodeModeService::new(
config.js_repl_node_path.clone(),
),
environment: Arc::new(Environment),
environment: Arc::new(
Environment::create(config.experimental_exec_server_url.clone()).await?,
),
};
let js_repl = Arc::new(JsReplHandle::with_node_path(
config.js_repl_node_path.clone(),
Expand Down
12 changes: 10 additions & 2 deletions codex-rs/core/src/codex_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2450,7 +2450,11 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
true,
));
let network_approval = Arc::new(NetworkApprovalService::default());
let environment = Arc::new(codex_exec_server::Environment);
let environment = Arc::new(
codex_exec_server::Environment::create(None)
.await
.expect("create environment"),
);

let file_watcher = Arc::new(FileWatcher::noop());
let services = SessionServices {
Expand Down Expand Up @@ -3244,7 +3248,11 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
true,
));
let network_approval = Arc::new(NetworkApprovalService::default());
let environment = Arc::new(codex_exec_server::Environment);
let environment = Arc::new(
codex_exec_server::Environment::create(None)
.await
.expect("create environment"),
);

let file_watcher = Arc::new(FileWatcher::noop());
let services = SessionServices {
Expand Down
32 changes: 32 additions & 0 deletions codex-rs/core/src/config/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4310,6 +4310,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
model_verbosity: None,
personality: Some(Personality::Pragmatic),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
experimental_exec_server_url: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_start_instructions: None,
experimental_realtime_ws_base_url: None,
Expand Down Expand Up @@ -4451,6 +4452,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
model_verbosity: None,
personality: Some(Personality::Pragmatic),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
experimental_exec_server_url: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_start_instructions: None,
experimental_realtime_ws_base_url: None,
Expand Down Expand Up @@ -4590,6 +4592,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
model_verbosity: None,
personality: Some(Personality::Pragmatic),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
experimental_exec_server_url: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_start_instructions: None,
experimental_realtime_ws_base_url: None,
Expand Down Expand Up @@ -4715,6 +4718,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
model_verbosity: Some(Verbosity::High),
personality: Some(Personality::Pragmatic),
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
experimental_exec_server_url: None,
realtime_audio: RealtimeAudioConfig::default(),
experimental_realtime_start_instructions: None,
experimental_realtime_ws_base_url: None,
Expand Down Expand Up @@ -5974,6 +5978,34 @@ experimental_realtime_start_instructions = "start instructions from config"
Ok(())
}

#[test]
fn experimental_exec_server_url_loads_from_config_toml() -> std::io::Result<()> {
let cfg: ConfigToml = toml::from_str(
r#"
experimental_exec_server_url = "http://127.0.0.1:8080"
"#,
)
.expect("TOML deserialization should succeed");

assert_eq!(
cfg.experimental_exec_server_url.as_deref(),
Some("http://127.0.0.1:8080")
);

let codex_home = TempDir::new()?;
let config = Config::load_from_base_config_with_overrides(
cfg,
ConfigOverrides::default(),
codex_home.path().to_path_buf(),
)?;

assert_eq!(
config.experimental_exec_server_url.as_deref(),
Some("http://127.0.0.1:8080")
);
Ok(())
}

#[test]
fn experimental_realtime_ws_base_url_loads_from_config_toml() -> std::io::Result<()> {
let cfg: ConfigToml = toml::from_str(
Expand Down
9 changes: 9 additions & 0 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,10 @@ pub struct Config {
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
pub chatgpt_base_url: String,

/// Experimental / do not use. Overrides the URL used when connecting to
/// a remote exec server.
pub experimental_exec_server_url: Option<String>,

/// Machine-local realtime audio device preferences used by realtime voice.
pub realtime_audio: RealtimeAudioConfig,

Expand Down Expand Up @@ -1393,6 +1397,10 @@ pub struct ConfigToml {
/// Base URL override for the built-in `openai` model provider.
pub openai_base_url: Option<String>,

/// Experimental / do not use. Overrides the URL used when connecting to
/// a remote exec server.
pub experimental_exec_server_url: Option<String>,

/// Machine-local realtime audio device preferences used by realtime voice.
#[serde(default)]
pub audio: Option<RealtimeAudioToml>,
Expand Down Expand Up @@ -2745,6 +2753,7 @@ impl Config {
.chatgpt_base_url
.or(cfg.chatgpt_base_url)
.unwrap_or("https://chatgpt.com/backend-api/".to_string()),
experimental_exec_server_url: cfg.experimental_exec_server_url,
realtime_audio: cfg
.audio
.map_or_else(RealtimeAudioConfig::default, |audio| RealtimeAudioConfig {
Expand Down
69 changes: 67 additions & 2 deletions codex-rs/exec-server/src/environment.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,76 @@
use crate::ExecServerClient;
use crate::ExecServerError;
use crate::RemoteExecServerConnectArgs;
use crate::fs;
use crate::fs::ExecutorFileSystem;

#[derive(Clone, Debug, Default)]
pub struct Environment;
#[derive(Clone, Default)]
pub struct Environment {
experimental_exec_server_url: Option<String>,
remote_exec_server_client: Option<ExecServerClient>,
}

impl std::fmt::Debug for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Environment")
.field(
"experimental_exec_server_url",
&self.experimental_exec_server_url,
)
.field(
"has_remote_exec_server_client",
&self.remote_exec_server_client.is_some(),
)
.finish()
}
}

impl Environment {
pub async fn create(
experimental_exec_server_url: Option<String>,
) -> Result<Self, ExecServerError> {
let remote_exec_server_client =
if let Some(websocket_url) = experimental_exec_server_url.as_deref() {
Some(
ExecServerClient::connect_websocket(RemoteExecServerConnectArgs::new(
websocket_url.to_string(),
"codex-core".to_string(),
))
.await?,
)
} else {
None
};

Ok(Self {
experimental_exec_server_url,
remote_exec_server_client,
})
}

pub fn experimental_exec_server_url(&self) -> Option<&str> {
self.experimental_exec_server_url.as_deref()
}

pub fn remote_exec_server_client(&self) -> Option<&ExecServerClient> {
self.remote_exec_server_client.as_ref()
}

pub fn get_filesystem(&self) -> impl ExecutorFileSystem + use<> {
fs::LocalFileSystem
}
}

#[cfg(test)]
mod tests {
use super::Environment;
use pretty_assertions::assert_eq;

#[tokio::test]
async fn create_without_remote_exec_server_url_does_not_connect() {
let environment = Environment::create(None).await.expect("create environment");

assert_eq!(environment.experimental_exec_server_url(), None);
assert!(environment.remote_exec_server_client().is_none());
}
}
Loading