use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::kernel::interrupt::{Interrupt, InterruptId};
pub type ResolveResult = Result<serde_json::Value, InterruptResolverError>;
#[async_trait]
pub trait InterruptResolver: Send + Sync {
async fn resolve(&self, interrupt: &Interrupt) -> ResolveResult;
}
#[derive(Debug, thiserror::Error)]
pub enum InterruptResolverError {
#[error("Resolver error: {0}")]
Resolver(String),
#[error("Interrupt not found: {0}")]
NotFound(InterruptId),
#[error("Resolution timeout")]
Timeout,
#[error("Resolution rejected")]
Rejected,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum InterruptSource {
Ui,
Agent,
PolicyEngine,
Api,
Custom(String),
}
impl InterruptSource {
pub fn as_str(&self) -> &str {
match self {
InterruptSource::Ui => "ui",
InterruptSource::Agent => "agent",
InterruptSource::PolicyEngine => "policy_engine",
InterruptSource::Api => "api",
InterruptSource::Custom(s) => s.as_str(),
}
}
}
pub type InterruptHandlerFn = Box<dyn Fn(&Interrupt) -> ResolveResult + Send + Sync>;
pub struct UnifiedInterruptResolver {
handlers: std::collections::HashMap<InterruptSource, InterruptHandlerFn>,
}
impl UnifiedInterruptResolver {
pub fn new() -> Self {
Self {
handlers: std::collections::HashMap::new(),
}
}
pub fn with_handler(mut self, source: InterruptSource, handler: InterruptHandlerFn) -> Self {
self.handlers.insert(source, handler);
self
}
pub fn resolve(&self, interrupt: &Interrupt) -> ResolveResult {
let source = extract_source(interrupt);
if let Some(handler) = self.handlers.get(&source) {
return handler(interrupt);
}
if let Some(handler) = self.handlers.get(&InterruptSource::Api) {
return handler(interrupt);
}
Err(InterruptResolverError::Resolver(format!(
"No handler for source: {}",
source.as_str()
)))
}
}
impl Default for UnifiedInterruptResolver {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl InterruptResolver for UnifiedInterruptResolver {
async fn resolve(&self, interrupt: &Interrupt) -> ResolveResult {
self.resolve(interrupt)
}
}
fn extract_source(interrupt: &Interrupt) -> InterruptSource {
InterruptSource::Api
}
#[cfg(test)]
pub fn create_test_resolver() -> UnifiedInterruptResolver {
let noop_handler = Box::new(|_: &Interrupt| Ok(serde_json::Value::Null));
UnifiedInterruptResolver::new()
.with_handler(InterruptSource::Ui, noop_handler.clone())
.with_handler(InterruptSource::Agent, noop_handler.clone())
.with_handler(InterruptSource::PolicyEngine, noop_handler.clone())
.with_handler(InterruptSource::Api, noop_handler)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::interrupt::InterruptKind;
#[test]
fn resolver_routes_by_source() {
let resolver = create_test_resolver();
let ui_interrupt = Interrupt::new(
"i1".into(),
"run-1".into(),
InterruptKind::HumanInTheLoop,
serde_json::json!({}),
);
let result = resolver.resolve(&ui_interrupt);
assert!(result.is_ok());
}
}