1pub mod documents;
2use crate::{cognitive_output_text::types::CognitiveOutputText, llm::LLMSafe};
3use crate::peer_input_text::types::PeerInputText;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
8pub enum DocumentIngestionPolicy {
9 Store,
11 StoreAndParse,
13}
14
15#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
16pub struct DocumentRegistrationRequest {
17 pub original_filename: String,
18 pub mime_type: String,
19 pub data: Vec<u8>,
20 pub policy: DocumentIngestionPolicy,
21}
22
23pub struct AddDocumentRequest {
24 pub request: DocumentRegistrationRequest,
25 pub reply_tx: tokio::sync::oneshot::Sender<DocumentId>,
26}
27crate::register_channel_name!(AddDocumentRequest, "add_document_request");
28
29#[derive(
30 Serialize,
31 Deserialize,
32 JsonSchema,
33 PartialEq,
34 Eq,
35 Debug,
36 Clone,
37 derive_more::Display,
38 derive_more::From,
39 derive_more::Deref,
40)]
41pub struct ToolCallId(pub String);
42crate::register_channel_name!(ToolCallId, "tool_call_id");
43
44#[derive(
46 Serialize,
47 Deserialize,
48 JsonSchema,
49 PartialEq,
50 Eq,
51 Debug,
52 Clone,
53 derive_more::Display,
54 derive_more::From,
55 derive_more::Deref,
56)]
57pub struct SenderId(pub String);
58
59#[derive(
61 Serialize,
62 Deserialize,
63 JsonSchema,
64 PartialEq,
65 Eq,
66 Debug,
67 Clone,
68 derive_more::Display,
69 derive_more::From,
70 derive_more::Deref,
71)]
72pub struct MessageText(pub String);
73
74#[derive(
76 Serialize,
77 Deserialize,
78 JsonSchema,
79 PartialEq,
80 Eq,
81 Debug,
82 Clone,
83 derive_more::Display,
84 derive_more::From,
85 derive_more::Deref,
86)]
87pub struct DocumentId(pub String);
88crate::register_channel_name!(DocumentId, "document_id");
89
90#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
92pub struct MessageChannel {
93 pub context: serde_json::Value,
95}
96
97pub use crate::speech_to_text::types::SpeakerId;
98
99#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
101pub enum Speaker {
102 Unknown(Option<SpeakerId>),
104 Recognized(SpeakerId),
106}
107
108#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
110pub struct PeerInputSpeech {
111 pub channel: MessageChannel,
113 pub speaker: Speaker,
115 pub transcript: MessageText,
117}
118crate::register_channel_name!(PeerInputSpeech, "peer_input_speech");
119
120#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, JsonSchema)]
122pub enum PeerInput {
123 Speech(PeerInputSpeech),
125 Text(crate::peer_input_text::types::PeerInputText),
127}
128
129#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
131pub struct CognitiveOutputSpeech {
132 pub target_channel: MessageChannel,
134 pub text: String,
136}
137crate::register_channel_name!(CognitiveOutputSpeech, "ai_output_speech");
138
139#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
141pub enum CognitiveState {
142 Thinking,
144 Searching,
146 Acting,
148 Idle,
150}
151
152#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
154pub struct CognitiveStateUpdate {
155 pub context: serde_json::Value,
157 pub state: CognitiveState,
159}
160crate::register_channel_name!(CognitiveStateUpdate, "cognitive_state");
161
162#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
164pub struct Enveloped<T> {
165 pub plugin: String,
167 pub payload: T,
169}
170
171impl<T> Enveloped<T> {
172 pub fn new(plugin: impl Into<String>, payload: T) -> Self {
174 Self {
175 plugin: plugin.into(),
176 payload,
177 }
178 }
179}
180
181crate::register_channel_name!(Enveloped<PeerInputText>, "peer_input_text_enveloped");
182crate::register_channel_name!(
183 Enveloped<CognitiveOutputText>,
184 "cognitive_output_text_enveloped"
185);
186crate::register_channel_name!(Enveloped<CognitiveStateUpdate>, "cognitive_state_enveloped");
187
188#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189pub enum TemporalScope {
190 Historical,
191 Current, Prospective,
193}
194
195#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
196pub struct ContextInteraction {
197 pub peer_input: Option<String>,
198 pub ai_reasoning: Option<String>,
199 pub ai_output: Option<String>,
200}
201
202#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)]
203pub struct ContextRequest {
204 pub recent_interactions: Vec<ContextInteraction>,
208 pub initial_run: bool,
209}
210
211#[async_trait::async_trait]
212pub trait ContextProvider: Send + Sync + 'static {
213 type Context: schemars::JsonSchema + serde::Serialize + LLMSafe + Send + Sync + 'static;
214
215 const NAME: &'static str;
217
218 const SCOPE: TemporalScope;
220
221 async fn context(&self, request: &ContextRequest) -> Result<Self::Context, String>;
223
224 fn subscribe(&self) -> Option<tokio::sync::watch::Receiver<()>> {
227 None
228 }
229}
230
231#[async_trait::async_trait]
232pub trait ErasedContextProvider: Send + Sync + 'static {
233 fn name(&self) -> &'static str;
234 fn scope(&self) -> TemporalScope;
235 fn schema(&self) -> schemars::Schema;
236 async fn erased_context(&self, request: &ContextRequest) -> Result<serde_json::Value, String>;
237 fn subscribe(&self) -> Option<tokio::sync::watch::Receiver<()>>;
238}
239
240#[async_trait::async_trait]
241impl<T> ErasedContextProvider for T
242where
243 T: ContextProvider,
244{
245 fn name(&self) -> &'static str {
246 <T as ContextProvider>::NAME
247 }
248 fn scope(&self) -> TemporalScope {
249 <T as ContextProvider>::SCOPE
250 }
251 fn schema(&self) -> schemars::Schema {
252 schemars::schema_for!(<T as ContextProvider>::Context)
253 }
254 async fn erased_context(&self, request: &ContextRequest) -> Result<serde_json::Value, String> {
255 let view = <T as ContextProvider>::context(self, request).await?;
256 serde_json::to_value(view).map_err(|e| e.to_string())
257 }
258 fn subscribe(&self) -> Option<tokio::sync::watch::Receiver<()>> {
259 <T as ContextProvider>::subscribe(self)
260 }
261}
262
263pub struct ContextRegistryBuilder {
264 pub providers: std::sync::RwLock<Vec<std::sync::Arc<dyn ErasedContextProvider>>>,
265 change_tx: tokio::sync::watch::Sender<()>,
266 change_rx: tokio::sync::watch::Receiver<()>,
267}
268
269impl Default for ContextRegistryBuilder {
270 fn default() -> Self {
271 let (change_tx, change_rx) = tokio::sync::watch::channel(());
272 Self {
273 providers: std::sync::RwLock::new(Vec::new()),
274 change_tx,
275 change_rx,
276 }
277 }
278}
279
280impl ContextRegistryBuilder {
281 pub fn register<T>(&self, provider: T)
282 where
283 T: ErasedContextProvider + 'static,
284 {
285 let provider_arc: std::sync::Arc<dyn ErasedContextProvider> = std::sync::Arc::new(provider);
286 self.register_erased(provider_arc);
287 }
288
289 pub fn register_erased(&self, provider: std::sync::Arc<dyn ErasedContextProvider>) {
290 self.providers
291 .write()
292 .unwrap_or_else(|e| panic!("Failed to acquire write lock on providers: {:?}", e))
293 .push(provider.clone());
294
295 if let Some(mut sub_rx) = provider.subscribe() {
297 let change_tx = self.change_tx.clone();
298 tokio::spawn(async move {
299 while sub_rx.changed().await.is_ok() {
300 change_tx
301 .send(())
302 .inspect_err(|e| tracing::error!("{}", e))
303 .ok();
304 }
305 });
306 }
307 }
308
309 pub fn subscribe(&self) -> tokio::sync::watch::Receiver<()> {
310 self.change_rx.clone()
311 }
312}
313
314#[derive(Clone)]
315pub struct PluginContext {
316 llm_executor: std::sync::Arc<dyn crate::llm::LlmExecutor>,
317 plugin_config: serde_json::Value,
318
319 storage: std::sync::Arc<crate::storage::StorageRegistry>,
322 plugin_namespace: String,
323 data_dir: std::path::PathBuf,
324 storage_config_resolver: std::sync::Arc<dyn crate::storage::StorageConfigResolver>,
325 current_context_rx: tokio::sync::watch::Receiver<serde_json::Value>,
326}
327
328impl PluginContext {
329 pub fn new(
331 data_dir: std::path::PathBuf,
332 llm_executor: std::sync::Arc<dyn crate::llm::LlmExecutor>,
333 plugin_config: serde_json::Value,
334 storage: std::sync::Arc<crate::storage::StorageRegistry>,
335 plugin_namespace: String,
336 storage_config_resolver: std::sync::Arc<dyn crate::storage::StorageConfigResolver>,
337 current_context_rx: tokio::sync::watch::Receiver<serde_json::Value>,
338 ) -> Self {
339 Self {
340 data_dir,
341 llm_executor,
342 plugin_config,
343 storage,
344 plugin_namespace,
345 storage_config_resolver,
346 current_context_rx,
347 }
348 }
349
350 pub fn llm_executor(&self) -> std::sync::Arc<dyn crate::llm::LlmExecutor> {
351 self.llm_executor.clone()
352 }
353
354 pub fn config<C: serde::de::DeserializeOwned>(&self) -> Result<C, String> {
365 serde_json::from_value(self.plugin_config.clone())
366 .map_err(|e| format!("Failed to parse plugin config: {}", e))
367 }
368
369 pub async fn store<S: crate::storage::StorageConnection>(&self) -> Result<S, String> {
371 let full_path = std::any::type_name::<S>();
372 let crate_name = full_path
373 .split("::")
374 .next()
375 .unwrap_or("")
376 .to_string()
377 .replace('-', "_");
378 let base_path = full_path.split('<').next().unwrap_or(full_path);
379 let storage_type_name = base_path.split("::").last().unwrap_or("").to_string();
380
381 let config_val = self
382 .storage_config_resolver
383 .resolve_config(&crate_name, &storage_type_name)
384 .unwrap_or_else(|| serde_json::json!({}));
385
386 let config: S::Config = serde_json::from_value(config_val).map_err(|e| {
387 format!(
388 "Failed to parse config for storage '{}::{}': {}",
389 crate_name, storage_type_name, e
390 )
391 })?;
392
393 S::connect(
394 config,
395 self.storage.clone(),
396 &self.data_dir,
397 &self.plugin_namespace,
398 )
399 .await
400 }
401
402 pub fn subscribe_context_updates(&self) -> tokio::sync::watch::Receiver<serde_json::Value> {
404 self.current_context_rx.clone()
405 }
406}
407
408#[derive(Default)]
409pub struct ContextRegistries {
410 pub historical: ContextRegistryBuilder,
411 pub current: ContextRegistryBuilder,
412 pub prospective: ContextRegistryBuilder,
413}
414
415impl ContextRegistries {
416 pub fn subscribe(&self, scope: TemporalScope) -> tokio::sync::watch::Receiver<()> {
417 match scope {
418 TemporalScope::Historical => self.historical.subscribe(),
419 TemporalScope::Current => self.current.subscribe(),
420 TemporalScope::Prospective => self.prospective.subscribe(),
421 }
422 }
423}
424
425#[async_trait::async_trait]
426pub trait Command: Send + Sync + 'static {
427 type Arguments: schemars::JsonSchema
428 + serde::de::DeserializeOwned
429 + LLMSafe
430 + Send
431 + Sync
432 + 'static;
433
434 const NAME: &'static str;
435
436 async fn execute(&self, args: Self::Arguments) -> Result<(), String>;
437}
438
439#[async_trait::async_trait]
440pub trait ErasedCommand: Send + Sync + 'static {
441 fn name(&self) -> &'static str;
442 fn schema(&self) -> schemars::Schema;
443 async fn erased_execute(&self, args: serde_json::Value) -> Result<(), String>;
444}
445
446#[async_trait::async_trait]
447impl<T> ErasedCommand for T
448where
449 T: Command,
450{
451 fn name(&self) -> &'static str {
452 <T as Command>::NAME
453 }
454 fn schema(&self) -> schemars::Schema {
455 schemars::schema_for!(<T as Command>::Arguments)
456 }
457 async fn erased_execute(&self, args: serde_json::Value) -> Result<(), String> {
458 let parsed_args = serde_json::from_value(args).map_err(|e| e.to_string())?;
459 <T as Command>::execute(self, parsed_args).await
460 }
461}
462
463#[derive(Default)]
464pub struct CommandRegistryBuilder {
465 pub commands:
466 std::sync::RwLock<std::collections::HashMap<String, std::sync::Arc<dyn ErasedCommand>>>,
467}
468
469impl CommandRegistryBuilder {
470 pub fn register<T>(&self, command: T)
471 where
472 T: ErasedCommand + 'static,
473 {
474 let command_arc: std::sync::Arc<dyn ErasedCommand> = std::sync::Arc::new(command);
475 self.register_erased(command_arc);
476 }
477
478 pub fn register_erased(&self, command: std::sync::Arc<dyn ErasedCommand>) {
479 self.commands
480 .write()
481 .unwrap_or_else(|e| panic!("Failed to acquire write lock on commands: {:?}", e))
482 .insert(command.name().to_string(), command);
483 }
484}
485
486#[async_trait::async_trait]
487pub trait Tool: Send + Sync + 'static {
488 type Arguments: schemars::JsonSchema
489 + serde::de::DeserializeOwned
490 + LLMSafe
491 + Send
492 + Sync
493 + 'static;
494
495 const NAME: &'static str;
496 const DESCRIPTION: &'static str;
497
498 async fn is_available(
501 &self,
502 _ctx_request: &ContextRequest,
503 _compiled_context: &serde_json::Value,
504 ) -> Result<bool, String> {
505 Ok(true)
506 }
507
508 async fn execute(
510 &self,
511 ctx_request: &ContextRequest,
512 args: Self::Arguments,
513 ) -> Result<serde_json::Value, String>;
514}
515
516#[async_trait::async_trait]
517pub trait ErasedTool: Send + Sync + 'static {
518 fn name(&self) -> &'static str;
519 fn description(&self) -> &'static str;
520 fn schema(&self) -> schemars::Schema;
521 async fn erased_is_available(
522 &self,
523 ctx_request: &ContextRequest,
524 compiled_context: &serde_json::Value,
525 ) -> Result<bool, String>;
526 async fn erased_execute(
527 &self,
528 ctx_request: &ContextRequest,
529 args: serde_json::Value,
530 ) -> Result<serde_json::Value, String>;
531}
532
533#[async_trait::async_trait]
534impl<T> ErasedTool for T
535where
536 T: Tool,
537{
538 fn name(&self) -> &'static str {
539 <T as Tool>::NAME
540 }
541 fn description(&self) -> &'static str {
542 <T as Tool>::DESCRIPTION
543 }
544 fn schema(&self) -> schemars::Schema {
545 schemars::schema_for!(<T as Tool>::Arguments)
546 }
547 async fn erased_is_available(
548 &self,
549 ctx_request: &ContextRequest,
550 compiled_context: &serde_json::Value,
551 ) -> Result<bool, String> {
552 <T as Tool>::is_available(self, ctx_request, compiled_context).await
553 }
554 async fn erased_execute(
555 &self,
556 ctx_request: &ContextRequest,
557 args: serde_json::Value,
558 ) -> Result<serde_json::Value, String> {
559 let parsed_args = serde_json::from_value(args).map_err(|e| e.to_string())?;
560 <T as Tool>::execute(self, ctx_request, parsed_args).await
561 }
562}
563
564#[derive(Default)]
565pub struct ToolRegistryBuilder {
566 pub tools: std::sync::RwLock<std::collections::HashMap<String, std::sync::Arc<dyn ErasedTool>>>,
567}
568
569impl ToolRegistryBuilder {
570 pub fn register<T>(&self, tool: T)
571 where
572 T: ErasedTool + 'static,
573 {
574 let tool_arc: std::sync::Arc<dyn ErasedTool> = std::sync::Arc::new(tool);
575 self.register_erased(tool_arc);
576 }
577
578 pub fn register_erased(&self, tool: std::sync::Arc<dyn ErasedTool>) {
579 self.tools
580 .write()
581 .unwrap_or_else(|e| panic!("Failed to acquire write lock on tools: {:?}", e))
582 .insert(tool.name().to_string(), tool);
583 }
584
585 pub fn get(&self, name: &str) -> Option<std::sync::Arc<dyn ErasedTool>> {
586 self.tools
587 .read()
588 .unwrap_or_else(|e| panic!("Failed to acquire read lock on tools: {:?}", e))
589 .get(name)
590 .cloned()
591 }
592
593 pub fn get_all(&self) -> Vec<std::sync::Arc<dyn ErasedTool>> {
594 self.tools
595 .read()
596 .unwrap_or_else(|e| panic!("Failed to acquire read lock on tools: {:?}", e))
597 .values()
598 .cloned()
599 .collect()
600 }
601}
602
603#[derive(
604 Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone, PartialOrd, Ord, Copy,
605)]
606pub struct Timestamp(pub i64);
607crate::register_channel_name!(Timestamp, "timestamp");
608
609#[derive(
610 Serialize,
611 Deserialize,
612 JsonSchema,
613 PartialEq,
614 Eq,
615 Debug,
616 Clone,
617 derive_more::Display,
618 derive_more::From,
619 derive_more::Deref,
620)]
621pub struct SpaceId(pub String);
622
623#[derive(
624 Serialize,
625 Deserialize,
626 JsonSchema,
627 PartialEq,
628 Eq,
629 Debug,
630 Clone,
631 derive_more::Display,
632 derive_more::From,
633 derive_more::Deref,
634)]
635pub struct ThreadId(pub String);
636
637#[derive(
638 Serialize,
639 Deserialize,
640 JsonSchema,
641 PartialEq,
642 Eq,
643 Debug,
644 Clone,
645 derive_more::Display,
646 derive_more::From,
647 derive_more::Deref,
648 Default,
649)]
650pub struct MessageId(pub String);
651
652#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
653pub struct AiSpoken(pub String);
654
655#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
656pub struct AiWritten {
657 pub target_channel: MessageChannel,
658 pub text: String,
659}
660
661#[derive(
662 Serialize,
663 Deserialize,
664 JsonSchema,
665 PartialEq,
666 Eq,
667 Debug,
668 Clone,
669 derive_more::Display,
670 derive_more::From,
671 derive_more::Deref,
672)]
673pub struct CognitiveReasoning(pub String);
674
675#[derive(
676 Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq, Eq,
677)]
678pub struct ObservedInteraction {
679 pub timestamp: Timestamp,
680 pub user_messages: Vec<PeerInput>,
681 pub ai_spoken: Option<AiSpoken>,
682 pub ai_written: Option<AiWritten>,
683 pub ai_reasoning: Option<CognitiveReasoning>,
684}
685
686crate::register_channel_name!(ObservedInteraction, "observed_interaction");
687
688#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, schemars::JsonSchema)]
689pub struct NotClearInteraction {
690 pub timestamp: Timestamp,
691 pub user_messages: Vec<PeerInput>,
692 pub ai_spoken: Option<AiSpoken>,
693 pub ai_written: Option<AiWritten>,
694}
695crate::register_channel_name!(NotClearInteraction, "not_clear_interaction");
696
697#[derive(
698 Serialize,
699 Deserialize,
700 PartialEq,
701 Eq,
702 Debug,
703 Clone,
704 Default,
705 schemars::JsonSchema,
706 derive_more::Deref,
707 derive_more::DerefMut,
708 derive_more::IntoIterator,
709)]
710pub struct NotClearInteractionMemory(pub std::collections::VecDeque<NotClearInteraction>);
711
712impl From<Vec<NotClearInteraction>> for NotClearInteractionMemory {
713 fn from(value: Vec<NotClearInteraction>) -> Self {
714 Self(value.into())
715 }
716}
717
718#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
720pub struct CameraInputFrame {
721 pub data: Vec<u8>,
723}
724
725crate::register_channel_name!(CameraInputFrame, "camera_input_frame");