exec-server: define prepared upload protocol

This commit is contained in:
Soheil Norouzi
2026-06-22 13:53:32 +00:00
parent 8bcc5e4957
commit 5fc174076a
3 changed files with 211 additions and 0 deletions
+2
View File
@@ -22,6 +22,7 @@ use crate::environment_toml::environment_provider_from_codex_home;
use crate::local_file_system::LocalFileSystem;
use crate::local_process::LocalProcess;
use crate::process::ExecBackend;
use crate::protocol::EnvironmentCapabilities;
use crate::protocol::EnvironmentInfo;
use crate::protocol::ShellInfo;
use crate::remote::NoiseRendezvousEnvironmentConfig;
@@ -608,6 +609,7 @@ impl EnvironmentInfo {
pub(crate) fn local() -> Self {
Self {
shell: codex_shell_command::shell_detect::default_user_shell().into(),
capabilities: EnvironmentCapabilities::default(),
}
}
}
+18
View File
@@ -80,6 +80,7 @@ pub use process::ExecProcessFuture;
pub use process::StartedExecProcess;
pub use process_id::ProcessId;
pub use protocol::ByteChunk;
pub use protocol::EnvironmentCapabilities;
pub use protocol::EnvironmentInfo;
pub use protocol::ExecClosedNotification;
pub use protocol::ExecEnvPolicy;
@@ -88,6 +89,20 @@ pub use protocol::ExecOutputDeltaNotification;
pub use protocol::ExecOutputStream;
pub use protocol::ExecParams;
pub use protocol::ExecResponse;
pub use protocol::FileTransferCancelParams;
pub use protocol::FileTransferCancelResponse;
pub use protocol::FileTransferDigest;
pub use protocol::FileTransferDigestAlgorithm;
pub use protocol::FileTransferHeader;
pub use protocol::FileTransferOperationState;
pub use protocol::FileTransferPrepareUploadParams;
pub use protocol::FileTransferPrepareUploadResponse;
pub use protocol::FileTransferStartUploadParams;
pub use protocol::FileTransferStartUploadResponse;
pub use protocol::FileTransferStatusParams;
pub use protocol::FileTransferStatusResponse;
pub use protocol::FileTransferUploadDescriptor;
pub use protocol::FileTransferUploadDescriptorKind;
pub use protocol::FsCanonicalizeParams;
pub use protocol::FsCanonicalizeResponse;
pub use protocol::FsCloseParams;
@@ -117,6 +132,9 @@ pub use protocol::HttpRequestParams;
pub use protocol::HttpRequestResponse;
pub use protocol::InitializeParams;
pub use protocol::InitializeResponse;
pub use protocol::MAX_PREPARED_FILE_UPLOAD_BYTES;
pub use protocol::PREPARED_FILE_UPLOAD_PROTOCOL_VERSION;
pub use protocol::PreparedFileUploadCapability;
pub use protocol::ProcessOutputChunk;
pub use protocol::ProcessSignal;
pub use protocol::ReadParams;
+191
View File
@@ -71,6 +71,166 @@ pub struct InitializeResponse {
#[serde(rename_all = "camelCase")]
pub struct EnvironmentInfo {
pub shell: ShellInfo,
#[serde(default)]
pub capabilities: EnvironmentCapabilities,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EnvironmentCapabilities {
/// Executor-owned upload support. This primitive is intended for trusted
/// orchestration code that performs approval before preparing a source and
/// again after presenting the exact name, size, and digest to the user.
#[serde(default)]
pub prepared_file_upload: Option<PreparedFileUploadCapability>,
}
pub const PREPARED_FILE_UPLOAD_PROTOCOL_VERSION: u32 = 1;
pub const MAX_PREPARED_FILE_UPLOAD_BYTES: u64 = 8 * 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PreparedFileUploadCapability {
pub protocol_version: u32,
pub max_upload_bytes: u64,
pub descriptor_kinds: Vec<FileTransferUploadDescriptorKind>,
pub supports_status_reconciliation: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum FileTransferUploadDescriptorKind {
HttpsPut,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum FileTransferDigestAlgorithm {
Sha256,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferDigest {
pub algorithm: FileTransferDigestAlgorithm,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferPrepareUploadParams {
pub path: PathUri,
/// Authoritative permission context selected by the trusted caller. This
/// may intentionally be full access after the caller's read approval.
pub sandbox: FileSystemSandboxContext,
pub max_bytes: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferPrepareUploadResponse {
/// Opaque, session-bound identifier used for the entire lifecycle.
pub transfer_id: String,
pub name: String,
pub size: u64,
pub digest: FileTransferDigest,
/// The snapshot's upper-bound wall-clock expiry. The session may expire
/// sooner when its transport remains detached.
pub expires_at_unix_seconds: i64,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferHeader {
pub name: String,
pub value: String,
}
impl std::fmt::Debug for FileTransferHeader {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("FileTransferHeader")
.field("name", &self.name)
.field("value", &"[REDACTED]")
.finish()
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum FileTransferUploadDescriptor {
HttpsPut {
url: String,
#[serde(default)]
headers: Vec<FileTransferHeader>,
expires_at_unix_seconds: i64,
},
}
impl std::fmt::Debug for FileTransferUploadDescriptor {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::HttpsPut { headers, .. } => formatter
.debug_struct("HttpsPut")
.field("url", &"[REDACTED]")
.field("header_count", &headers.len())
.field("expires_at_unix_seconds", &"[REDACTED]")
.finish(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferStartUploadParams {
pub transfer_id: String,
pub descriptor: FileTransferUploadDescriptor,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferStartUploadResponse {
pub transfer_id: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum FileTransferOperationState {
Prepared,
Uploading,
CancelRequested,
Succeeded,
Failed,
Canceled,
CompletionUnknown,
Expired,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferStatusParams {
pub transfer_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferStatusResponse {
pub transfer_id: String,
pub state: FileTransferOperationState,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferCancelParams {
pub transfer_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileTransferCancelResponse {
pub state: FileTransferOperationState,
}
/// Shell detected for an execution/filesystem environment.
@@ -503,6 +663,10 @@ mod base64_bytes {
#[cfg(test)]
mod tests {
use super::EnvironmentCapabilities;
use super::EnvironmentInfo;
use super::FileTransferHeader;
use super::FileTransferUploadDescriptor;
use super::FsReadFileParams;
use super::HttpRequestParams;
use codex_file_system::FileSystemSandboxContext;
@@ -510,6 +674,33 @@ mod tests {
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
#[test]
fn environment_info_accepts_legacy_payload_without_capabilities() {
let info: EnvironmentInfo = serde_json::from_value(serde_json::json!({
"shell": {"name": "sh", "path": "/bin/sh"},
}))
.expect("legacy environment info should deserialize");
assert_eq!(info.capabilities, EnvironmentCapabilities::default());
}
#[test]
fn upload_descriptor_debug_redacts_secrets() {
let descriptor = FileTransferUploadDescriptor::HttpsPut {
url: "https://account.blob.core.windows.net/object?sig=secret".to_string(),
headers: vec![FileTransferHeader {
name: "x-ms-meta-token".to_string(),
value: "secret-header".to_string(),
}],
expires_at_unix_seconds: 123,
};
let debug = format!("{descriptor:?}");
assert!(!debug.contains("sig=secret"));
assert!(!debug.contains("secret-header"));
assert!(debug.contains("header_count"));
}
#[test]
fn filesystem_protocol_accepts_legacy_absolute_paths_and_serializes_path_uris() {
let legacy_path = std::env::current_dir()