use async_trait::async_trait;
use oxicode_sdk::SdkError;
use oxicode_sdk::ports::{ProtocolHandler, ResolveContext, ResolvedUrl};
use std::path::Path;
use std::sync::Arc;
use crate::foundation::brain::{BrainHealth, BrainMemoryBackend};
pub struct MemoryProtocolHandler {
backend: Arc<BrainMemoryBackend>,
scope: Option<String>,
}
impl std::fmt::Debug for MemoryProtocolHandler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryProtocolHandler")
.field("backend_health", &self.backend.health().info())
.field("scope", &self.scope)
.finish()
}
}
impl MemoryProtocolHandler {
pub fn new(backend: Arc<BrainMemoryBackend>) -> Self {
Self {
backend,
scope: None,
}
}
pub fn with_scope(backend: Arc<BrainMemoryBackend>, scope: impl Into<String>) -> Self {
Self {
backend,
scope: Some(scope.into()),
}
}
pub fn resolve_memory_url(&self, url: &str) -> Option<String> {
let suffix = url.strip_prefix("memory://")?;
let suffix = suffix.trim_start_matches("root/");
let suffix = suffix.trim_start_matches("root");
let suffix = suffix.trim_start_matches('/');
if suffix.is_empty() {
return Some(self.list_root());
}
match suffix {
"MEMORY.md" => Some(self.query_markdown("memory://root/MEMORY.md")),
"memory_summary.md" => Some(self.query_markdown("memory://root/memory_summary.md")),
"learned.md" => Some(self.query_markdown("memory://root/learned.md")),
other => {
if other.starts_with("skills/") && other.ends_with("/SKILL.md") {
Some(self.query_markdown(url))
} else {
None
}
}
}
}
fn list_root(&self) -> String {
let scope = self
.scope
.clone()
.unwrap_or_else(|| crate::foundation::brain::DEFAULT_BRAIN_SCOPE.to_string());
let mut out =
String::from("# Memory root\n\nListing of artifacts at the project memory root.\n");
match self.backend.health() {
BrainHealth::Unavailable | BrainHealth::Degraded => {
out.push_str(
"(degraded — durable memory is the oxibrain daemon; see `memory_info`)\n",
);
out.push_str("- Brain health: ");
out.push_str(self.backend.health().info());
out.push('\n');
out.push_str("- Scope: ");
out.push_str(&scope);
out.push('\n');
return out;
}
BrainHealth::Connected => {}
}
out.push_str("- `memory://root/MEMORY.md`\n");
out.push_str("- `memory://root/memory_summary.md`\n");
out.push_str("- `memory://root/learned.md`\n");
out.push_str("- `memory://root/skills/<name>/SKILL.md`\n");
out
}
fn query_markdown(&self, url: &str) -> String {
let scope = self
.scope
.clone()
.unwrap_or_else(|| crate::foundation::brain::DEFAULT_BRAIN_SCOPE.to_string());
let mut out = String::new();
out.push_str("# ");
out.push_str(url);
out.push_str("\n\n_scope_: `");
out.push_str(&scope);
out.push_str("`\n\n");
match self.backend.health() {
BrainHealth::Unavailable | BrainHealth::Degraded => {
out.push_str(
"(degraded — durable memory is the oxibrain daemon; see `memory_info`)\n",
);
out.push_str("- Brain health: ");
out.push_str(self.backend.health().info());
out.push('\n');
return out;
}
BrainHealth::Connected => {}
}
out.push_str("```\n(per-URL artifact reads are summarized from the brain scope; ");
out.push_str("use `memory_recall` / `memory_search` for live content)\n```\n");
out
}
}
#[async_trait]
impl ProtocolHandler for MemoryProtocolHandler {
fn scheme(&self) -> &str {
"memory"
}
async fn resolve(
&self,
url: &str,
_selector: Option<&str>,
_ctx: &ResolveContext,
) -> Result<ResolvedUrl, SdkError> {
let content = self
.resolve_memory_url(url)
.ok_or_else(|| SdkError::PortNotConfigured { port: "memory" })?;
let size = content.len();
Ok(ResolvedUrl {
url: url.to_string(),
content,
content_type: "text/markdown".to_string(),
size: Some(size),
source_path: None,
notes: vec![],
immutable: true,
})
}
}
pub fn resolve_memory_url_legacy(url: &str, memory_root: &Path) -> Option<String> {
let suffix = url.strip_prefix("memory://")?;
let suffix = suffix.trim_start_matches("root/");
let suffix = suffix.trim_start_matches("root");
let suffix = suffix.trim_start_matches('/');
if suffix.is_empty() {
let mut out = String::from("# Memory root\n\n(legacy disk-rooted listing; deprecated)\n");
if !memory_root.exists() {
out.push_str("(memory_root not present)\n");
return Some(out);
}
let entries = std::fs::read_dir(memory_root).ok();
let has_files = entries
.map(|rd| rd.flatten().any(|e| e.path().exists()))
.unwrap_or(false);
if !has_files {
out.push_str("(empty — pipeline has not run yet)\n");
return Some(out);
}
out.push_str("- `memory://root/MEMORY.md`\n");
out.push_str("- `memory://root/memory_summary.md`\n");
out.push_str("- `memory://root/learned.md`\n");
out.push_str("- `memory://root/skills/<name>/SKILL.md`\n");
return Some(out);
}
let candidate = memory_root.join(suffix);
if !is_within(memory_root, &candidate) {
return None;
}
std::fs::read_to_string(&candidate).ok()
}
fn is_within(root: &Path, candidate: &Path) -> bool {
let Ok(r) = root.canonicalize() else {
return false;
};
let Ok(c) = candidate.canonicalize() else {
return false;
};
c.starts_with(r)
}
#[cfg(test)]
mod tests {
use super::*;
fn fake() -> Arc<BrainMemoryBackend> {
Arc::new(BrainMemoryBackend::new("/tmp/does-not-exist.sock"))
}
#[test]
fn degraded_root_lists_health() {
let backend = fake();
let handler = MemoryProtocolHandler::new(backend.clone());
assert_eq!(backend.health(), BrainHealth::Unavailable);
let listing = handler.resolve_memory_url("memory://root").unwrap();
assert!(listing.contains("degraded"));
assert!(listing.contains("oxibrain"));
}
#[test]
fn memory_md_returns_markdown_shape() {
let backend = fake();
let handler = MemoryProtocolHandler::new(backend);
let md = handler
.resolve_memory_url("memory://root/MEMORY.md")
.unwrap();
assert!(md.contains("memory://root/MEMORY.md"));
assert!(md.contains("degraded") || md.contains("Scope"));
}
#[test]
fn skill_paths_resolve_to_markdown() {
let backend = fake();
let handler = MemoryProtocolHandler::new(backend);
let skill = handler
.resolve_memory_url("memory://root/skills/foundation/SKILL.md")
.unwrap();
assert!(skill.contains("memory://root/skills/foundation/SKILL.md"));
}
#[test]
fn unknown_url_returns_none() {
let backend = fake();
let handler = MemoryProtocolHandler::new(backend);
let result = handler.resolve_memory_url("memory://root/random/path.md");
assert!(result.is_none());
}
#[test]
fn non_memory_scheme_returns_none() {
let backend = fake();
let handler = MemoryProtocolHandler::new(backend);
let result = handler.resolve_memory_url("https://example.com");
assert!(result.is_none());
}
#[test]
fn with_scope_keeps_scope() {
let backend = fake();
let handler = MemoryProtocolHandler::with_scope(backend, "oxicode/main");
assert_eq!(handler.scope.as_deref(), Some("oxicode/main"));
}
#[test]
fn legacy_path_still_compiles() {
let tmp = tempfile::tempdir().unwrap();
let md = resolve_memory_url_legacy("memory://root", tmp.path()).unwrap();
assert!(md.contains("(memory_root not present)") || md.contains("legacy"));
}
}