agentic_core/storage/
conversation.rs1use std::convert::TryFrom;
4use std::sync::Arc;
5
6use super::models::{conversation, item, response};
7use super::pool::DbPool;
8use super::types::{
9 ConversationData, ConversationSnapshot, ConversationVersion, InOutItem, ResponseMetadata, StorageError, StoreResult,
10};
11use crate::utils::common::{serialize_to_string, uuid7_str};
12
13#[derive(Clone, Debug)]
15pub struct ConversationStore {
16 pool: Option<Arc<DbPool>>,
17}
18
19impl ConversationStore {
20 #[must_use]
22 pub fn disabled() -> Self {
23 Self { pool: None }
24 }
25
26 #[must_use]
28 pub fn new(pool: Arc<DbPool>) -> Self {
29 Self { pool: Some(pool) }
30 }
31
32 fn pool(&self) -> StoreResult<&DbPool> {
38 self.pool.as_deref().ok_or(StorageError::NotConfigured)
39 }
40
41 pub async fn create(&self) -> StoreResult<ConversationData> {
47 let pool = self.pool()?;
48 let row = conversation::create(pool, &uuid7_str("conv_")).await?;
49 Ok(row.into())
50 }
51
52 pub async fn get_or_create(&self, conversation_id: &str) -> StoreResult<ConversationData> {
58 let pool = self.pool()?;
59 let row = conversation::get_or_create(pool, conversation_id).await?;
60 Ok(row.into())
61 }
62
63 pub async fn get(&self, conversation_id: &str) -> StoreResult<ConversationData> {
69 let pool = self.pool()?;
70 let row = conversation::get(pool, conversation_id)
71 .await?
72 .ok_or_else(|| StorageError::not_found("Conversation", conversation_id))?;
73 Ok(row.into())
74 }
75
76 pub async fn rehydrate(&self, conversation_id: &str) -> StoreResult<Vec<InOutItem>> {
82 Ok(self.rehydrate_snapshot(conversation_id).await?.items)
83 }
84
85 pub async fn rehydrate_snapshot(&self, conversation_id: &str) -> StoreResult<ConversationSnapshot> {
91 let pool = self.pool()?;
92 let rows = item::get_items_by_conversation(pool, conversation_id).await?;
93
94 let mut last_sequence = None;
95 for row in &rows {
96 last_sequence = Some(row.seq.ok_or_else(|| StorageError::InvalidConversationSequence {
97 conversation_id: conversation_id.to_string(),
98 item_id: row.id.clone(),
99 })?);
100 }
101
102 Ok(ConversationSnapshot {
103 items: rows.into_iter().filter_map(|row| row.as_inout()).collect(),
104 version: ConversationVersion::from_last_sequence(last_sequence),
105 })
106 }
107
108 pub async fn persist(
116 &self,
117 conversation_id: &str,
118 response_id: &str,
119 previous_response_id: Option<&str>,
120 new_items: Vec<InOutItem>,
121 metadata: &ResponseMetadata,
122 ) -> StoreResult<()> {
123 self.persist_impl(
124 conversation_id,
125 None,
126 response_id,
127 previous_response_id,
128 new_items,
129 metadata,
130 )
131 .await
132 }
133
134 pub async fn persist_if_version(
140 &self,
141 conversation_id: &str,
142 expected_version: ConversationVersion,
143 response_id: &str,
144 previous_response_id: Option<&str>,
145 new_items: Vec<InOutItem>,
146 metadata: &ResponseMetadata,
147 ) -> StoreResult<()> {
148 self.persist_impl(
149 conversation_id,
150 Some(expected_version),
151 response_id,
152 previous_response_id,
153 new_items,
154 metadata,
155 )
156 .await
157 }
158
159 async fn persist_impl(
160 &self,
161 conversation_id: &str,
162 expected_version: Option<ConversationVersion>,
163 response_id: &str,
164 previous_response_id: Option<&str>,
165 new_items: Vec<InOutItem>,
166 metadata: &ResponseMetadata,
167 ) -> StoreResult<()> {
168 let pool = self.pool()?;
169
170 let mut item_ids: Vec<String> = Vec::new();
171 let mut items_: Vec<(String, String)> = Vec::new();
172 for any_item in new_items {
173 let item_id = uuid7_str("item_");
174 item_ids.push(item_id.clone());
175 let data_str = String::try_from(&any_item)?;
176 items_.push((item_id, data_str));
177 }
178 let history_item_ids_json = serialize_to_string(&item_ids)?;
179 let metadata_json = String::try_from(metadata)?;
180
181 let mut tx = pool.begin().await?;
182
183 match conversation::lock_in_tx(&mut tx, conversation_id).await {
184 Ok(()) => {}
185 Err(sqlx::Error::RowNotFound) => {
186 return Err(StorageError::not_found("Conversation", conversation_id));
187 }
188 Err(error) => return Err(error.into()),
189 }
190 if let Some(expected_version) = expected_version {
191 let current_version = ConversationVersion::from_last_sequence(
192 item::last_conversation_sequence_in_tx(&mut tx, conversation_id).await?,
193 );
194 if current_version != expected_version {
195 return Err(StorageError::ConversationConflict {
196 conversation_id: conversation_id.to_owned(),
197 });
198 }
199 }
200 item::create_in_tx(&mut tx, items_, Some(conversation_id)).await?;
201
202 response::create_in_tx(
203 &mut tx,
204 response_id,
205 Some(conversation_id),
206 previous_response_id,
207 Some(&history_item_ids_json),
208 Some(&metadata_json),
209 )
210 .await?;
211 tx.commit().await?;
212
213 Ok(())
214 }
215}