Watch
1
0
Fork
You've already forked souveraine
0
souveraine/src/server/gitea_memory.rs

131 lines
4.1 KiB
Rust

#![allow(dead_code)] // WIP scaffolding not yet wired
//! Gitea-backed Memory
//!
//! Send-safe alternative to GitMemory using Gitea HTTP API.
//! All operations go through REST API calls instead of libgit2.
use anyhow::{anyhow, Result};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::core::config::ConsciousnessConfig;
use crate::server::gitea_client::GiteaClient;
#[derive(Clone)]
pub struct GiteaMemory {
client: GiteaClient,
config: Arc<RwLock<ConsciousnessConfig>>,
/// Local cache of file contents (path -> content)
cache: Arc<RwLock<HashMap<String, String>>>,
}
impl GiteaMemory {
/// Create new GiteaMemory from config
///
/// Expects config.gitea.url and config.gitea.token
pub async fn new(config: Arc<RwLock<ConsciousnessConfig>>) -> Result<Self> {
let cfg = config.read().await;
// Get Gitea URL from config - fall back to env var or default
let gitea_url = std::env::var("SOUVERAINE_GITEA_URL").unwrap_or_else(|_| {
// Default to localhost Gitea
"http://localhost:3000".to_string()
});
let gitea_token = std::env::var("SOUVERAINE_GITEA_TOKEN")
.map_err(|_| anyhow!("SOUVERAINE_GITEA_TOKEN env var required for server mode"))?;
drop(cfg);
let client = GiteaClient::new(gitea_url, gitea_token);
Ok(Self {
client,
config,
cache: Arc::new(RwLock::new(HashMap::new())),
})
}
/// Read file from Gitea
pub async fn read(&self, agent_id: &str, path: &str) -> Result<Option<String>> {
// Try cache first
let cache_key = format!("{}/{}", agent_id, path);
{
let cache = self.cache.read().await;
if let Some(content) = cache.get(&cache_key) {
return Ok(Some(content.clone()));
}
}
// Fetch from Gitea
match self.client.get_file(agent_id, path).await? {
Some(content) => {
// Update cache
let mut cache = self.cache.write().await;
cache.insert(cache_key, content.clone());
Ok(Some(content))
}
None => Ok(None),
}
}
/// Write file to Gitea
pub async fn write(&self, agent_id: &str, path: &str, content: &str) -> Result<()> {
let cache_key = format!("{}/{}", agent_id, path);
// Ensure repo exists
if !self.client.repo_exists(agent_id).await? {
self.client.create_repo(agent_id).await?;
}
// Write to Gitea
self.client
.put_file(agent_id, path, content, &format!("Update {}", path))
.await?;
// Update cache
let mut cache = self.cache.write().await;
cache.insert(cache_key, content.to_string());
Ok(())
}
/// Append to file in Gitea
pub async fn append(&self, agent_id: &str, path: &str, content: &str) -> Result<()> {
let existing = self.read(agent_id, path).await?;
let new_content = match existing {
Some(mut existing) => {
if !existing.ends_with('\n') {
existing.push('\n');
}
existing + content
}
None => content.to_string(),
};
self.write(agent_id, path, &new_content).await
}
/// List files in agent's memory
pub async fn list(&self, agent_id: &str, path: &str) -> Result<Vec<String>> {
self.client.list_files(agent_id, path).await
}
/// Sync all memory from Gitea to local cache
pub async fn sync_from_gitea(&self, agent_id: &str) -> Result<Vec<(String, String)>> {
let files = self.list(agent_id, "").await?;
let mut synced = Vec::new();
for file in files {
if let Some(content) = self.read(agent_id, &file).await? {
synced.push((file, content));
}
}
Ok(synced)
}
}
// The blanket `impl Memory for GiteaMemory` lived here. It will return when
// the `Memory` trait is reintroduced in `crates/memory` (Stage 1). For now
// GiteaMemory exposes its inherent agent-scoped methods directly.