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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
22pub struct ThreadListFilter {
23 pub agent_id: Option<String>,
25 pub external_id: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub attributes: Option<serde_json::Value>,
30 pub search: Option<String>,
32 pub from_date: Option<DateTime<Utc>>,
34 pub to_date: Option<DateTime<Utc>>,
36 pub tags: Option<Vec<String>>,
38}
39
40#[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#[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#[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 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#[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#[async_trait::async_trait]
195pub trait MemoryStore: Send + Sync {
196 async fn store_memory(
198 &self,
199 user_id: &str,
200 session_memory: SessionMemory,
201 ) -> anyhow::Result<()>;
202
203 async fn search_memories(
205 &self,
206 user_id: &str,
207 query: &str,
208 limit: Option<usize>,
209 ) -> anyhow::Result<Vec<String>>;
210
211 async fn get_user_memories(&self, user_id: &str) -> anyhow::Result<Vec<String>>;
213
214 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#[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#[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 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 async fn get_home_stats(&self) -> anyhow::Result<HomeStats>;
355
356 async fn get_agents_by_usage(&self, search: Option<&str>) -> anyhow::Result<Vec<AgentUsageInfo>>;
360
361 async fn get_agent_stats_map(
363 &self,
364 ) -> anyhow::Result<std::collections::HashMap<String, AgentStatsInfo>>;
365
366 async fn mark_message_read(
370 &self,
371 thread_id: &str,
372 message_id: &str,
373 ) -> anyhow::Result<MessageReadStatus>;
374
375 async fn get_message_read_status(
377 &self,
378 thread_id: &str,
379 message_id: &str,
380 ) -> anyhow::Result<Option<MessageReadStatus>>;
381
382 async fn get_thread_read_status(
384 &self,
385 thread_id: &str,
386 ) -> anyhow::Result<Vec<MessageReadStatus>>;
387
388 async fn vote_message(&self, request: VoteMessageRequest) -> anyhow::Result<MessageVote>;
393
394 async fn remove_vote(&self, thread_id: &str, message_id: &str) -> anyhow::Result<()>;
396
397 async fn get_user_vote(
399 &self,
400 thread_id: &str,
401 message_id: &str,
402 ) -> anyhow::Result<Option<MessageVote>>;
403
404 async fn get_message_vote_summary(
406 &self,
407 thread_id: &str,
408 message_id: &str,
409 ) -> anyhow::Result<MessageVoteSummary>;
410
411 async fn get_message_votes(
413 &self,
414 thread_id: &str,
415 message_id: &str,
416 ) -> anyhow::Result<Vec<MessageVote>>;
417}
418
419#[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 #[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 #[serde(skip_serializing_if = "Option::is_none")]
437 pub recently_used_agents: Option<Vec<RecentlyUsedAgent>>,
438 #[serde(skip_serializing_if = "Option::is_none")]
441 pub custom_metrics: Option<std::collections::HashMap<String, CustomMetric>>,
442}
443
444#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
446pub struct CustomMetric {
447 pub label: String,
449 pub value: String,
451 #[serde(skip_serializing_if = "Option::is_none")]
453 pub helper: Option<String>,
454 #[serde(skip_serializing_if = "Option::is_none")]
456 pub limit: Option<String>,
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub raw_value: Option<i64>,
460 #[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#[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#[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 async fn update(&self, config: crate::configuration::AgentConfig) -> anyhow::Result<()>;
510
511 async fn clear(&self) -> anyhow::Result<()>;
512}
513
514#[async_trait::async_trait]
516pub trait ScratchpadStore: Send + Sync + std::fmt::Debug {
517 async fn add_entry(
519 &self,
520 thread_id: &str,
521 entry: ScratchpadEntry,
522 ) -> Result<(), crate::AgentError>;
523
524 async fn clear_entries(&self, thread_id: &str) -> Result<(), crate::AgentError>;
526
527 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#[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#[async_trait]
560pub trait CrawlStore: Send + Sync {
561 async fn store_crawl_result(&self, result: CrawlResult) -> anyhow::Result<String>;
563
564 async fn get_crawl_result(&self, id: &str) -> anyhow::Result<Option<CrawlResult>>;
566
567 async fn get_crawl_results_by_url(&self, url: &str) -> anyhow::Result<Vec<CrawlResult>>;
569
570 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 async fn is_url_recently_crawled(
579 &self,
580 url: &str,
581 cache_duration: chrono::Duration,
582 ) -> anyhow::Result<Option<CrawlResult>>;
583
584 async fn delete_crawl_result(&self, id: &str) -> anyhow::Result<()>;
586
587 async fn cleanup_old_results(
589 &self,
590 before: chrono::DateTime<chrono::Utc>,
591 ) -> anyhow::Result<usize>;
592}
593
594#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
598#[serde(rename_all = "lowercase")]
599pub enum VoteType {
600 Upvote,
601 Downvote,
602}
603
604#[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#[derive(Debug, Clone, Serialize, Deserialize)]
615pub struct MarkMessageReadRequest {
616 pub thread_id: String,
617 pub message_id: String,
618}
619
620#[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 pub comment: Option<String>,
630 pub created_at: chrono::DateTime<chrono::Utc>,
631 pub updated_at: chrono::DateTime<chrono::Utc>,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct VoteMessageRequest {
637 pub thread_id: String,
638 pub message_id: String,
639 pub vote_type: VoteType,
640 pub comment: Option<String>,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize, Default)]
646pub struct MessageVoteSummary {
647 pub message_id: String,
648 pub upvotes: i64,
649 pub downvotes: i64,
650 pub user_vote: Option<VoteType>,
652}
653
654#[async_trait]
656pub trait ExternalToolCallsStore: Send + Sync + std::fmt::Debug {
657 async fn register_external_tool_call(
659 &self,
660 session_id: &str,
661 ) -> anyhow::Result<oneshot::Receiver<ToolResponse>>;
662
663 async fn complete_external_tool_call(
665 &self,
666 session_id: &str,
667 tool_response: ToolResponse,
668 ) -> anyhow::Result<()>;
669
670 async fn remove_tool_call(&self, session_id: &str) -> anyhow::Result<()>;
672
673 async fn list_pending_tool_calls(&self) -> anyhow::Result<Vec<String>>;
675}
676
677#[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#[async_trait]
732pub trait PluginToolLoader: Send + Sync + std::fmt::Debug {
733 async fn list_packages(&self) -> anyhow::Result<Vec<String>>;
735
736 async fn get_package_tools(&self, package_name: &str) -> anyhow::Result<Vec<Arc<dyn crate::Tool>>>;
738
739 async fn has_package(&self, package_name: &str) -> anyhow::Result<bool>;
741}
742
743#[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#[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 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 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}