systemprompt_sync/local/
agents_sync.rs1use crate::diff::AgentsDiffCalculator;
2use crate::export::export_agent_to_disk;
3use crate::models::{AgentsDiffResult, LocalSyncDirection, LocalSyncResult};
4use anyhow::Result;
5use std::path::PathBuf;
6use systemprompt_agent::repository::content::AgentRepository;
7use systemprompt_agent::services::AgentIngestionService;
8use systemprompt_database::DbPool;
9use systemprompt_identifiers::SourceId;
10use tracing::info;
11
12#[derive(Debug)]
13pub struct AgentsLocalSync {
14 db: DbPool,
15 agents_path: PathBuf,
16}
17
18impl AgentsLocalSync {
19 pub const fn new(db: DbPool, agents_path: PathBuf) -> Self {
20 Self { db, agents_path }
21 }
22
23 pub async fn calculate_diff(&self) -> Result<AgentsDiffResult> {
24 let calculator = AgentsDiffCalculator::new(&self.db)?;
25 calculator.calculate_diff(&self.agents_path).await
26 }
27
28 pub async fn sync_to_disk(
29 &self,
30 diff: &AgentsDiffResult,
31 delete_orphans: bool,
32 ) -> Result<LocalSyncResult> {
33 let agent_repo = AgentRepository::new(&self.db)?;
34 let mut result = LocalSyncResult {
35 direction: LocalSyncDirection::ToDisk,
36 ..Default::default()
37 };
38
39 for item in &diff.modified {
40 match agent_repo.get_by_agent_id(&item.agent_id).await? {
41 Some(agent) => {
42 export_agent_to_disk(&agent, &self.agents_path)?;
43 result.items_synced += 1;
44 info!("Exported modified agent: {}", item.agent_id);
45 },
46 None => {
47 result
48 .errors
49 .push(format!("Agent not found in DB: {}", item.agent_id));
50 },
51 }
52 }
53
54 for item in &diff.removed {
55 match agent_repo.get_by_agent_id(&item.agent_id).await? {
56 Some(agent) => {
57 export_agent_to_disk(&agent, &self.agents_path)?;
58 result.items_synced += 1;
59 info!("Created agent on disk: {}", item.agent_id);
60 },
61 None => {
62 result
63 .errors
64 .push(format!("Agent not found in DB: {}", item.agent_id));
65 },
66 }
67 }
68
69 if delete_orphans {
70 for item in &diff.added {
71 let agent_dir = self.agents_path.join(&item.name);
72
73 if agent_dir.exists() {
74 std::fs::remove_dir_all(&agent_dir)?;
75 result.items_deleted += 1;
76 info!("Deleted orphan agent: {}", item.agent_id);
77 }
78 }
79 } else {
80 result.items_skipped += diff.added.len();
81 }
82
83 Ok(result)
84 }
85
86 pub async fn sync_to_db(
87 &self,
88 diff: &AgentsDiffResult,
89 delete_orphans: bool,
90 ) -> Result<LocalSyncResult> {
91 let ingestion_service = AgentIngestionService::new(&self.db)?;
92 let mut result = LocalSyncResult {
93 direction: LocalSyncDirection::ToDatabase,
94 ..Default::default()
95 };
96
97 let source_id = SourceId::new("agents");
98 let report = ingestion_service
99 .ingest_directory(&self.agents_path, source_id, true)
100 .await?;
101
102 result.items_synced += report.files_processed;
103
104 for error in report.errors {
105 result.errors.push(error);
106 }
107
108 info!("Ingested {} agents", report.files_processed);
109
110 if delete_orphans && !diff.removed.is_empty() {
111 tracing::warn!(
112 count = diff.removed.len(),
113 "Agent deletion from database not supported, skipping orphan removal"
114 );
115 }
116 result.items_skipped += diff.removed.len();
117
118 Ok(result)
119 }
120}