use std::fmt;
use std::io;
use std::path::PathBuf;
use crate::catalog::Catalog;
use crate::consent::Consent;
use crate::env::Environment;
use crate::install::{Action, Approval, Assessment, Installer, Outcome, Program, Step, Why};
use crate::plan::PlanError;
use crate::skill::Skill;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
NoHome,
Plan(PlanError),
Consent(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoHome => f.write_str("no home directory to look for AI tools in"),
Self::Plan(error) => error.fmt(f),
Self::Consent(error) => write!(f, "the answers about the skill: {error}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NoHome | Self::Plan(_) => None,
Self::Consent(error) => Some(error),
}
}
}
impl From<PlanError> for Error {
fn from(error: PlanError) -> Self {
Self::Plan(error)
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Self::Consent(error)
}
}
#[derive(Debug)]
pub struct AgentSkill {
catalog: Catalog,
env: Environment,
skill: Skill,
program: Program,
consent_file: PathBuf,
}
impl AgentSkill {
pub fn new(
skill: Skill,
program: Program,
consent_file: impl Into<PathBuf>,
) -> Result<Self, Error> {
Ok(Self {
catalog: Catalog::builtin(),
env: Environment::from_process().ok_or(Error::NoHome)?,
skill,
program,
consent_file: consent_file.into(),
})
}
#[must_use]
pub fn with_environment(mut self, env: Environment) -> Self {
self.env = env;
self
}
#[must_use]
pub fn with_catalog(mut self, catalog: Catalog) -> Self {
self.catalog = catalog;
self
}
#[must_use]
pub const fn catalog(&self) -> &Catalog {
&self.catalog
}
#[must_use]
pub const fn installer(&self) -> Installer<'_> {
Installer::new(&self.catalog, &self.env, &self.skill, &self.program)
}
pub fn status(&self) -> Result<Assessment, Error> {
Ok(self.installer().assess(&self.consent()?)?)
}
pub fn update(&self) -> Result<Vec<Outcome>, Error> {
Ok(self.installer().update()?)
}
pub fn offer(&self) -> Result<Option<Offer<'_>>, Error> {
let assessment = self.status()?;
let steps: Vec<Step> = assessment.awaiting_approval().cloned().collect();
Ok((!steps.is_empty()).then_some(Offer { agent: self, steps }))
}
pub fn install_everywhere(&self) -> Result<Vec<Outcome>, Error> {
let assessment = self.status()?;
let places: Vec<PathBuf> = assessment
.steps
.iter()
.filter(|step| {
matches!(
step.action,
Action::Install | Action::Nothing(Why::Declined)
)
})
.map(|step| step.place.clone())
.collect();
self.install(places)
}
pub fn remove(&self) -> Result<Vec<Outcome>, Error> {
Ok(self.installer().remove()?)
}
fn consent(&self) -> io::Result<Consent> {
Consent::load(&self.consent_file)
}
fn install(&self, places: Vec<PathBuf>) -> Result<Vec<Outcome>, Error> {
let mut consent = self.consent()?;
for place in &places {
consent.withdraw(place);
}
consent.save()?;
let assessment = self.installer().assess(&consent)?;
Ok(self
.installer()
.apply(&assessment, &Approval::install(places)))
}
}
#[derive(Debug)]
pub struct Offer<'a> {
agent: &'a AgentSkill,
steps: Vec<Step>,
}
impl Offer<'_> {
#[must_use]
pub fn steps(&self) -> &[Step] {
&self.steps
}
pub fn accept(self) -> Result<Vec<Outcome>, Error> {
self.agent
.install(self.steps.into_iter().map(|step| step.place).collect())
}
pub fn decline(self) -> Result<(), Error> {
let mut consent = self.agent.consent()?;
for step in &self.steps {
consent.decline(&step.place)?;
}
Ok(consent.save()?)
}
}