systemprompt_agent/repository/task/
mod.rs1pub mod constructor;
11mod mutations;
12mod queries;
13mod state;
14mod task_messages;
15mod task_updates;
16
17pub use constructor::TaskConstructor;
18pub use mutations::{
19 CreateTaskParams, create_task, task_state_to_db_string, track_agent_in_context,
20};
21pub use queries::{
22 TaskContextInfo, get_task, get_task_context_info, get_tasks_by_user_id, list_tasks_by_context,
23};
24pub use state::{apply_notification_status, update_task_failed_with_error, update_task_state};
25pub use task_updates::UpdateTaskAndSaveMessagesParams;
26
27use crate::models::a2a::{Task, TaskState};
28use sqlx::PgPool;
29use std::sync::Arc;
30use systemprompt_database::DbPool;
31use systemprompt_identifiers::{SessionId, TraceId, UserId};
32use systemprompt_traits::{DynFileUploadProvider, DynSessionAnalyticsProvider, RepositoryError};
33
34#[expect(
35 missing_debug_implementations,
36 reason = "params struct holds non-Debug references"
37)]
38pub struct RepoCreateTaskParams<'a> {
39 pub task: &'a Task,
40 pub user_id: &'a UserId,
41 pub session_id: &'a SessionId,
42 pub trace_id: &'a TraceId,
43 pub agent_name: &'a str,
44}
45
46#[derive(Clone)]
47pub struct TaskRepository {
48 pool: Arc<PgPool>,
49 write_pool: Arc<PgPool>,
50 db_pool: DbPool,
51 pub(crate) session_analytics_provider: Option<DynSessionAnalyticsProvider>,
52 pub(crate) file_upload_provider: Option<DynFileUploadProvider>,
53}
54
55impl std::fmt::Debug for TaskRepository {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("TaskRepository")
58 .field("pool", &"<PgPool>")
59 .field("write_pool", &"<PgPool>")
60 .field("db_pool", &"<DbPool>")
61 .field(
62 "session_analytics_provider",
63 &self.session_analytics_provider.is_some(),
64 )
65 .field("file_upload_provider", &self.file_upload_provider.is_some())
66 .finish()
67 }
68}
69
70impl TaskRepository {
71 pub fn new(db: &DbPool) -> Result<Self, crate::error::AgentError> {
72 let pool = db
73 .pool_arc()
74 .map_err(|e| crate::error::AgentError::Init(e.to_string()))?;
75 let write_pool = db
76 .write_pool_arc()
77 .map_err(|e| crate::error::AgentError::Init(e.to_string()))?;
78 Ok(Self {
79 pool,
80 write_pool,
81 db_pool: Arc::clone(db),
82 session_analytics_provider: None,
83 file_upload_provider: None,
84 })
85 }
86
87 #[must_use]
88 pub fn with_session_analytics_provider(
89 mut self,
90 provider: DynSessionAnalyticsProvider,
91 ) -> Self {
92 self.session_analytics_provider = Some(provider);
93 self
94 }
95
96 #[must_use]
97 pub fn with_file_upload_provider(mut self, provider: DynFileUploadProvider) -> Self {
98 self.file_upload_provider = Some(provider);
99 self
100 }
101
102 pub(crate) const fn db_pool(&self) -> &DbPool {
103 &self.db_pool
104 }
105
106 pub async fn create_task(
107 &self,
108 params: RepoCreateTaskParams<'_>,
109 ) -> Result<String, RepositoryError> {
110 let result = create_task(CreateTaskParams {
111 pool: &self.write_pool,
112 task: params.task,
113 user_id: params.user_id,
114 session_id: params.session_id,
115 trace_id: params.trace_id,
116 agent_name: params.agent_name,
117 })
118 .await?;
119
120 if let Some(ref provider) = self.session_analytics_provider {
121 if let Err(e) = provider.increment_task_count(params.session_id).await {
122 tracing::warn!(error = %e, "Failed to increment analytics task count");
123 }
124 }
125
126 Ok(result)
127 }
128
129 pub async fn get_task(
130 &self,
131 task_id: &systemprompt_identifiers::TaskId,
132 ) -> Result<Option<Task>, RepositoryError> {
133 get_task(&self.pool, &self.db_pool, task_id).await
134 }
135
136 pub async fn list_tasks_by_context(
137 &self,
138 context_id: &systemprompt_identifiers::ContextId,
139 ) -> Result<Vec<Task>, RepositoryError> {
140 list_tasks_by_context(&self.pool, &self.db_pool, context_id).await
141 }
142
143 pub async fn get_tasks_by_user_id(
144 &self,
145 user_id: &UserId,
146 limit: Option<i32>,
147 offset: Option<i32>,
148 ) -> Result<Vec<Task>, RepositoryError> {
149 get_tasks_by_user_id(&self.pool, &self.db_pool, user_id, limit, offset).await
150 }
151
152 pub async fn track_agent_in_context(
153 &self,
154 context_id: &systemprompt_identifiers::ContextId,
155 agent_name: &str,
156 ) -> Result<(), RepositoryError> {
157 track_agent_in_context(&self.write_pool, context_id, agent_name).await
158 }
159
160 pub async fn update_task_state(
161 &self,
162 task_id: &systemprompt_identifiers::TaskId,
163 state: TaskState,
164 timestamp: &chrono::DateTime<chrono::Utc>,
165 ) -> Result<(), RepositoryError> {
166 update_task_state(&self.write_pool, task_id, state, timestamp).await
167 }
168
169 pub async fn apply_notification_status(
170 &self,
171 task_id: &systemprompt_identifiers::TaskId,
172 state: &str,
173 timestamp: &chrono::DateTime<chrono::Utc>,
174 ) -> Result<(), RepositoryError> {
175 apply_notification_status(&self.write_pool, task_id, state, timestamp).await
176 }
177
178 pub async fn update_task_failed_with_error(
179 &self,
180 task_id: &systemprompt_identifiers::TaskId,
181 error_message: &str,
182 timestamp: &chrono::DateTime<chrono::Utc>,
183 ) -> Result<(), RepositoryError> {
184 update_task_failed_with_error(&self.write_pool, task_id, error_message, timestamp).await
185 }
186
187 pub async fn get_task_context_info(
188 &self,
189 task_id: &systemprompt_identifiers::TaskId,
190 ) -> Result<Option<TaskContextInfo>, RepositoryError> {
191 get_task_context_info(&self.pool, task_id).await
192 }
193
194 pub async fn validate_task_ownership(
195 &self,
196 task_id: &systemprompt_identifiers::TaskId,
197 user_id: &UserId,
198 ) -> Result<(), RepositoryError> {
199 let result = sqlx::query_scalar!(
200 "SELECT t.task_id FROM agent_tasks t JOIN user_contexts c ON t.context_id = \
201 c.context_id WHERE t.task_id = $1 AND c.user_id = $2",
202 task_id.as_str(),
203 user_id.as_str()
204 )
205 .fetch_optional(self.pool.as_ref())
206 .await
207 .map_err(RepositoryError::database)?;
208
209 match result {
210 Some(_) => Ok(()),
211 None => Err(RepositoryError::NotFound(format!(
212 "Task {task_id} not found or user {user_id} does not have access"
213 ))),
214 }
215 }
216}