Skip to main content

distri_types/
stores.rs

1use crate::{
2    ScratchpadEntry, ToolAuthStore, ToolResponse, configuration::PluginArtifact,
3    workflow::WorkflowStore,
4};
5use async_trait::async_trait;
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize, de::DeserializeOwned};
8use serde_json::Value;
9use std::{collections::HashMap, sync::Arc};
10use tokio::sync::oneshot;
11use uuid::Uuid;
12
13use crate::{
14    AgentEvent, CreateThreadRequest, Message, Task, TaskMessage, TaskStatus, Thread,
15    UpdateThreadRequest,
16};
17
18// Redis and PostgreSQL stores moved to distri-stores crate
19
20/// Filter for listing threads
21#[derive(Debug, Clone, Default, Serialize, Deserialize)]
22pub struct ThreadListFilter {
23    /// Filter by agent ID
24    pub agent_id: Option<String>,
25    /// Filter by external ID (for integration with external systems)
26    pub external_id: Option<String>,
27    /// Filter by thread attributes (JSON matching)
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub attributes: Option<serde_json::Value>,
30    /// Full-text search across title and last_message
31    pub search: Option<String>,
32    /// Filter threads updated after this time
33    pub from_date: Option<DateTime<Utc>>,
34    /// Filter threads updated before this time
35    pub to_date: Option<DateTime<Utc>>,
36    /// Filter by tags (array of tag strings to match)
37    pub tags: Option<Vec<String>>,
38}
39
40/// Paginated response for thread listing
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ThreadListResponse {
43    pub threads: Vec<crate::ThreadSummary>,
44    pub total: i64,
45    pub page: u32,
46    pub page_size: u32,
47}
48
49/// Agent usage information for sorting agents by thread count
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct AgentUsageInfo {
52    pub agent_id: String,
53    pub agent_name: String,
54    pub thread_count: i64,
55}
56
57/// Initialized store collection
58#[derive(Clone)]
59pub struct InitializedStores {
60    pub session_store: Arc<dyn SessionStore>,
61    pub agent_store: Arc<dyn AgentStore>,
62    pub task_store: Arc<dyn TaskStore>,
63    pub thread_store: Arc<dyn ThreadStore>,
64    pub tool_auth_store: Arc<dyn ToolAuthStore>,
65    pub scratchpad_store: Arc<dyn ScratchpadStore>,
66    pub workflow_store: Arc<dyn WorkflowStore>,
67    pub memory_store: Option<Arc<dyn MemoryStore>>,
68    pub crawl_store: Option<Arc<dyn CrawlStore>>,
69    pub external_tool_calls_store: Arc<dyn ExternalToolCallsStore>,
70    pub plugin_store: Arc<dyn PluginCatalogStore>,
71    pub prompt_template_store: Option<Arc<dyn PromptTemplateStore>>,
72    pub secret_store: Option<Arc<dyn SecretStore>>,
73    /// Plugin tool loader for dynamic tool resolution
74    /// OSS: uses registry-based loader (filesystem plugins)
75    /// Cloud: uses DB-based loader (tenant plugins)
76    pub plugin_tool_loader: Option<Arc<dyn PluginToolLoader>>,
77    pub skill_store: Option<Arc<dyn SkillStore>>,
78}
79impl InitializedStores {
80    pub fn set_tool_auth_store(&mut self, tool_auth_store: Arc<dyn ToolAuthStore>) {
81        self.tool_auth_store = tool_auth_store;
82    }
83
84    pub fn set_external_tool_calls_store(mut self, store: Arc<dyn ExternalToolCallsStore>) {
85        self.external_tool_calls_store = store;
86    }
87
88    pub fn set_session_store(&mut self, session_store: Arc<dyn SessionStore>) {
89        self.session_store = session_store;
90    }
91
92    pub fn set_agent_store(&mut self, agent_store: Arc<dyn AgentStore>) {
93        self.agent_store = agent_store;
94    }
95
96    pub fn with_task_store(&mut self, task_store: Arc<dyn TaskStore>) {
97        self.task_store = task_store;
98    }
99
100    pub fn with_thread_store(&mut self, thread_store: Arc<dyn ThreadStore>) {
101        self.thread_store = thread_store;
102    }
103
104    pub fn with_scratchpad_store(&mut self, scratchpad_store: Arc<dyn ScratchpadStore>) {
105        self.scratchpad_store = scratchpad_store;
106    }
107
108    pub fn with_workflow_store(mut self, workflow_store: Arc<dyn WorkflowStore>) {
109        self.workflow_store = workflow_store;
110    }
111
112    pub fn with_plugin_store(&mut self, plugin_store: Arc<dyn PluginCatalogStore>) {
113        self.plugin_store = plugin_store;
114    }
115}
116
117impl std::fmt::Debug for InitializedStores {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        f.debug_struct("InitializedStores").finish()
120    }
121}
122
123#[derive(Debug, Serialize, Deserialize, Clone)]
124pub struct SessionSummary {
125    pub session_id: String,
126    pub keys: Vec<String>,
127    pub key_count: usize,
128    pub updated_at: Option<DateTime<Utc>>,
129}
130
131// SessionStore trait - manages current conversation thread/run
132#[async_trait::async_trait]
133pub trait SessionStore: Send + Sync + std::fmt::Debug {
134    async fn clear_session(&self, namespace: &str) -> anyhow::Result<()>;
135
136    async fn set_value(&self, namespace: &str, key: &str, value: &Value) -> anyhow::Result<()>;
137
138    async fn set_value_with_expiry(
139        &self,
140        namespace: &str,
141        key: &str,
142        value: &Value,
143        expiry: Option<chrono::DateTime<chrono::Utc>>,
144    ) -> anyhow::Result<()>;
145
146    async fn get_value(&self, namespace: &str, key: &str) -> anyhow::Result<Option<Value>>;
147
148    async fn delete_value(&self, namespace: &str, key: &str) -> anyhow::Result<()>;
149
150    async fn get_all_values(&self, namespace: &str) -> anyhow::Result<HashMap<String, Value>>;
151
152    async fn list_sessions(
153        &self,
154        namespace: Option<&str>,
155        limit: Option<usize>,
156        offset: Option<usize>,
157    ) -> anyhow::Result<Vec<SessionSummary>>;
158}
159#[async_trait::async_trait]
160pub trait SessionStoreExt: SessionStore {
161    async fn set<T: Serialize + Sync>(
162        &self,
163        namespace: &str,
164        key: &str,
165        value: &T,
166    ) -> anyhow::Result<()> {
167        self.set_value(namespace, key, &serde_json::to_value(value)?)
168            .await
169    }
170    async fn set_with_expiry<T: Serialize + Sync>(
171        &self,
172        namespace: &str,
173        key: &str,
174        value: &T,
175        expiry: Option<chrono::DateTime<chrono::Utc>>,
176    ) -> anyhow::Result<()> {
177        self.set_value_with_expiry(namespace, key, &serde_json::to_value(value)?, expiry)
178            .await
179    }
180    async fn get<T: DeserializeOwned + Sync>(
181        &self,
182        namespace: &str,
183        key: &str,
184    ) -> anyhow::Result<Option<T>> {
185        match self.get_value(namespace, key).await? {
186            Some(b) => Ok(Some(serde_json::from_value(b)?)),
187            None => Ok(None),
188        }
189    }
190}
191impl<T: SessionStore + ?Sized> SessionStoreExt for T {}
192
193// Higher-level MemoryStore trait - manages cross-session permanent memory using user_id
194#[async_trait::async_trait]
195pub trait MemoryStore: Send + Sync {
196    /// Store permanent memory from a session for cross-session access
197    async fn store_memory(
198        &self,
199        user_id: &str,
200        session_memory: SessionMemory,
201    ) -> anyhow::Result<()>;
202
203    /// Search for relevant memories across sessions for a user
204    async fn search_memories(
205        &self,
206        user_id: &str,
207        query: &str,
208        limit: Option<usize>,
209    ) -> anyhow::Result<Vec<String>>;
210
211    /// Get all permanent memories for a user
212    async fn get_user_memories(&self, user_id: &str) -> anyhow::Result<Vec<String>>;
213
214    /// Clear all memories for a user
215    async fn clear_user_memories(&self, user_id: &str) -> anyhow::Result<()>;
216}
217
218#[derive(Debug, Clone)]
219pub struct SessionMemory {
220    pub agent_id: String,
221    pub thread_id: String,
222    pub session_summary: String,
223    pub key_insights: Vec<String>,
224    pub important_facts: Vec<String>,
225    pub timestamp: chrono::DateTime<chrono::Utc>,
226}
227#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
228#[serde(tag = "type", rename_all = "snake_case")]
229pub enum FilterMessageType {
230    Events,
231    Messages,
232    Artifacts,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct MessageFilter {
237    pub filter: Option<Vec<FilterMessageType>>,
238    pub limit: Option<usize>,
239    pub offset: Option<usize>,
240}
241
242// Task Store trait for A2A task management
243#[async_trait]
244pub trait TaskStore: Send + Sync {
245    fn init_task(
246        &self,
247        context_id: &str,
248        task_id: Option<&str>,
249        status: Option<TaskStatus>,
250    ) -> Task {
251        let task_id = task_id.unwrap_or(&Uuid::new_v4().to_string()).to_string();
252        Task {
253            id: task_id,
254            status: status.unwrap_or(TaskStatus::Pending),
255            created_at: chrono::Utc::now().timestamp_millis(),
256            updated_at: chrono::Utc::now().timestamp_millis(),
257            thread_id: context_id.to_string(),
258            parent_task_id: None,
259        }
260    }
261    async fn get_or_create_task(
262        &self,
263        thread_id: &str,
264        task_id: &str,
265    ) -> Result<(), anyhow::Error> {
266        match self.get_task(&task_id).await? {
267            Some(task) => task,
268            None => {
269                self.create_task(&thread_id, Some(&task_id), Some(TaskStatus::Running))
270                    .await?
271            }
272        };
273
274        Ok(())
275    }
276    async fn create_task(
277        &self,
278        context_id: &str,
279        task_id: Option<&str>,
280        task_status: Option<TaskStatus>,
281    ) -> anyhow::Result<Task>;
282    async fn get_task(&self, task_id: &str) -> anyhow::Result<Option<Task>>;
283    async fn update_task_status(&self, task_id: &str, status: TaskStatus) -> anyhow::Result<()>;
284    async fn add_event_to_task(&self, task_id: &str, event: AgentEvent) -> anyhow::Result<()>;
285    async fn add_message_to_task(&self, task_id: &str, message: &Message) -> anyhow::Result<()>;
286    async fn cancel_task(&self, task_id: &str) -> anyhow::Result<Task>;
287    async fn list_tasks(&self, thread_id: Option<&str>) -> anyhow::Result<Vec<Task>>;
288
289    async fn get_history(
290        &self,
291        thread_id: &str,
292        filter: Option<MessageFilter>,
293    ) -> anyhow::Result<Vec<(Task, Vec<TaskMessage>)>>;
294
295    async fn update_parent_task(
296        &self,
297        task_id: &str,
298        parent_task_id: Option<&str>,
299    ) -> anyhow::Result<()>;
300}
301
302#[derive(Debug, Clone)]
303pub struct PluginMetadataRecord {
304    pub package_name: String,
305    pub version: Option<String>,
306    pub object_prefix: String,
307    pub entrypoint: Option<String>,
308    pub artifact: PluginArtifact,
309    pub updated_at: chrono::DateTime<chrono::Utc>,
310}
311
312#[async_trait]
313pub trait PluginCatalogStore: Send + Sync {
314    async fn list_plugins(&self) -> anyhow::Result<Vec<PluginMetadataRecord>>;
315
316    async fn get_plugin(&self, package_name: &str) -> anyhow::Result<Option<PluginMetadataRecord>>;
317
318    async fn upsert_plugin(&self, record: &PluginMetadataRecord) -> anyhow::Result<()>;
319
320    async fn remove_plugin(&self, package_name: &str) -> anyhow::Result<()>;
321
322    async fn clear(&self) -> anyhow::Result<()>;
323}
324
325// Thread Store trait for thread management
326#[async_trait]
327pub trait ThreadStore: Send + Sync {
328    fn as_any(&self) -> &dyn std::any::Any;
329    async fn create_thread(&self, request: CreateThreadRequest) -> anyhow::Result<Thread>;
330    async fn get_thread(&self, thread_id: &str) -> anyhow::Result<Option<Thread>>;
331    async fn update_thread(
332        &self,
333        thread_id: &str,
334        request: UpdateThreadRequest,
335    ) -> anyhow::Result<Thread>;
336    async fn delete_thread(&self, thread_id: &str) -> anyhow::Result<()>;
337
338    /// List threads with pagination and filtering
339    /// Returns a paginated response with total count
340    async fn list_threads(
341        &self,
342        filter: &ThreadListFilter,
343        limit: Option<u32>,
344        offset: Option<u32>,
345    ) -> anyhow::Result<ThreadListResponse>;
346
347    async fn update_thread_with_message(
348        &self,
349        thread_id: &str,
350        message: &str,
351    ) -> anyhow::Result<()>;
352
353    /// Get aggregated home statistics
354    async fn get_home_stats(&self) -> anyhow::Result<HomeStats>;
355
356    /// Get agents sorted by thread count (most active first)
357    /// Includes all registered agents (even those with 0 threads).
358    /// Optionally filters by name using a search string.
359    async fn get_agents_by_usage(&self, search: Option<&str>) -> anyhow::Result<Vec<AgentUsageInfo>>;
360
361    /// Get a map of agent name -> stats for all agents with activity
362    async fn get_agent_stats_map(
363        &self,
364    ) -> anyhow::Result<std::collections::HashMap<String, AgentStatsInfo>>;
365
366    // ========== Message Read Status Methods ==========
367
368    /// Mark a message as read by the current user
369    async fn mark_message_read(
370        &self,
371        thread_id: &str,
372        message_id: &str,
373    ) -> anyhow::Result<MessageReadStatus>;
374
375    /// Get read status for a specific message
376    async fn get_message_read_status(
377        &self,
378        thread_id: &str,
379        message_id: &str,
380    ) -> anyhow::Result<Option<MessageReadStatus>>;
381
382    /// Get read status for all messages in a thread for the current user
383    async fn get_thread_read_status(
384        &self,
385        thread_id: &str,
386    ) -> anyhow::Result<Vec<MessageReadStatus>>;
387
388    // ========== Message Voting Methods ==========
389
390    /// Vote on a message (upvote or downvote)
391    /// For downvotes, a comment is required
392    async fn vote_message(&self, request: VoteMessageRequest) -> anyhow::Result<MessageVote>;
393
394    /// Remove a vote from a message
395    async fn remove_vote(&self, thread_id: &str, message_id: &str) -> anyhow::Result<()>;
396
397    /// Get the current user's vote on a message
398    async fn get_user_vote(
399        &self,
400        thread_id: &str,
401        message_id: &str,
402    ) -> anyhow::Result<Option<MessageVote>>;
403
404    /// Get vote summary for a message (counts + current user's vote)
405    async fn get_message_vote_summary(
406        &self,
407        thread_id: &str,
408        message_id: &str,
409    ) -> anyhow::Result<MessageVoteSummary>;
410
411    /// Get all votes for a message (admin/analytics use)
412    async fn get_message_votes(
413        &self,
414        thread_id: &str,
415        message_id: &str,
416    ) -> anyhow::Result<Vec<MessageVote>>;
417}
418
419/// Home statistics for dashboard
420#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
421pub struct HomeStats {
422    pub total_agents: i64,
423    pub total_threads: i64,
424    pub total_messages: i64,
425    pub avg_run_time_ms: Option<f64>,
426    // Cloud-specific fields (optional)
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub total_owned_agents: Option<i64>,
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub total_accessible_agents: Option<i64>,
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub most_active_agent: Option<MostActiveAgent>,
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub latest_threads: Option<Vec<LatestThreadInfo>>,
435    /// Recently used agents (last 10 by most recent thread activity)
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub recently_used_agents: Option<Vec<RecentlyUsedAgent>>,
438    /// Custom metrics that can be displayed in the stats overview
439    /// Key is the metric name (e.g., "usage"), value is the metric data
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub custom_metrics: Option<std::collections::HashMap<String, CustomMetric>>,
442}
443
444/// A custom metric for display in the stats overview
445#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
446pub struct CustomMetric {
447    /// Display label (e.g., "Monthly Calls")
448    pub label: String,
449    /// Current value as a string (formatted)
450    pub value: String,
451    /// Optional helper text below the value
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub helper: Option<String>,
454    /// Optional limit (for progress display)
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub limit: Option<String>,
457    /// Optional raw numeric value for calculations
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub raw_value: Option<i64>,
460    /// Optional raw limit for calculations
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub raw_limit: Option<i64>,
463}
464
465#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
466pub struct MostActiveAgent {
467    pub id: String,
468    pub name: String,
469    pub thread_count: i64,
470}
471
472/// Agent that was recently used (based on thread activity)
473#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
474pub struct RecentlyUsedAgent {
475    pub id: String,
476    pub name: String,
477    pub description: Option<String>,
478    pub last_used_at: chrono::DateTime<chrono::Utc>,
479}
480
481#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
482pub struct LatestThreadInfo {
483    pub id: String,
484    pub title: String,
485    pub agent_id: String,
486    pub agent_name: String,
487    pub updated_at: chrono::DateTime<chrono::Utc>,
488}
489
490/// Agent statistics for display
491#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
492pub struct AgentStatsInfo {
493    pub thread_count: i64,
494    pub sub_agent_usage_count: i64,
495    pub last_used_at: Option<chrono::DateTime<chrono::Utc>>,
496}
497
498#[async_trait]
499pub trait AgentStore: Send + Sync {
500    async fn list(
501        &self,
502        cursor: Option<String>,
503        limit: Option<usize>,
504    ) -> (Vec<crate::configuration::AgentConfig>, Option<String>);
505
506    async fn get(&self, name: &str) -> Option<crate::configuration::AgentConfig>;
507    async fn register(&self, config: crate::configuration::AgentConfig) -> anyhow::Result<()>;
508    /// Update an existing agent with new definition
509    async fn update(&self, config: crate::configuration::AgentConfig) -> anyhow::Result<()>;
510
511    async fn clear(&self) -> anyhow::Result<()>;
512}
513
514/// Store for managing scratchpad entries across conversations
515#[async_trait::async_trait]
516pub trait ScratchpadStore: Send + Sync + std::fmt::Debug {
517    /// Add a scratchpad entry for a specific thread
518    async fn add_entry(
519        &self,
520        thread_id: &str,
521        entry: ScratchpadEntry,
522    ) -> Result<(), crate::AgentError>;
523
524    /// Clear all scratchpad entries for a thread
525    async fn clear_entries(&self, thread_id: &str) -> Result<(), crate::AgentError>;
526
527    /// Get entries for a specific task within a thread
528    async fn get_entries(
529        &self,
530        thread_id: &str,
531        task_id: &str,
532        limit: Option<usize>,
533    ) -> Result<Vec<ScratchpadEntry>, crate::AgentError>;
534
535    async fn get_all_entries(
536        &self,
537        thread_id: &str,
538        limit: Option<usize>,
539    ) -> Result<Vec<ScratchpadEntry>, crate::AgentError>;
540}
541
542/// Web crawl result data
543#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct CrawlResult {
545    pub id: String,
546    pub url: String,
547    pub title: Option<String>,
548    pub content: String,
549    pub html: Option<String>,
550    pub metadata: serde_json::Value,
551    pub links: Vec<String>,
552    pub images: Vec<String>,
553    pub status_code: Option<u16>,
554    pub crawled_at: chrono::DateTime<chrono::Utc>,
555    pub processing_time_ms: Option<u64>,
556}
557
558/// Store for managing web crawl results
559#[async_trait]
560pub trait CrawlStore: Send + Sync {
561    /// Store a crawl result
562    async fn store_crawl_result(&self, result: CrawlResult) -> anyhow::Result<String>;
563
564    /// Get a crawl result by ID
565    async fn get_crawl_result(&self, id: &str) -> anyhow::Result<Option<CrawlResult>>;
566
567    /// Get crawl results for a specific URL
568    async fn get_crawl_results_by_url(&self, url: &str) -> anyhow::Result<Vec<CrawlResult>>;
569
570    /// Get recent crawl results (within time limit)
571    async fn get_recent_crawl_results(
572        &self,
573        limit: Option<usize>,
574        since: Option<chrono::DateTime<chrono::Utc>>,
575    ) -> anyhow::Result<Vec<CrawlResult>>;
576
577    /// Check if URL was recently crawled (within cache duration)
578    async fn is_url_recently_crawled(
579        &self,
580        url: &str,
581        cache_duration: chrono::Duration,
582    ) -> anyhow::Result<Option<CrawlResult>>;
583
584    /// Delete crawl result
585    async fn delete_crawl_result(&self, id: &str) -> anyhow::Result<()>;
586
587    /// Clear all crawl results older than specified date
588    async fn cleanup_old_results(
589        &self,
590        before: chrono::DateTime<chrono::Utc>,
591    ) -> anyhow::Result<usize>;
592}
593
594// ========== Message Read & Voting Types ==========
595
596/// Vote type for message feedback
597#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
598#[serde(rename_all = "lowercase")]
599pub enum VoteType {
600    Upvote,
601    Downvote,
602}
603
604/// Record of a message being read
605#[derive(Debug, Clone, Serialize, Deserialize)]
606pub struct MessageReadStatus {
607    pub thread_id: String,
608    pub message_id: String,
609    pub user_id: String,
610    pub read_at: chrono::DateTime<chrono::Utc>,
611}
612
613/// Request to mark a message as read
614#[derive(Debug, Clone, Serialize, Deserialize)]
615pub struct MarkMessageReadRequest {
616    pub thread_id: String,
617    pub message_id: String,
618}
619
620/// A vote on a message with optional feedback comment
621#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct MessageVote {
623    pub id: String,
624    pub thread_id: String,
625    pub message_id: String,
626    pub user_id: String,
627    pub vote_type: VoteType,
628    /// Comment is required for downvotes, optional for upvotes
629    pub comment: Option<String>,
630    pub created_at: chrono::DateTime<chrono::Utc>,
631    pub updated_at: chrono::DateTime<chrono::Utc>,
632}
633
634/// Request to vote on a message
635#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct VoteMessageRequest {
637    pub thread_id: String,
638    pub message_id: String,
639    pub vote_type: VoteType,
640    /// Required for downvotes
641    pub comment: Option<String>,
642}
643
644/// Summary of votes for a message
645#[derive(Debug, Clone, Serialize, Deserialize, Default)]
646pub struct MessageVoteSummary {
647    pub message_id: String,
648    pub upvotes: i64,
649    pub downvotes: i64,
650    /// Current user's vote on this message, if any
651    pub user_vote: Option<VoteType>,
652}
653
654/// Store for managing external tool call completions using oneshot channels
655#[async_trait]
656pub trait ExternalToolCallsStore: Send + Sync + std::fmt::Debug {
657    /// Register a new external tool call session and return a receiver for the response
658    async fn register_external_tool_call(
659        &self,
660        session_id: &str,
661    ) -> anyhow::Result<oneshot::Receiver<ToolResponse>>;
662
663    /// Complete an external tool call by sending the response
664    async fn complete_external_tool_call(
665        &self,
666        session_id: &str,
667        tool_response: ToolResponse,
668    ) -> anyhow::Result<()>;
669
670    /// Remove a session (cleanup)
671    async fn remove_tool_call(&self, session_id: &str) -> anyhow::Result<()>;
672
673    /// List all pending sessions (for debugging)
674    async fn list_pending_tool_calls(&self) -> anyhow::Result<Vec<String>>;
675}
676
677// ========== Prompt Template Store ==========
678
679#[derive(Debug, Clone, Serialize, Deserialize)]
680pub struct PromptTemplateRecord {
681    pub id: String,
682    pub name: String,
683    pub template: String,
684    pub description: Option<String>,
685    pub version: Option<String>,
686    pub is_system: bool,
687    pub created_at: chrono::DateTime<chrono::Utc>,
688    pub updated_at: chrono::DateTime<chrono::Utc>,
689}
690
691#[derive(Debug, Clone, Serialize, Deserialize)]
692pub struct NewPromptTemplate {
693    pub name: String,
694    pub template: String,
695    pub description: Option<String>,
696    pub version: Option<String>,
697    #[serde(default)]
698    pub is_system: bool,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
702pub struct UpdatePromptTemplate {
703    pub name: String,
704    pub template: String,
705    pub description: Option<String>,
706}
707
708#[async_trait]
709pub trait PromptTemplateStore: Send + Sync {
710    async fn list(&self) -> anyhow::Result<Vec<PromptTemplateRecord>>;
711    async fn get(&self, id: &str) -> anyhow::Result<Option<PromptTemplateRecord>>;
712    async fn create(&self, template: NewPromptTemplate) -> anyhow::Result<PromptTemplateRecord>;
713    async fn update(
714        &self,
715        id: &str,
716        update: UpdatePromptTemplate,
717    ) -> anyhow::Result<PromptTemplateRecord>;
718    async fn delete(&self, id: &str) -> anyhow::Result<()>;
719    async fn clone_template(&self, id: &str) -> anyhow::Result<PromptTemplateRecord>;
720    async fn sync_system_templates(&self, templates: Vec<NewPromptTemplate>) -> anyhow::Result<()>;
721}
722
723// ========== Plugin Tool Loader ==========
724
725/// Trait for loading plugin tools dynamically.
726/// This abstraction allows different implementations for:
727/// - OSS: loads from filesystem/plugin registry
728/// - Cloud: loads from database (tenant plugins)
729///
730/// The loader is used by resolve_tools_config to get tools based on ToolsConfig.
731#[async_trait]
732pub trait PluginToolLoader: Send + Sync + std::fmt::Debug {
733    /// List all available package names (for wildcard resolution)
734    async fn list_packages(&self) -> anyhow::Result<Vec<String>>;
735
736    /// Get all tools for a specific package
737    async fn get_package_tools(&self, package_name: &str) -> anyhow::Result<Vec<Arc<dyn crate::Tool>>>;
738
739    /// Check if a package exists
740    async fn has_package(&self, package_name: &str) -> anyhow::Result<bool>;
741}
742
743// ========== Secret Store ==========
744
745#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct SecretRecord {
747    pub id: String,
748    pub key: String,
749    pub value: String,
750    pub created_at: chrono::DateTime<chrono::Utc>,
751    pub updated_at: chrono::DateTime<chrono::Utc>,
752}
753
754#[derive(Debug, Clone, Serialize, Deserialize)]
755pub struct NewSecret {
756    pub key: String,
757    pub value: String,
758}
759
760#[async_trait]
761pub trait SecretStore: Send + Sync {
762    async fn list(&self) -> anyhow::Result<Vec<SecretRecord>>;
763    async fn get(&self, key: &str) -> anyhow::Result<Option<SecretRecord>>;
764    async fn create(&self, secret: NewSecret) -> anyhow::Result<SecretRecord>;
765    async fn update(&self, key: &str, value: &str) -> anyhow::Result<SecretRecord>;
766    async fn delete(&self, key: &str) -> anyhow::Result<()>;
767}
768
769// ========== Skill Store ==========
770
771#[derive(Debug, Clone, Serialize, Deserialize)]
772pub struct SkillRecord {
773    pub id: String,
774    pub name: String,
775    pub description: Option<String>,
776    pub content: String,
777    pub tags: Vec<String>,
778    pub is_public: bool,
779    pub is_system: bool,
780    pub star_count: i32,
781    pub clone_count: i32,
782    pub scripts: Vec<SkillScriptRecord>,
783    pub created_at: chrono::DateTime<chrono::Utc>,
784    pub updated_at: chrono::DateTime<chrono::Utc>,
785}
786
787#[derive(Debug, Clone, Serialize, Deserialize)]
788pub struct SkillScriptRecord {
789    pub id: String,
790    pub skill_id: String,
791    pub name: String,
792    pub description: Option<String>,
793    pub code: String,
794    pub language: String,
795    pub created_at: chrono::DateTime<chrono::Utc>,
796    pub updated_at: chrono::DateTime<chrono::Utc>,
797}
798
799#[derive(Debug, Clone, Serialize, Deserialize)]
800pub struct NewSkill {
801    pub name: String,
802    pub description: Option<String>,
803    pub content: String,
804    #[serde(default)]
805    pub tags: Vec<String>,
806    #[serde(default)]
807    pub is_public: bool,
808    #[serde(default)]
809    pub scripts: Vec<NewSkillScript>,
810}
811
812#[derive(Debug, Clone, Serialize, Deserialize)]
813pub struct NewSkillScript {
814    pub name: String,
815    pub description: Option<String>,
816    pub code: String,
817    #[serde(default = "default_script_language")]
818    pub language: String,
819}
820
821fn default_script_language() -> String {
822    "javascript".to_string()
823}
824
825#[derive(Debug, Clone, Serialize, Deserialize)]
826pub struct UpdateSkill {
827    pub name: Option<String>,
828    pub description: Option<String>,
829    pub content: Option<String>,
830    pub tags: Option<Vec<String>>,
831    pub is_public: Option<bool>,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize)]
835pub struct UpdateSkillScript {
836    pub name: Option<String>,
837    pub description: Option<String>,
838    pub code: Option<String>,
839    pub language: Option<String>,
840}
841
842#[async_trait]
843pub trait SkillStore: Send + Sync {
844    async fn list_skills(&self) -> anyhow::Result<Vec<SkillRecord>>;
845    async fn get_skill(&self, id: &str) -> anyhow::Result<Option<SkillRecord>>;
846    async fn create_skill(&self, skill: NewSkill) -> anyhow::Result<SkillRecord>;
847    async fn update_skill(&self, id: &str, update: UpdateSkill) -> anyhow::Result<SkillRecord>;
848    async fn delete_skill(&self, id: &str) -> anyhow::Result<()>;
849
850    // Script management
851    async fn add_script(&self, skill_id: &str, script: NewSkillScript) -> anyhow::Result<SkillScriptRecord>;
852    async fn update_script(&self, script_id: &str, update: UpdateSkillScript) -> anyhow::Result<SkillScriptRecord>;
853    async fn delete_script(&self, script_id: &str) -> anyhow::Result<()>;
854
855    // Discovery
856    async fn list_public_skills(&self) -> anyhow::Result<Vec<SkillRecord>>;
857    async fn star_skill(&self, skill_id: &str) -> anyhow::Result<()>;
858    async fn unstar_skill(&self, skill_id: &str) -> anyhow::Result<()>;
859    async fn list_starred_skills(&self) -> anyhow::Result<Vec<SkillRecord>>;
860    async fn clone_skill(&self, skill_id: &str) -> anyhow::Result<SkillRecord>;
861}