Skip to main content

roder_api/
memory.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use serde::{Deserialize, Serialize};
5use time::OffsetDateTime;
6
7use crate::extension::{EmbeddingProviderId, MemoryStoreId};
8
9pub type MemoryId = String;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12pub enum MemoryScope {
13    Global,
14    User(String),
15    Workspace(String),
16    Project(String),
17    Thread(String),
18}
19
20impl MemoryScope {
21    pub fn stable_id(&self) -> String {
22        match self {
23            MemoryScope::Global => "global".to_string(),
24            MemoryScope::User(id) => format!("user:{id}"),
25            MemoryScope::Workspace(id) => format!("workspace:{id}"),
26            MemoryScope::Project(id) => format!("project:{id}"),
27            MemoryScope::Thread(id) => format!("thread:{id}"),
28        }
29    }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(rename_all = "camelCase")]
34pub struct MemoryScopeDescriptor {
35    pub id: String,
36    pub scope: MemoryScope,
37    pub label: String,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41#[serde(rename_all = "camelCase")]
42pub struct MemoryUsageMetadata {
43    pub use_count: u64,
44    #[serde(default, with = "time::serde::rfc3339::option")]
45    pub last_used_at: Option<OffsetDateTime>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49#[serde(rename_all = "camelCase")]
50pub struct MemoryCitation {
51    pub memory_id: MemoryId,
52    pub scope_id: String,
53    pub snippet: String,
54    pub score_millis: u32,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
58#[serde(rename_all = "camelCase")]
59pub struct MemoryRecord {
60    pub id: Option<MemoryId>,
61    pub scope: MemoryScope,
62    pub text: String,
63    #[serde(default)]
64    pub content_hash: Option<String>,
65    pub metadata: serde_json::Value,
66    #[serde(default)]
67    pub usage: Option<MemoryUsageMetadata>,
68    #[serde(default)]
69    pub deleted: bool,
70    #[serde(with = "time::serde::rfc3339")]
71    pub created_at: OffsetDateTime,
72    #[serde(with = "time::serde::rfc3339")]
73    pub updated_at: OffsetDateTime,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
77pub struct MemoryQuery {
78    pub scope: Option<MemoryScope>,
79    pub text: String,
80    pub limit: usize,
81    #[serde(default)]
82    pub include_global: bool,
83    #[serde(default)]
84    pub provider_id: Option<EmbeddingProviderId>,
85    #[serde(default)]
86    pub model: Option<String>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
90#[serde(rename_all = "camelCase")]
91pub struct MemorySearchResult {
92    pub record: MemoryRecord,
93    pub score: f32,
94    #[serde(default)]
95    pub citation: Option<MemoryCitation>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
99#[serde(rename_all = "camelCase")]
100pub struct MemorySaveRequest {
101    pub scope: MemoryScope,
102    pub text: String,
103    #[serde(default)]
104    pub metadata: serde_json::Value,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
108#[serde(rename_all = "camelCase")]
109pub struct MemoryUpdateRequest {
110    pub id: MemoryId,
111    pub text: String,
112    #[serde(default)]
113    pub metadata: serde_json::Value,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117#[serde(rename_all = "camelCase")]
118pub struct MemoryProviderSelection {
119    pub provider_id: EmbeddingProviderId,
120    pub model: String,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
124#[serde(rename_all = "camelCase")]
125pub struct MemoryJobLease {
126    pub id: String,
127    pub scope_id: String,
128    pub provider_id: EmbeddingProviderId,
129    pub model: String,
130    #[serde(with = "time::serde::rfc3339")]
131    pub leased_until: OffsetDateTime,
132    #[serde(default)]
133    pub attempts: u32,
134    #[serde(default)]
135    pub metadata: BTreeMap<String, String>,
136}
137
138#[async_trait::async_trait]
139pub trait MemoryStore: Send + Sync {
140    fn id(&self) -> MemoryStoreId;
141
142    async fn put(&self, record: MemoryRecord) -> anyhow::Result<MemoryId>;
143    async fn get(&self, id: &MemoryId) -> anyhow::Result<Option<MemoryRecord>>;
144    async fn search(&self, query: MemoryQuery) -> anyhow::Result<Vec<MemorySearchResult>>;
145    async fn delete(&self, id: &MemoryId) -> anyhow::Result<()>;
146    async fn list(
147        &self,
148        scope: Option<MemoryScope>,
149        limit: usize,
150    ) -> anyhow::Result<Vec<MemoryRecord>> {
151        let text = String::new();
152        let results = self
153            .search(MemoryQuery {
154                scope,
155                text,
156                limit,
157                include_global: false,
158                provider_id: None,
159                model: None,
160            })
161            .await?;
162        Ok(results.into_iter().map(|result| result.record).collect())
163    }
164}
165
166pub trait MemoryStoreFactory: Send + Sync + 'static {
167    fn id(&self) -> MemoryStoreId;
168    fn create(&self) -> Arc<dyn MemoryStore>;
169}