| use bex_types::plugin_info::PluginInfo; |
| use bex_types::{Capabilities, Manifest}; |
| use indexmap::IndexMap; |
| use std::sync::atomic::{AtomicU32, AtomicU64, AtomicU8, Ordering}; |
| use std::sync::Arc; |
| use wasmtime::component::Component; |
|
|
| |
| |
| pub struct PluginHealth { |
| consecutive_failures: AtomicU32, |
| last_failure_at: AtomicU64, |
| circuit_state: AtomicU8, |
| threshold: u32, |
| cooldown_ms: u64, |
| } |
|
|
| impl PluginHealth { |
| pub fn new(threshold: u32, cooldown_ms: u64) -> Self { |
| Self { |
| consecutive_failures: AtomicU32::new(0), |
| last_failure_at: AtomicU64::new(0), |
| circuit_state: AtomicU8::new(0), |
| threshold, |
| cooldown_ms, |
| } |
| } |
|
|
| pub fn record_success(&self) { |
| self.consecutive_failures.store(0, Ordering::Relaxed); |
| self.circuit_state.store(0, Ordering::Relaxed); |
| } |
|
|
| pub fn record_failure(&self) { |
| let count = self.consecutive_failures.fetch_add(1, Ordering::Relaxed) + 1; |
| self.last_failure_at.store(now_ms(), Ordering::Relaxed); |
| if count >= self.threshold { |
| self.circuit_state.store(1, Ordering::Relaxed); |
| } |
| } |
|
|
| pub fn is_available(&self) -> bool { |
| match self.circuit_state.load(Ordering::Relaxed) { |
| 0 => true, |
| 1 => { |
| if now_ms() - self.last_failure_at.load(Ordering::Relaxed) > self.cooldown_ms { |
| self.circuit_state.store(2, Ordering::Relaxed); |
| true |
| } else { |
| false |
| } |
| } |
| 2 => true, |
| _ => false, |
| } |
| } |
| } |
|
|
| fn now_ms() -> u64 { |
| std::time::SystemTime::now() |
| .duration_since(std::time::UNIX_EPOCH) |
| .unwrap_or_default() |
| .as_millis() as u64 |
| } |
|
|
| pub struct PluginRecord { |
| pub id: String, |
| pub manifest: Arc<Manifest>, |
| pub capabilities: Capabilities, |
| pub enabled: bool, |
| pub installed_at: u64, |
| pub component: Arc<Component>, |
| pub health: PluginHealth, |
| } |
|
|
| impl PluginRecord { |
| pub fn new(manifest: Manifest, component: Arc<Component>, threshold: u32, cooldown_ms: u64) -> Self { |
| let id = manifest.id.clone(); |
| let capabilities = manifest.capabilities(); |
| let installed_at = std::time::SystemTime::now() |
| .duration_since(std::time::UNIX_EPOCH) |
| .unwrap_or_default() |
| .as_millis() as u64; |
| Self { |
| id, |
| manifest: Arc::new(manifest), |
| capabilities, |
| enabled: true, |
| installed_at, |
| component, |
| health: PluginHealth::new(threshold, cooldown_ms), |
| } |
| } |
|
|
| pub fn to_plugin_info(&self) -> PluginInfo { |
| PluginInfo { |
| id: self.id.clone(), |
| name: self.manifest.name.clone(), |
| version: self.manifest.version.clone(), |
| capabilities: self.capabilities.bits(), |
| description: self.manifest.display.description.clone().unwrap_or_default(), |
| tags: self.manifest.display.tags.clone(), |
| priority: self.manifest.display.priority, |
| enabled: self.enabled, |
| installed_at: self.installed_at, |
| } |
| } |
|
|
| |
| pub fn matches_capability(&self, cap: Capabilities) -> bool { |
| self.enabled && self.capabilities.has(cap) |
| } |
| } |
|
|
| |
| pub struct PluginRegistry { |
| by_id: IndexMap<String, Arc<PluginRecord>>, |
| } |
|
|
| impl PluginRegistry { |
| pub fn new() -> Self { |
| Self { |
| by_id: IndexMap::new(), |
| } |
| } |
|
|
| pub fn insert(&mut self, record: Arc<PluginRecord>) { |
| self.by_id.insert(record.id.clone(), record); |
| } |
|
|
| pub fn remove(&mut self, id: &str) -> Option<Arc<PluginRecord>> { |
| self.by_id.shift_remove(id) |
| } |
|
|
| pub fn get(&self, id: &str) -> Option<&Arc<PluginRecord>> { |
| self.by_id.get(id) |
| } |
|
|
| |
| pub fn list(&self) -> Vec<PluginInfo> { |
| self.by_id.values().map(|r| r.to_plugin_info()).collect() |
| } |
|
|
| |
| pub fn list_sorted_by_priority(&self) -> Vec<Arc<PluginRecord>> { |
| let mut plugins: Vec<_> = self.by_id.values().cloned().collect(); |
| plugins.sort_by(|a, b| { |
| b.manifest.display.priority |
| .cmp(&a.manifest.display.priority) |
| .then_with(|| a.id.cmp(&b.id)) |
| }); |
| plugins |
| } |
|
|
| |
| pub fn list_with_cap(&self, cap: Capabilities) -> Vec<Arc<PluginRecord>> { |
| let mut plugins: Vec<_> = self |
| .by_id |
| .values() |
| .filter(|r| r.matches_capability(cap)) |
| .cloned() |
| .collect(); |
| plugins.sort_by(|a, b| { |
| b.manifest.display.priority |
| .cmp(&a.manifest.display.priority) |
| .then_with(|| a.id.cmp(&b.id)) |
| }); |
| plugins |
| } |
|
|
| |
| pub fn get_arc_mut(&mut self, id: &str) -> Option<&mut Arc<PluginRecord>> { |
| self.by_id.get_mut(id) |
| } |
| } |
|
|