use crate::product::agent::CODEX_APPLY_PATCH_ARG1;
use crate::product::agent::exec::ExecToolCallOutput;
use crate::product::agent::sandboxing::CommandSpec;
use crate::product::agent::sandboxing::SandboxPermissions;
use crate::product::agent::sandboxing::execute_env;
use crate::product::agent::tools::sandboxing::Approvable;
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::Sandboxable;
use crate::product::agent::tools::sandboxing::SandboxablePreference;
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::with_cached_approval;
use crate::product::apply_patch::ApplyPatchAction;
use crate::product::protocol::protocol::AskForApproval;
use crate::product::protocol::protocol::FileChange;
use crate::product::protocol::protocol::ReviewDecision;
use crate::product::utils_absolute_path::AbsolutePathBuf;
use futures::future::BoxFuture;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug)]
pub struct ApplyPatchRequest {
pub action: ApplyPatchAction,
pub file_paths: Vec<AbsolutePathBuf>,
pub changes: std::collections::HashMap<PathBuf, FileChange>,
pub exec_approval_requirement: ExecApprovalRequirement,
pub timeout_ms: Option<u64>,
pub codex_exe: Option<PathBuf>,
}
#[derive(Default)]
pub struct ApplyPatchRuntime;
impl ApplyPatchRuntime {
pub fn new() -> Self {
Self
}
fn build_command_spec(req: &ApplyPatchRequest) -> Result<CommandSpec, ToolError> {
use std::env;
let exe = if let Some(path) = &req.codex_exe {
path.clone()
} else {
env::current_exe()
.map_err(|e| ToolError::Rejected(format!("failed to determine codex exe: {e}")))?
};
let program = exe.to_string_lossy().to_string();
Ok(CommandSpec {
program,
args: vec![CODEX_APPLY_PATCH_ARG1.to_string(), req.action.patch.clone()],
cwd: req.action.cwd.clone(),
expiration: req.timeout_ms.into(),
env: HashMap::new(),
sandbox_permissions: SandboxPermissions::UseDefault,
justification: None,
})
}
fn stdout_stream(ctx: &ToolCtx<'_>) -> Option<crate::product::agent::exec::StdoutStream> {
Some(crate::product::agent::exec::StdoutStream {
sub_id: ctx.turn.sub_id.clone(),
call_id: ctx.call_id.clone(),
tx_event: ctx.session.get_tx_event(),
})
}
}
impl Sandboxable for ApplyPatchRuntime {
fn sandbox_preference(&self) -> SandboxablePreference {
SandboxablePreference::Auto
}
fn escalate_on_failure(&self) -> bool {
true
}
}
impl Approvable<ApplyPatchRequest> for ApplyPatchRuntime {
type ApprovalKey = AbsolutePathBuf;
fn approval_keys(&self, req: &ApplyPatchRequest) -> Vec<Self::ApprovalKey> {
req.file_paths.clone()
}
fn start_approval_async<'a>(
&'a mut self,
req: &'a ApplyPatchRequest,
ctx: ApprovalCtx<'a>,
) -> BoxFuture<'a, ReviewDecision> {
let session = ctx.session;
let turn = ctx.turn;
let call_id = ctx.call_id.to_string();
let retry_reason = ctx.retry_reason.clone();
let approval_keys = self.approval_keys(req);
let changes = req.changes.clone();
Box::pin(async move {
if let Some(reason) = retry_reason {
let rx_approve = session
.request_patch_approval(turn, call_id, changes.clone(), Some(reason), None)
.await;
return rx_approve.await.unwrap_or_default();
}
with_cached_approval(
&session.services,
"apply_patch",
approval_keys,
|| async move {
let rx_approve = session
.request_patch_approval(turn, call_id, changes, None, None)
.await;
rx_approve.await.unwrap_or_default()
},
)
.await
})
}
fn wants_no_sandbox_approval(&self, policy: AskForApproval) -> bool {
!matches!(policy, AskForApproval::Never)
}
fn exec_approval_requirement(
&self,
req: &ApplyPatchRequest,
) -> Option<ExecApprovalRequirement> {
Some(req.exec_approval_requirement.clone())
}
}
impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
async fn run(
&mut self,
req: &ApplyPatchRequest,
attempt: &SandboxAttempt<'_>,
ctx: &ToolCtx<'_>,
) -> Result<ExecToolCallOutput, ToolError> {
let spec = Self::build_command_spec(req)?;
let env = attempt
.env_for(spec)
.map_err(|err| ToolError::LHA(err.into()))?;
let out = execute_env(env, attempt.policy, Self::stdout_stream(ctx))
.await
.map_err(ToolError::LHA)?;
Ok(out)
}
}