File size: 4,201 Bytes
52a9af3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | 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_http_client::HttpClientFactory;
use codex_login::CodexAuth;
use std::future::Future;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum RetryableFailureKind {
Request { status_code: Option<u16> },
}
impl RetryableFailureKind {
pub(crate) fn status_code(self) -> Option<u16> {
match self {
Self::Request { status_code } => status_code,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum BundleRequestError {
Retryable(RetryableFailureKind),
Unauthorized {
status_code: Option<u16>,
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<Output = Result<CloudConfigBundle, BundleRequestError>> + Send;
}
pub(crate) struct BackendBundleClient {
base_url: String,
http_client_factory: HttpClientFactory,
}
impl BackendBundleClient {
pub(crate) fn new(base_url: String, http_client_factory: HttpClientFactory) -> Self {
Self {
base_url,
http_client_factory,
}
}
}
impl BundleClient for BackendBundleClient {
async fn get_bundle(&self, auth: &CodexAuth) -> Result<CloudConfigBundle, BundleRequestError> {
let client = BackendClient::from_auth(
self.base_url.clone(),
auth,
self.http_client_factory.clone(),
);
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,
}
}
|