mirror of
https://github.com/openai/codex.git
synced 2026-05-06 06:12:59 +03:00
app-server: Add streaming and tty/pty capabilities to command/exec (#13640)
* Add an ability to stream stdin, stdout, and stderr * Streaming of stdout and stderr has a configurable cap for total amount of transmitted bytes (with an ability to disable it) * Add support for overriding environment variables * Add an ability to terminate running applications (using `command/exec/terminate`) * Add TTY/PTY support, with an ability to resize the terminal (using `command/exec/resize`)
This commit is contained in:
committed by
GitHub
parent
61098c7f51
commit
e9bd8b20a1
@@ -34,6 +34,7 @@ use crate::spawn::StdioPolicy;
|
||||
use crate::spawn::spawn_child_async;
|
||||
use crate::text_encoding::bytes_to_string_smart;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_utils_pty::DEFAULT_OUTPUT_BYTES_CAP;
|
||||
use codex_utils_pty::process_group::kill_child_process_group;
|
||||
|
||||
pub const DEFAULT_EXEC_COMMAND_TIMEOUT_MS: u64 = 10_000;
|
||||
@@ -53,12 +54,21 @@ const AGGREGATE_BUFFER_INITIAL_CAPACITY: usize = 8 * 1024; // 8 KiB
|
||||
///
|
||||
/// This mirrors unified exec's output cap so a single runaway command cannot
|
||||
/// OOM the process by dumping huge amounts of data to stdout/stderr.
|
||||
const EXEC_OUTPUT_MAX_BYTES: usize = 1024 * 1024; // 1 MiB
|
||||
const EXEC_OUTPUT_MAX_BYTES: usize = DEFAULT_OUTPUT_BYTES_CAP;
|
||||
|
||||
/// Limit the number of ExecCommandOutputDelta events emitted per exec call.
|
||||
/// Aggregation still collects full output; only the live event stream is capped.
|
||||
pub(crate) const MAX_EXEC_OUTPUT_DELTAS_PER_CALL: usize = 10_000;
|
||||
|
||||
// Wait for the stdout/stderr collection tasks but guard against them
|
||||
// hanging forever. In the normal case, both pipes are closed once the child
|
||||
// terminates so the tasks exit quickly. However, if the child process
|
||||
// spawned grandchildren that inherited its stdout/stderr file descriptors
|
||||
// those pipes may stay open after we `kill` the direct child on timeout.
|
||||
// That would cause the `read_capped` tasks to block on `read()`
|
||||
// indefinitely, effectively hanging the whole agent.
|
||||
pub const IO_DRAIN_TIMEOUT_MS: u64 = 2_000; // 2 s should be plenty for local pipes
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExecParams {
|
||||
pub command: Vec<String>,
|
||||
@@ -157,6 +167,27 @@ pub async fn process_exec_tool_call(
|
||||
use_linux_sandbox_bwrap: bool,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
) -> Result<ExecToolCallOutput> {
|
||||
let exec_req = build_exec_request(
|
||||
params,
|
||||
sandbox_policy,
|
||||
sandbox_cwd,
|
||||
codex_linux_sandbox_exe,
|
||||
use_linux_sandbox_bwrap,
|
||||
)?;
|
||||
|
||||
// Route through the sandboxing module for a single, unified execution path.
|
||||
crate::sandboxing::execute_env(exec_req, stdout_stream).await
|
||||
}
|
||||
|
||||
/// Transform a portable exec request into the concrete argv/env that should be
|
||||
/// spawned under the requested sandbox policy.
|
||||
pub fn build_exec_request(
|
||||
params: ExecParams,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
sandbox_cwd: &Path,
|
||||
codex_linux_sandbox_exe: &Option<PathBuf>,
|
||||
use_linux_sandbox_bwrap: bool,
|
||||
) -> Result<ExecRequest> {
|
||||
let windows_sandbox_level = params.windows_sandbox_level;
|
||||
let enforce_managed_network = params.network.is_some();
|
||||
let sandbox_type = match &sandbox_policy {
|
||||
@@ -226,9 +257,7 @@ pub async fn process_exec_tool_call(
|
||||
windows_sandbox_level,
|
||||
})
|
||||
.map_err(CodexErr::from)?;
|
||||
|
||||
// Route through the sandboxing module for a single, unified execution path.
|
||||
crate::sandboxing::execute_env(exec_req, stdout_stream).await
|
||||
Ok(exec_req)
|
||||
}
|
||||
|
||||
pub(crate) async fn execute_exec_request(
|
||||
@@ -796,16 +825,6 @@ async fn consume_truncated_output(
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for the stdout/stderr collection tasks but guard against them
|
||||
// hanging forever. In the normal case, both pipes are closed once the child
|
||||
// terminates so the tasks exit quickly. However, if the child process
|
||||
// spawned grandchildren that inherited its stdout/stderr file descriptors
|
||||
// those pipes may stay open after we `kill` the direct child on timeout.
|
||||
// That would cause the `read_capped` tasks to block on `read()`
|
||||
// indefinitely, effectively hanging the whole agent.
|
||||
|
||||
const IO_DRAIN_TIMEOUT_MS: u64 = 2_000; // 2 s should be plenty for local pipes
|
||||
|
||||
// We need mutable bindings so we can `abort()` them on timeout.
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user