Skip to main content

oris_kernel/kernel/
interrupt_resolver.rs

1//! Unified interrupt routing layer: single resolver for all external interrupt sources.
2//!
3//! This module provides [InterruptResolver] trait and implementations for handling
4//! interrupts from various sources: UI, agents, policy engines, and APIs.
5
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8
9use crate::kernel::interrupt::{Interrupt, InterruptId};
10
11/// Result of resolving an interrupt.
12pub type ResolveResult = Result<serde_json::Value, InterruptResolverError>;
13
14/// Unified interrupt resolver: handles interrupts from all external sources.
15#[async_trait]
16pub trait InterruptResolver: Send + Sync {
17    /// Resolves an interrupt, returning the value to resume with.
18    async fn resolve(&self, interrupt: &Interrupt) -> ResolveResult;
19}
20
21/// Errors for interrupt resolution.
22#[derive(Debug, thiserror::Error)]
23pub enum InterruptResolverError {
24    #[error("Resolver error: {0}")]
25    Resolver(String),
26    #[error("Interrupt not found: {0}")]
27    NotFound(InterruptId),
28    #[error("Resolution timeout")]
29    Timeout,
30    #[error("Resolution rejected")]
31    Rejected,
32}
33
34/// Source of an interrupt request.
35#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
36pub enum InterruptSource {
37    /// Human input via UI.
38    Ui,
39    /// Agent-generated interrupt.
40    Agent,
41    /// Policy engine decision.
42    PolicyEngine,
43    /// API request.
44    Api,
45    /// Custom source.
46    Custom(String),
47}
48
49impl InterruptSource {
50    pub fn as_str(&self) -> &str {
51        match self {
52            InterruptSource::Ui => "ui",
53            InterruptSource::Agent => "agent",
54            InterruptSource::PolicyEngine => "policy_engine",
55            InterruptSource::Api => "api",
56            InterruptSource::Custom(s) => s.as_str(),
57        }
58    }
59}
60
61/// Handler function type for resolving interrupts from a specific source.
62pub type InterruptHandlerFn = Box<dyn Fn(&Interrupt) -> ResolveResult + Send + Sync>;
63
64/// Unified interrupt resolver that routes to source-specific handlers.
65pub struct UnifiedInterruptResolver {
66    handlers: std::collections::HashMap<InterruptSource, InterruptHandlerFn>,
67}
68
69impl UnifiedInterruptResolver {
70    /// Creates a new resolver with no handlers.
71    pub fn new() -> Self {
72        Self {
73            handlers: std::collections::HashMap::new(),
74        }
75    }
76
77    /// Registers a handler for a specific source.
78    pub fn with_handler(mut self, source: InterruptSource, handler: InterruptHandlerFn) -> Self {
79        self.handlers.insert(source, handler);
80        self
81    }
82
83    /// Resolves an interrupt by routing to the appropriate handler.
84    pub fn resolve(&self, interrupt: &Interrupt) -> ResolveResult {
85        let source = extract_source(interrupt);
86
87        if let Some(handler) = self.handlers.get(&source) {
88            return handler(interrupt);
89        }
90
91        // Fallback to API handler if registered
92        if let Some(handler) = self.handlers.get(&InterruptSource::Api) {
93            return handler(interrupt);
94        }
95
96        Err(InterruptResolverError::Resolver(format!(
97            "No handler for source: {}",
98            source.as_str()
99        )))
100    }
101}
102
103impl Default for UnifiedInterruptResolver {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109#[async_trait]
110impl InterruptResolver for UnifiedInterruptResolver {
111    async fn resolve(&self, interrupt: &Interrupt) -> ResolveResult {
112        self.resolve(interrupt)
113    }
114}
115
116/// Extract source from interrupt (fallback to Api).
117fn extract_source(_interrupt: &Interrupt) -> InterruptSource {
118    // Check if there's metadata indicating the source
119    // For now, default to API
120    InterruptSource::Api
121}
122
123/// Creates a default unified resolver with no-op handlers (for testing).
124#[cfg(test)]
125pub fn create_test_resolver() -> UnifiedInterruptResolver {
126    let noop_handler = Box::new(|_: &Interrupt| Ok(serde_json::Value::Null));
127
128    UnifiedInterruptResolver::new()
129        .with_handler(InterruptSource::Ui, noop_handler.clone())
130        .with_handler(InterruptSource::Agent, noop_handler.clone())
131        .with_handler(InterruptSource::PolicyEngine, noop_handler.clone())
132        .with_handler(InterruptSource::Api, noop_handler)
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use crate::kernel::interrupt::InterruptKind;
139
140    #[test]
141    fn resolver_routes_by_source() {
142        let resolver = create_test_resolver();
143
144        let ui_interrupt = Interrupt::new(
145            "i1".into(),
146            "run-1".into(),
147            InterruptKind::HumanInTheLoop,
148            serde_json::json!({}),
149        );
150
151        let result = resolver.resolve(&ui_interrupt);
152        assert!(result.is_ok());
153    }
154}