use codex_backend_client::Client as BackendClient; use codex_backend_client::ConfigBundleResponse; use codex_backend_client::DeliveredTomlFragment; use codex_config::CloudConfigBundle; use codex_config::CloudConfigFragment; use codex_config::CloudConfigTomlBundle; use codex_config::CloudRequirementsFragment; use codex_config::CloudRequirementsTomlBundle; use codex_login::CodexAuth; use std::future::Future; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum RetryableFailureKind { BackendClientInit, Request { status_code: Option }, } impl RetryableFailureKind { pub(crate) fn status_code(self) -> Option { match self { Self::BackendClientInit => None, Self::Request { status_code } => status_code, } } } #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum BundleRequestError { Retryable(RetryableFailureKind), Unauthorized { status_code: Option, message: String, }, } /// Retrieves one cloud config bundle from the backend. /// /// Implementations should return the backend-selected bundle exactly as delivered and leave /// validation, caching, and config/requirements parsing decisions to the service layer. pub(crate) trait BundleClient: Send + Sync { fn get_bundle( &self, auth: &CodexAuth, ) -> impl Future> + Send; } pub(crate) struct BackendBundleClient { base_url: String, } impl BackendBundleClient { pub(crate) fn new(base_url: String) -> Self { Self { base_url } } } impl BundleClient for BackendBundleClient { async fn get_bundle(&self, auth: &CodexAuth) -> Result { let client = BackendClient::from_auth(self.base_url.clone(), auth) .inspect_err(|err| { tracing::warn!( error = %err, "Failed to construct backend client for cloud config bundle" ); }) .map_err(|_| BundleRequestError::Retryable(RetryableFailureKind::BackendClientInit))?; let response = client .get_config_bundle() .await .inspect_err(|err| { tracing::warn!(error = %err, "Failed to fetch cloud config bundle"); }) .map_err(|err| { let status_code = err.status().map(|status| status.as_u16()); if err.is_unauthorized() { BundleRequestError::Unauthorized { status_code, message: err.to_string(), } } else { BundleRequestError::Retryable(RetryableFailureKind::Request { status_code }) } })?; Ok(bundle_from_response(response)) } } pub(crate) fn bundle_from_response(response: ConfigBundleResponse) -> CloudConfigBundle { let config_toml = response .config_toml .flatten() .map(|config_toml| *config_toml) .and_then(|config_toml| config_toml.enterprise_managed.flatten()) .unwrap_or_default() .into_iter() .map(config_fragment_from_delivered) .collect(); let requirements_toml = response .requirements_toml .flatten() .map(|requirements_toml| *requirements_toml) .and_then(|requirements_toml| requirements_toml.enterprise_managed.flatten()) .unwrap_or_default() .into_iter() .map(requirements_fragment_from_delivered) .collect(); CloudConfigBundle { config_toml: CloudConfigTomlBundle { enterprise_managed: config_toml, }, requirements_toml: CloudRequirementsTomlBundle { enterprise_managed: requirements_toml, }, } } fn config_fragment_from_delivered(fragment: DeliveredTomlFragment) -> CloudConfigFragment { CloudConfigFragment { id: fragment.id, name: fragment.name, contents: fragment.contents, } } fn requirements_fragment_from_delivered( fragment: DeliveredTomlFragment, ) -> CloudRequirementsFragment { CloudRequirementsFragment { id: fragment.id, name: fragment.name, contents: fragment.contents, } }