use std::time::Duration;
use super::opencrabs_install::{
INSTALL_PATH, InstallError, IssuedToken, OPENCRABS_BASE_URL, install_request_body,
new_install_id, parse_install_response, reissue_request_body,
};
use super::opencrabs_store::{self, StoredInstall};
use crate::config::Config;
const TIMEOUT: Duration = Duration::from_secs(15);
pub async fn ensure_install() -> Result<StoredInstall, InstallError> {
ensure_install_at(OPENCRABS_BASE_URL).await
}
pub async fn ensure_install_at(base_url: &str) -> Result<StoredInstall, InstallError> {
if let Some(install) = opencrabs_store::load() {
return Ok(install);
}
let client = reqwest::Client::builder()
.timeout(TIMEOUT)
.build()
.map_err(|e| {
tracing::error!("Could not build the HTTP client for the built-in provider: {e}");
InstallError::Unreachable
})?;
let url = format!("{}{}", base_url.trim_end_matches('/'), INSTALL_PATH);
let install_id = match opencrabs_store::load_install_id() {
Some(id) => id,
None => mint_and_persist_id()?,
};
match post_install(&client, &url, &install_id, false).await {
Ok(issued) => return store_install(install_id, issued),
Err(InstallError::AlreadyIssued) => {}
Err(other) => return Err(other),
}
match post_install(&client, &url, &install_id, true).await {
Ok(issued) => return store_install(install_id, issued),
Err(InstallError::AlreadyIssued) => {}
Err(other) => return Err(other),
}
if !opencrabs_store::token_store_is_writable() {
tracing::error!(
"The built-in provider would not reissue a token for install {install_id}, and \
keys.toml cannot accept one, so no new identity is minted. Repair keys.toml and \
retry: the install ID is kept."
);
return Err(InstallError::StorageFailed);
}
let fresh = mint_and_persist_id()?;
tracing::warn!(
"The built-in provider would not reissue a token for install {install_id}; \
minting a new install identity {fresh}"
);
let issued = post_install(&client, &url, &fresh, false).await?;
store_install(fresh, issued)
}
fn mint_and_persist_id() -> Result<String, InstallError> {
let install_id = new_install_id();
opencrabs_store::write_install_id(&install_id).map_err(|e| {
tracing::error!("Could not write the install ID before requesting a token: {e}");
InstallError::StorageFailed
})?;
Ok(install_id)
}
fn store_install(install_id: String, issued: IssuedToken) -> Result<StoredInstall, InstallError> {
let install = StoredInstall {
install_id,
token: issued.token,
};
opencrabs_store::save(&install).map_err(|e| {
tracing::error!("Could not save the built-in provider token: {e}");
InstallError::StorageFailed
})?;
Ok(install)
}
async fn post_install(
client: &reqwest::Client,
url: &str,
install_id: &str,
reissue: bool,
) -> Result<IssuedToken, InstallError> {
let body = if reissue {
reissue_request_body(install_id)
} else {
install_request_body(install_id)
};
let response = client.post(url).json(&body).send().await.map_err(|e| {
tracing::warn!("Built-in provider install request failed: {e}");
InstallError::Unreachable
})?;
let status = response.status().as_u16();
let body = response.text().await.unwrap_or_default();
parse_install_response(status, &body)
}
pub fn needs_builtin_provision(config: &Config) -> bool {
let Some(section) = config.providers.opencrabs.as_ref() else {
return false;
};
if section.api_key.as_ref().is_some_and(|key| !key.is_empty()) {
return false;
}
let mut without_builtin = config.clone();
if let Some(section) = without_builtin.providers.opencrabs.as_mut() {
section.enabled = false;
}
let (provider, _) = without_builtin.providers.active_provider_and_model();
provider == "none"
}
pub fn apply_install(config: &mut Config, token: &str) {
if let Some(section) = config.providers.opencrabs.as_mut() {
section.enabled = true;
section.api_key = Some(token.to_string());
}
}
pub async fn provision_builtin_provider(config: &mut Config) -> bool {
if !needs_builtin_provision(config) {
return false;
}
tracing::info!("First run: requesting the built-in provider token");
if let Err(e) = Config::write_key("providers.opencrabs", "enabled", "true") {
tracing::warn!("Could not enable the built-in provider in config.toml: {e}");
}
let install = match ensure_install().await {
Ok(install) => install,
Err(e) => {
tracing::warn!("Built-in provider token request failed: {e:?}");
return false;
}
};
apply_install(config, &install.token);
crate::tui::onboarding::state::OnboardingState::mark_completed();
true
}