mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
RV2
This commit is contained in:
+80
-51
@@ -18,13 +18,14 @@ use tokio::process::Child;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result;
|
||||
use crate::error::SandboxErr;
|
||||
use crate::landlock::spawn_command_under_linux_sandbox;
|
||||
use crate::executor::SandboxLaunch;
|
||||
use crate::executor::SandboxLaunchError;
|
||||
use crate::executor::sandbox::build_launch_for_sandbox;
|
||||
use crate::protocol::Event;
|
||||
use crate::protocol::EventMsg;
|
||||
use crate::protocol::ExecCommandOutputDeltaEvent;
|
||||
use crate::protocol::ExecOutputStream;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::seatbelt::spawn_command_under_seatbelt;
|
||||
use crate::spawn::StdioPolicy;
|
||||
use crate::spawn::spawn_child_async;
|
||||
|
||||
@@ -86,58 +87,36 @@ pub async fn process_exec_tool_call(
|
||||
sandbox_cwd: &Path,
|
||||
codex_linux_sandbox_exe: &Option<PathBuf>,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
) -> Result<ExecToolCallOutput> {
|
||||
let launch = build_launch_for_sandbox(
|
||||
sandbox_type,
|
||||
¶ms.command,
|
||||
sandbox_policy,
|
||||
sandbox_cwd,
|
||||
codex_linux_sandbox_exe.as_ref(),
|
||||
)
|
||||
.map_err(sandbox_launch_error_to_codex)?;
|
||||
execute_sandbox_launch(params, launch, sandbox_type, sandbox_policy, stdout_stream).await
|
||||
}
|
||||
|
||||
pub(crate) async fn execute_sandbox_launch(
|
||||
params: ExecParams,
|
||||
launch: SandboxLaunch,
|
||||
sandbox_type: SandboxType,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
) -> Result<ExecToolCallOutput> {
|
||||
let start = Instant::now();
|
||||
|
||||
let timeout_duration = params.timeout_duration();
|
||||
|
||||
let raw_output_result: std::result::Result<RawExecToolCallOutput, CodexErr> = match sandbox_type
|
||||
{
|
||||
SandboxType::None => exec(params, sandbox_policy, stdout_stream.clone()).await,
|
||||
SandboxType::MacosSeatbelt => {
|
||||
let ExecParams {
|
||||
command,
|
||||
cwd: command_cwd,
|
||||
env,
|
||||
..
|
||||
} = params;
|
||||
let child = spawn_command_under_seatbelt(
|
||||
command,
|
||||
command_cwd,
|
||||
sandbox_policy,
|
||||
sandbox_cwd,
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
env,
|
||||
)
|
||||
.await?;
|
||||
consume_truncated_output(child, timeout_duration, stdout_stream.clone()).await
|
||||
}
|
||||
SandboxType::LinuxSeccomp => {
|
||||
let ExecParams {
|
||||
command,
|
||||
cwd: command_cwd,
|
||||
env,
|
||||
..
|
||||
} = params;
|
||||
|
||||
let codex_linux_sandbox_exe = codex_linux_sandbox_exe
|
||||
.as_ref()
|
||||
.ok_or(CodexErr::LandlockSandboxExecutableNotProvided)?;
|
||||
let child = spawn_command_under_linux_sandbox(
|
||||
codex_linux_sandbox_exe,
|
||||
command,
|
||||
command_cwd,
|
||||
sandbox_policy,
|
||||
sandbox_cwd,
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
env,
|
||||
)
|
||||
.await?;
|
||||
|
||||
consume_truncated_output(child, timeout_duration, stdout_stream).await
|
||||
}
|
||||
};
|
||||
let raw_output_result = spawn_with_launch(params, launch, sandbox_policy, stdout_stream).await;
|
||||
let duration = start.elapsed();
|
||||
finalize_exec_result(raw_output_result, sandbox_type, duration)
|
||||
}
|
||||
|
||||
fn finalize_exec_result(
|
||||
raw_output_result: std::result::Result<RawExecToolCallOutput, CodexErr>,
|
||||
sandbox_type: SandboxType,
|
||||
duration: Duration,
|
||||
) -> Result<ExecToolCallOutput> {
|
||||
match raw_output_result {
|
||||
Ok(raw_output) => {
|
||||
#[allow(unused_mut)]
|
||||
@@ -192,6 +171,56 @@ pub async fn process_exec_tool_call(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sandbox_launch_error_to_codex(err: SandboxLaunchError) -> CodexErr {
|
||||
match err {
|
||||
SandboxLaunchError::MissingCommandLine => CodexErr::Io(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"command args are empty",
|
||||
)),
|
||||
SandboxLaunchError::MissingLinuxSandboxExecutable => {
|
||||
CodexErr::LandlockSandboxExecutableNotProvided
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn spawn_with_launch(
|
||||
params: ExecParams,
|
||||
launch: SandboxLaunch,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
) -> std::result::Result<RawExecToolCallOutput, CodexErr> {
|
||||
let ExecParams {
|
||||
command: _,
|
||||
cwd,
|
||||
timeout_ms,
|
||||
mut env,
|
||||
with_escalated_permissions,
|
||||
justification,
|
||||
} = params;
|
||||
|
||||
let SandboxLaunch {
|
||||
program,
|
||||
args,
|
||||
env: launch_env,
|
||||
} = launch;
|
||||
env.extend(launch_env);
|
||||
|
||||
let mut command = Vec::with_capacity(1 + args.len());
|
||||
command.push(program);
|
||||
command.extend(args);
|
||||
|
||||
let updated_params = ExecParams {
|
||||
command,
|
||||
cwd,
|
||||
timeout_ms,
|
||||
env,
|
||||
with_escalated_permissions,
|
||||
justification,
|
||||
};
|
||||
|
||||
exec(updated_params, sandbox_policy, stdout_stream).await
|
||||
}
|
||||
|
||||
/// We don't have a fully deterministic way to tell if our command failed
|
||||
/// because of the sandbox - a command in the user's zshrc file might hit an
|
||||
/// error, but the command itself might fail or succeed for other reasons.
|
||||
|
||||
@@ -11,6 +11,7 @@ pub(crate) use runner::ExecutorConfig;
|
||||
pub(crate) use runner::normalize_exec_result;
|
||||
pub(crate) use sandbox::SandboxLaunch;
|
||||
pub(crate) use sandbox::SandboxLaunchError;
|
||||
pub(crate) use sandbox::build_launch_for_sandbox;
|
||||
|
||||
pub(crate) mod linkers {
|
||||
use crate::exec::ExecParams;
|
||||
|
||||
@@ -15,7 +15,8 @@ use crate::exec::ExecToolCallOutput;
|
||||
use crate::exec::SandboxType;
|
||||
use crate::exec::StdoutStream;
|
||||
use crate::exec::StreamOutput;
|
||||
use crate::exec::process_exec_tool_call;
|
||||
use crate::exec::execute_sandbox_launch;
|
||||
use crate::exec::sandbox_launch_error_to_codex;
|
||||
use crate::executor::errors::ExecError;
|
||||
use crate::executor::sandbox::RetrySandboxContext;
|
||||
use crate::executor::sandbox::SandboxDecision;
|
||||
@@ -225,14 +226,18 @@ impl Executor {
|
||||
.await?;
|
||||
|
||||
let stdout_stream = plan.stdout_stream();
|
||||
let first_attempt = self
|
||||
.spawn(
|
||||
plan.request().params.clone(),
|
||||
plan.initial_sandbox(),
|
||||
plan.config(),
|
||||
stdout_stream.clone(),
|
||||
)
|
||||
.await;
|
||||
let sandbox_policy = plan.config().sandbox_policy.clone();
|
||||
let initial_launch = plan
|
||||
.initial_launch()
|
||||
.map_err(|err| ExecError::Codex(sandbox_launch_error_to_codex(err)))?;
|
||||
let first_attempt = execute_sandbox_launch(
|
||||
plan.request().params.clone(),
|
||||
initial_launch,
|
||||
plan.initial_sandbox(),
|
||||
&sandbox_policy,
|
||||
stdout_stream.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match first_attempt {
|
||||
Ok(output) => Ok(output),
|
||||
@@ -245,10 +250,14 @@ impl Executor {
|
||||
.prompt_retry_without_sandbox(session, format!("Execution failed: {error}"))
|
||||
.await
|
||||
{
|
||||
self.spawn(
|
||||
let retry_launch = plan
|
||||
.retry_launch()
|
||||
.map_err(|err| ExecError::Codex(sandbox_launch_error_to_codex(err)))?;
|
||||
execute_sandbox_launch(
|
||||
plan.request().params.clone(),
|
||||
retry_launch,
|
||||
SandboxType::None,
|
||||
plan.config(),
|
||||
&sandbox_policy,
|
||||
stdout_stream,
|
||||
)
|
||||
.await
|
||||
@@ -264,24 +273,6 @@ impl Executor {
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn spawn(
|
||||
&self,
|
||||
params: ExecParams,
|
||||
sandbox: SandboxType,
|
||||
config: &ExecutorConfig,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
) -> Result<ExecToolCallOutput, CodexErr> {
|
||||
process_exec_tool_call(
|
||||
params,
|
||||
sandbox,
|
||||
&config.sandbox_policy,
|
||||
&config.sandbox_cwd,
|
||||
&config.codex_linux_sandbox_exe,
|
||||
stdout_stream,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_translate_shell_command(
|
||||
|
||||
@@ -79,16 +79,10 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
};
|
||||
|
||||
let request = UnifiedExecRequest {
|
||||
session_id: parsed_session_id,
|
||||
input_chunks: &input,
|
||||
timeout_ms,
|
||||
};
|
||||
|
||||
session
|
||||
.services
|
||||
.executor
|
||||
.update_environment(turn.sandbox_policy.clone(), turn.cwd.clone());
|
||||
|
||||
let value = session
|
||||
.services
|
||||
.unified_exec_manager
|
||||
@@ -100,6 +94,7 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
sub_id: &sub_id,
|
||||
call_id: &call_id,
|
||||
tool_name: &tool_name,
|
||||
session_id: parsed_session_id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -3,11 +3,8 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum UnifiedExecError {
|
||||
#[error("Failed to create unified exec session: {pty_error}")]
|
||||
CreateSession {
|
||||
#[source]
|
||||
pty_error: anyhow::Error,
|
||||
},
|
||||
#[error("Failed to create unified exec session: {message}")]
|
||||
CreateSession { message: String },
|
||||
#[error("Unknown session id {session_id}")]
|
||||
UnknownSessionId { session_id: i32 },
|
||||
#[error("failed to write to stdin")]
|
||||
@@ -21,8 +18,8 @@ pub(crate) enum UnifiedExecError {
|
||||
}
|
||||
|
||||
impl UnifiedExecError {
|
||||
pub(crate) fn create_session(error: anyhow::Error) -> Self {
|
||||
Self::CreateSession { pty_error: error }
|
||||
pub(crate) fn create_session(message: String) -> Self {
|
||||
Self::CreateSession { message }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use anyhow::anyhow;
|
||||
use portable_pty::CommandBuilder;
|
||||
use portable_pty::PtySize;
|
||||
use portable_pty::native_pty_system;
|
||||
@@ -43,11 +42,11 @@ pub(crate) struct UnifiedExecContext<'a> {
|
||||
pub sub_id: &'a str,
|
||||
pub call_id: &'a str,
|
||||
pub tool_name: &'a str,
|
||||
pub session_id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnifiedExecRequest<'a> {
|
||||
pub session_id: Option<i32>,
|
||||
pub input_chunks: &'a [String],
|
||||
pub timeout_ms: Option<u64>,
|
||||
}
|
||||
@@ -224,7 +223,7 @@ impl UnifiedExecSessionManager {
|
||||
&exec_context,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| UnifiedExecError::create_session(anyhow!(err.to_string())))?;
|
||||
.map_err(|err| UnifiedExecError::create_session(err.to_string()))?;
|
||||
|
||||
let launch = plan.initial_launch()?;
|
||||
|
||||
@@ -270,7 +269,7 @@ impl UnifiedExecSessionManager {
|
||||
let output_buffer;
|
||||
let output_notify;
|
||||
|
||||
if let Some(existing_id) = request.session_id {
|
||||
if let Some(existing_id) = context.session_id {
|
||||
let mut sessions = self.sessions.lock().await;
|
||||
match sessions.get(&existing_id) {
|
||||
Some(session) => {
|
||||
@@ -307,7 +306,7 @@ impl UnifiedExecSessionManager {
|
||||
new_session = Some(managed_session);
|
||||
};
|
||||
|
||||
if request.session_id.is_some() {
|
||||
if context.session_id.is_some() {
|
||||
let joined_input = request.input_chunks.join(" ");
|
||||
if !joined_input.is_empty() && writer_tx.send(joined_input.into_bytes()).await.is_err()
|
||||
{
|
||||
@@ -366,7 +365,7 @@ impl UnifiedExecSessionManager {
|
||||
|
||||
let should_store_session = if let Some(session) = new_session.as_ref() {
|
||||
!session.has_exited()
|
||||
} else if request.session_id.is_some() {
|
||||
} else if context.session_id.is_some() {
|
||||
let mut sessions = self.sessions.lock().await;
|
||||
if let Some(existing) = sessions.get(&session_id) {
|
||||
if existing.has_exited() {
|
||||
@@ -421,7 +420,7 @@ async fn create_unified_exec_session(
|
||||
pixel_width: 0,
|
||||
pixel_height: 0,
|
||||
})
|
||||
.map_err(UnifiedExecError::create_session)?;
|
||||
.map_err(|err| UnifiedExecError::create_session(err.to_string()))?;
|
||||
|
||||
// Safe thanks to the check at the top of the function.
|
||||
let mut command_builder = CommandBuilder::new(launch.program.clone());
|
||||
@@ -435,7 +434,7 @@ async fn create_unified_exec_session(
|
||||
let mut child = pair
|
||||
.slave
|
||||
.spawn_command(command_builder)
|
||||
.map_err(UnifiedExecError::create_session)?;
|
||||
.map_err(|err| UnifiedExecError::create_session(err.to_string()))?;
|
||||
let killer = child.clone_killer();
|
||||
|
||||
let (writer_tx, mut writer_rx) = mpsc::channel::<Vec<u8>>(128);
|
||||
@@ -444,7 +443,7 @@ async fn create_unified_exec_session(
|
||||
let mut reader = pair
|
||||
.master
|
||||
.try_clone_reader()
|
||||
.map_err(UnifiedExecError::create_session)?;
|
||||
.map_err(|err| UnifiedExecError::create_session(err.to_string()))?;
|
||||
let output_tx_clone = output_tx.clone();
|
||||
let reader_handle = tokio::task::spawn_blocking(move || {
|
||||
let mut buf = [0u8; 8192];
|
||||
@@ -467,7 +466,7 @@ async fn create_unified_exec_session(
|
||||
let writer = pair
|
||||
.master
|
||||
.take_writer()
|
||||
.map_err(UnifiedExecError::create_session)?;
|
||||
.map_err(|err| UnifiedExecError::create_session(err.to_string()))?;
|
||||
let writer = Arc::new(StdMutex::new(writer));
|
||||
let writer_handle = tokio::spawn({
|
||||
let writer = writer.clone();
|
||||
|
||||
Reference in New Issue
Block a user