Skip to content

Commit 1ad991b

Browse files
committed
feat: fallback unified_exec to shell_command
1 parent 5b472c9 commit 1ad991b

File tree

3 files changed

+54
-0
lines changed

3 files changed

+54
-0
lines changed

codex-rs/core/src/codex.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use async_channel::Sender;
3232
use codex_protocol::ConversationId;
3333
use codex_protocol::approvals::ExecPolicyAmendment;
3434
use codex_protocol::items::TurnItem;
35+
use codex_protocol::openai_models::ConfigShellToolType;
3536
use codex_protocol::protocol::FileChange;
3637
use codex_protocol::protocol::HasLegacyEvent;
3738
use codex_protocol::protocol::ItemCompletedEvent;
@@ -880,6 +881,21 @@ impl Session {
880881
self.conversation_id,
881882
sub_id,
882883
);
884+
// Check if `unified_exec` is supported by the OS. Fallback on ShellCommand otherwise.
885+
if matches!(
886+
turn_context.tools_config.shell_type,
887+
ConfigShellToolType::UnifiedExec
888+
) {
889+
let hb_command = self.user_shell().derive_exec_args("exit 0", false);
890+
if !self
891+
.services
892+
.unified_exec_manager
893+
.unified_exec_available(&hb_command)
894+
.await
895+
{
896+
turn_context.tools_config.shell_type = ConfigShellToolType::ShellCommand;
897+
}
898+
}
883899
if let Some(final_schema) = updates.final_output_json_schema {
884900
turn_context.final_output_json_schema = final_schema;
885901
}

codex-rs/core/src/unified_exec/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,14 @@ impl SessionStore {
135135

136136
pub(crate) struct UnifiedExecSessionManager {
137137
session_store: Mutex<SessionStore>,
138+
availability: Mutex<Option<bool>>,
138139
}
139140

140141
impl Default for UnifiedExecSessionManager {
141142
fn default() -> Self {
142143
Self {
143144
session_store: Mutex::new(SessionStore::default()),
145+
availability: Mutex::new(None),
144146
}
145147
}
146148
}

codex-rs/core/src/unified_exec/session_manager.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use tokio::sync::mpsc;
99
use tokio::time::Duration;
1010
use tokio::time::Instant;
1111
use tokio_util::sync::CancellationToken;
12+
use tracing::warn;
1213

1314
use crate::bash::extract_bash_command;
1415
use crate::codex::Session;
@@ -78,6 +79,41 @@ struct PreparedSessionHandles {
7879
}
7980

8081
impl UnifiedExecSessionManager {
82+
pub(crate) async fn unified_exec_available(&self, command: &[String]) -> bool {
83+
{
84+
let availability = self.availability.lock().await;
85+
if let Some(available) = *availability {
86+
return available;
87+
}
88+
}
89+
90+
let available = Self::probe_unified_exec_heartbeat(command).await;
91+
92+
let mut availability = self.availability.lock().await;
93+
*availability = Some(available);
94+
available
95+
}
96+
97+
async fn probe_unified_exec_heartbeat(command: &[String]) -> bool {
98+
let Some((program, args)) = command.split_first() else {
99+
warn!("unified exec heartbeat failed: missing command");
100+
return false;
101+
};
102+
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
103+
let env = HashMap::new();
104+
105+
match codex_utils_pty::spawn_pty_process(program, args, &cwd, &env, &None).await {
106+
Ok(spawned) => {
107+
spawned.session.terminate();
108+
true
109+
}
110+
Err(err) => {
111+
warn!("unified exec heartbeat failed; falling back to other tool: {err:#}");
112+
false
113+
}
114+
}
115+
}
116+
81117
pub(crate) async fn allocate_process_id(&self) -> String {
82118
loop {
83119
let mut store = self.session_store.lock().await;

0 commit comments

Comments
 (0)