diff --git a/codex-rs/protocol/src/executor.rs b/codex-rs/protocol/src/executor.rs new file mode 100644 index 0000000000..ffb8c72de5 --- /dev/null +++ b/codex-rs/protocol/src/executor.rs @@ -0,0 +1,138 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +use crate::approvals::ExecPolicyAmendment; +use crate::models::PermissionProfile; +use crate::models::SandboxPermissions; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; + +/// Approval plan chosen by the orchestrator for a unified-exec command. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "camelCase", rename_all_fields = "camelCase")] +pub enum UnifiedExecApprovalRequirement { + Skip { + bypass_sandbox: bool, + proposed_exec_policy_amendment: Option, + }, + NeedsApproval { + reason: Option, + proposed_exec_policy_amendment: Option, + }, + Forbidden { + reason: String, + }, +} + +/// Fully resolved unified-exec startup request suitable for an executor wire. +/// +/// The executor, not the orchestrator, mints the long-lived session handle for +/// any interactive process that survives the initial poll. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct UnifiedExecExecCommandRequest { + pub command: Vec, + pub cwd: PathBuf, + pub env: HashMap, + pub tty: bool, + pub yield_time_ms: u64, + pub max_output_tokens: usize, + pub sandbox_permissions: SandboxPermissions, + pub additional_permissions: Option, + pub additional_permissions_preapproved: bool, + pub justification: Option, + pub exec_approval_requirement: UnifiedExecApprovalRequirement, +} + +/// Fully resolved unified-exec follow-up request suitable for an executor wire. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct UnifiedExecWriteStdinRequest { + pub process_id: i32, + pub input: String, + pub yield_time_ms: u64, + pub max_output_tokens: usize, +} + +#[cfg(test)] +mod tests { + use super::UnifiedExecApprovalRequirement; + use super::UnifiedExecExecCommandRequest; + use super::UnifiedExecWriteStdinRequest; + use crate::models::SandboxPermissions; + use pretty_assertions::assert_eq; + use serde_json::json; + use std::collections::HashMap; + use std::path::PathBuf; + + #[test] + fn unified_exec_exec_command_request_uses_camel_case() { + let request = UnifiedExecExecCommandRequest { + command: vec!["bash".to_string(), "-lc".to_string(), "pwd".to_string()], + cwd: PathBuf::from("/tmp/example"), + env: HashMap::from([("TERM".to_string(), "dumb".to_string())]), + tty: true, + yield_time_ms: 2_500, + max_output_tokens: 4_000, + sandbox_permissions: SandboxPermissions::UseDefault, + additional_permissions: None, + additional_permissions_preapproved: false, + justification: Some("Need to inspect the repo".to_string()), + exec_approval_requirement: UnifiedExecApprovalRequirement::NeedsApproval { + reason: Some("sandbox escalation requested".to_string()), + proposed_exec_policy_amendment: None, + }, + }; + + let value = serde_json::to_value(&request).expect("serialize request"); + assert_eq!( + value, + json!({ + "command": ["bash", "-lc", "pwd"], + "cwd": "/tmp/example", + "env": { + "TERM": "dumb", + }, + "tty": true, + "yieldTimeMs": 2_500, + "maxOutputTokens": 4_000, + "sandboxPermissions": "use_default", + "additionalPermissions": null, + "additionalPermissionsPreapproved": false, + "justification": "Need to inspect the repo", + "execApprovalRequirement": { + "needsApproval": { + "reason": "sandbox escalation requested", + "proposedExecPolicyAmendment": null, + } + } + }) + ); + } + + #[test] + fn unified_exec_write_stdin_request_round_trips() { + let value = json!({ + "processId": 7, + "input": "echo hello\n", + "yieldTimeMs": 5_000, + "maxOutputTokens": 1_500, + }); + + let request = serde_json::from_value::(value.clone()) + .expect("deserialize request"); + assert_eq!( + request, + UnifiedExecWriteStdinRequest { + process_id: 7, + input: "echo hello\n".to_string(), + yield_time_ms: 5_000, + max_output_tokens: 1_500, + } + ); + + let encoded = serde_json::to_value(&request).expect("serialize request"); + assert_eq!(encoded, value); + } +} diff --git a/codex-rs/protocol/src/lib.rs b/codex-rs/protocol/src/lib.rs index 56924cc50d..88cb868cc6 100644 --- a/codex-rs/protocol/src/lib.rs +++ b/codex-rs/protocol/src/lib.rs @@ -7,6 +7,7 @@ pub mod approvals; pub mod config_types; pub mod custom_prompts; pub mod dynamic_tools; +pub mod executor; pub mod items; pub mod mcp; pub mod memory_citation;