use std::{
path::{Path, PathBuf},
sync::Arc,
};
use mentra::{
error::RuntimeError,
runtime::{
AfterDecision, BeforeDecision, ExecutionHookParticipant, PostExecutionContext,
PreExecutionContext,
},
tool::ToolAudience,
};
use crate::{
error::RunError,
hooks::{HookRunner, Interceptor},
};
use super::Runtime;
pub(crate) struct HostInterceptors {
interceptors: Vec<Arc<dyn Interceptor>>,
}
impl HostInterceptors {
pub(crate) fn new(interceptors: Vec<Arc<dyn Interceptor>>) -> Option<Self> {
if interceptors.is_empty() {
return None;
}
Some(Self { interceptors })
}
fn runner(&self, workspace: &Path) -> HookRunner {
self.interceptors.iter().cloned().fold(
HookRunner::new(workspace, Vec::new()),
HookRunner::with_interceptor,
)
}
}
#[async_trait::async_trait]
impl ExecutionHookParticipant for HostInterceptors {
fn name(&self) -> &str {
"basis host interceptors"
}
async fn before(&self, context: &PreExecutionContext) -> Result<BeforeDecision, RuntimeError> {
self.runner(&context.working_directory)
.before(context)
.await
}
async fn after(&self, context: &PostExecutionContext) -> Result<AfterDecision, RuntimeError> {
self.runner(&context.working_directory).after(context).await
}
}
#[derive(Debug)]
pub(super) struct HookChainClaim {
root: PathBuf,
holders: usize,
hooks: Vec<crate::hooks::HookSpec>,
#[allow(dead_code, reason = "held for its Drop")]
registration: mentra::runtime::ExecutionHookRegistration,
}
pub(crate) struct HookChainHold {
runtime: Arc<Runtime>,
audience: String,
}
impl std::fmt::Debug for HookChainHold {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HookChainHold")
.field("audience", &self.audience)
.finish_non_exhaustive()
}
}
impl Drop for HookChainHold {
fn drop(&mut self) {
self.runtime.release_hook_chain(&self.audience);
}
}
impl Runtime {
pub(crate) fn register_hook_chain(
self: &Arc<Self>,
audience: &ToolAudience,
root: &Path,
runner: crate::hooks::HookRunner,
) -> Result<HookChainHold, RunError> {
let key = audience.as_str().to_string();
let mut chains = self.hook_chains.lock().expect("hook chain map poisoned");
match chains.get_mut(&key) {
Some(claim) if claim.hooks != *runner.hooks() => {
return Err(RunError::WorkspaceGuardConflict {
root: claim.root.clone(),
});
}
Some(claim) => claim.holders += 1,
None => {
let hooks = runner.hooks().to_vec();
let registration = self
.mentra
.register_execution_hook_for_audience(audience.clone(), runner);
chains.insert(
key.clone(),
HookChainClaim {
root: root.to_path_buf(),
holders: 1,
hooks,
registration,
},
);
}
}
drop(chains);
Ok(HookChainHold {
runtime: Arc::clone(self),
audience: key,
})
}
fn release_hook_chain(&self, audience: &str) {
let mut chains = self.hook_chains.lock().expect("hook chain map poisoned");
let Some(claim) = chains.get_mut(audience) else {
return;
};
claim.holders = claim.holders.saturating_sub(1);
if claim.holders == 0 {
chains.remove(audience);
}
}
}