1use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub struct MemoryOwner {
11 session_id: crate::types::SessionId,
13}
14
15impl MemoryOwner {
16 pub fn canonical_session(session_id: crate::types::SessionId) -> Self {
17 Self { session_id }
18 }
19
20 pub fn session_id(&self) -> &crate::types::SessionId {
21 &self.session_id
22 }
23
24 fn includes(&self, metadata: &MemoryMetadata) -> bool {
25 metadata.session_id == self.session_id
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct MemoryMetadata {
32 pub session_id: crate::types::SessionId,
34 pub turn: Option<u32>,
36 pub indexed_at: crate::time_compat::SystemTime,
38}
39
40#[derive(Debug, Clone)]
42pub struct MemoryResult {
43 pub content: String,
45 pub metadata: MemoryMetadata,
47 pub score: f32,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53pub struct MemorySearchScope {
54 pub owner: MemoryOwner,
56}
57
58impl MemorySearchScope {
59 pub fn for_session(session_id: crate::types::SessionId) -> Self {
60 Self {
61 owner: MemoryOwner::canonical_session(session_id),
62 }
63 }
64
65 pub fn for_owner(owner: MemoryOwner) -> Self {
66 Self { owner }
67 }
68
69 pub fn session_id(&self) -> &crate::types::SessionId {
70 self.owner.session_id()
71 }
72
73 pub fn includes(&self, metadata: &MemoryMetadata) -> bool {
74 self.owner.includes(metadata)
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
80pub struct MemoryIndexScope {
81 pub owner: MemoryOwner,
83}
84
85impl MemoryIndexScope {
86 pub fn for_session(session_id: crate::types::SessionId) -> Self {
87 Self {
88 owner: MemoryOwner::canonical_session(session_id),
89 }
90 }
91
92 pub fn for_owner(owner: MemoryOwner) -> Self {
93 Self { owner }
94 }
95
96 pub fn session_id(&self) -> &crate::types::SessionId {
97 self.owner.session_id()
98 }
99
100 pub fn includes(&self, metadata: &MemoryMetadata) -> bool {
101 self.owner.includes(metadata)
102 }
103}
104
105#[derive(Debug, Clone)]
107pub struct MemoryIndexRequest {
108 scope: MemoryIndexScope,
109 content: String,
110 metadata: MemoryMetadata,
111}
112
113impl MemoryIndexRequest {
114 pub fn new(
115 scope: MemoryIndexScope,
116 content: String,
117 metadata: MemoryMetadata,
118 ) -> Result<Self, MemoryStoreError> {
119 if !scope.includes(&metadata) {
120 return Err(MemoryStoreError::Scope(format!(
121 "memory metadata session {} is outside indexing scope {}",
122 metadata.session_id,
123 scope.session_id()
124 )));
125 }
126 Ok(Self {
127 scope,
128 content,
129 metadata,
130 })
131 }
132
133 pub fn scope(&self) -> &MemoryIndexScope {
134 &self.scope
135 }
136
137 pub fn content(&self) -> &str {
138 &self.content
139 }
140
141 pub fn metadata(&self) -> &MemoryMetadata {
142 &self.metadata
143 }
144
145 pub fn into_parts(self) -> (MemoryIndexScope, String, MemoryMetadata) {
146 (self.scope, self.content, self.metadata)
147 }
148}
149
150#[derive(Debug, Clone)]
152pub struct MemoryIndexBatch {
153 scope: MemoryIndexScope,
154 requests: Vec<MemoryIndexRequest>,
155}
156
157impl MemoryIndexBatch {
158 pub fn new(
159 scope: MemoryIndexScope,
160 requests: Vec<MemoryIndexRequest>,
161 ) -> Result<Self, MemoryStoreError> {
162 for request in &requests {
163 if request.scope() != &scope {
164 return Err(MemoryStoreError::Scope(format!(
165 "memory index request scope {} is outside batch scope {}",
166 request.scope().session_id(),
167 scope.session_id()
168 )));
169 }
170 }
171 Ok(Self { scope, requests })
172 }
173
174 pub fn single(request: MemoryIndexRequest) -> Self {
175 Self {
176 scope: request.scope.clone(),
177 requests: vec![request],
178 }
179 }
180
181 pub fn scope(&self) -> &MemoryIndexScope {
182 &self.scope
183 }
184
185 pub fn len(&self) -> usize {
186 self.requests.len()
187 }
188
189 pub fn is_empty(&self) -> bool {
190 self.requests.is_empty()
191 }
192
193 pub fn into_parts(self) -> (MemoryIndexScope, Vec<MemoryIndexRequest>) {
194 (self.scope, self.requests)
195 }
196}
197
198#[derive(Debug, Clone)]
200pub struct MemoryIndexReceipt {
201 pub scope: MemoryIndexScope,
202 pub indexed_entries: usize,
203}
204
205#[derive(Debug)]
207pub enum MemoryIndexDelivery {
208 NoStore {
209 scope: MemoryIndexScope,
210 },
211 Delivered(MemoryIndexReceipt),
212 Rejected {
213 scope: MemoryIndexScope,
214 attempted_entries: usize,
215 error: MemoryStoreError,
216 },
217}
218
219#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
221#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
222pub trait MemoryStore: Send + Sync {
223 async fn index_scoped(
225 &self,
226 request: MemoryIndexRequest,
227 ) -> Result<MemoryIndexReceipt, MemoryStoreError> {
228 self.index_scoped_batch(MemoryIndexBatch::single(request))
229 .await
230 }
231
232 async fn index_scoped_batch(
237 &self,
238 batch: MemoryIndexBatch,
239 ) -> Result<MemoryIndexReceipt, MemoryStoreError>;
240
241 async fn search(
243 &self,
244 scope: &MemorySearchScope,
245 query: &str,
246 limit: usize,
247 ) -> Result<Vec<MemoryResult>, MemoryStoreError>;
248}
249
250#[derive(Debug, thiserror::Error)]
252pub enum MemoryStoreError {
253 #[error("Scope error: {0}")]
254 Scope(String),
255
256 #[error("Embedding error: {0}")]
257 Embedding(String),
258
259 #[error("Index error: {0}")]
260 Index(String),
261
262 #[error("IO error: {0}")]
263 Io(#[from] std::io::Error),
264}