claude_agent/context/
provider.rs

1//! Memory provider trait and implementations.
2//!
3//! Providers abstract the source of memory content, allowing for both
4//! in-memory testing and file-based production use.
5
6use std::path::PathBuf;
7
8use async_trait::async_trait;
9
10use super::{ContextResult, MemoryContent, MemoryLoader};
11
12/// Trait for providing memory content from various sources.
13///
14/// Implementations can load CLAUDE.md content from:
15/// - In-memory strings (for testing/programmatic use)
16/// - File system (for CLI-compatible behavior)
17/// - Remote sources (for enterprise deployment)
18#[async_trait]
19pub trait MemoryProvider: Send + Sync {
20    /// Returns the provider name for debugging/logging.
21    fn name(&self) -> &str;
22
23    /// Loads memory content from this provider.
24    async fn load(&self) -> ContextResult<MemoryContent>;
25}
26
27/// In-memory provider for testing and programmatic use.
28///
29/// # Example
30/// ```
31/// use claude_agent::context::InMemoryProvider;
32///
33/// let provider = InMemoryProvider::new()
34///     .with_claude_md("# Project Rules\nUse async/await for all I/O.");
35/// ```
36#[derive(Debug, Clone, Default)]
37pub struct InMemoryProvider {
38    /// Content to include as CLAUDE.md.
39    pub claude_md: Vec<String>,
40    /// Content to include as CLAUDE.local.md.
41    pub local_md: Vec<String>,
42}
43
44impl InMemoryProvider {
45    /// Creates a new empty InMemoryProvider.
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Adds content to the CLAUDE.md section.
51    pub fn with_claude_md(mut self, content: impl Into<String>) -> Self {
52        self.claude_md.push(content.into());
53        self
54    }
55
56    /// Adds content to the CLAUDE.local.md section.
57    pub fn with_local_md(mut self, content: impl Into<String>) -> Self {
58        self.local_md.push(content.into());
59        self
60    }
61}
62
63#[async_trait]
64impl MemoryProvider for InMemoryProvider {
65    fn name(&self) -> &str {
66        "in-memory"
67    }
68
69    async fn load(&self) -> ContextResult<MemoryContent> {
70        Ok(MemoryContent {
71            claude_md: self.claude_md.clone(),
72            local_md: self.local_md.clone(),
73            rule_indices: Vec::new(),
74        })
75    }
76}
77
78/// File-based memory provider for CLI-compatible behavior.
79///
80/// Loads CLAUDE.md and CLAUDE.local.md files from the file system
81/// with full @import support.
82///
83/// # Example
84/// ```no_run
85/// use claude_agent::context::FileMemoryProvider;
86///
87/// let provider = FileMemoryProvider::new("/path/to/project");
88/// ```
89#[derive(Debug, Clone)]
90pub struct FileMemoryProvider {
91    /// Root path to load memory files from.
92    pub path: PathBuf,
93}
94
95impl FileMemoryProvider {
96    /// Creates a new FileMemoryProvider for the given directory.
97    pub fn new(path: impl Into<PathBuf>) -> Self {
98        Self { path: path.into() }
99    }
100}
101
102#[async_trait]
103impl MemoryProvider for FileMemoryProvider {
104    fn name(&self) -> &str {
105        "file"
106    }
107
108    async fn load(&self) -> ContextResult<MemoryContent> {
109        let loader = MemoryLoader::new();
110        loader.load(&self.path).await
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[tokio::test]
119    async fn test_in_memory_provider() {
120        let provider = InMemoryProvider::new()
121            .with_claude_md("# Project Rules")
122            .with_claude_md("Use async/await.");
123
124        let content = provider.load().await.unwrap();
125        assert_eq!(content.claude_md.len(), 2);
126        assert!(content.claude_md[0].contains("Project Rules"));
127    }
128
129    #[tokio::test]
130    async fn test_in_memory_provider_with_local() {
131        let provider = InMemoryProvider::new()
132            .with_claude_md("Shared rules")
133            .with_local_md("Local settings");
134
135        let content = provider.load().await.unwrap();
136        assert_eq!(content.claude_md.len(), 1);
137        assert_eq!(content.local_md.len(), 1);
138    }
139
140    #[tokio::test]
141    async fn test_empty_provider() {
142        let provider = InMemoryProvider::new();
143        let content = provider.load().await.unwrap();
144        assert!(content.is_empty());
145    }
146}