1use std::path::{Path, PathBuf};
19use std::sync::Arc;
20
21use anyhow::{Context, Result};
22
23use oxi_sdk::fs::{
24 FileAuthProvider, FileConfigStore, FilePersonaProvider, FileSkillLoader, FileStateStore,
25 SimpleAccessGate, TomlCapabilityResolver,
26};
27use oxi_sdk::inmem::{
28 CountingResourceMonitor, InMemoryCronScheduler, InMemoryMemoryStore, InProcessEventBus,
29};
30use oxi_sdk::Oxi;
31
32#[derive(Debug, Clone)]
34pub struct OxiPaths {
35 pub home: PathBuf,
37 pub auth: PathBuf,
39 pub config: PathBuf,
41 pub sessions: PathBuf,
43 pub skills: PathBuf,
45}
46
47impl OxiPaths {
48 pub fn from_home(home: impl Into<PathBuf>) -> Self {
50 let home = home.into();
51 Self {
52 auth: home.join("auth.json"),
53 config: home.join("settings.toml"),
54 sessions: home.join("sessions"),
55 skills: home.join("skills"),
56 home,
57 }
58 }
59
60 pub fn default_paths() -> Result<Self> {
62 oxi_sdk::fs::home_dir()
63 .map(Self::from_home)
64 .context("could not resolve oxi home directory")
65 }
66}
67
68pub fn build_oxi(paths: &OxiPaths) -> Result<Oxi> {
74 ensure_parent(&paths.auth)?;
75 ensure_parent(&paths.config)?;
76 ensure_parent(&paths.sessions)?;
77
78 let oxi = oxi_sdk::OxiBuilder::new()
79 .with_builtins()
80 .with_state(Arc::new(FileStateStore::new(&paths.sessions)))
81 .with_auth(Arc::new(FileAuthProvider::new(&paths.auth)))
82 .with_config(Arc::new(FileConfigStore::new(&paths.config)))
83 .with_skills(Arc::new(FileSkillLoader::single(&paths.skills)))
84 .with_personas(Arc::new(FilePersonaProvider::new(
85 paths.home.join("personas"),
86 )))
87 .with_access(Arc::new(SimpleAccessGate::from_file(
88 paths.home.join("access.toml"),
89 )))
90 .with_capabilities(Arc::new(TomlCapabilityResolver::from_file(
91 paths.home.join("capabilities.toml"),
92 )))
93 .with_event_bus(InProcessEventBus::new(64))
94 .with_memory(Arc::new(InMemoryMemoryStore::new()))
95 .with_cron(Arc::new(InMemoryCronScheduler::new()))
96 .with_resources(Arc::new(CountingResourceMonitor::new()))
97 .build();
98
99 Ok(oxi)
100}
101
102fn ensure_parent(path: &Path) -> Result<()> {
103 if let Some(parent) = path.parent() {
104 std::fs::create_dir_all(parent)
105 .with_context(|| format!("create_dir_all {}", parent.display()))?;
106 }
107 Ok(())
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn paths_are_consistent() {
116 let p = OxiPaths::from_home("/tmp/oxi-test");
117 assert!(p.auth.starts_with("/tmp/oxi-test"));
118 assert!(p.config.starts_with("/tmp/oxi-test"));
119 assert!(p.sessions.starts_with("/tmp/oxi-test"));
120 assert!(p.skills.starts_with("/tmp/oxi-test"));
121 }
122
123 #[test]
124 fn build_oxi_succeeds() {
125 let tmp = tempfile::TempDir::new().unwrap();
126 let paths = OxiPaths::from_home(tmp.path());
127 let oxi = build_oxi(&paths).unwrap();
128 let _ = oxi.ports().state;
130 }
131}