use crate::product::agent::error::CodexErr;
use crate::product::agent::error::SandboxErr;
use crate::product::agent::exec::ExecToolCallOutput;
use crate::product::agent::sandboxing::SandboxManager;
use crate::product::agent::tools::sandboxing::ApprovalCtx;
use crate::product::agent::tools::sandboxing::ExecApprovalRequirement;
use crate::product::agent::tools::sandboxing::SandboxAttempt;
use crate::product::agent::tools::sandboxing::SandboxOverride;
use crate::product::agent::tools::sandboxing::ToolCtx;
use crate::product::agent::tools::sandboxing::ToolError;
use crate::product::agent::tools::sandboxing::ToolRuntime;
use crate::product::agent::tools::sandboxing::default_exec_approval_requirement;
use crate::product::otel::ToolDecisionSource;
use crate::product::protocol::protocol::AskForApproval;
use crate::product::protocol::protocol::ReviewDecision;
pub(crate) struct ToolOrchestrator {
sandbox: SandboxManager,
}
impl ToolOrchestrator {
pub fn new() -> Self {
Self {
sandbox: SandboxManager::new(),
}
}
pub async fn run<Rq, Out, T>(
&mut self,
tool: &mut T,
req: &Rq,
tool_ctx: &ToolCtx<'_>,
turn_ctx: &crate::product::agent::codex::TurnContext,
approval_policy: AskForApproval,
) -> Result<Out, ToolError>
where
T: ToolRuntime<Rq, Out>,
{
let otel = turn_ctx.runtime.get_otel_manager();
let otel_tn = &tool_ctx.tool_name;
let otel_ci = &tool_ctx.call_id;
let otel_user = ToolDecisionSource::User;
let otel_cfg = ToolDecisionSource::Config;
let mut already_approved = false;
let requirement = tool.exec_approval_requirement(req).unwrap_or_else(|| {
default_exec_approval_requirement(approval_policy, &turn_ctx.sandbox_policy)
});
match requirement {
ExecApprovalRequirement::Skip { .. } => {
otel.tool_decision(otel_tn, otel_ci, &ReviewDecision::Approved, otel_cfg);
}
ExecApprovalRequirement::Forbidden { reason } => {
return Err(ToolError::Rejected(reason));
}
ExecApprovalRequirement::NeedsApproval { reason, .. } => {
let approval_ctx = ApprovalCtx {
session: tool_ctx.session,
turn: turn_ctx,
call_id: &tool_ctx.call_id,
retry_reason: reason,
};
let decision = tool.start_approval_async(req, approval_ctx).await;
otel.tool_decision(otel_tn, otel_ci, &decision, otel_user.clone());
match decision {
ReviewDecision::Denied | ReviewDecision::Abort => {
return Err(ToolError::Rejected("rejected by user".to_string()));
}
ReviewDecision::Approved
| ReviewDecision::ApprovedExecpolicyAmendment { .. }
| ReviewDecision::ApprovedForSession => {}
}
already_approved = true;
}
}
let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
SandboxOverride::BypassSandboxFirstAttempt => {
crate::product::agent::exec::SandboxType::None
}
SandboxOverride::NoOverride => self.sandbox.select_initial(
&turn_ctx.sandbox_policy,
tool.sandbox_preference(),
turn_ctx.windows_sandbox_level,
),
};
let initial_attempt = SandboxAttempt {
sandbox: initial_sandbox,
policy: &turn_ctx.sandbox_policy,
manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd,
codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(),
windows_sandbox_level: turn_ctx.windows_sandbox_level,
};
match tool.run(req, &initial_attempt, tool_ctx).await {
Ok(out) => {
Ok(out)
}
Err(ToolError::LHA(CodexErr::Sandbox(SandboxErr::Denied { output }))) => {
if !tool.escalate_on_failure() {
return Err(ToolError::LHA(CodexErr::Sandbox(SandboxErr::Denied {
output,
})));
}
if !tool.wants_no_sandbox_approval(approval_policy) {
return Err(ToolError::LHA(CodexErr::Sandbox(SandboxErr::Denied {
output,
})));
}
if !tool.should_bypass_approval(approval_policy, already_approved) {
let reason_msg = build_denial_reason_from_output(output.as_ref());
let approval_ctx = ApprovalCtx {
session: tool_ctx.session,
turn: turn_ctx,
call_id: &tool_ctx.call_id,
retry_reason: Some(reason_msg),
};
let decision = tool.start_approval_async(req, approval_ctx).await;
otel.tool_decision(otel_tn, otel_ci, &decision, otel_user);
match decision {
ReviewDecision::Denied | ReviewDecision::Abort => {
return Err(ToolError::Rejected("rejected by user".to_string()));
}
ReviewDecision::Approved
| ReviewDecision::ApprovedExecpolicyAmendment { .. }
| ReviewDecision::ApprovedForSession => {}
}
}
let escalated_attempt = SandboxAttempt {
sandbox: crate::product::agent::exec::SandboxType::None,
policy: &turn_ctx.sandbox_policy,
manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd,
codex_linux_sandbox_exe: None,
windows_sandbox_level: turn_ctx.windows_sandbox_level,
};
(*tool).run(req, &escalated_attempt, tool_ctx).await
}
other => other,
}
}
}
fn build_denial_reason_from_output(_output: &ExecToolCallOutput) -> String {
"command failed; retry without sandbox?".to_string()
}