pub mod documents;
use crate::{cognitive_output_text::types::CognitiveOutputText, llm::LLMSafe};
use crate::peer_input_text::types::PeerInputText;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
pub enum DocumentIngestionPolicy {
Store,
StoreAndParse,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
pub struct DocumentRegistrationRequest {
pub original_filename: String,
pub mime_type: String,
pub data: Vec<u8>,
pub policy: DocumentIngestionPolicy,
}
pub struct AddDocumentRequest {
pub request: DocumentRegistrationRequest,
pub reply_tx: tokio::sync::oneshot::Sender<DocumentId>,
}
crate::register_channel_name!(AddDocumentRequest, "add_document_request");
#[derive(
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Debug,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
pub struct ToolCallId(pub String);
crate::register_channel_name!(ToolCallId, "tool_call_id");
#[derive(
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Debug,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
pub struct SenderId(pub String);
#[derive(
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Debug,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
pub struct MessageText(pub String);
#[derive(
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Debug,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
pub struct DocumentId(pub String);
crate::register_channel_name!(DocumentId, "document_id");
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
pub struct MessageChannel {
pub context: serde_json::Value,
}
pub use crate::speech_to_text::types::SpeakerId;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
pub enum Speaker {
Unknown(Option<SpeakerId>),
Recognized(SpeakerId),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
pub struct PeerInputSpeech {
pub channel: MessageChannel,
pub speaker: Speaker,
pub transcript: MessageText,
}
crate::register_channel_name!(PeerInputSpeech, "peer_input_speech");
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, JsonSchema)]
pub enum PeerInput {
Speech(PeerInputSpeech),
Text(crate::peer_input_text::types::PeerInputText),
}
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
pub struct CognitiveOutputSpeech {
pub target_channel: MessageChannel,
pub text: String,
}
crate::register_channel_name!(CognitiveOutputSpeech, "ai_output_speech");
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
pub enum CognitiveState {
Thinking,
Searching,
Acting,
Idle,
}
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
pub struct CognitiveStateUpdate {
pub context: serde_json::Value,
pub state: CognitiveState,
}
crate::register_channel_name!(CognitiveStateUpdate, "cognitive_state");
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct Enveloped<T> {
pub plugin: String,
pub payload: T,
}
impl<T> Enveloped<T> {
pub fn new(plugin: impl Into<String>, payload: T) -> Self {
Self {
plugin: plugin.into(),
payload,
}
}
}
crate::register_channel_name!(Enveloped<PeerInputText>, "peer_input_text_enveloped");
crate::register_channel_name!(
Enveloped<CognitiveOutputText>,
"cognitive_output_text_enveloped"
);
crate::register_channel_name!(Enveloped<CognitiveStateUpdate>, "cognitive_state_enveloped");
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TemporalScope {
Historical,
Current, Prospective,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ContextInteraction {
pub peer_input: Option<String>,
pub ai_reasoning: Option<String>,
pub ai_output: Option<String>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)]
pub struct ContextRequest {
pub recent_interactions: Vec<ContextInteraction>,
pub initial_run: bool,
}
#[async_trait::async_trait]
pub trait ContextProvider: Send + Sync + 'static {
type Context: schemars::JsonSchema + serde::Serialize + LLMSafe + Send + Sync + 'static;
const NAME: &'static str;
const SCOPE: TemporalScope;
async fn context(&self, request: &ContextRequest) -> Result<Self::Context, String>;
fn subscribe(&self) -> Option<tokio::sync::watch::Receiver<()>> {
None
}
}
#[async_trait::async_trait]
pub trait ErasedContextProvider: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn scope(&self) -> TemporalScope;
fn schema(&self) -> schemars::Schema;
async fn erased_context(&self, request: &ContextRequest) -> Result<serde_json::Value, String>;
fn subscribe(&self) -> Option<tokio::sync::watch::Receiver<()>>;
}
#[async_trait::async_trait]
impl<T> ErasedContextProvider for T
where
T: ContextProvider,
{
fn name(&self) -> &'static str {
<T as ContextProvider>::NAME
}
fn scope(&self) -> TemporalScope {
<T as ContextProvider>::SCOPE
}
fn schema(&self) -> schemars::Schema {
schemars::schema_for!(<T as ContextProvider>::Context)
}
async fn erased_context(&self, request: &ContextRequest) -> Result<serde_json::Value, String> {
let view = <T as ContextProvider>::context(self, request).await?;
serde_json::to_value(view).map_err(|e| e.to_string())
}
fn subscribe(&self) -> Option<tokio::sync::watch::Receiver<()>> {
<T as ContextProvider>::subscribe(self)
}
}
pub struct ContextRegistryBuilder {
pub providers: std::sync::RwLock<Vec<std::sync::Arc<dyn ErasedContextProvider>>>,
change_tx: tokio::sync::watch::Sender<()>,
change_rx: tokio::sync::watch::Receiver<()>,
}
impl Default for ContextRegistryBuilder {
fn default() -> Self {
let (change_tx, change_rx) = tokio::sync::watch::channel(());
Self {
providers: std::sync::RwLock::new(Vec::new()),
change_tx,
change_rx,
}
}
}
impl ContextRegistryBuilder {
pub fn register<T>(&self, provider: T)
where
T: ErasedContextProvider + 'static,
{
let provider_arc: std::sync::Arc<dyn ErasedContextProvider> = std::sync::Arc::new(provider);
self.register_erased(provider_arc);
}
pub fn register_erased(&self, provider: std::sync::Arc<dyn ErasedContextProvider>) {
self.providers
.write()
.unwrap_or_else(|e| panic!("Failed to acquire write lock on providers: {:?}", e))
.push(provider.clone());
if let Some(mut sub_rx) = provider.subscribe() {
let change_tx = self.change_tx.clone();
tokio::spawn(async move {
while sub_rx.changed().await.is_ok() {
change_tx
.send(())
.inspect_err(|e| tracing::error!("{}", e))
.ok();
}
});
}
}
pub fn subscribe(&self) -> tokio::sync::watch::Receiver<()> {
self.change_rx.clone()
}
}
#[derive(Clone)]
pub struct PluginContext {
llm_executor: std::sync::Arc<dyn crate::llm::LlmExecutor>,
plugin_config: serde_json::Value,
storage: std::sync::Arc<crate::storage::StorageRegistry>,
plugin_namespace: String,
data_dir: std::path::PathBuf,
storage_config_resolver: std::sync::Arc<dyn crate::storage::StorageConfigResolver>,
current_context_rx: tokio::sync::watch::Receiver<serde_json::Value>,
}
impl PluginContext {
pub fn new(
data_dir: std::path::PathBuf,
llm_executor: std::sync::Arc<dyn crate::llm::LlmExecutor>,
plugin_config: serde_json::Value,
storage: std::sync::Arc<crate::storage::StorageRegistry>,
plugin_namespace: String,
storage_config_resolver: std::sync::Arc<dyn crate::storage::StorageConfigResolver>,
current_context_rx: tokio::sync::watch::Receiver<serde_json::Value>,
) -> Self {
Self {
data_dir,
llm_executor,
plugin_config,
storage,
plugin_namespace,
storage_config_resolver,
current_context_rx,
}
}
pub fn llm_executor(&self) -> std::sync::Arc<dyn crate::llm::LlmExecutor> {
self.llm_executor.clone()
}
pub fn config<C: serde::de::DeserializeOwned>(&self) -> Result<C, String> {
serde_json::from_value(self.plugin_config.clone())
.map_err(|e| format!("Failed to parse plugin config: {}", e))
}
pub async fn store<S: crate::storage::StorageConnection>(&self) -> Result<S, String> {
let full_path = std::any::type_name::<S>();
let crate_name = full_path
.split("::")
.next()
.unwrap_or("")
.to_string()
.replace('-', "_");
let base_path = full_path.split('<').next().unwrap_or(full_path);
let storage_type_name = base_path.split("::").last().unwrap_or("").to_string();
let config_val = self
.storage_config_resolver
.resolve_config(&crate_name, &storage_type_name)
.unwrap_or_else(|| serde_json::json!({}));
let config: S::Config = serde_json::from_value(config_val).map_err(|e| {
format!(
"Failed to parse config for storage '{}::{}': {}",
crate_name, storage_type_name, e
)
})?;
S::connect(
config,
self.storage.clone(),
&self.data_dir,
&self.plugin_namespace,
)
.await
}
pub fn subscribe_context_updates(&self) -> tokio::sync::watch::Receiver<serde_json::Value> {
self.current_context_rx.clone()
}
}
#[derive(Default)]
pub struct ContextRegistries {
pub historical: ContextRegistryBuilder,
pub current: ContextRegistryBuilder,
pub prospective: ContextRegistryBuilder,
}
impl ContextRegistries {
pub fn subscribe(&self, scope: TemporalScope) -> tokio::sync::watch::Receiver<()> {
match scope {
TemporalScope::Historical => self.historical.subscribe(),
TemporalScope::Current => self.current.subscribe(),
TemporalScope::Prospective => self.prospective.subscribe(),
}
}
}
#[async_trait::async_trait]
pub trait Command: Send + Sync + 'static {
type Arguments: schemars::JsonSchema
+ serde::de::DeserializeOwned
+ LLMSafe
+ Send
+ Sync
+ 'static;
const NAME: &'static str;
async fn execute(&self, args: Self::Arguments) -> Result<(), String>;
}
#[async_trait::async_trait]
pub trait ErasedCommand: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn schema(&self) -> schemars::Schema;
async fn erased_execute(&self, args: serde_json::Value) -> Result<(), String>;
}
#[async_trait::async_trait]
impl<T> ErasedCommand for T
where
T: Command,
{
fn name(&self) -> &'static str {
<T as Command>::NAME
}
fn schema(&self) -> schemars::Schema {
schemars::schema_for!(<T as Command>::Arguments)
}
async fn erased_execute(&self, args: serde_json::Value) -> Result<(), String> {
let parsed_args = serde_json::from_value(args).map_err(|e| e.to_string())?;
<T as Command>::execute(self, parsed_args).await
}
}
#[derive(Default)]
pub struct CommandRegistryBuilder {
pub commands:
std::sync::RwLock<std::collections::HashMap<String, std::sync::Arc<dyn ErasedCommand>>>,
}
impl CommandRegistryBuilder {
pub fn register<T>(&self, command: T)
where
T: ErasedCommand + 'static,
{
let command_arc: std::sync::Arc<dyn ErasedCommand> = std::sync::Arc::new(command);
self.register_erased(command_arc);
}
pub fn register_erased(&self, command: std::sync::Arc<dyn ErasedCommand>) {
self.commands
.write()
.unwrap_or_else(|e| panic!("Failed to acquire write lock on commands: {:?}", e))
.insert(command.name().to_string(), command);
}
}
#[async_trait::async_trait]
pub trait Tool: Send + Sync + 'static {
type Arguments: schemars::JsonSchema
+ serde::de::DeserializeOwned
+ LLMSafe
+ Send
+ Sync
+ 'static;
const NAME: &'static str;
const DESCRIPTION: &'static str;
async fn is_available(
&self,
_ctx_request: &ContextRequest,
_compiled_context: &serde_json::Value,
) -> Result<bool, String> {
Ok(true)
}
async fn execute(
&self,
ctx_request: &ContextRequest,
args: Self::Arguments,
) -> Result<serde_json::Value, String>;
}
#[async_trait::async_trait]
pub trait ErasedTool: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn schema(&self) -> schemars::Schema;
async fn erased_is_available(
&self,
ctx_request: &ContextRequest,
compiled_context: &serde_json::Value,
) -> Result<bool, String>;
async fn erased_execute(
&self,
ctx_request: &ContextRequest,
args: serde_json::Value,
) -> Result<serde_json::Value, String>;
}
#[async_trait::async_trait]
impl<T> ErasedTool for T
where
T: Tool,
{
fn name(&self) -> &'static str {
<T as Tool>::NAME
}
fn description(&self) -> &'static str {
<T as Tool>::DESCRIPTION
}
fn schema(&self) -> schemars::Schema {
schemars::schema_for!(<T as Tool>::Arguments)
}
async fn erased_is_available(
&self,
ctx_request: &ContextRequest,
compiled_context: &serde_json::Value,
) -> Result<bool, String> {
<T as Tool>::is_available(self, ctx_request, compiled_context).await
}
async fn erased_execute(
&self,
ctx_request: &ContextRequest,
args: serde_json::Value,
) -> Result<serde_json::Value, String> {
let parsed_args = serde_json::from_value(args).map_err(|e| e.to_string())?;
<T as Tool>::execute(self, ctx_request, parsed_args).await
}
}
#[derive(Default)]
pub struct ToolRegistryBuilder {
pub tools: std::sync::RwLock<std::collections::HashMap<String, std::sync::Arc<dyn ErasedTool>>>,
}
impl ToolRegistryBuilder {
pub fn register<T>(&self, tool: T)
where
T: ErasedTool + 'static,
{
let tool_arc: std::sync::Arc<dyn ErasedTool> = std::sync::Arc::new(tool);
self.register_erased(tool_arc);
}
pub fn register_erased(&self, tool: std::sync::Arc<dyn ErasedTool>) {
self.tools
.write()
.unwrap_or_else(|e| panic!("Failed to acquire write lock on tools: {:?}", e))
.insert(tool.name().to_string(), tool);
}
pub fn get(&self, name: &str) -> Option<std::sync::Arc<dyn ErasedTool>> {
self.tools
.read()
.unwrap_or_else(|e| panic!("Failed to acquire read lock on tools: {:?}", e))
.get(name)
.cloned()
}
pub fn get_all(&self) -> Vec<std::sync::Arc<dyn ErasedTool>> {
self.tools
.read()
.unwrap_or_else(|e| panic!("Failed to acquire read lock on tools: {:?}", e))
.values()
.cloned()
.collect()
}
}
#[derive(
Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone, PartialOrd, Ord, Copy,
)]
pub struct Timestamp(pub i64);
crate::register_channel_name!(Timestamp, "timestamp");
#[derive(
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Debug,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
pub struct SpaceId(pub String);
#[derive(
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Debug,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
pub struct ThreadId(pub String);
#[derive(
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Debug,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
Default,
)]
pub struct MessageId(pub String);
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
pub struct AiSpoken(pub String);
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Debug, Clone)]
pub struct AiWritten {
pub target_channel: MessageChannel,
pub text: String,
}
#[derive(
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Debug,
Clone,
derive_more::Display,
derive_more::From,
derive_more::Deref,
)]
pub struct CognitiveReasoning(pub String);
#[derive(
Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq, Eq,
)]
pub struct ObservedInteraction {
pub timestamp: Timestamp,
pub user_messages: Vec<PeerInput>,
pub ai_spoken: Option<AiSpoken>,
pub ai_written: Option<AiWritten>,
pub ai_reasoning: Option<CognitiveReasoning>,
}
crate::register_channel_name!(ObservedInteraction, "observed_interaction");
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, schemars::JsonSchema)]
pub struct NotClearInteraction {
pub timestamp: Timestamp,
pub user_messages: Vec<PeerInput>,
pub ai_spoken: Option<AiSpoken>,
pub ai_written: Option<AiWritten>,
}
crate::register_channel_name!(NotClearInteraction, "not_clear_interaction");
#[derive(
Serialize,
Deserialize,
PartialEq,
Eq,
Debug,
Clone,
Default,
schemars::JsonSchema,
derive_more::Deref,
derive_more::DerefMut,
derive_more::IntoIterator,
)]
pub struct NotClearInteractionMemory(pub std::collections::VecDeque<NotClearInteraction>);
impl From<Vec<NotClearInteraction>> for NotClearInteractionMemory {
fn from(value: Vec<NotClearInteraction>) -> Self {
Self(value.into())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct CameraInputFrame {
pub data: Vec<u8>,
}
crate::register_channel_name!(CameraInputFrame, "camera_input_frame");