use std::time::Duration;
use super::opencrabs_install::{
INSTALL_PATH, InstallError, IssuedToken, OPENCRABS_BASE_URL, install_request_body,
new_install_id, parse_install_response,
};
use super::opencrabs_store::{self, StoredInstall};
use crate::config::Config;
const ATTEMPTS: usize = 2;
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 mut last = InstallError::ProxyFailed;
for _ in 0..ATTEMPTS {
let install_id = new_install_id();
match post_install(&client, &url, &install_id).await {
Ok(issued) => {
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
})?;
return Ok(install);
}
Err(InstallError::AlreadyIssued) => last = InstallError::AlreadyIssued,
Err(other) => return Err(other),
}
}
Err(last)
}
async fn post_install(
client: &reqwest::Client,
url: &str,
install_id: &str,
) -> Result<IssuedToken, InstallError> {
let response = client
.post(url)
.json(&install_request_body(install_id))
.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
}