feat(app-server) install runtime

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Dylan Hurd
2026-05-12 00:52:52 -07:00
co-authored by Codex
parent 5fe33443b0
commit a77dc85abc
26 changed files with 2101 additions and 9 deletions
+3 -1
View File
@@ -26,6 +26,8 @@ futures = { workspace = true }
reqwest = { workspace = true, features = ["json", "rustls-tls", "stream"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
tokio = { workspace = true, features = [
@@ -43,6 +45,7 @@ tokio-util = { workspace = true, features = ["rt"] }
tokio-tungstenite = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true, features = ["v4"] }
zip = { workspace = true }
[dev-dependencies]
anyhow = { workspace = true }
@@ -50,6 +53,5 @@ codex-test-binary-support = { workspace = true }
ctor = { workspace = true }
pretty_assertions = { workspace = true }
serial_test = { workspace = true }
tempfile = { workspace = true }
test-case = "3.3.1"
wiremock = { workspace = true }
+10
View File
@@ -8,6 +8,8 @@ use std::time::Duration;
use arc_swap::ArcSwap;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::RuntimeInstallParams;
use codex_app_server_protocol::RuntimeInstallResponse;
use futures::FutureExt;
use futures::future::BoxFuture;
use serde_json::Value;
@@ -69,6 +71,7 @@ use crate::protocol::INITIALIZED_METHOD;
use crate::protocol::InitializeParams;
use crate::protocol::InitializeResponse;
use crate::protocol::ProcessOutputChunk;
use crate::protocol::RUNTIME_INSTALL_METHOD;
use crate::protocol::ReadParams;
use crate::protocol::ReadResponse;
use crate::protocol::TerminateParams;
@@ -397,6 +400,13 @@ impl ExecServerClient {
self.call(FS_COPY_METHOD, &params).await
}
pub async fn runtime_install(
&self,
params: RuntimeInstallParams,
) -> Result<RuntimeInstallResponse, ExecServerError> {
self.call(RUNTIME_INSTALL_METHOD, &params).await
}
pub(crate) async fn register_session(
&self,
process_id: &ProcessId,
+53 -1
View File
@@ -2,6 +2,10 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::RuntimeInstallParams;
use codex_app_server_protocol::RuntimeInstallResponse;
use crate::ExecServerError;
use crate::ExecServerRuntimePaths;
use crate::ExecutorFileSystem;
@@ -20,6 +24,7 @@ use crate::local_process::LocalProcess;
use crate::process::ExecBackend;
use crate::remote_file_system::RemoteFileSystem;
use crate::remote_process::RemoteProcess;
use crate::rpc::internal_error;
pub const CODEX_EXEC_SERVER_URL_ENV_VAR: &str = "CODEX_EXEC_SERVER_URL";
@@ -294,9 +299,45 @@ pub struct Environment {
exec_backend: Arc<dyn ExecBackend>,
filesystem: Arc<dyn ExecutorFileSystem>,
http_client: Arc<dyn HttpClient>,
runtime_installer: RuntimeInstaller,
local_runtime_paths: Option<ExecServerRuntimePaths>,
}
#[derive(Clone)]
enum RuntimeInstaller {
Local,
Remote(LazyRemoteExecServerClient),
}
impl RuntimeInstaller {
async fn install_runtime(
&self,
params: RuntimeInstallParams,
) -> Result<RuntimeInstallResponse, JSONRPCErrorError> {
match self {
RuntimeInstaller::Local => crate::runtime_install::install_runtime(params).await,
RuntimeInstaller::Remote(client) => {
let client = client.get().await.map_err(exec_server_error_to_jsonrpc)?;
client
.runtime_install(params)
.await
.map_err(exec_server_error_to_jsonrpc)
}
}
}
}
fn exec_server_error_to_jsonrpc(err: ExecServerError) -> JSONRPCErrorError {
match err {
ExecServerError::Server { code, message } => JSONRPCErrorError {
code,
data: None,
message,
},
_ => internal_error(err.to_string()),
}
}
impl Environment {
/// Builds a test-only local environment without configured sandbox helper paths.
pub fn default_for_tests() -> Self {
@@ -306,6 +347,7 @@ impl Environment {
exec_backend: Arc::new(LocalProcess::default()),
filesystem: Arc::new(LocalFileSystem::unsandboxed()),
http_client: Arc::new(ReqwestHttpClient),
runtime_installer: RuntimeInstaller::Local,
local_runtime_paths: None,
}
}
@@ -364,6 +406,7 @@ impl Environment {
local_runtime_paths.clone(),
)),
http_client: Arc::new(ReqwestHttpClient),
runtime_installer: RuntimeInstaller::Local,
local_runtime_paths: Some(local_runtime_paths),
}
}
@@ -393,13 +436,15 @@ impl Environment {
let exec_backend: Arc<dyn ExecBackend> = Arc::new(RemoteProcess::new(client.clone()));
let filesystem: Arc<dyn ExecutorFileSystem> =
Arc::new(RemoteFileSystem::new(client.clone()));
let http_client = client.clone();
Self {
exec_server_url,
remote_transport: Some(remote_transport),
exec_backend,
filesystem,
http_client: Arc::new(client),
http_client: Arc::new(http_client),
runtime_installer: RuntimeInstaller::Remote(client),
local_runtime_paths,
}
}
@@ -428,6 +473,13 @@ impl Environment {
pub fn get_filesystem(&self) -> Arc<dyn ExecutorFileSystem> {
Arc::clone(&self.filesystem)
}
pub async fn install_runtime(
&self,
params: RuntimeInstallParams,
) -> Result<RuntimeInstallResponse, JSONRPCErrorError> {
self.runtime_installer.install_runtime(params).await
}
}
#[cfg(test)]
+2
View File
@@ -17,6 +17,7 @@ mod remote;
mod remote_file_system;
mod remote_process;
mod rpc;
mod runtime_install;
mod runtime_paths;
mod sandboxed_file_system;
mod server;
@@ -82,6 +83,7 @@ pub use protocol::HttpRequestResponse;
pub use protocol::InitializeParams;
pub use protocol::InitializeResponse;
pub use protocol::ProcessOutputChunk;
pub use protocol::RUNTIME_INSTALL_METHOD;
pub use protocol::ReadParams;
pub use protocol::ReadResponse;
pub use protocol::TerminateParams;
+1
View File
@@ -30,6 +30,7 @@ pub const FS_COPY_METHOD: &str = "fs/copy";
pub const HTTP_REQUEST_METHOD: &str = "http/request";
/// JSON-RPC notification method for streamed executor HTTP response bodies.
pub const HTTP_REQUEST_BODY_DELTA_METHOD: &str = "http/request/bodyDelta";
pub const RUNTIME_INSTALL_METHOD: &str = "runtime/install";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
+6 -6
View File
@@ -80,7 +80,7 @@ impl RpcNotificationSender {
self.outgoing_tx
.send(RpcServerOutboundMessage::Response { request_id, result })
.await
.map_err(|_| internal_error("RPC connection closed while sending response".into()))
.map_err(|_| internal_error("RPC connection closed while sending response"))
}
pub(crate) async fn notify<P: Serialize>(
@@ -97,7 +97,7 @@ impl RpcNotificationSender {
},
))
.await
.map_err(|_| internal_error("RPC connection closed while sending notification".into()))
.map_err(|_| internal_error("RPC connection closed while sending notification"))
}
}
@@ -421,11 +421,11 @@ pub(crate) fn method_not_found(message: String) -> JSONRPCErrorError {
}
}
pub(crate) fn invalid_params(message: String) -> JSONRPCErrorError {
pub(crate) fn invalid_params(message: impl Into<String>) -> JSONRPCErrorError {
JSONRPCErrorError {
code: -32602,
data: None,
message,
message: message.into(),
}
}
@@ -437,11 +437,11 @@ pub(crate) fn not_found(message: String) -> JSONRPCErrorError {
}
}
pub(crate) fn internal_error(message: String) -> JSONRPCErrorError {
pub(crate) fn internal_error(message: impl Into<String>) -> JSONRPCErrorError {
JSONRPCErrorError {
code: -32603,
data: None,
message,
message: message.into(),
}
}
File diff suppressed because it is too large Load Diff
@@ -5,6 +5,8 @@ use std::sync::atomic::Ordering;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::RuntimeInstallParams;
use codex_app_server_protocol::RuntimeInstallResponse;
use serde_json::to_value;
use std::collections::HashSet;
use tokio::sync::Mutex;
@@ -264,6 +266,14 @@ impl ExecServerHandler {
self.file_system.copy(params).await
}
pub(crate) async fn runtime_install(
&self,
params: RuntimeInstallParams,
) -> Result<RuntimeInstallResponse, JSONRPCErrorError> {
self.require_initialized_for("runtime")?;
crate::runtime_install::install_runtime(params).await
}
fn require_initialized_for(
&self,
method_family: &str,
@@ -24,11 +24,13 @@ use crate::protocol::HttpRequestParams;
use crate::protocol::INITIALIZE_METHOD;
use crate::protocol::INITIALIZED_METHOD;
use crate::protocol::InitializeParams;
use crate::protocol::RUNTIME_INSTALL_METHOD;
use crate::protocol::ReadParams;
use crate::protocol::TerminateParams;
use crate::protocol::WriteParams;
use crate::rpc::RpcRouter;
use crate::server::ExecServerHandler;
use codex_app_server_protocol::RuntimeInstallParams;
pub(crate) fn build_router() -> RpcRouter<ExecServerHandler> {
let mut router = RpcRouter::new();
@@ -114,5 +116,11 @@ pub(crate) fn build_router() -> RpcRouter<ExecServerHandler> {
handler.fs_copy(params).await
},
);
router.request(
RUNTIME_INSTALL_METHOD,
|handler: Arc<ExecServerHandler>, params: RuntimeInstallParams| async move {
handler.runtime_install(params).await
},
);
router
}