Files
codex/codex-rs/artifacts/src/runtime/manager.rs
T
2026-03-05 13:03:01 +00:00

254 lines
8.1 KiB
Rust

use super::ArtifactRuntimeError;
use super::ArtifactRuntimePlatform;
use super::InstalledArtifactRuntime;
use super::ReleaseManifest;
use codex_package_manager::ManagedPackage;
use codex_package_manager::PackageManager;
use codex_package_manager::PackageManagerConfig;
use codex_package_manager::PackageManagerError;
use codex_package_manager::PackageReleaseArchive;
use reqwest::Client;
use std::path::Path;
use std::path::PathBuf;
use url::Url;
/// Release tag prefix used for artifact runtime assets.
pub const DEFAULT_RELEASE_TAG_PREFIX: &str = "artifact-runtime-v";
/// Relative cache root for installed artifact runtimes under `codex_home`.
pub const DEFAULT_CACHE_ROOT_RELATIVE: &str = "packages/artifacts";
/// Base URL used by default when downloading runtime assets from GitHub releases.
pub const DEFAULT_RELEASE_BASE_URL: &str = "https://github.com/openai/codex/releases/download/";
/// Describes where a particular artifact runtime release can be downloaded from.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArtifactRuntimeReleaseLocator {
base_url: Url,
runtime_version: String,
release_tag_prefix: String,
}
impl ArtifactRuntimeReleaseLocator {
/// Creates a locator for a runtime version under a release base URL.
pub fn new(base_url: Url, runtime_version: impl Into<String>) -> Self {
Self {
base_url,
runtime_version: runtime_version.into(),
release_tag_prefix: DEFAULT_RELEASE_TAG_PREFIX.to_string(),
}
}
/// Overrides the release-tag prefix used when constructing asset names.
pub fn with_tag_prefix(mut self, release_tag_prefix: impl Into<String>) -> Self {
self.release_tag_prefix = release_tag_prefix.into();
self
}
/// Returns the release asset base URL.
pub fn base_url(&self) -> &Url {
&self.base_url
}
/// Returns the expected runtime version.
pub fn runtime_version(&self) -> &str {
&self.runtime_version
}
/// Returns the full release tag for the runtime version.
pub fn release_tag(&self) -> String {
format!("{}{}", self.release_tag_prefix, self.runtime_version)
}
/// Returns the expected manifest filename for the release.
pub fn manifest_file_name(&self) -> String {
format!("{}-manifest.json", self.release_tag())
}
/// Returns the manifest URL for this runtime release.
pub fn manifest_url(&self) -> Result<Url, PackageManagerError> {
self.base_url
.join(&format!(
"{}/{}",
self.release_tag(),
self.manifest_file_name()
))
.map_err(PackageManagerError::InvalidBaseUrl)
}
/// Returns the default GitHub-release locator for a runtime version.
pub fn default(runtime_version: impl Into<String>) -> Self {
Self::new(
match Url::parse(DEFAULT_RELEASE_BASE_URL) {
Ok(url) => url,
Err(error) => {
panic!("hard-coded artifact runtime release base URL must be valid: {error}")
}
},
runtime_version,
)
}
}
/// Configuration for resolving artifact runtimes under a Codex home directory.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArtifactRuntimeManagerConfig {
package_manager: PackageManagerConfig<ArtifactRuntimePackage>,
release: ArtifactRuntimeReleaseLocator,
}
impl ArtifactRuntimeManagerConfig {
/// Creates a runtime-manager config from a Codex home and explicit release locator.
pub fn new(codex_home: PathBuf, release: ArtifactRuntimeReleaseLocator) -> Self {
Self {
package_manager: PackageManagerConfig::new(
codex_home,
ArtifactRuntimePackage::new(release.clone()),
),
release,
}
}
/// Creates a runtime-manager config that downloads from the default GitHub release location.
pub fn with_default_release(codex_home: PathBuf, runtime_version: impl Into<String>) -> Self {
Self::new(
codex_home,
ArtifactRuntimeReleaseLocator::default(runtime_version),
)
}
/// Overrides the runtime cache root.
pub fn with_cache_root(mut self, cache_root: PathBuf) -> Self {
self.package_manager = self.package_manager.with_cache_root(cache_root);
self
}
/// Returns the runtime cache root.
pub fn cache_root(&self) -> PathBuf {
self.package_manager.cache_root()
}
/// Returns the release locator used by this config.
pub fn release(&self) -> &ArtifactRuntimeReleaseLocator {
&self.release
}
}
/// Package-manager-backed artifact runtime resolver and installer.
#[derive(Clone, Debug)]
pub struct ArtifactRuntimeManager {
package_manager: PackageManager<ArtifactRuntimePackage>,
config: ArtifactRuntimeManagerConfig,
}
impl ArtifactRuntimeManager {
/// Creates a runtime manager using the default `reqwest` client.
pub fn new(config: ArtifactRuntimeManagerConfig) -> Self {
let package_manager = PackageManager::new(config.package_manager.clone());
Self {
package_manager,
config,
}
}
/// Creates a runtime manager with a caller-provided HTTP client.
pub fn with_client(config: ArtifactRuntimeManagerConfig, client: Client) -> Self {
let package_manager = PackageManager::with_client(config.package_manager.clone(), client);
Self {
package_manager,
config,
}
}
/// Returns the manager configuration.
pub fn config(&self) -> &ArtifactRuntimeManagerConfig {
&self.config
}
/// Returns the installed runtime if it is already present and valid.
pub async fn resolve_cached(
&self,
) -> Result<Option<InstalledArtifactRuntime>, ArtifactRuntimeError> {
self.package_manager.resolve_cached().await
}
/// Returns the installed runtime, downloading and caching it if necessary.
pub async fn ensure_installed(&self) -> Result<InstalledArtifactRuntime, ArtifactRuntimeError> {
self.package_manager.ensure_installed().await
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct ArtifactRuntimePackage {
release: ArtifactRuntimeReleaseLocator,
}
impl ArtifactRuntimePackage {
fn new(release: ArtifactRuntimeReleaseLocator) -> Self {
Self { release }
}
}
impl ManagedPackage for ArtifactRuntimePackage {
type Error = ArtifactRuntimeError;
type Installed = InstalledArtifactRuntime;
type ReleaseManifest = ReleaseManifest;
fn default_cache_root_relative(&self) -> &str {
DEFAULT_CACHE_ROOT_RELATIVE
}
fn version(&self) -> &str {
self.release.runtime_version()
}
fn manifest_url(&self) -> Result<Url, PackageManagerError> {
self.release.manifest_url()
}
fn archive_url(&self, archive: &PackageReleaseArchive) -> Result<Url, PackageManagerError> {
self.release
.base_url()
.join(&format!(
"{}/{}",
self.release.release_tag(),
archive.archive
))
.map_err(PackageManagerError::InvalidBaseUrl)
}
fn release_version<'a>(&self, manifest: &'a Self::ReleaseManifest) -> &'a str {
&manifest.runtime_version
}
fn platform_archive(
&self,
manifest: &Self::ReleaseManifest,
platform: ArtifactRuntimePlatform,
) -> Result<PackageReleaseArchive, Self::Error> {
manifest
.platforms
.get(platform.as_str())
.cloned()
.ok_or_else(|| {
PackageManagerError::MissingPlatform(platform.as_str().to_string()).into()
})
}
fn install_dir(&self, cache_root: &Path, platform: ArtifactRuntimePlatform) -> PathBuf {
cache_root.join(self.version()).join(platform.as_str())
}
fn installed_version<'a>(&self, package: &'a Self::Installed) -> &'a str {
package.runtime_version()
}
fn load_installed(
&self,
root_dir: PathBuf,
platform: ArtifactRuntimePlatform,
) -> Result<Self::Installed, Self::Error> {
InstalledArtifactRuntime::load(root_dir, platform)
}
}