Skip to content
Open
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
35 changes: 33 additions & 2 deletions codex-rs/core/src/tools/handlers/unified_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ struct WriteStdinArgs {
max_output_tokens: Option<usize>,
}

#[derive(Debug, Deserialize)]
struct TerminateSessionArgs {
session_id: i32,
}

fn default_exec_yield_time_ms() -> u64 {
10000
}
Expand Down Expand Up @@ -144,7 +149,7 @@ impl ToolHandler for UnifiedExecHandler {
codex_protocol::protocol::AskForApproval::OnRequest
)
{
manager.release_process_id(&process_id).await;
manager.terminate_session(&process_id).await;
return Err(FunctionCallError::RespondToModel(format!(
"approval policy is {policy:?}; reject command — you cannot ask for escalated permissions if the approval policy is {policy:?}",
policy = context.turn.approval_policy
Expand All @@ -168,7 +173,7 @@ impl ToolHandler for UnifiedExecHandler {
)
.await?
{
manager.release_process_id(&process_id).await;
manager.terminate_session(&process_id).await;
return Ok(output);
}

Expand Down Expand Up @@ -233,6 +238,32 @@ impl ToolHandler for UnifiedExecHandler {

response
}
"terminate_session" => {
let args: TerminateSessionArgs =
serde_json::from_str(&arguments).map_err(|err| {
FunctionCallError::RespondToModel(format!(
"failed to parse terminate_session arguments: {err:?}"
))
})?;
let success = manager
.terminate_session(&args.session_id.to_string())
.await;
UnifiedExecResponse {
event_call_id: context.call_id.clone(),
chunk_id: crate::unified_exec::generate_chunk_id(),
wall_time: std::time::Duration::ZERO,
output: if success {
"Session terminated.".to_string()
} else {
"Session not found.".to_string()
},
raw_output: Vec::new(),
process_id: None,
exit_code: None,
original_token_count: None,
session_command: None,
}
}
other => {
return Err(FunctionCallError::RespondToModel(format!(
"unsupported unified exec function {other}"
Expand Down
38 changes: 36 additions & 2 deletions codex-rs/core/src/tools/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,27 @@ fn create_write_stdin_tool() -> ToolSpec {
})
}

fn create_terminate_session_tool() -> ToolSpec {
let mut properties = BTreeMap::new();
properties.insert(
"session_id".to_string(),
JsonSchema::Number {
description: Some("Identifier of the unified exec session to terminate.".to_string()),
},
);

ToolSpec::Function(ResponsesApiTool {
name: "terminate_session".to_string(),
description: "Terminates a running unified exec session.".to_string(),
strict: false,
parameters: JsonSchema::Object {
properties,
required: Some(vec!["session_id".to_string()]),
additional_properties: Some(false.into()),
},
})
}

fn create_shell_tool() -> ToolSpec {
let mut properties = BTreeMap::new();
properties.insert(
Expand Down Expand Up @@ -1015,8 +1036,10 @@ pub(crate) fn build_specs(
ConfigShellToolType::UnifiedExec => {
builder.push_spec(create_exec_command_tool());
builder.push_spec(create_write_stdin_tool());
builder.push_spec(create_terminate_session_tool());
builder.register_handler("exec_command", unified_exec_handler.clone());
builder.register_handler("write_stdin", unified_exec_handler);
builder.register_handler("write_stdin", unified_exec_handler.clone());
builder.register_handler("terminate_session", unified_exec_handler);
}
ConfigShellToolType::Disabled => {
// Do nothing.
Expand Down Expand Up @@ -1254,6 +1277,7 @@ mod tests {
for spec in [
create_exec_command_tool(),
create_write_stdin_tool(),
create_terminate_session_tool(),
create_list_mcp_resources_tool(),
create_list_mcp_resource_templates_tool(),
create_read_mcp_resource_tool(),
Expand Down Expand Up @@ -1336,6 +1360,7 @@ mod tests {
&[
"exec_command",
"write_stdin",
"terminate_session",
"list_mcp_resources",
"list_mcp_resource_templates",
"read_mcp_resource",
Expand All @@ -1357,6 +1382,7 @@ mod tests {
&[
"exec_command",
"write_stdin",
"terminate_session",
"list_mcp_resources",
"list_mcp_resource_templates",
"read_mcp_resource",
Expand Down Expand Up @@ -1442,6 +1468,7 @@ mod tests {
&[
"exec_command",
"write_stdin",
"terminate_session",
"list_mcp_resources",
"list_mcp_resource_templates",
"read_mcp_resource",
Expand All @@ -1462,6 +1489,7 @@ mod tests {
&[
"exec_command",
"write_stdin",
"terminate_session",
"list_mcp_resources",
"list_mcp_resource_templates",
"read_mcp_resource",
Expand All @@ -1486,7 +1514,12 @@ mod tests {
let (tools, _) = build_specs(&tools_config, Some(HashMap::new())).build();

// Only check the shell variant and a couple of core tools.
let mut subset = vec!["exec_command", "write_stdin", "update_plan"];
let mut subset = vec![
"exec_command",
"write_stdin",
"terminate_session",
"update_plan",
];
if let Some(shell_tool) = shell_tool_name(&tools_config) {
subset.push(shell_tool);
}
Expand All @@ -1509,6 +1542,7 @@ mod tests {

assert!(!find_tool(&tools, "exec_command").supports_parallel_tool_calls);
assert!(!find_tool(&tools, "write_stdin").supports_parallel_tool_calls);
assert!(!find_tool(&tools, "terminate_session").supports_parallel_tool_calls);
assert!(find_tool(&tools, "grep_files").supports_parallel_tool_calls);
assert!(find_tool(&tools, "list_dir").supports_parallel_tool_calls);
assert!(find_tool(&tools, "read_file").supports_parallel_tool_calls);
Expand Down
14 changes: 10 additions & 4 deletions codex-rs/core/src/unified_exec/session_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,15 @@ impl UnifiedExecSessionManager {
}
}

pub(crate) async fn release_process_id(&self, process_id: &str) {
pub(crate) async fn terminate_session(&self, process_id: &str) -> bool {
let mut store = self.session_store.lock().await;
store.remove(process_id);
if let Some(entry) = store.remove(process_id) {
entry.session.terminate();
true
} else {
// It might just be a reserved ID not yet in sessions
store.reserved_sessions_id.remove(process_id)
}
}

pub(crate) async fn exec_command(
Expand All @@ -134,7 +140,7 @@ impl UnifiedExecSessionManager {
let session = match session {
Ok(session) => Arc::new(session),
Err(err) => {
self.release_process_id(&request.process_id).await;
self.terminate_session(&request.process_id).await;
return Err(err);
}
};
Expand Down Expand Up @@ -189,7 +195,7 @@ impl UnifiedExecSessionManager {
)
.await;

self.release_process_id(&request.process_id).await;
self.terminate_session(&request.process_id).await;
session.check_for_sandbox_denial_with_text(&text).await?;
} else {
// Long‑lived command: persist the session so write_stdin can reuse
Expand Down
Loading