Skip to content
Merged
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
25 changes: 20 additions & 5 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ impl Session {
duration,
exit_code,
timed_out: _,
..
} = output;
// Send full stdout/stderr to clients; do not truncate.
let stdout = stdout.text.clone();
Expand Down Expand Up @@ -974,15 +975,28 @@ impl Session {
let sub_id = context.sub_id.clone();
let call_id = context.call_id.clone();

self.on_exec_command_begin(turn_diff_tracker.clone(), context.clone())
.await;

let begin_turn_diff = turn_diff_tracker.clone();
let begin_context = context.clone();
let session = self;
let result = self
.services
.executor
.run(request, self, approval_policy, &context)
.run(request, self, approval_policy, &context, move || {
let turn_diff = begin_turn_diff.clone();
let ctx = begin_context.clone();
async move {
session.on_exec_command_begin(turn_diff, ctx).await;
}
})
.await;

if matches!(
&result,
Err(ExecError::Function(FunctionCallError::Denied(_)))
) {
return result;
Comment thread
jif-oai marked this conversation as resolved.
}

let normalized = normalize_exec_result(&result);
let borrowed = normalized.event_output();

Expand Down Expand Up @@ -2216,7 +2230,8 @@ async fn try_run_turn(
response: Some(response),
});
}
Err(FunctionCallError::RespondToModel(message)) => {
Err(FunctionCallError::RespondToModel(message))
| Err(FunctionCallError::Denied(message)) => {
let response = ResponseInputItem::FunctionCallOutput {
call_id: String::new(),
output: FunctionCallOutputPayload {
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/core/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,9 @@ pub mod errors {
pub(crate) fn rejection(msg: impl Into<String>) -> Self {
FunctionCallError::RespondToModel(msg.into()).into()
}

pub(crate) fn denied(msg: impl Into<String>) -> Self {
FunctionCallError::Denied(msg.into()).into()
}
}
}
17 changes: 12 additions & 5 deletions codex-rs/core/src/executor/runner.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::future::Future;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::RwLock;
Expand Down Expand Up @@ -74,13 +75,18 @@ impl Executor {
/// Runs a prepared execution request end-to-end: prepares parameters, decides on
/// sandbox placement (prompting the user when necessary), launches the command,
/// and lets the backend post-process the final output.
pub(crate) async fn run(
pub(crate) async fn run<F, Fut>(
&self,
mut request: ExecutionRequest,
session: &Session,
approval_policy: AskForApproval,
context: &ExecCommandContext,
) -> Result<ExecToolCallOutput, ExecError> {
on_exec_begin: F,
) -> Result<ExecToolCallOutput, ExecError>
where
F: FnOnce() -> Fut,
Fut: Future<Output = ()>,
{
if matches!(request.mode, ExecutionMode::Shell) {
request.params =
maybe_translate_shell_command(request.params, session, request.use_shell_profile);
Expand Down Expand Up @@ -119,7 +125,7 @@ impl Executor {
if sandbox_decision.record_session_approval {
self.approval_cache.insert(request.approval_command.clone());
}

on_exec_begin().await;
// Step 4: Launch the command within the chosen sandbox.
let first_attempt = self
.spawn(
Expand Down Expand Up @@ -210,7 +216,7 @@ impl Executor {
Ok(retry_output)
}
ReviewDecision::Denied | ReviewDecision::Abort => {
Err(ExecError::rejection("exec command rejected by user"))
Err(ExecError::denied("exec command rejected by user"))
}
}
}
Expand Down Expand Up @@ -301,7 +307,8 @@ pub(crate) fn normalize_exec_result(
}
Err(err) => {
let message = match err {
ExecError::Function(FunctionCallError::RespondToModel(msg)) => msg.clone(),
ExecError::Function(FunctionCallError::RespondToModel(msg))
| ExecError::Function(FunctionCallError::Denied(msg)) => msg.clone(),
ExecError::Codex(e) => get_error_message_ui(e),
err => err.to_string(),
};
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/executor/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ async fn select_shell_sandbox(
ReviewDecision::Approved => Ok(SandboxDecision::user_override(false)),
ReviewDecision::ApprovedForSession => Ok(SandboxDecision::user_override(true)),
ReviewDecision::Denied | ReviewDecision::Abort => {
Err(ExecError::rejection("exec command rejected by user"))
Err(ExecError::denied("exec command rejected by user"))
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/function_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use thiserror::Error;
pub enum FunctionCallError {
#[error("{0}")]
RespondToModel(String),
#[error("{0}")]
Denied(String),
#[error("LocalShellCall without call_id or id")]
MissingLocalShellCallId,
#[error("Fatal error: {0}")]
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ fn truncate_function_error(err: FunctionCallError) -> FunctionCallError {
FunctionCallError::RespondToModel(msg) => {
FunctionCallError::RespondToModel(format_exec_output(&msg))
}
FunctionCallError::Denied(msg) => FunctionCallError::Denied(format_exec_output(&msg)),
FunctionCallError::Fatal(msg) => FunctionCallError::Fatal(format_exec_output(&msg)),
other => other,
}
Expand Down
Loading