fix(runtime-install): address API review feedback

This commit is contained in:
acrognale-oai
2026-06-15 14:38:41 +00:00
parent 2ac7dc97f3
commit afa24ee86e
22 changed files with 53 additions and 536 deletions
+1
View File
@@ -2847,6 +2847,7 @@ dependencies = [
"codex-shell-command",
"codex-test-binary-support",
"codex-utils-absolute-path",
"codex-utils-home-dir",
"codex-utils-path-uri",
"codex-utils-pty",
"codex-utils-rustls-provider",
@@ -2982,27 +2982,6 @@
],
"type": "object"
},
"RuntimeInstallParams": {
"properties": {
"environmentId": {
"type": [
"string",
"null"
]
},
"manifest": {
"$ref": "#/definitions/RuntimeInstallManifestParams"
},
"release": {
"type": "string"
}
},
"required": [
"manifest",
"release"
],
"type": "object"
},
"SandboxMode": {
"enum": [
"read-only",
@@ -5684,53 +5663,6 @@
"title": "Plugin/installRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"runtime/install"
],
"title": "Runtime/installRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/RuntimeInstallParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Runtime/installRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"runtime/install/cancel"
],
"title": "Runtime/install/cancelRequestMethod",
"type": "string"
},
"params": {
"type": "null"
}
},
"required": [
"id",
"method"
],
"title": "Runtime/install/cancelRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -1357,53 +1357,6 @@
"title": "Plugin/installRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"runtime/install"
],
"title": "Runtime/installRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/RuntimeInstallParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Runtime/installRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"runtime/install/cancel"
],
"title": "Runtime/install/cancelRequestMethod",
"type": "string"
},
"params": {
"type": "null"
}
},
"required": [
"id",
"method"
],
"title": "Runtime/install/cancelRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -15167,19 +15120,6 @@
}
]
},
"RuntimeInstallCancelResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"status": {
"$ref": "#/definitions/v2/RuntimeInstallCancelStatus"
}
},
"required": [
"status"
],
"title": "RuntimeInstallCancelResponse",
"type": "object"
},
"RuntimeInstallCancelStatus": {
"enum": [
"canceled",
@@ -15242,29 +15182,6 @@
],
"type": "object"
},
"RuntimeInstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"environmentId": {
"type": [
"string",
"null"
]
},
"manifest": {
"$ref": "#/definitions/v2/RuntimeInstallManifestParams"
},
"release": {
"type": "string"
}
},
"required": [
"manifest",
"release"
],
"title": "RuntimeInstallParams",
"type": "object"
},
"RuntimeInstallPaths": {
"properties": {
"bundledPluginMarketplacePaths": {
@@ -15352,29 +15269,6 @@
],
"type": "string"
},
"RuntimeInstallResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"bundleVersion": {
"type": [
"string",
"null"
]
},
"paths": {
"$ref": "#/definitions/v2/RuntimeInstallPaths"
},
"status": {
"$ref": "#/definitions/v2/RuntimeInstallStatus"
}
},
"required": [
"paths",
"status"
],
"title": "RuntimeInstallResponse",
"type": "object"
},
"RuntimeInstallStatus": {
"enum": [
"already-current",
@@ -2341,53 +2341,6 @@
"title": "Plugin/installRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"runtime/install"
],
"title": "Runtime/installRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/RuntimeInstallParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Runtime/installRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"runtime/install/cancel"
],
"title": "Runtime/install/cancelRequestMethod",
"type": "string"
},
"params": {
"type": "null"
}
},
"required": [
"id",
"method"
],
"title": "Runtime/install/cancelRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -11620,19 +11573,6 @@
}
]
},
"RuntimeInstallCancelResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"status": {
"$ref": "#/definitions/RuntimeInstallCancelStatus"
}
},
"required": [
"status"
],
"title": "RuntimeInstallCancelResponse",
"type": "object"
},
"RuntimeInstallCancelStatus": {
"enum": [
"canceled",
@@ -11695,29 +11635,6 @@
],
"type": "object"
},
"RuntimeInstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"environmentId": {
"type": [
"string",
"null"
]
},
"manifest": {
"$ref": "#/definitions/RuntimeInstallManifestParams"
},
"release": {
"type": "string"
}
},
"required": [
"manifest",
"release"
],
"title": "RuntimeInstallParams",
"type": "object"
},
"RuntimeInstallPaths": {
"properties": {
"bundledPluginMarketplacePaths": {
@@ -11805,29 +11722,6 @@
],
"type": "string"
},
"RuntimeInstallResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"bundleVersion": {
"type": [
"string",
"null"
]
},
"paths": {
"$ref": "#/definitions/RuntimeInstallPaths"
},
"status": {
"$ref": "#/definitions/RuntimeInstallStatus"
}
},
"required": [
"paths",
"status"
],
"title": "RuntimeInstallResponse",
"type": "object"
},
"RuntimeInstallStatus": {
"enum": [
"already-current",
@@ -1,22 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"RuntimeInstallCancelStatus": {
"enum": [
"canceled",
"not-found"
],
"type": "string"
}
},
"properties": {
"status": {
"$ref": "#/definitions/RuntimeInstallCancelStatus"
}
},
"required": [
"status"
],
"title": "RuntimeInstallCancelResponse",
"type": "object"
}
@@ -1,80 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"RuntimeInstallManifestParams": {
"properties": {
"archiveName": {
"type": [
"string",
"null"
]
},
"archiveSha256": {
"type": "string"
},
"archiveSizeBytes": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"archiveUrl": {
"type": "string"
},
"bundleFormatVersion": {
"format": "uint32",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"bundleVersion": {
"type": [
"string",
"null"
]
},
"format": {
"type": [
"string",
"null"
]
},
"runtimeRootDirectoryName": {
"type": [
"string",
"null"
]
}
},
"required": [
"archiveSha256",
"archiveUrl"
],
"type": "object"
}
},
"properties": {
"environmentId": {
"type": [
"string",
"null"
]
},
"manifest": {
"$ref": "#/definitions/RuntimeInstallManifestParams"
},
"release": {
"type": "string"
}
},
"required": [
"manifest",
"release"
],
"title": "RuntimeInstallParams",
"type": "object"
}
@@ -1,76 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"RuntimeInstallPaths": {
"properties": {
"bundledPluginMarketplacePaths": {
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"bundledSkillPaths": {
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"nodeModulesPath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"nodePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"pythonPath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"skillsToRemove": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"bundledPluginMarketplacePaths",
"bundledSkillPaths",
"nodeModulesPath",
"nodePath",
"pythonPath",
"skillsToRemove"
],
"type": "object"
},
"RuntimeInstallStatus": {
"enum": [
"already-current",
"installed"
],
"type": "string"
}
},
"properties": {
"bundleVersion": {
"type": [
"string",
"null"
]
},
"paths": {
"$ref": "#/definitions/RuntimeInstallPaths"
},
"status": {
"$ref": "#/definitions/RuntimeInstallStatus"
}
},
"required": [
"paths",
"status"
],
"title": "RuntimeInstallResponse",
"type": "object"
}
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RuntimeInstallCancelStatus } from "./RuntimeInstallCancelStatus";
export type RuntimeInstallCancelResponse = { status: RuntimeInstallCancelStatus, };
@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RuntimeInstallManifestParams } from "./RuntimeInstallManifestParams";
export type RuntimeInstallParams = { environmentId?: string | null, manifest: RuntimeInstallManifestParams, release: string, };
@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RuntimeInstallPaths } from "./RuntimeInstallPaths";
import type { RuntimeInstallStatus } from "./RuntimeInstallStatus";
export type RuntimeInstallResponse = { bundleVersion: string | null, paths: RuntimeInstallPaths, status: RuntimeInstallStatus, };
@@ -334,14 +334,11 @@ export type { ReviewDelivery } from "./ReviewDelivery";
export type { ReviewStartParams } from "./ReviewStartParams";
export type { ReviewStartResponse } from "./ReviewStartResponse";
export type { ReviewTarget } from "./ReviewTarget";
export type { RuntimeInstallCancelResponse } from "./RuntimeInstallCancelResponse";
export type { RuntimeInstallCancelStatus } from "./RuntimeInstallCancelStatus";
export type { RuntimeInstallManifestParams } from "./RuntimeInstallManifestParams";
export type { RuntimeInstallParams } from "./RuntimeInstallParams";
export type { RuntimeInstallPaths } from "./RuntimeInstallPaths";
export type { RuntimeInstallProgressNotification } from "./RuntimeInstallProgressNotification";
export type { RuntimeInstallProgressPhase } from "./RuntimeInstallProgressPhase";
export type { RuntimeInstallResponse } from "./RuntimeInstallResponse";
export type { RuntimeInstallStatus } from "./RuntimeInstallStatus";
export type { SandboxMode } from "./SandboxMode";
export type { SandboxPolicy } from "./SandboxPolicy";
@@ -791,11 +791,13 @@ client_request_definitions! {
serialization: global("config"),
response: v2::PluginInstallResponse,
},
#[experimental("runtime/install")]
RuntimeInstall => "runtime/install" {
params: v2::RuntimeInstallParams,
serialization: global("runtime-install"),
response: v2::RuntimeInstallResponse,
},
#[experimental("runtime/install/cancel")]
RuntimeInstallCancel => "runtime/install/cancel" {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
serialization: None,
+2 -2
View File
@@ -220,9 +220,9 @@ Example with notification opt-out:
- `remoteControl/status/changed` — notification emitted when the remote-control status or client-visible environment id changes. `status` is one of `disabled`, `connecting`, `connected`, or `errored`; `serverName` is the local machine name used by this app-server process; `environmentId` is a string when the app-server has a current enrollment and `null` when that enrollment is cleared, invalidated, or remote control is disabled. Newly initialized app-server clients always receive the current status snapshot.
- `skills/config/write` — write user-level skill config by name or absolute path.
- `plugin/install` — install a plugin from a discovered marketplace entry, rejecting marketplace entries marked unavailable for install, install MCPs if any, and return the effective plugin auth policy plus any apps that still need auth (**under development; do not call from production clients yet**).
- `runtime/install` — install the selected primary runtime bundle and return its executable, dependency, bundled-skill, and bundled-marketplace paths after app-server config synchronization. App-server coordinates the install through the selected environment's generic process and filesystem operations; runtime installs are process-wide and serialized.
- `runtime/install` — experimental; install the selected primary runtime bundle and return its executable, dependency, bundled-skill, and bundled-marketplace paths after app-server config synchronization. App-server coordinates the install through the selected environment's generic process and filesystem operations; runtime installs are process-wide and serialized.
- `runtime/install/progress` — notification sent to the connection that requested `runtime/install` as the active install moves through checking, downloading, verifying, extracting, validating, installed, and configuring phases. Download notifications include byte counts when available.
- `runtime/install/cancel` — cancel the one active `runtime/install` request for this app-server process and report whether an install was found.
- `runtime/install/cancel` — experimental; cancel the one active `runtime/install` request for this app-server process and report whether an install was found.
- `plugin/uninstall` — uninstall a local plugin by `pluginId` in `<plugin>@<marketplace>` form by removing its cached files and clearing its user-level config entry, or uninstall a remote ChatGPT plugin by backend `pluginId` by forwarding the uninstall to the ChatGPT plugin backend and removing any downloaded remote-plugin cache (**under development; do not call from production clients yet**).
- `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.
- `tool/requestUserInput` — prompt the user with 1–3 short questions for a tool call and return their answers (experimental).
+16 -1
View File
@@ -11,6 +11,7 @@ use codex_app_server_protocol::RuntimeInstallResponse;
use codex_exec_server::CopyOptions;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::Environment;
use codex_exec_server::ExecServerError;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::RemoveOptions;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -33,12 +34,26 @@ pub(crate) async fn finalize_runtime_install(
return Ok(response);
}
let codex_home = environment.codex_home().await?;
let codex_home = environment
.codex_home()
.await
.map_err(exec_server_error_to_jsonrpc)?;
response.paths =
finalize_runtime_paths(environment.get_filesystem(), &codex_home, response.paths).await?;
Ok(response)
}
fn exec_server_error_to_jsonrpc(err: ExecServerError) -> JSONRPCErrorError {
match err {
ExecServerError::Server { code, message } => JSONRPCErrorError {
code,
message,
data: None,
},
_ => internal_error(err.to_string()),
}
}
async fn finalize_runtime_paths(
fs: Arc<dyn ExecutorFileSystem>,
codex_home: &AbsolutePathBuf,
+1
View File
@@ -23,6 +23,7 @@ codex-protocol = { workspace = true }
codex-sandboxing = { workspace = true }
codex-shell-command = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-home-dir = { workspace = true }
codex-utils-path-uri = { workspace = true }
codex-utils-pty = { workspace = true }
codex-utils-rustls-provider = { workspace = true }
-26
View File
@@ -1,26 +0,0 @@
use std::path::PathBuf;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_utils_absolute_path::AbsolutePathBuf;
use crate::rpc::internal_error;
pub(crate) fn default_codex_home() -> Result<AbsolutePathBuf, JSONRPCErrorError> {
default_codex_home_path()
.and_then(|path| {
AbsolutePathBuf::from_absolute_path_checked(path)
.map_err(|err| format!("runtime codex home is not absolute: {err}"))
})
.map_err(internal_error)
}
pub(crate) fn default_codex_home_path() -> Result<PathBuf, String> {
if let Some(codex_home) = std::env::var_os("CODEX_HOME") {
return Ok(PathBuf::from(codex_home));
}
let home = std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
.ok_or_else(|| "failed to locate home directory".to_string())?;
Ok(home.join(".codex"))
}
+10 -22
View File
@@ -2,8 +2,8 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_home_dir::find_codex_home;
use futures::FutureExt;
use futures::future::BoxFuture;
@@ -14,7 +14,6 @@ use crate::HttpClient;
use crate::client::LazyRemoteExecServerClient;
use crate::client::http_client::ReqwestHttpClient;
use crate::client_api::ExecServerTransportParams;
use crate::codex_home::default_codex_home_path;
use crate::environment_provider::DefaultEnvironmentProvider;
use crate::environment_provider::EnvironmentDefault;
use crate::environment_provider::EnvironmentProvider;
@@ -28,7 +27,6 @@ use crate::protocol::EnvironmentInfo;
use crate::protocol::ShellInfo;
use crate::remote_file_system::RemoteFileSystem;
use crate::remote_process::RemoteProcess;
use crate::rpc::internal_error;
use codex_shell_command::shell_detect::DetectedShell;
pub const CODEX_EXEC_SERVER_URL_ENV_VAR: &str = "CODEX_EXEC_SERVER_URL";
@@ -354,16 +352,6 @@ impl EnvironmentInfoProvider for RemoteEnvironmentInfoProvider {
async move { self.client.environment_info().await }.boxed()
}
}
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.
@@ -519,24 +507,24 @@ impl Environment {
Arc::clone(&self.filesystem)
}
pub async fn codex_home(&self) -> Result<AbsolutePathBuf, JSONRPCErrorError> {
pub async fn codex_home(&self) -> Result<AbsolutePathBuf, ExecServerError> {
if let Some(codex_home) = self.codex_home.clone() {
return Ok(codex_home);
}
let client = self.remote_client.as_ref().ok_or_else(|| {
internal_error("failed to locate local codex home for runtime install")
ExecServerError::Protocol(
"failed to locate local codex home for runtime install".to_string(),
)
})?;
let client = client.get().await.map_err(exec_server_error_to_jsonrpc)?;
client
.codex_home()
.ok_or_else(|| internal_error("remote exec-server did not report a codex home"))
let client = client.get().await?;
client.codex_home().ok_or_else(|| {
ExecServerError::Protocol("remote exec-server did not report a codex home".to_string())
})
}
}
fn default_local_codex_home() -> Option<AbsolutePathBuf> {
default_codex_home_path()
.ok()
.and_then(|path| AbsolutePathBuf::from_absolute_path_checked(path).ok())
find_codex_home().ok()
}
impl EnvironmentInfo {
-1
View File
@@ -1,7 +1,6 @@
mod client;
mod client_api;
mod client_transport;
mod codex_home;
mod connection;
mod environment;
mod environment_provider;
+3 -1
View File
@@ -4,6 +4,7 @@ use std::sync::atomic::Ordering;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::RequestId;
use codex_utils_home_dir::find_codex_home;
use serde_json::to_value;
use std::collections::HashSet;
use tokio::sync::Mutex;
@@ -129,7 +130,8 @@ impl ExecServerHandler {
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(session);
Ok(InitializeResponse {
session_id,
codex_home: crate::codex_home::default_codex_home()?,
codex_home: find_codex_home()
.map_err(|err| internal_error(format!("failed to locate Codex home: {err}")))?,
})
}
+12
View File
@@ -1,4 +1,5 @@
use codex_app_server_protocol::JSONRPCErrorError;
use codex_exec_server::ExecServerError;
const INVALID_PARAMS_ERROR_CODE: i64 = -32602;
const INTERNAL_ERROR_CODE: i64 = -32603;
@@ -11,6 +12,17 @@ pub(crate) fn internal_error(message: impl Into<String>) -> JSONRPCErrorError {
error(INTERNAL_ERROR_CODE, message)
}
pub(crate) fn exec_server_error_to_jsonrpc(err: ExecServerError) -> JSONRPCErrorError {
match err {
ExecServerError::Server { code, message } => JSONRPCErrorError {
code,
message,
data: None,
},
_ => internal_error(err.to_string()),
}
}
fn error(code: i64, message: impl Into<String>) -> JSONRPCErrorError {
JSONRPCErrorError {
code,
+5 -1
View File
@@ -17,6 +17,7 @@ use codex_utils_path_uri::PathUri;
use tokio_util::sync::CancellationToken;
use uuid::Uuid;
use crate::errors::exec_server_error_to_jsonrpc;
use crate::errors::internal_error;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -55,7 +56,10 @@ pub(crate) struct RuntimeExecutor {
impl RuntimeExecutor {
pub(crate) async fn new(environment: &Environment) -> Result<Self, JSONRPCErrorError> {
let codex_home = environment.codex_home().await?;
let codex_home = environment
.codex_home()
.await
.map_err(exec_server_error_to_jsonrpc)?;
Ok(Self {
backend: environment.get_exec_backend(),
filesystem: environment.get_filesystem(),