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) -> 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) -> 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 { 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) -> 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, 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) -> 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, 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, ArtifactRuntimeError> { self.package_manager.resolve_cached().await } /// Returns the installed runtime, downloading and caching it if necessary. pub async fn ensure_installed(&self) -> Result { 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 { self.release.manifest_url() } fn archive_url(&self, archive: &PackageReleaseArchive) -> Result { 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 { 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 { InstalledArtifactRuntime::load(root_dir, platform) } }