1use crate::{ScratchpadEntry, ToolAuthStore, ToolResponse};
2use async_trait::async_trait;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize, de::DeserializeOwned};
5use serde_json::Value;
6use std::{collections::HashMap, sync::Arc};
7use tokio::sync::oneshot;
8use uuid::Uuid;
9
10use crate::{
11 AgentEvent, CreateThreadRequest, Message, Task, TaskMessage, TaskStatus, Thread,
12 UpdateThreadRequest,
13};
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
19pub struct ThreadListFilter {
20 pub agent_id: Option<String>,
22 pub external_id: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub attributes: Option<serde_json::Value>,
27 pub search: Option<String>,
29 pub from_date: Option<DateTime<Utc>>,
31 pub to_date: Option<DateTime<Utc>>,
33 pub tags: Option<Vec<String>>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ThreadListResponse {
40 pub threads: Vec<crate::ThreadSummary>,
41 pub total: i64,
42 pub page: u32,
43 pub page_size: u32,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct AgentUsageInfo {
49 pub agent_id: String,
50 pub agent_name: String,
51 pub thread_count: i64,
52}
53
54#[derive(Clone)]
56pub struct InitializedStores {
57 pub session_store: Arc<dyn SessionStore>,
58 pub agent_store: Arc<dyn AgentStore>,
59 pub task_store: Arc<dyn TaskStore>,
60 pub thread_store: Arc<dyn ThreadStore>,
61 pub tool_auth_store: Arc<dyn ToolAuthStore>,
62 pub scratchpad_store: Arc<dyn ScratchpadStore>,
63 pub memory_store: Option<Arc<dyn MemoryStore>>,
64 pub crawl_store: Option<Arc<dyn CrawlStore>>,
65 pub external_tool_calls_store: Arc<dyn ExternalToolCallsStore>,
66 pub prompt_template_store: Option<Arc<dyn PromptTemplateStore>>,
67 pub secret_store: Option<Arc<dyn SecretStore>>,
68 pub skill_store: Option<Arc<dyn SkillStore>>,
69 pub workflow_store: Option<Arc<dyn WorkflowStore>>,
70}
71impl InitializedStores {
72 pub fn set_tool_auth_store(&mut self, tool_auth_store: Arc<dyn ToolAuthStore>) {
73 self.tool_auth_store = tool_auth_store;
74 }
75
76 pub fn set_external_tool_calls_store(mut self, store: Arc<dyn ExternalToolCallsStore>) {
77 self.external_tool_calls_store = store;
78 }
79
80 pub fn set_session_store(&mut self, session_store: Arc<dyn SessionStore>) {
81 self.session_store = session_store;
82 }
83
84 pub fn set_agent_store(&mut self, agent_store: Arc<dyn AgentStore>) {
85 self.agent_store = agent_store;
86 }
87
88 pub fn with_task_store(&mut self, task_store: Arc<dyn TaskStore>) {
89 self.task_store = task_store;
90 }
91
92 pub fn with_thread_store(&mut self, thread_store: Arc<dyn ThreadStore>) {
93 self.thread_store = thread_store;
94 }
95
96 pub fn with_scratchpad_store(&mut self, scratchpad_store: Arc<dyn ScratchpadStore>) {
97 self.scratchpad_store = scratchpad_store;
98 }
99}
100
101impl std::fmt::Debug for InitializedStores {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 f.debug_struct("InitializedStores").finish()
104 }
105}
106
107#[derive(Debug, Serialize, Deserialize, Clone)]
108pub struct SessionSummary {
109 pub session_id: String,
110 pub keys: Vec<String>,
111 pub key_count: usize,
112 pub updated_at: Option<DateTime<Utc>>,
113}
114
115#[async_trait::async_trait]
117pub trait SessionStore: Send + Sync + std::fmt::Debug {
118 async fn clear_session(&self, namespace: &str) -> anyhow::Result<()>;
119
120 async fn set_value(&self, namespace: &str, key: &str, value: &Value) -> anyhow::Result<()>;
121
122 async fn set_value_with_expiry(
123 &self,
124 namespace: &str,
125 key: &str,
126 value: &Value,
127 expiry: Option<chrono::DateTime<chrono::Utc>>,
128 ) -> anyhow::Result<()>;
129
130 async fn get_value(&self, namespace: &str, key: &str) -> anyhow::Result<Option<Value>>;
131
132 async fn delete_value(&self, namespace: &str, key: &str) -> anyhow::Result<()>;
133
134 async fn get_all_values(&self, namespace: &str) -> anyhow::Result<HashMap<String, Value>>;
135
136 async fn list_sessions(
137 &self,
138 namespace: Option<&str>,
139 limit: Option<usize>,
140 offset: Option<usize>,
141 ) -> anyhow::Result<Vec<SessionSummary>>;
142}
143#[async_trait::async_trait]
144pub trait SessionStoreExt: SessionStore {
145 async fn set<T: Serialize + Sync>(
146 &self,
147 namespace: &str,
148 key: &str,
149 value: &T,
150 ) -> anyhow::Result<()> {
151 self.set_value(namespace, key, &serde_json::to_value(value)?)
152 .await
153 }
154 async fn set_with_expiry<T: Serialize + Sync>(
155 &self,
156 namespace: &str,
157 key: &str,
158 value: &T,
159 expiry: Option<chrono::DateTime<chrono::Utc>>,
160 ) -> anyhow::Result<()> {
161 self.set_value_with_expiry(namespace, key, &serde_json::to_value(value)?, expiry)
162 .await
163 }
164 async fn get<T: DeserializeOwned + Sync>(
165 &self,
166 namespace: &str,
167 key: &str,
168 ) -> anyhow::Result<Option<T>> {
169 match self.get_value(namespace, key).await? {
170 Some(b) => Ok(Some(serde_json::from_value(b)?)),
171 None => Ok(None),
172 }
173 }
174}
175impl<T: SessionStore + ?Sized> SessionStoreExt for T {}
176
177#[async_trait::async_trait]
179pub trait MemoryStore: Send + Sync {
180 async fn store_memory(
182 &self,
183 user_id: &str,
184 session_memory: SessionMemory,
185 ) -> anyhow::Result<()>;
186
187 async fn search_memories(
189 &self,
190 user_id: &str,
191 query: &str,
192 limit: Option<usize>,
193 ) -> anyhow::Result<Vec<String>>;
194
195 async fn get_user_memories(&self, user_id: &str) -> anyhow::Result<Vec<String>>;
197
198 async fn clear_user_memories(&self, user_id: &str) -> anyhow::Result<()>;
200}
201
202#[derive(Debug, Clone)]
203pub struct SessionMemory {
204 pub agent_id: String,
205 pub thread_id: String,
206 pub session_summary: String,
207 pub key_insights: Vec<String>,
208 pub important_facts: Vec<String>,
209 pub timestamp: chrono::DateTime<chrono::Utc>,
210}
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
212#[serde(tag = "type", rename_all = "snake_case")]
213pub enum FilterMessageType {
214 Events,
215 Messages,
216 Artifacts,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct MessageFilter {
221 pub filter: Option<Vec<FilterMessageType>>,
222 pub limit: Option<usize>,
223 pub offset: Option<usize>,
224}
225
226#[async_trait]
228pub trait TaskStore: Send + Sync {
229 fn init_task(
230 &self,
231 context_id: &str,
232 task_id: Option<&str>,
233 status: Option<TaskStatus>,
234 ) -> Task {
235 let task_id = task_id.unwrap_or(&Uuid::new_v4().to_string()).to_string();
236 Task {
237 id: task_id,
238 status: status.unwrap_or(TaskStatus::Pending),
239 created_at: chrono::Utc::now().timestamp_millis(),
240 updated_at: chrono::Utc::now().timestamp_millis(),
241 thread_id: context_id.to_string(),
242 parent_task_id: None,
243 }
244 }
245 async fn get_or_create_task(
246 &self,
247 thread_id: &str,
248 task_id: &str,
249 ) -> Result<(), anyhow::Error> {
250 match self.get_task(task_id).await? {
251 Some(task) => task,
252 None => {
253 self.create_task(thread_id, Some(task_id), Some(TaskStatus::Running))
254 .await?
255 }
256 };
257
258 Ok(())
259 }
260 async fn create_task(
261 &self,
262 context_id: &str,
263 task_id: Option<&str>,
264 task_status: Option<TaskStatus>,
265 ) -> anyhow::Result<Task>;
266 async fn get_task(&self, task_id: &str) -> anyhow::Result<Option<Task>>;
267 async fn update_task_status(&self, task_id: &str, status: TaskStatus) -> anyhow::Result<()>;
268 async fn add_event_to_task(&self, task_id: &str, event: AgentEvent) -> anyhow::Result<()>;
269 async fn add_message_to_task(&self, task_id: &str, message: &Message) -> anyhow::Result<()>;
270 async fn cancel_task(&self, task_id: &str) -> anyhow::Result<Task>;
271 async fn list_tasks(&self, thread_id: Option<&str>) -> anyhow::Result<Vec<Task>>;
272
273 async fn get_history(
274 &self,
275 thread_id: &str,
276 filter: Option<MessageFilter>,
277 ) -> anyhow::Result<Vec<(Task, Vec<TaskMessage>)>>;
278
279 async fn update_parent_task(
280 &self,
281 task_id: &str,
282 parent_task_id: Option<&str>,
283 ) -> anyhow::Result<()>;
284}
285
286#[async_trait]
288pub trait ThreadStore: Send + Sync {
289 fn as_any(&self) -> &dyn std::any::Any;
290 async fn create_thread(&self, request: CreateThreadRequest) -> anyhow::Result<Thread>;
291 async fn get_thread(&self, thread_id: &str) -> anyhow::Result<Option<Thread>>;
292 async fn update_thread(
293 &self,
294 thread_id: &str,
295 request: UpdateThreadRequest,
296 ) -> anyhow::Result<Thread>;
297 async fn delete_thread(&self, thread_id: &str) -> anyhow::Result<()>;
298
299 async fn list_threads(
302 &self,
303 filter: &ThreadListFilter,
304 limit: Option<u32>,
305 offset: Option<u32>,
306 ) -> anyhow::Result<ThreadListResponse>;
307
308 async fn update_thread_with_message(
309 &self,
310 thread_id: &str,
311 message: &str,
312 ) -> anyhow::Result<()>;
313
314 async fn get_home_stats(&self) -> anyhow::Result<HomeStats>;
316
317 async fn get_agents_by_usage(
321 &self,
322 search: Option<&str>,
323 ) -> anyhow::Result<Vec<AgentUsageInfo>>;
324
325 async fn get_agent_stats_map(
327 &self,
328 ) -> anyhow::Result<std::collections::HashMap<String, AgentStatsInfo>>;
329
330 async fn mark_message_read(
334 &self,
335 thread_id: &str,
336 message_id: &str,
337 ) -> anyhow::Result<MessageReadStatus>;
338
339 async fn get_message_read_status(
341 &self,
342 thread_id: &str,
343 message_id: &str,
344 ) -> anyhow::Result<Option<MessageReadStatus>>;
345
346 async fn get_thread_read_status(
348 &self,
349 thread_id: &str,
350 ) -> anyhow::Result<Vec<MessageReadStatus>>;
351
352 async fn vote_message(&self, request: VoteMessageRequest) -> anyhow::Result<MessageVote>;
357
358 async fn remove_vote(&self, thread_id: &str, message_id: &str) -> anyhow::Result<()>;
360
361 async fn get_user_vote(
363 &self,
364 thread_id: &str,
365 message_id: &str,
366 ) -> anyhow::Result<Option<MessageVote>>;
367
368 async fn get_message_vote_summary(
370 &self,
371 thread_id: &str,
372 message_id: &str,
373 ) -> anyhow::Result<MessageVoteSummary>;
374
375 async fn get_message_votes(
377 &self,
378 thread_id: &str,
379 message_id: &str,
380 ) -> anyhow::Result<Vec<MessageVote>>;
381}
382
383#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
385pub struct HomeStats {
386 pub total_agents: i64,
387 pub total_threads: i64,
388 pub total_messages: i64,
389 pub avg_run_time_ms: Option<f64>,
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub total_owned_agents: Option<i64>,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub total_accessible_agents: Option<i64>,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 pub most_active_agent: Option<MostActiveAgent>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub latest_threads: Option<Vec<LatestThreadInfo>>,
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub recently_used_agents: Option<Vec<RecentlyUsedAgent>>,
402 #[serde(skip_serializing_if = "Option::is_none")]
405 pub custom_metrics: Option<std::collections::HashMap<String, CustomMetric>>,
406}
407
408#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
410pub struct CustomMetric {
411 pub label: String,
413 pub value: String,
415 #[serde(skip_serializing_if = "Option::is_none")]
417 pub helper: Option<String>,
418 #[serde(skip_serializing_if = "Option::is_none")]
420 pub limit: Option<String>,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub raw_value: Option<i64>,
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub raw_limit: Option<i64>,
427}
428
429#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
430pub struct MostActiveAgent {
431 pub id: String,
432 pub name: String,
433 pub thread_count: i64,
434}
435
436#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
438pub struct RecentlyUsedAgent {
439 pub id: String,
440 pub name: String,
441 pub description: Option<String>,
442 pub last_used_at: chrono::DateTime<chrono::Utc>,
443}
444
445#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
446pub struct LatestThreadInfo {
447 pub id: String,
448 pub title: String,
449 pub agent_id: String,
450 pub agent_name: String,
451 pub updated_at: chrono::DateTime<chrono::Utc>,
452}
453
454#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
456pub struct AgentStatsInfo {
457 pub thread_count: i64,
458 pub sub_agent_usage_count: i64,
459 pub last_used_at: Option<chrono::DateTime<chrono::Utc>>,
460}
461
462#[async_trait]
463pub trait AgentStore: Send + Sync {
464 async fn list(
465 &self,
466 cursor: Option<String>,
467 limit: Option<usize>,
468 ) -> (Vec<crate::configuration::AgentConfig>, Option<String>);
469
470 async fn get(&self, name: &str) -> Option<crate::configuration::AgentConfig>;
471 async fn register(&self, config: crate::configuration::AgentConfig) -> anyhow::Result<()>;
472 async fn update(&self, config: crate::configuration::AgentConfig) -> anyhow::Result<()>;
474
475 async fn clear(&self) -> anyhow::Result<()>;
476
477 async fn delete(&self, id: &str) -> anyhow::Result<()>;
479}
480
481#[async_trait::async_trait]
483pub trait ScratchpadStore: Send + Sync + std::fmt::Debug {
484 async fn add_entry(
486 &self,
487 thread_id: &str,
488 entry: ScratchpadEntry,
489 ) -> Result<(), crate::AgentError>;
490
491 async fn clear_entries(&self, thread_id: &str) -> Result<(), crate::AgentError>;
493
494 async fn get_entries(
496 &self,
497 thread_id: &str,
498 task_id: &str,
499 limit: Option<usize>,
500 ) -> Result<Vec<ScratchpadEntry>, crate::AgentError>;
501
502 async fn get_all_entries(
503 &self,
504 thread_id: &str,
505 limit: Option<usize>,
506 ) -> Result<Vec<ScratchpadEntry>, crate::AgentError>;
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
511pub struct CrawlResult {
512 pub id: String,
513 pub url: String,
514 pub title: Option<String>,
515 pub content: String,
516 pub html: Option<String>,
517 pub metadata: serde_json::Value,
518 pub links: Vec<String>,
519 pub images: Vec<String>,
520 pub status_code: Option<u16>,
521 pub crawled_at: chrono::DateTime<chrono::Utc>,
522 pub processing_time_ms: Option<u64>,
523}
524
525#[async_trait]
527pub trait CrawlStore: Send + Sync {
528 async fn store_crawl_result(&self, result: CrawlResult) -> anyhow::Result<String>;
530
531 async fn get_crawl_result(&self, id: &str) -> anyhow::Result<Option<CrawlResult>>;
533
534 async fn get_crawl_results_by_url(&self, url: &str) -> anyhow::Result<Vec<CrawlResult>>;
536
537 async fn get_recent_crawl_results(
539 &self,
540 limit: Option<usize>,
541 since: Option<chrono::DateTime<chrono::Utc>>,
542 ) -> anyhow::Result<Vec<CrawlResult>>;
543
544 async fn is_url_recently_crawled(
546 &self,
547 url: &str,
548 cache_duration: chrono::Duration,
549 ) -> anyhow::Result<Option<CrawlResult>>;
550
551 async fn delete_crawl_result(&self, id: &str) -> anyhow::Result<()>;
553
554 async fn cleanup_old_results(
556 &self,
557 before: chrono::DateTime<chrono::Utc>,
558 ) -> anyhow::Result<usize>;
559}
560
561#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
565#[serde(rename_all = "lowercase")]
566pub enum VoteType {
567 Upvote,
568 Downvote,
569}
570
571#[derive(Debug, Clone, Serialize, Deserialize)]
573pub struct MessageReadStatus {
574 pub thread_id: String,
575 pub message_id: String,
576 pub user_id: String,
577 pub read_at: chrono::DateTime<chrono::Utc>,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct MarkMessageReadRequest {
583 pub thread_id: String,
584 pub message_id: String,
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct MessageVote {
590 pub id: String,
591 pub thread_id: String,
592 pub message_id: String,
593 pub user_id: String,
594 pub vote_type: VoteType,
595 pub comment: Option<String>,
597 pub created_at: chrono::DateTime<chrono::Utc>,
598 pub updated_at: chrono::DateTime<chrono::Utc>,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct VoteMessageRequest {
604 pub thread_id: String,
605 pub message_id: String,
606 pub vote_type: VoteType,
607 pub comment: Option<String>,
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize, Default)]
613pub struct MessageVoteSummary {
614 pub message_id: String,
615 pub upvotes: i64,
616 pub downvotes: i64,
617 pub user_vote: Option<VoteType>,
619}
620
621#[async_trait]
623pub trait ExternalToolCallsStore: Send + Sync + std::fmt::Debug {
624 async fn register_external_tool_call(
626 &self,
627 session_id: &str,
628 ) -> anyhow::Result<oneshot::Receiver<ToolResponse>>;
629
630 async fn complete_external_tool_call(
632 &self,
633 session_id: &str,
634 tool_response: ToolResponse,
635 ) -> anyhow::Result<()>;
636
637 async fn remove_tool_call(&self, session_id: &str) -> anyhow::Result<()>;
639
640 async fn list_pending_tool_calls(&self) -> anyhow::Result<Vec<String>>;
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize)]
647pub struct PromptTemplateRecord {
648 pub id: String,
649 pub name: String,
650 pub template: String,
651 pub description: Option<String>,
652 pub version: Option<String>,
653 pub is_system: bool,
654 pub created_at: chrono::DateTime<chrono::Utc>,
655 pub updated_at: chrono::DateTime<chrono::Utc>,
656}
657
658#[derive(Debug, Clone, Serialize, Deserialize)]
659pub struct NewPromptTemplate {
660 pub name: String,
661 pub template: String,
662 pub description: Option<String>,
663 pub version: Option<String>,
664 #[serde(default)]
665 pub is_system: bool,
666}
667
668#[derive(Debug, Clone, Serialize, Deserialize)]
669pub struct UpdatePromptTemplate {
670 pub name: String,
671 pub template: String,
672 pub description: Option<String>,
673}
674
675#[async_trait]
676pub trait PromptTemplateStore: Send + Sync {
677 async fn list(&self) -> anyhow::Result<Vec<PromptTemplateRecord>>;
678 async fn get(&self, id: &str) -> anyhow::Result<Option<PromptTemplateRecord>>;
679 async fn create(&self, template: NewPromptTemplate) -> anyhow::Result<PromptTemplateRecord>;
680 async fn update(
681 &self,
682 id: &str,
683 update: UpdatePromptTemplate,
684 ) -> anyhow::Result<PromptTemplateRecord>;
685 async fn delete(&self, id: &str) -> anyhow::Result<()>;
686 async fn clone_template(&self, id: &str) -> anyhow::Result<PromptTemplateRecord>;
687 async fn sync_system_templates(&self, templates: Vec<NewPromptTemplate>) -> anyhow::Result<()>;
688}
689
690#[derive(Debug, Clone, Serialize, Deserialize)]
693pub struct SecretRecord {
694 pub id: String,
695 pub key: String,
696 pub value: String,
697 pub created_at: chrono::DateTime<chrono::Utc>,
698 pub updated_at: chrono::DateTime<chrono::Utc>,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
702pub struct NewSecret {
703 pub key: String,
704 pub value: String,
705}
706
707#[async_trait]
708pub trait SecretStore: Send + Sync {
709 async fn list(&self) -> anyhow::Result<Vec<SecretRecord>>;
710 async fn get(&self, key: &str) -> anyhow::Result<Option<SecretRecord>>;
711 async fn create(&self, secret: NewSecret) -> anyhow::Result<SecretRecord>;
712 async fn update(&self, key: &str, value: &str) -> anyhow::Result<SecretRecord>;
713 async fn delete(&self, key: &str) -> anyhow::Result<()>;
714}
715
716#[derive(Debug, Clone, Serialize, Deserialize)]
719pub struct CustomProviderConfig {
720 pub id: String,
721 pub name: String,
722 pub base_url: String,
723 #[serde(default, skip_serializing_if = "Option::is_none")]
724 pub project_id: Option<String>,
725}
726
727#[derive(Debug, Clone, Serialize, Deserialize)]
728pub struct CustomModelEntry {
729 pub provider: String,
730 pub model: String,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct ConnectionProviderConfig {
736 pub id: String,
738 pub name: String,
740 pub authorization_url: String,
742 pub token_url: String,
744 #[serde(default, skip_serializing_if = "Option::is_none")]
746 pub refresh_url: Option<String>,
747 #[serde(default)]
749 pub scopes_supported: Vec<String>,
750 #[serde(default)]
752 pub default_scopes: Vec<String>,
753 #[serde(default)]
755 pub scope_mappings: std::collections::HashMap<String, String>,
756}
757
758#[derive(Debug, Clone, Serialize, Deserialize)]
760pub struct UpsertProviderRequest {
761 pub provider_id: String,
762 #[serde(default)]
763 pub secrets: std::collections::HashMap<String, String>,
764 #[serde(default)]
765 pub config: Option<CustomProviderConfig>,
766 #[serde(default)]
767 pub custom_models: Option<Vec<CustomModelEntry>>,
768 #[serde(default)]
770 pub default_model: Option<String>,
771 #[serde(default)]
773 pub connection_provider: Option<ConnectionProviderConfig>,
774}
775
776#[derive(Debug, Clone, Serialize, Deserialize)]
778pub struct UpsertProviderResponse {
779 pub provider_id: String,
780 pub secrets_saved: usize,
781 pub config_saved: bool,
782}
783
784#[async_trait]
785pub trait ProviderStore: Send + Sync {
786 async fn upsert_provider(
787 &self,
788 req: UpsertProviderRequest,
789 ) -> anyhow::Result<UpsertProviderResponse>;
790
791 async fn delete_provider(&self, provider_id: &str) -> anyhow::Result<()>;
792
793 async fn get_default_model(&self) -> anyhow::Result<Option<String>>;
794}
795
796#[derive(Debug, Clone, Serialize, Deserialize)]
800pub struct SkillsListResponse {
801 pub skills: Vec<SkillListItem>,
802}
803
804#[derive(Debug, Clone, Serialize, Deserialize)]
807pub struct SkillListItem {
808 pub id: String,
809 #[serde(default)]
810 pub workspace_slug: String,
811 pub name: String,
812 #[serde(default)]
813 pub full_name: String,
814 #[serde(default)]
815 pub description: Option<String>,
816 #[serde(default)]
817 pub tags: Vec<String>,
818 #[serde(default)]
819 pub is_public: bool,
820 #[serde(default)]
821 pub is_system: bool,
822 #[serde(default)]
823 pub is_owner: bool,
824 #[serde(default)]
825 pub star_count: i32,
826 #[serde(default)]
827 pub clone_count: i32,
828 #[serde(default)]
829 pub is_starred: bool,
830 pub created_at: chrono::DateTime<chrono::Utc>,
831 pub updated_at: chrono::DateTime<chrono::Utc>,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize)]
835pub struct SkillRecord {
836 pub id: String,
837 pub name: String,
838 pub description: Option<String>,
839 pub content: String,
840 pub tags: Vec<String>,
841 pub is_public: bool,
842 pub is_system: bool,
843 pub star_count: i32,
844 pub clone_count: i32,
845 pub scripts: Vec<SkillScriptRecord>,
846 pub created_at: chrono::DateTime<chrono::Utc>,
847 pub updated_at: chrono::DateTime<chrono::Utc>,
848}
849
850#[derive(Debug, Clone, Serialize, Deserialize)]
851pub struct SkillScriptRecord {
852 pub id: String,
853 pub skill_id: String,
854 pub name: String,
855 pub description: Option<String>,
856 pub code: String,
857 pub language: String,
858 pub created_at: chrono::DateTime<chrono::Utc>,
859 pub updated_at: chrono::DateTime<chrono::Utc>,
860}
861
862#[derive(Debug, Clone, Serialize, Deserialize)]
863pub struct NewSkill {
864 pub name: String,
865 pub description: Option<String>,
866 pub content: String,
867 #[serde(default)]
868 pub tags: Vec<String>,
869 #[serde(default)]
870 pub is_public: bool,
871 #[serde(default)]
872 pub scripts: Vec<NewSkillScript>,
873}
874
875#[derive(Debug, Clone, Serialize, Deserialize)]
876pub struct NewSkillScript {
877 pub name: String,
878 pub description: Option<String>,
879 pub code: String,
880 #[serde(default = "default_script_language")]
881 pub language: String,
882}
883
884fn default_script_language() -> String {
885 "javascript".to_string()
886}
887
888#[derive(Debug, Clone, Serialize, Deserialize)]
889pub struct UpdateSkill {
890 pub name: Option<String>,
891 pub description: Option<String>,
892 pub content: Option<String>,
893 pub tags: Option<Vec<String>>,
894 pub is_public: Option<bool>,
895}
896
897#[derive(Debug, Clone, Serialize, Deserialize)]
898pub struct UpdateSkillScript {
899 pub name: Option<String>,
900 pub description: Option<String>,
901 pub code: Option<String>,
902 pub language: Option<String>,
903}
904
905#[async_trait]
906pub trait SkillStore: Send + Sync {
907 async fn list_skills(&self) -> anyhow::Result<Vec<SkillRecord>>;
908 async fn get_skill(&self, id: &str) -> anyhow::Result<Option<SkillRecord>>;
909 async fn create_skill(&self, skill: NewSkill) -> anyhow::Result<SkillRecord>;
910 async fn update_skill(&self, id: &str, update: UpdateSkill) -> anyhow::Result<SkillRecord>;
911 async fn delete_skill(&self, id: &str) -> anyhow::Result<()>;
912
913 async fn add_script(
915 &self,
916 skill_id: &str,
917 script: NewSkillScript,
918 ) -> anyhow::Result<SkillScriptRecord>;
919 async fn update_script(
920 &self,
921 script_id: &str,
922 update: UpdateSkillScript,
923 ) -> anyhow::Result<SkillScriptRecord>;
924 async fn delete_script(&self, script_id: &str) -> anyhow::Result<()>;
925
926 async fn list_public_skills(&self) -> anyhow::Result<Vec<SkillRecord>>;
928 async fn star_skill(&self, skill_id: &str) -> anyhow::Result<()>;
929 async fn unstar_skill(&self, skill_id: &str) -> anyhow::Result<()>;
930 async fn list_starred_skills(&self) -> anyhow::Result<Vec<SkillRecord>>;
931 async fn clone_skill(&self, skill_id: &str) -> anyhow::Result<SkillRecord>;
932}
933
934#[derive(Debug, Clone, Serialize, Deserialize)]
938pub struct WorkflowsListResponse {
939 pub workflows: Vec<WorkflowListItem>,
940 pub total: i64,
941}
942
943#[derive(Debug, Clone, Serialize, Deserialize)]
945pub struct WorkflowListItem {
946 pub id: String,
947 pub name: String,
948 #[serde(default)]
949 pub description: Option<String>,
950 #[serde(default)]
951 pub tags: Vec<String>,
952 #[serde(default)]
953 pub is_public: bool,
954 #[serde(default)]
955 pub is_template: bool,
956 #[serde(default)]
957 pub is_owner: bool,
958 #[serde(default)]
959 pub star_count: i32,
960 #[serde(default)]
961 pub clone_count: i32,
962 #[serde(default)]
963 pub is_starred: bool,
964 pub status: String,
965 pub step_count: usize,
966 pub created_at: chrono::DateTime<chrono::Utc>,
967 pub updated_at: chrono::DateTime<chrono::Utc>,
968}
969
970#[derive(Debug, Clone, Serialize, Deserialize)]
974pub struct WorkflowRecord {
975 pub id: String,
976 pub name: String,
977 pub description: Option<String>,
978 #[serde(default)]
979 pub definition: serde_json::Value,
981 pub tags: Vec<String>,
982 pub is_public: bool,
983 pub is_template: bool,
984 pub star_count: i32,
985 pub clone_count: i32,
986 pub created_at: chrono::DateTime<chrono::Utc>,
987 pub updated_at: chrono::DateTime<chrono::Utc>,
988}
989
990#[derive(Debug, Clone, Serialize, Deserialize)]
992pub struct NewWorkflow {
993 pub name: String,
994 pub description: Option<String>,
995 #[serde(default)]
996 pub definition: serde_json::Value,
997 #[serde(default)]
998 pub tags: Vec<String>,
999 #[serde(default)]
1000 pub is_public: bool,
1001 #[serde(default)]
1002 pub is_template: bool,
1003}
1004
1005#[derive(Debug, Clone, Serialize, Deserialize)]
1007pub struct UpdateWorkflow {
1008 pub name: Option<String>,
1009 pub description: Option<String>,
1010 pub definition: Option<serde_json::Value>,
1011 pub tags: Option<Vec<String>>,
1012 pub is_public: Option<bool>,
1013}
1014
1015#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1017pub struct WorkflowFilter {
1018 pub is_public: Option<bool>,
1019 pub is_template: Option<bool>,
1020 pub tags: Option<Vec<String>>,
1021 pub search: Option<String>,
1022 pub limit: Option<i64>,
1023 pub offset: Option<i64>,
1024}
1025
1026#[async_trait]
1027pub trait WorkflowStore: Send + Sync {
1028 async fn list_workflows(&self, filter: WorkflowFilter) -> anyhow::Result<Vec<WorkflowRecord>>;
1029 async fn get_workflow(&self, id: &str) -> anyhow::Result<Option<WorkflowRecord>>;
1030 async fn create_workflow(&self, workflow: NewWorkflow) -> anyhow::Result<WorkflowRecord>;
1031 async fn update_workflow(
1032 &self,
1033 id: &str,
1034 update: UpdateWorkflow,
1035 ) -> anyhow::Result<WorkflowRecord>;
1036 async fn delete_workflow(&self, id: &str) -> anyhow::Result<()>;
1037
1038 async fn list_public_workflows(&self) -> anyhow::Result<Vec<WorkflowRecord>>;
1040 async fn star_workflow(&self, workflow_id: &str) -> anyhow::Result<()>;
1041 async fn unstar_workflow(&self, workflow_id: &str) -> anyhow::Result<()>;
1042 async fn list_starred_workflows(&self) -> anyhow::Result<Vec<WorkflowRecord>>;
1043 async fn clone_workflow(&self, workflow_id: &str) -> anyhow::Result<WorkflowRecord>;
1044}
1045
1046#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1050pub struct UsageSnapshot {
1051 pub day_tokens: i64,
1052 pub week_tokens: i64,
1053 pub month_tokens: i64,
1054}
1055
1056#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1058pub struct UsageLimits {
1059 pub daily_tokens: Option<i64>,
1060 pub weekly_tokens: Option<i64>,
1061 pub monthly_tokens: Option<i64>,
1062}
1063
1064#[derive(Debug, Clone)]
1066pub enum UsageCheckResult {
1067 Allowed,
1068 Denied { reason: String },
1069}
1070
1071#[async_trait]
1076pub trait UsageService: Send + Sync {
1077 async fn check_request(
1082 &self,
1083 workspace_id: &str,
1084 user_id: &str,
1085 is_llm: bool,
1086 auth_source: &str,
1087 ) -> UsageCheckResult;
1088
1089 async fn record_usage(
1091 &self,
1092 workspace_id: &str,
1093 user_id: &str,
1094 tokens_used: i64,
1095 ) -> anyhow::Result<()>;
1096
1097 async fn get_usage(&self, workspace_id: &str, user_id: &str) -> anyhow::Result<UsageSnapshot>;
1099
1100 async fn get_limits(&self, workspace_id: &str) -> anyhow::Result<UsageLimits>;
1102}
1103
1104#[derive(Debug, Clone)]
1107pub struct NoOpUsageService;
1108
1109#[async_trait]
1110impl UsageService for NoOpUsageService {
1111 async fn check_request(
1112 &self,
1113 _workspace_id: &str,
1114 _user_id: &str,
1115 _is_llm: bool,
1116 _auth_source: &str,
1117 ) -> UsageCheckResult {
1118 UsageCheckResult::Allowed
1119 }
1120
1121 async fn record_usage(
1122 &self,
1123 _workspace_id: &str,
1124 _user_id: &str,
1125 _tokens_used: i64,
1126 ) -> anyhow::Result<()> {
1127 Ok(())
1128 }
1129
1130 async fn get_usage(
1131 &self,
1132 _workspace_id: &str,
1133 _user_id: &str,
1134 ) -> anyhow::Result<UsageSnapshot> {
1135 Ok(UsageSnapshot::default())
1136 }
1137
1138 async fn get_limits(&self, _workspace_id: &str) -> anyhow::Result<UsageLimits> {
1139 Ok(UsageLimits::default())
1140 }
1141}
1142
1143#[cfg(test)]
1144mod tests {
1145 use super::*;
1146
1147 #[test]
1148 fn test_skills_list_response_deserialize_cloud_format() {
1149 let json = r#"{"skills":[{"id":"abc","workspace_slug":"ws","name":"test","full_name":"ws/test","description":"desc","tags":["t"],"is_public":true,"is_system":false,"is_owner":true,"star_count":0,"clone_count":0,"is_starred":false,"created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z"}]}"#;
1150 let resp: SkillsListResponse = serde_json::from_str(json).unwrap();
1151 assert_eq!(resp.skills.len(), 1);
1152 assert_eq!(resp.skills[0].name, "test");
1153 assert_eq!(resp.skills[0].workspace_slug, "ws");
1154 assert_eq!(resp.skills[0].full_name, "ws/test");
1155 assert!(resp.skills[0].is_public);
1156 }
1157
1158 #[test]
1159 fn test_skills_list_response_deserialize_defaults() {
1160 let json = r#"{"skills":[{"id":"abc","name":"test","created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z"}]}"#;
1161 let resp: SkillsListResponse = serde_json::from_str(json).unwrap();
1162 assert_eq!(resp.skills[0].workspace_slug, "");
1163 assert_eq!(resp.skills[0].full_name, "");
1164 assert!(!resp.skills[0].is_public);
1165 assert!(!resp.skills[0].is_owner);
1166 }
1167
1168 #[test]
1169 fn test_skills_list_response_roundtrip() {
1170 let resp = SkillsListResponse {
1171 skills: vec![SkillListItem {
1172 id: "id1".into(),
1173 workspace_slug: "local".into(),
1174 name: "my_skill".into(),
1175 full_name: "local/my_skill".into(),
1176 description: Some("A skill".into()),
1177 tags: vec!["tag1".into()],
1178 is_public: false,
1179 is_system: false,
1180 is_owner: true,
1181 star_count: 5,
1182 clone_count: 2,
1183 is_starred: true,
1184 created_at: chrono::Utc::now(),
1185 updated_at: chrono::Utc::now(),
1186 }],
1187 };
1188 let json = serde_json::to_string(&resp).unwrap();
1189 let decoded: SkillsListResponse = serde_json::from_str(&json).unwrap();
1190 assert_eq!(decoded.skills[0].name, "my_skill");
1191 assert_eq!(decoded.skills[0].star_count, 5);
1192 }
1193}