1mod database;
4mod discovery;
5mod editing;
6pub mod handshake;
7mod locations;
8pub mod native_import;
9pub mod shared;
10
11pub use editing::save;
12
13use crate::FileContent;
14use serde::{Deserialize, Serialize};
15use std::path::PathBuf;
16use thiserror::Error;
17
18pub const SOURCE_LIMIT: usize = 1_024;
19
20#[derive(Clone, Debug, Default, Deserialize, Serialize)]
21#[serde(rename_all = "camelCase", deny_unknown_fields)]
22pub struct MemoryRequest {
23 pub folder: Option<PathBuf>,
24 pub source_id: Option<String>,
25 pub examples_root: Option<PathBuf>,
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub enum MemoryHarness {
31 Codex,
32 ClaudeCode,
33 Antigravity,
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub enum MemoryScope {
39 Global,
40 Folder,
41 OtherFolder,
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
45#[serde(rename_all = "camelCase")]
46pub enum MemoryKind {
47 Summary,
48 Durable,
49 RawMemory,
50 RolloutSummary,
51 Instructions,
52 Rule,
53 AgentMemory,
54 Skill,
55 Extension,
56 Artifact,
57 StoreStatus,
58 Configuration,
59}
60
61#[derive(Clone, Debug, Serialize)]
62#[serde(rename_all = "camelCase")]
63pub struct MemorySource {
64 pub id: String,
65 pub title: String,
66 pub harness: MemoryHarness,
67 pub scope: MemoryScope,
68 pub kind: MemoryKind,
69 pub location: String,
70 pub group: String,
71 pub format: String,
72 pub modified_at: Option<u64>,
73 pub example: bool,
74 pub association: Option<String>,
75}
76
77#[derive(Clone, Debug, Serialize)]
78#[serde(rename_all = "camelCase")]
79pub struct MemoryCatalog {
80 pub sources: Vec<MemorySource>,
81 pub diagnostics: Vec<String>,
82}
83
84#[derive(Clone, Debug, Serialize)]
85#[serde(rename_all = "camelCase")]
86pub struct MemoryRead {
87 pub source: MemorySource,
88 pub text: Option<String>,
89 pub message: Option<String>,
90 #[serde(skip)]
91 pub file: Option<crate::FilePreview>,
92}
93
94#[derive(Debug, Error)]
95pub enum MemoryError {
96 #[error("Memory discovery requires absolute folder paths.")]
97 InvalidPath,
98 #[error("The source is no longer available. Refresh the source list.")]
99 MissingSource,
100 #[error("Only local Markdown files can be edited from Memories.")]
101 UnsupportedEdit,
102 #[error(transparent)]
103 File(#[from] crate::FileError),
104}
105
106pub fn catalog(request: &MemoryRequest) -> Result<MemoryCatalog, MemoryError> {
111 let (sources, diagnostics) = discovery::discover(request)?;
112 Ok(MemoryCatalog {
113 sources: sources.into_iter().map(|e| e.source).collect(),
114 diagnostics,
115 })
116}
117
118pub fn read(request: &MemoryRequest) -> Result<MemoryRead, MemoryError> {
123 let id = request
124 .source_id
125 .as_ref()
126 .ok_or(MemoryError::MissingSource)?;
127 let (entries, _) = discovery::discover(request)?;
128 let entry = entries
129 .into_iter()
130 .find(|e| &e.source.id == id)
131 .ok_or(MemoryError::MissingSource)?;
132 let mut file = None;
133 let content = match entry.backing {
134 discovery::Backing::File { root, path } => {
135 let preview = crate::files::read_preview(&root, &path);
136 let content = preview.content.clone();
137 file = Some(preview);
138 content
139 }
140 discovery::Backing::Text(text) => FileContent::Text(text),
141 discovery::Backing::Database {
142 root,
143 path,
144 thread,
145 column,
146 } => database::read_record(&root, &path, &thread, &column),
147 };
148 let (text, message) = match content {
149 FileContent::Text(text) => (Some(text), None),
150 FileContent::Binary => (
151 None,
152 Some("This source is binary, rather than UTF-8 text.".into()),
153 ),
154 FileContent::TooLarge => (
155 None,
156 Some("This source exceeds the 2 MiB reading limit.".into()),
157 ),
158 FileContent::Missing => (
159 None,
160 Some("This source was removed. Refresh the list.".into()),
161 ),
162 FileContent::Unsupported => (
163 None,
164 Some("Symbolic links and special files cannot be read.".into()),
165 ),
166 FileContent::Unavailable(message) => (None, Some(message)),
167 };
168 Ok(MemoryRead {
169 source: entry.source,
170 text,
171 message,
172 file,
173 })
174}