use aion::Engine;
use super::document::{EmbeddedAssistant, embedded_assistant};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AssistantInstall {
Installed {
workflow_type: String,
content_hash: String,
},
AlreadyCurrent {
workflow_type: String,
content_hash: String,
},
Deferred {
workflow_type: String,
embedded_hash: String,
routed_hash: Option<String>,
},
Failed {
reason: String,
},
}
impl AssistantInstall {
#[must_use]
pub const fn outcome(&self) -> &'static str {
match self {
Self::Installed { .. } => "installed",
Self::AlreadyCurrent { .. } => "already_current",
Self::Deferred { .. } => "deferred",
Self::Failed { .. } => "failed",
}
}
}
pub async fn install_embedded_assistant(engine: &Engine) -> AssistantInstall {
let embedded = match embedded_assistant() {
Ok(embedded) => embedded,
Err(error) => {
let reason = error.to_string();
tracing::error!(
operation = "assistant.install",
outcome = "failed",
%reason,
"the embedded assistant document could not be prepared; this server has no \
built-in assistant"
);
return AssistantInstall::Failed { reason };
}
};
let outcome = install_verified(engine, embedded).await;
log_outcome(&outcome);
outcome
}
pub async fn install_embedded_assistant_for_server(state: &crate::ServerState) -> AssistantInstall {
match state.engine() {
Ok(engine) => install_embedded_assistant(engine.as_ref()).await,
Err(error) => {
let reason = format!("the engine is not available: {error}");
let outcome = AssistantInstall::Failed { reason };
log_outcome(&outcome);
outcome
}
}
}
async fn install_verified(engine: &Engine, embedded: &EmbeddedAssistant) -> AssistantInstall {
let workflow_type = embedded.workflow_type().to_owned();
let embedded_hash = embedded.content_hash().to_string();
let versions = match engine.list_workflow_versions() {
Ok(versions) => versions,
Err(error) => {
return AssistantInstall::Failed {
reason: format!("the engine catalog could not be read: {error}"),
};
}
};
let resident: Vec<_> = versions
.into_iter()
.filter(|version| version.workflow_type == workflow_type)
.collect();
if resident.is_empty() {
return match engine.load_package(embedded.package().clone()).await {
Ok(_) => AssistantInstall::Installed {
workflow_type,
content_hash: embedded_hash,
},
Err(error) => AssistantInstall::Failed {
reason: format!(
"the embedded assistant package `{embedded_hash}` did not load: {error}"
),
},
};
}
let routed_hash = resident
.iter()
.find(|version| version.route_active)
.map(|version| version.content_hash.to_string());
if routed_hash.as_deref() == Some(embedded_hash.as_str()) {
return AssistantInstall::AlreadyCurrent {
workflow_type,
content_hash: embedded_hash,
};
}
AssistantInstall::Deferred {
workflow_type,
embedded_hash,
routed_hash,
}
}
fn log_outcome(outcome: &AssistantInstall) {
match outcome {
AssistantInstall::Installed {
workflow_type,
content_hash,
} => tracing::info!(
operation = "assistant.install",
outcome = outcome.outcome(),
%workflow_type,
%content_hash,
"the built-in assistant was installed and routed on a catalog that held no version \
of it"
),
AssistantInstall::AlreadyCurrent {
workflow_type,
content_hash,
} => tracing::info!(
operation = "assistant.install",
outcome = outcome.outcome(),
%workflow_type,
%content_hash,
"the built-in assistant is already the routed version"
),
AssistantInstall::Deferred {
workflow_type,
embedded_hash,
routed_hash,
} => tracing::warn!(
operation = "assistant.install",
outcome = outcome.outcome(),
%workflow_type,
%embedded_hash,
routed_hash = routed_hash.as_deref().unwrap_or("none"),
"the embedded assistant document is not the routed version on this catalog, and \
routing was NOT changed — a restart must never move a route an operator chose. \
To cut over deliberately: `aion assistant document --output assistant.awl`, then \
`aion deploy assistant.awl` (which loads AND routes it), and only then restart \
the worker serving its queue"
),
AssistantInstall::Failed { reason } => tracing::error!(
operation = "assistant.install",
outcome = outcome.outcome(),
%reason,
"the built-in assistant was not installed"
),
}
}
#[cfg(test)]
#[path = "install_tests.rs"]
mod install_tests;