Skip to content
Closed
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
8 changes: 8 additions & 0 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,10 @@ impl Session {
)
.with_web_search_config(per_turn_config.web_search_config.clone())
.with_allow_login_shell(per_turn_config.permissions.allow_login_shell)
.with_environment_capabilities(codex_tools::ToolEnvironmentCapabilities::new(
environment.exec_enabled(),
environment.filesystem_enabled(),
))
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
&per_turn_config.agent_roles,
));
Expand Down Expand Up @@ -5537,6 +5541,10 @@ async fn spawn_review_thread(
)
.with_web_search_config(/*web_search_config*/ None)
.with_allow_login_shell(config.permissions.allow_login_shell)
.with_environment_capabilities(codex_tools::ToolEnvironmentCapabilities::new(
parent_turn_context.environment.exec_enabled(),
parent_turn_context.environment.filesystem_enabled(),
))
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
&config.agent_roles,
));
Expand Down
7 changes: 6 additions & 1 deletion codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2063,7 +2063,12 @@ impl Config {
network: network_requirements,
} = config_layer_stack.requirements().clone();

let user_instructions = Self::load_instructions(Some(&codex_home));
let user_instructions =
if codex_exec_server::ExecServerMode::from_env().skips_project_docs() {
None
} else {
Self::load_instructions(Some(&codex_home))
};
let mut startup_warnings = Vec::new();

// Destructure ConfigOverrides fully to ensure all overrides are applied.
Expand Down
5 changes: 3 additions & 2 deletions codex-rs/core/src/project_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fn render_js_repl_instructions(config: &Config) -> Option<String> {
/// Combines `Config::instructions` and `AGENTS.md` (if present) into a single
/// string of instructions.
pub(crate) async fn get_user_instructions(config: &Config) -> Option<String> {
let skip_project_docs = codex_exec_server::ExecServerMode::from_env().skips_project_docs();
let project_docs = read_project_docs(config).await;

let mut output = String::new();
Expand Down Expand Up @@ -105,7 +106,7 @@ pub(crate) async fn get_user_instructions(config: &Config) -> Option<String> {
output.push_str(&js_repl_section);
}

if config.features.enabled(Feature::ChildAgentsMd) {
if config.features.enabled(Feature::ChildAgentsMd) && !skip_project_docs {
if !output.is_empty() {
output.push_str("\n\n");
}
Expand All @@ -128,7 +129,7 @@ pub(crate) async fn get_user_instructions(config: &Config) -> Option<String> {
pub async fn read_project_docs(config: &Config) -> std::io::Result<Option<String>> {
let max_total = config.project_doc_max_bytes;

if max_total == 0 {
if max_total == 0 || codex_exec_server::ExecServerMode::from_env().skips_project_docs() {
return Ok(None);
}

Expand Down
45 changes: 45 additions & 0 deletions codex-rs/core/src/project_doc_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ use core_test_support::PathBufExt;
use core_test_support::TempDirExt;
use std::fs;
use std::path::PathBuf;
use std::sync::OnceLock;
use tempfile::TempDir;
use tokio::sync::Mutex;

const CODEX_EXEC_SERVER_URL_ENV_VAR: &str = codex_exec_server::CODEX_EXEC_SERVER_URL_ENV_VAR;

/// Helper that returns a `Config` pointing at `root` and using `limit` as
/// the maximum number of bytes to embed from AGENTS.md. The caller can
Expand Down Expand Up @@ -70,6 +74,32 @@ async fn make_config_with_project_root_markers(
config
}

fn exec_server_env_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}

async fn with_exec_server_url_env<T>(
exec_server_url: Option<&str>,
future: impl std::future::Future<Output = T>,
) -> T {
let _guard = exec_server_env_lock().lock().await;
let previous = std::env::var(CODEX_EXEC_SERVER_URL_ENV_VAR).ok();
match exec_server_url {
Some(value) => unsafe { std::env::set_var(CODEX_EXEC_SERVER_URL_ENV_VAR, value) },
None => unsafe { std::env::remove_var(CODEX_EXEC_SERVER_URL_ENV_VAR) },
}

let result = future.await;

match previous {
Some(value) => unsafe { std::env::set_var(CODEX_EXEC_SERVER_URL_ENV_VAR, value) },
None => unsafe { std::env::remove_var(CODEX_EXEC_SERVER_URL_ENV_VAR) },
}

result
}

/// AGENTS.md missing – should yield `None`.
#[tokio::test]
async fn no_doc_file_returns_none() {
Expand Down Expand Up @@ -161,6 +191,21 @@ async fn zero_byte_limit_disables_docs() {
);
}

#[tokio::test(flavor = "current_thread")]
async fn disabled_exec_server_mode_skips_project_docs() {
let tmp = tempfile::tempdir().expect("tempdir");
fs::write(tmp.path().join("AGENTS.md"), "something").unwrap();

let mut cfg = make_config(&tmp, /*limit*/ 4096, Some("base instructions")).await;
cfg.features
.enable(Feature::ChildAgentsMd)
.expect("test config should allow hierarchical AGENTS instructions");

let res = with_exec_server_url_env(Some("none"), get_user_instructions(&cfg)).await;

assert_eq!(res, Some("base instructions".to_string()));
}

#[tokio::test]
async fn js_repl_instructions_are_appended_when_enabled() {
let tmp = tempfile::tempdir().expect("tempdir");
Expand Down
9 changes: 9 additions & 0 deletions codex-rs/core/src/tools/handlers/apply_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ use std::sync::Arc;

pub struct ApplyPatchHandler;

const APPLY_PATCH_DISABLED_MESSAGE: &str =
"apply_patch is unavailable because the environment is disabled";

fn file_paths_for_action(action: &ApplyPatchAction) -> Vec<AbsolutePathBuf> {
let mut keys = Vec::new();
let cwd = action.cwd.as_path();
Expand Down Expand Up @@ -150,6 +153,12 @@ impl ToolHandler for ApplyPatchHandler {
..
} = invocation;

if !turn.environment.exec_enabled() {
return Err(FunctionCallError::RespondToModel(
APPLY_PATCH_DISABLED_MESSAGE.to_string(),
));
}

let patch_input = match payload {
ToolPayload::Function { arguments } => {
let args: ApplyPatchToolArgs = parse_arguments(&arguments)?;
Expand Down
13 changes: 13 additions & 0 deletions codex-rs/core/src/tools/handlers/js_repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use codex_protocol::protocol::ExecCommandSource;
pub struct JsReplHandler;
pub struct JsReplResetHandler;

const JS_REPL_DISABLED_MESSAGE: &str = "js_repl is unavailable because the environment is disabled";

fn join_outputs(stdout: &str, stderr: &str) -> String {
if stdout.is_empty() {
stderr.to_string()
Expand Down Expand Up @@ -115,6 +117,12 @@ impl ToolHandler for JsReplHandler {
..
} = invocation;

if !turn.environment.exec_enabled() {
return Err(FunctionCallError::RespondToModel(
JS_REPL_DISABLED_MESSAGE.to_string(),
));
}

if !session.features().enabled(Feature::JsRepl) {
return Err(FunctionCallError::RespondToModel(
"js_repl is disabled by feature flag".to_string(),
Expand Down Expand Up @@ -188,6 +196,11 @@ impl ToolHandler for JsReplResetHandler {
}

async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
if !invocation.turn.environment.exec_enabled() {
return Err(FunctionCallError::RespondToModel(
JS_REPL_DISABLED_MESSAGE.to_string(),
));
}
if !invocation.session.features().enabled(Feature::JsRepl) {
return Err(FunctionCallError::RespondToModel(
"js_repl is disabled by feature flag".to_string(),
Expand Down
9 changes: 9 additions & 0 deletions codex-rs/core/src/tools/handlers/list_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ use crate::tools::registry::ToolKind;

pub struct ListDirHandler;

const LIST_DIR_DISABLED_MESSAGE: &str =
"list_dir is unavailable because the environment is disabled";

const MAX_ENTRY_LENGTH: usize = 500;
const INDENTATION_SPACES: usize = 2;

Expand Down Expand Up @@ -52,6 +55,12 @@ impl ToolHandler for ListDirHandler {
}

async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
if !invocation.turn.environment.filesystem_enabled() {
return Err(FunctionCallError::RespondToModel(
LIST_DIR_DISABLED_MESSAGE.to_string(),
));
}

let ToolInvocation { payload, .. } = invocation;

let arguments = match payload {
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/core/src/tools/handlers/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ use codex_tools::ShellCommandBackendConfig;

pub struct ShellHandler;

const SHELL_DISABLED_MESSAGE: &str = "shell is unavailable because the environment is disabled";

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ShellCommandBackend {
Classic,
Expand Down Expand Up @@ -394,6 +396,12 @@ impl ShellHandler {
shell_runtime_backend,
} = args;

if !turn.environment.exec_enabled() {
return Err(FunctionCallError::RespondToModel(
SHELL_DISABLED_MESSAGE.to_string(),
));
}

let mut exec_params = exec_params;
let dependency_env = session.dependency_env().await;
if !dependency_env.is_empty() {
Expand Down
9 changes: 9 additions & 0 deletions codex-rs/core/src/tools/handlers/unified_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ use std::sync::Arc;

pub struct UnifiedExecHandler;

const UNIFIED_EXEC_DISABLED_MESSAGE: &str =
"exec_command is unavailable because the environment is disabled";

#[derive(Debug, Deserialize)]
pub(crate) struct ExecCommandArgs {
cmd: String,
Expand Down Expand Up @@ -179,6 +182,12 @@ impl ToolHandler for UnifiedExecHandler {
let manager: &UnifiedExecProcessManager = &session.services.unified_exec_manager;
let context = UnifiedExecContext::new(session.clone(), turn.clone(), call_id.clone());

if !turn.environment.exec_enabled() {
return Err(FunctionCallError::RespondToModel(
UNIFIED_EXEC_DISABLED_MESSAGE.to_string(),
));
}

let response = match tool_name.as_str() {
"exec_command" => {
let cwd = resolve_workdir_base_path(&arguments, context.turn.cwd.as_path())?;
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/core/src/tools/handlers/view_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub struct ViewImageHandler;

const VIEW_IMAGE_UNSUPPORTED_MESSAGE: &str =
"view_image is not allowed because you do not support image inputs";
const VIEW_IMAGE_DISABLED_MESSAGE: &str =
"view_image is unavailable because the environment is disabled";

#[derive(Deserialize)]
struct ViewImageArgs {
Expand All @@ -44,6 +46,12 @@ impl ToolHandler for ViewImageHandler {
}

async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
if !invocation.turn.environment.filesystem_enabled() {
return Err(FunctionCallError::RespondToModel(
VIEW_IMAGE_DISABLED_MESSAGE.to_string(),
));
}

if !invocation
.turn
.model_info
Expand Down
5 changes: 4 additions & 1 deletion codex-rs/core/tests/suite/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@ $lines | Select-Object -Skip 1 | Set-Content -Path tokens.txt
ModelProviderAuthInfo {
command: self.command.clone(),
args: self.args.clone(),
timeout_ms: non_zero_u64(/*value*/ 1_000),
// Process startup can be slow on loaded Windows CI workers, so keep this aligned with
// `codex-login` auth tests and avoid turning provider-auth assertions into a
// process-launch timing test.
timeout_ms: non_zero_u64(/*value*/ 10_000),
refresh_interval_ms: 60_000,
cwd: match codex_utils_absolute_path::AbsolutePathBuf::try_from(self.tempdir.path()) {
Ok(cwd) => cwd,
Expand Down
Loading
Loading