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