1use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::future::Future;
6
7#[derive(Debug, thiserror::Error)]
12pub enum ContextError {
13 #[error("{0}")]
14 NotFound(String),
15 #[error("{0}")]
16 AlreadyExists(String),
17 #[error("{0}")]
18 InvalidRequest(String),
19 #[error("{0}")]
20 Internal(String),
21 #[error("Compaction already in progress")]
22 CompactionInProgress,
23}
24
25pub type ContextResult<T> = Result<T, ContextError>;
26
27pub trait ContextStoreApi {
32 fn add(
33 &mut self,
34 records: &[AddRecordRequest],
35 ) -> impl Future<Output = ContextResult<AddRecordsResponse>> + Send;
36
37 fn get(&self, id: &str) -> impl Future<Output = ContextResult<Option<RecordDto>>> + Send;
38
39 fn list(
40 &self,
41 limit: Option<usize>,
42 offset: Option<usize>,
43 ) -> impl Future<Output = ContextResult<Vec<RecordDto>>> + Send;
44
45 fn search(
46 &self,
47 query: &[f32],
48 limit: Option<usize>,
49 ) -> impl Future<Output = ContextResult<Vec<SearchResultDto>>> + Send;
50
51 fn version(&self) -> u64;
52
53 fn checkout(&mut self, version: u64) -> impl Future<Output = ContextResult<()>> + Send;
54
55 fn compact(
56 &mut self,
57 options: Option<CompactRequest>,
58 ) -> impl Future<Output = ContextResult<CompactResponse>> + Send;
59
60 fn compaction_stats(&self) -> impl Future<Output = ContextResult<CompactStatsResponse>> + Send;
61}
62
63#[derive(Debug, Serialize, Deserialize)]
68pub struct CreateContextRequest {
69 pub name: String,
70 #[serde(default)]
71 pub storage_options: Option<std::collections::HashMap<String, String>>,
72 #[serde(default)]
73 pub id_index_type: Option<String>,
74 #[serde(default)]
75 pub blob_columns: Option<Vec<String>>,
76}
77
78#[derive(Debug, Serialize, Deserialize)]
79pub struct ContextInfo {
80 pub name: String,
81 pub uri: String,
82 pub version: u64,
83}
84
85#[derive(Debug, Serialize, Deserialize)]
86pub struct ListContextsResponse {
87 pub contexts: Vec<ContextInfo>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct StateMetadataDto {
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub step: Option<i32>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub active_plan_id: Option<String>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub tokens_used: Option<i32>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub custom: Option<String>,
104}
105
106#[derive(Debug, Clone, Default, Serialize, Deserialize)]
107pub struct AddRecordRequest {
108 #[serde(default = "default_role")]
109 pub role: String,
110 #[serde(default = "default_content_type")]
111 pub content_type: String,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub text_payload: Option<String>,
114 #[serde(
115 default,
116 skip_serializing_if = "Option::is_none",
117 serialize_with = "serialize_base64_opt",
118 deserialize_with = "deserialize_base64_opt"
119 )]
120 pub binary_payload: Option<Vec<u8>>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub embedding: Option<Vec<f32>>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub bot_id: Option<String>,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub session_id: Option<String>,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
128 pub external_id: Option<String>,
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub state_metadata: Option<StateMetadataDto>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub metadata: Option<Value>,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub expires_at: Option<DateTime<Utc>>,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub retention_policy: Option<String>,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub supersedes_id: Option<String>,
139}
140
141#[derive(Debug, Serialize, Deserialize)]
142pub struct AddRecordsRequest {
143 pub records: Vec<AddRecordRequest>,
144}
145
146#[derive(Debug, Serialize, Deserialize)]
147pub struct AddRecordsResponse {
148 pub version: u64,
149 pub ids: Vec<String>,
150 pub count: usize,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct RecordDto {
155 pub id: String,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub external_id: Option<String>,
158 pub run_id: String,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub bot_id: Option<String>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub session_id: Option<String>,
163 pub created_at: DateTime<Utc>,
164 pub role: String,
165 pub content_type: String,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub text_payload: Option<String>,
168 #[serde(
169 default,
170 skip_serializing_if = "Option::is_none",
171 serialize_with = "serialize_base64_opt",
172 deserialize_with = "deserialize_base64_opt"
173 )]
174 pub binary_payload: Option<Vec<u8>>,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub embedding: Option<Vec<f32>>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub state_metadata: Option<StateMetadataDto>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub metadata: Option<Value>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub expires_at: Option<DateTime<Utc>>,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub retention_policy: Option<String>,
185 pub lifecycle_status: String,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub retired_at: Option<DateTime<Utc>>,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub retired_reason: Option<String>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub supersedes_id: Option<String>,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub superseded_by_id: Option<String>,
194}
195
196#[derive(Debug, Serialize, Deserialize)]
197pub struct ListRecordsResponse {
198 pub records: Vec<RecordDto>,
199}
200
201#[derive(Debug, Serialize, Deserialize)]
206pub struct GetRecordResponse {
207 pub record: Option<RecordDto>,
208}
209
210#[derive(Debug, Serialize, Deserialize)]
215pub struct SearchRequest {
216 pub query: Vec<f32>,
217 #[serde(default = "default_search_limit")]
218 pub limit: usize,
219}
220
221#[derive(Debug, Serialize, Deserialize)]
222pub struct SearchResultDto {
223 pub record: RecordDto,
224 pub distance: f32,
225}
226
227#[derive(Debug, Serialize, Deserialize)]
228pub struct SearchResponse {
229 pub results: Vec<SearchResultDto>,
230}
231
232#[derive(Debug, Serialize, Deserialize)]
237pub struct VersionResponse {
238 pub version: u64,
239}
240
241#[derive(Debug, Serialize, Deserialize)]
242pub struct CheckoutRequest {
243 pub version: u64,
244}
245
246#[derive(Debug, Default, Serialize, Deserialize)]
251pub struct CompactRequest {
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub target_rows_per_fragment: Option<usize>,
254 #[serde(default, skip_serializing_if = "Option::is_none")]
255 pub materialize_deletions: Option<bool>,
256}
257
258#[derive(Debug, Serialize, Deserialize)]
259pub struct CompactResponse {
260 pub fragments_removed: usize,
261 pub fragments_added: usize,
262 pub files_removed: usize,
263 pub files_added: usize,
264}
265
266#[derive(Debug, Serialize, Deserialize)]
267pub struct CompactStatsResponse {
268 pub total_fragments: usize,
269 pub is_compacting: bool,
270 #[serde(default, skip_serializing_if = "Option::is_none")]
271 pub last_compaction: Option<DateTime<Utc>>,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
273 pub last_error: Option<String>,
274 pub total_compactions: u64,
275}
276
277#[derive(Debug, Serialize, Deserialize)]
282pub struct ErrorBody {
283 pub code: String,
284 pub message: String,
285}
286
287#[derive(Debug, Serialize, Deserialize)]
288pub struct ErrorResponse {
289 pub error: ErrorBody,
290}
291
292fn default_content_type() -> String {
297 "text/plain".to_string()
298}
299
300fn default_role() -> String {
301 "user".to_string()
302}
303
304fn default_search_limit() -> usize {
305 10
306}
307
308fn serialize_base64_opt<S>(data: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
309where
310 S: serde::Serializer,
311{
312 match data {
313 Some(bytes) => serializer.serialize_some(&BASE64.encode(bytes)),
314 None => serializer.serialize_none(),
315 }
316}
317
318fn deserialize_base64_opt<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
319where
320 D: serde::Deserializer<'de>,
321{
322 let opt: Option<String> = Option::deserialize(deserializer)?;
323 match opt {
324 Some(s) => BASE64
325 .decode(&s)
326 .map(Some)
327 .map_err(serde::de::Error::custom),
328 None => Ok(None),
329 }
330}