Skip to main content

convergio_types/
extension.rs

1//! The Extension trait — the only way to exist in Convergio.
2//!
3//! Every module (core or plugin) implements this trait.
4//! No alternative, no workaround. If you don't implement Extension, you don't exist.
5
6use crate::events::{DomainEvent, EventFilter};
7use crate::manifest::Manifest;
8
9/// Result type used across all extensions.
10pub type ExtResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
11
12/// A database migration owned by a module.
13pub struct Migration {
14    /// Monotonically increasing version number for this module.
15    pub version: u32,
16    /// Human-readable description.
17    pub description: &'static str,
18    /// SQL to apply (forward only for now).
19    pub up: &'static str,
20}
21
22/// Health status reported by each extension.
23#[derive(Debug, Clone, serde::Serialize)]
24pub enum Health {
25    Ok,
26    Degraded { reason: String },
27    Down { reason: String },
28}
29
30/// A named metric emitted by an extension.
31#[derive(Debug, Clone, serde::Serialize)]
32pub struct Metric {
33    pub name: String,
34    pub value: f64,
35    pub labels: Vec<(String, String)>,
36}
37
38/// A scheduled task declared by an extension.
39pub struct ScheduledTask {
40    pub name: &'static str,
41    /// Cron expression (e.g. "0 2 * * *" for 2 AM daily).
42    pub cron: &'static str,
43}
44
45/// An MCP tool definition declared by an extension.
46///
47/// The MCP server discovers these at runtime via `/api/meta/mcp-tools`.
48/// When an extension adds routes, it also declares the corresponding
49/// MCP tools here — no manual registry maintenance needed.
50#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
51pub struct McpToolDef {
52    pub name: String,
53    pub description: String,
54    /// HTTP method: "GET", "POST", "PUT", "DELETE"
55    pub method: String,
56    /// URL path with `:param` placeholders (e.g. "/api/plan-db/json/:plan_id")
57    pub path: String,
58    /// JSON Schema for the tool's input parameters.
59    pub input_schema: serde_json::Value,
60    /// Minimum ring required: "sandboxed", "community", "trusted", "core"
61    pub min_ring: String,
62    /// Path parameter names to interpolate from input args.
63    #[serde(default)]
64    pub path_params: Vec<String>,
65}
66
67/// The one trait to rule them all.
68///
69/// Implement this to register your module with the Convergio daemon.
70/// The daemon calls these methods at startup and runtime.
71pub trait Extension: Send + Sync {
72    /// Identity and capabilities — who you are, what you do.
73    fn manifest(&self) -> Manifest;
74
75    /// Your database tables. The migration runner applies these at startup,
76    /// tracked in `_schema_registry`.
77    fn migrations(&self) -> Vec<Migration> {
78        vec![]
79    }
80
81    /// Your HTTP routes. The server mounts these under the appropriate prefix.
82    fn routes(&self, ctx: &AppContext) -> Option<axum::Router> {
83        let _ = ctx;
84        None
85    }
86
87    /// Called once after migrations and before routes are served.
88    fn on_start(&self, ctx: &AppContext) -> ExtResult<()> {
89        let _ = ctx;
90        Ok(())
91    }
92
93    /// Called on graceful shutdown.
94    fn on_shutdown(&self) -> ExtResult<()> {
95        Ok(())
96    }
97
98    /// Health check — aggregated into `/health/deep`.
99    fn health(&self) -> Health {
100        Health::Ok
101    }
102
103    /// Metrics — collected by the telemetry system.
104    fn metrics(&self) -> Vec<Metric> {
105        vec![]
106    }
107
108    /// Domain events this extension subscribes to.
109    fn subscriptions(&self) -> Vec<EventFilter> {
110        vec![]
111    }
112
113    /// Called when a subscribed domain event fires.
114    fn on_event(&self, event: &DomainEvent) {
115        let _ = event;
116    }
117
118    /// Periodic tasks (cron-like).
119    fn scheduled_tasks(&self) -> Vec<ScheduledTask> {
120        vec![]
121    }
122
123    /// Called when a scheduled task fires (cron match).
124    fn on_scheduled_task(&self, task_name: &str) {
125        let _ = task_name;
126    }
127
128    /// Called when configuration changes at runtime.
129    fn on_config_change(&self, key: &str, value: &serde_json::Value) -> ExtResult<()> {
130        let _ = (key, value);
131        Ok(())
132    }
133
134    /// MCP tool definitions exposed by this extension.
135    ///
136    /// The daemon aggregates these into `/api/meta/mcp-tools` and the MCP
137    /// server discovers them at startup. Add entries here whenever you add
138    /// HTTP routes that agents or Jarvis should be able to call.
139    fn mcp_tools(&self) -> Vec<McpToolDef> {
140        vec![]
141    }
142}
143
144/// Shared application context passed to extensions.
145///
146/// Type-erased resource map: the server fills it with concrete types
147/// (ConnPool, config, health registry, …) and extensions retrieve
148/// them by type via `get::<T>()`.
149#[derive(Default)]
150pub struct AppContext {
151    resources: std::collections::HashMap<
152        std::any::TypeId,
153        std::sync::Arc<dyn std::any::Any + Send + Sync>,
154    >,
155}
156
157impl AppContext {
158    pub fn new() -> Self {
159        Self::default()
160    }
161
162    /// Insert a resource. Overwrites any previous value of the same type.
163    pub fn insert<T: 'static + Send + Sync>(&mut self, val: T) {
164        self.resources
165            .insert(std::any::TypeId::of::<T>(), std::sync::Arc::new(val));
166    }
167
168    /// Retrieve a shared reference to a resource by type.
169    pub fn get<T: 'static + Send + Sync>(&self) -> Option<&T> {
170        self.resources
171            .get(&std::any::TypeId::of::<T>())
172            .and_then(|v| v.downcast_ref())
173    }
174
175    /// Retrieve an `Arc<T>` clone for a resource.
176    pub fn get_arc<T: 'static + Send + Sync>(&self) -> Option<std::sync::Arc<T>> {
177        self.resources
178            .get(&std::any::TypeId::of::<T>())
179            .and_then(|v| v.clone().downcast().ok())
180    }
181}