diff --git a/codex-rs/core/src/environment_selection.rs b/codex-rs/core/src/environment_selection.rs index d96d9425db..013db32cf2 100644 --- a/codex-rs/core/src/environment_selection.rs +++ b/codex-rs/core/src/environment_selection.rs @@ -41,14 +41,17 @@ impl ResolvedTurnEnvironments { self.turn_environments.first() } - pub(crate) fn get(&self, environment_id: &str) -> Option<&TurnEnvironment> { + pub(crate) fn get_by_id(&self, environment_id: &str) -> Option<&TurnEnvironment> { self.turn_environments .iter() .find(|environment| environment.environment_id == environment_id) } - pub(crate) fn select(&self, environment_id: Option<&str>) -> Option<&TurnEnvironment> { - environment_id.map_or_else(|| self.primary(), |environment_id| self.get(environment_id)) + pub(crate) fn get_or_primary(&self, environment_id: Option<&str>) -> Option<&TurnEnvironment> { + environment_id.map_or_else( + || self.primary(), + |environment_id| self.get_by_id(environment_id), + ) } pub(crate) fn primary_environment(&self) -> Option> { @@ -189,7 +192,7 @@ mod tests { } #[tokio::test] - async fn resolved_environment_selections_selects_by_id_or_primary() { + async fn resolved_environment_selections_gets_by_id_or_primary() { let cwd = AbsolutePathBuf::current_dir().expect("cwd"); let manager = EnvironmentManager::default_for_tests(); @@ -204,18 +207,18 @@ mod tests { assert_eq!( resolved - .select(/*environment_id*/ None) + .get_or_primary(/*environment_id*/ None) .expect("primary environment") .environment_id, "local" ); assert_eq!( resolved - .select(Some("local")) + .get_or_primary(Some("local")) .expect("selected environment") .environment_id, "local" ); - assert!(resolved.select(Some("unknown")).is_none()); + assert!(resolved.get_or_primary(Some("unknown")).is_none()); } } diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index ae915e33ef..27058d059b 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -94,7 +94,7 @@ fn resolve_workdir_base_path( } #[derive(Debug, Deserialize)] -struct EnvironmentWorkdirArgs { +struct EnvironmentTargetArgs { #[serde(default)] environment_id: Option, // Keep this raw until after environment selection; relative paths must be @@ -108,12 +108,13 @@ struct EnvironmentWorkdirArgs { /// Returns the selected turn environment plus the effective execution cwd. The /// returned cwd is `turn_environment.cwd.join(workdir)` when `workdir` is /// provided and non-empty, otherwise it is the selected `turn_environment.cwd`. -fn resolve_environment_workdir_target( +fn resolve_environment_target( arguments: &str, environments: &ResolvedTurnEnvironments, ) -> Result, FunctionCallError> { - let target_args: EnvironmentWorkdirArgs = parse_arguments(arguments)?; - let Some(turn_environment) = environments.select(target_args.environment_id.as_deref()) else { + let target_args: EnvironmentTargetArgs = parse_arguments(arguments)?; + let Some(turn_environment) = environments.get_or_primary(target_args.environment_id.as_deref()) + else { return Ok(None); }; let cwd = target_args diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index ea91e27ec4..3dd17c5e8a 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -26,8 +26,7 @@ use crate::tools::handlers::implicit_granted_permissions; use crate::tools::handlers::normalize_and_validate_additional_permissions; use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments_with_base_path; -use crate::tools::handlers::resolve_environment_workdir_target; -use crate::tools::handlers::resolve_workdir_base_path; +use crate::tools::handlers::resolve_environment_target; use crate::tools::hook_names::HookToolName; use crate::tools::orchestrator::ToolOrchestrator; use crate::tools::registry::PostToolUsePayload; @@ -109,10 +108,11 @@ impl ShellHandler { params: &ShellToolCallParams, turn_context: &TurnContext, thread_id: ThreadId, + cwd: codex_utils_absolute_path::AbsolutePathBuf, ) -> ExecParams { ExecParams { command: params.command.clone(), - cwd: turn_context.resolve_path(params.workdir.clone()), + cwd, expiration: params.timeout_ms.into(), capture_policy: ExecCapturePolicy::ShellTool, env: create_env(&turn_context.shell_environment_policy, Some(thread_id)), @@ -254,7 +254,7 @@ impl ToolHandler for ShellHandler { let params: ShellToolCallParams = parse_arguments_with_base_path(&arguments, &cwd)?; let prefix_rule = params.prefix_rule.clone(); let exec_params = - ShellHandler::to_exec_params(¶ms, turn.as_ref(), session.conversation_id); + ShellHandler::to_exec_params(¶ms, turn.as_ref(), session.conversation_id, cwd); ShellHandler::run_exec_like(RunExecLikeArgs { tool_name: "shell".to_string(), exec_params, @@ -341,7 +341,7 @@ impl ToolHandler for ContainerExecHandler { let params: ShellToolCallParams = parse_arguments_with_base_path(&arguments, &cwd)?; let prefix_rule = params.prefix_rule.clone(); let exec_params = - ShellHandler::to_exec_params(¶ms, turn.as_ref(), session.conversation_id); + ShellHandler::to_exec_params(¶ms, turn.as_ref(), session.conversation_id, cwd); ShellHandler::run_exec_like(RunExecLikeArgs { tool_name: "container.exec".to_string(), exec_params, @@ -430,8 +430,12 @@ impl ToolHandler for LocalShellHandler { )); }; - let exec_params = - ShellHandler::to_exec_params(¶ms, turn.as_ref(), session.conversation_id); + let exec_params = ShellHandler::to_exec_params( + ¶ms, + turn.as_ref(), + session.conversation_id, + turn.resolve_path(params.workdir.clone()), + ); ShellHandler::run_exec_like(RunExecLikeArgs { tool_name: "local_shell".to_string(), exec_params, @@ -556,7 +560,7 @@ impl ToolHandler for ShellCommandHandler { }; let Some((turn_environment, cwd)) = - resolve_environment_workdir_target(&arguments, &turn.environments)? + resolve_environment_target(&arguments, &turn.environments)? else { return Err(FunctionCallError::RespondToModel( "shell is unavailable in this session".to_string(), diff --git a/codex-rs/core/src/tools/handlers/unified_exec.rs b/codex-rs/core/src/tools/handlers/unified_exec.rs index 4cc7e52760..4612e8be24 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec.rs @@ -13,7 +13,7 @@ use crate::tools::handlers::implicit_granted_permissions; use crate::tools::handlers::normalize_and_validate_additional_permissions; use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments_with_base_path; -use crate::tools::handlers::resolve_environment_workdir_target; +use crate::tools::handlers::resolve_environment_target; use crate::tools::hook_names::HookToolName; use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PreToolUsePayload; @@ -182,7 +182,7 @@ impl ToolHandler for ExecCommandHandler { let manager: &UnifiedExecProcessManager = &session.services.unified_exec_manager; let context = UnifiedExecContext::new(session.clone(), turn.clone(), call_id.clone()); let Some((turn_environment, cwd)) = - resolve_environment_workdir_target(&arguments, &turn.environments)? + resolve_environment_target(&arguments, &turn.environments)? else { return Err(FunctionCallError::RespondToModel( "unified exec is unavailable in this session".to_string(),