Skip to main content

synapto_interface/
plugin.rs

1use crate::audio_recorder::AudioRecorderPlugin;
2use crate::call::CallPlugin;
3use crate::camera::CameraPlugin;
4use crate::chat::ChatPlugin;
5use crate::cognitive_output_audio::AudioOutputPlugin;
6use crate::document::DocumentsPlugin;
7use crate::gui::GuiPlugin;
8use crate::interaction::{InteractionObserver, RetrospectiveConsolidationPlugin};
9use crate::peer_input_audio::AudioInputPlugin;
10use crate::rollout::RolloutController;
11use crate::speech_to_text::{DiarizationPlugin, STTPlugin, TTSPlugin};
12use async_trait::async_trait;
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16#[derive(Clone)]
17pub struct PluginContext {
18    llm_executor: std::sync::Arc<dyn crate::llm::LlmExecutor>,
19    plugin_config: serde_json::Value,
20    storage: std::sync::Arc<crate::storage::StorageRegistry>,
21    plugin_namespace: String,
22    data_dir: std::path::PathBuf,
23    storage_config_resolver: std::sync::Arc<dyn crate::storage::StorageConfigResolver>,
24    current_context_rx: tokio::sync::watch::Receiver<serde_json::Value>,
25}
26
27impl PluginContext {
28    #[doc = " Internal constructor used by the Core AI engine."]
29    pub fn new(
30        data_dir: std::path::PathBuf,
31        llm_executor: std::sync::Arc<dyn crate::llm::LlmExecutor>,
32        plugin_config: serde_json::Value,
33        storage: std::sync::Arc<crate::storage::StorageRegistry>,
34        plugin_namespace: String,
35        storage_config_resolver: std::sync::Arc<dyn crate::storage::StorageConfigResolver>,
36        current_context_rx: tokio::sync::watch::Receiver<serde_json::Value>,
37    ) -> Self {
38        Self {
39            data_dir,
40            llm_executor,
41            plugin_config,
42            storage,
43            plugin_namespace,
44            storage_config_resolver,
45            current_context_rx,
46        }
47    }
48    pub fn llm_executor(&self) -> std::sync::Arc<dyn crate::llm::LlmExecutor> {
49        self.llm_executor.clone()
50    }
51    #[doc = " Deserializes the raw JSON configuration into the plugin's requested config struct."]
52    #[doc = ""]
53    #[doc = " **Note on Serde Configuration Defaults:**"]
54    #[doc = " This performs strict structural deserialization. If the JSON object provided by the"]
55    #[doc = " `ConfigProvider` is missing a field that your Rust struct (which must derive `Deserialize`) expects, `serde` will"]
56    #[doc = " return a `missing field` error — even if your struct implements `Default`."]
57    #[doc = ""]
58    #[doc = " To make a configuration field optional, use the `#[serde(default)]`"]
59    #[doc = " attribute on the struct field. This instructs `serde` to fall back to `Default::default()`"]
60    #[doc = " when the key is omitted."]
61    pub fn config<C: serde::de::DeserializeOwned>(&self) -> Result<C, String> {
62        serde_json::from_value(self.plugin_config.clone())
63            .map_err(|e| format!("Failed to parse plugin config: {}", e))
64    }
65    #[doc = " Initializes and returns a database connection scoped strictly to this plugin's namespace."]
66    pub async fn store<S: crate::storage::StorageConnection>(&self) -> Result<S, String> {
67        let full_path = std::any::type_name::<S>();
68        let crate_name = full_path
69            .split("::")
70            .next()
71            .unwrap_or("")
72            .to_string()
73            .replace('-', "_");
74        let base_path = full_path.split('<').next().unwrap_or(full_path);
75        let storage_type_name = base_path.split("::").last().unwrap_or("").to_string();
76        let config_val = self
77            .storage_config_resolver
78            .resolve_config(&crate_name, &storage_type_name)
79            .unwrap_or_else(|| serde_json::json!({}));
80        let config: S::Config = serde_json::from_value(config_val).map_err(|e| {
81            format!(
82                "Failed to parse config for storage '{}::{}': {}",
83                crate_name, storage_type_name, e
84            )
85        })?;
86        S::connect(
87            config,
88            self.storage.clone(),
89            &self.data_dir,
90            &self.plugin_namespace,
91        )
92        .await
93    }
94    #[doc = " Read-only subscription channel to receive the global state updates"]
95    pub fn subscribe_context_updates(&self) -> tokio::sync::watch::Receiver<serde_json::Value> {
96        self.current_context_rx.clone()
97    }
98}
99
100pub trait PluginRegistry {
101    fn register_gui<P: GuiPlugin>(&mut self, plugin: std::sync::Arc<P>);
102    fn register_audio_input<P: AudioInputPlugin>(&mut self, plugin: std::sync::Arc<P>);
103    fn register_audio_output<P: AudioOutputPlugin>(&mut self, plugin: std::sync::Arc<P>);
104    fn register_stt<P: STTPlugin>(&mut self, plugin: std::sync::Arc<P>);
105    fn register_tts<P: TTSPlugin>(&mut self, plugin: std::sync::Arc<P>);
106    fn register_diarization<P: DiarizationPlugin>(&mut self, plugin: std::sync::Arc<P>);
107    fn register_chat<P: ChatPlugin>(&mut self, plugin: std::sync::Arc<P>);
108    fn register_documents<P: DocumentsPlugin>(&mut self, plugin: std::sync::Arc<P>);
109    fn register_interaction_observer<P: InteractionObserver>(&mut self, plugin: std::sync::Arc<P>);
110    fn register_rollout_controller<P: RolloutController>(&mut self, plugin: std::sync::Arc<P>);
111    fn register_retrospective_consolidation<P: RetrospectiveConsolidationPlugin>(
112        &mut self,
113        plugin: std::sync::Arc<P>,
114    );
115    fn register_camera<P: CameraPlugin>(&mut self, plugin: std::sync::Arc<P>);
116    fn register_context_provider<P: crate::context::ContextProvider>(
117        &mut self,
118        provider: std::sync::Arc<P>,
119    );
120    fn register_command<C: crate::command::Command>(&mut self, command: C);
121    fn register_tool<T: crate::tool::Tool>(&mut self, tool: T);
122    fn register_call<P: CallPlugin>(
123        &mut self,
124        plugin: std::sync::Arc<P>,
125        capability: Option<&'static str>,
126    );
127    fn register_recorder<P: AudioRecorderPlugin>(&mut self, plugin: std::sync::Arc<P>);
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131pub struct EmptyPluginConfig {}
132
133#[async_trait]
134pub trait Plugin: Send + Sync + 'static {
135    #[doc = " Compile-time semantic description of this plugin's capability for the LLM."]
136    const CAPABILITY: Option<&'static str> = None;
137    #[doc = " This is the method for instantiating plugins, allowing them to await"]
138    #[doc = " their database connections (via `context.store::<S>().await`) before returning."]
139    #[doc = ""]
140    #[doc = " **Note on Configuration:** When calling `context.config()?` to extract your configuration struct,"]
141    #[doc = " ensure any optional fields in your struct are marked with `#[serde(default)]`. Otherwise,"]
142    #[doc = " omitted fields in the config file will cause strict deserialization errors."]
143    async fn create(context: crate::plugin::PluginContext) -> Result<Self, String>
144    where
145        Self: Sized;
146    fn register<R: PluginRegistry + ?Sized>(self: std::sync::Arc<Self>, registry: &mut R)
147    where
148        Self: Sized;
149}
150
151#[doc = " An opaque channel identifier used to route messages within the system."]
152#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
153pub struct MessageChannel {
154    #[doc = " Opaque JSON context provided by plugins or core modules."]
155    pub context: serde_json::Value,
156}