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(paths.home.join("personas"))))
85 .with_access(Arc::new(SimpleAccessGate::from_file(
86 paths.home.join("access.toml"),
87 )))
88 .with_capabilities(Arc::new(TomlCapabilityResolver::from_file(
89 paths.home.join("capabilities.toml"),
90 )))
91 .with_event_bus(InProcessEventBus::new(64))
92 .with_memory(Arc::new(InMemoryMemoryStore::new()))
93 .with_cron(Arc::new(InMemoryCronScheduler::new()))
94 .with_resources(Arc::new(CountingResourceMonitor::new()))
95 .build();
96
97 Ok(oxi)
98}
99
100fn ensure_parent(path: &Path) -> Result<()> {
101 if let Some(parent) = path.parent() {
102 std::fs::create_dir_all(parent)
103 .with_context(|| format!("create_dir_all {}", parent.display()))?;
104 }
105 Ok(())
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn paths_are_consistent() {
114 let p = OxiPaths::from_home("/tmp/oxi-test");
115 assert!(p.auth.starts_with("/tmp/oxi-test"));
116 assert!(p.config.starts_with("/tmp/oxi-test"));
117 assert!(p.sessions.starts_with("/tmp/oxi-test"));
118 assert!(p.skills.starts_with("/tmp/oxi-test"));
119 }
120
121 #[test]
122 fn build_oxi_succeeds() {
123 let tmp = tempfile::TempDir::new().unwrap();
124 let paths = OxiPaths::from_home(tmp.path());
125 let oxi = build_oxi(&paths).unwrap();
126 let _ = oxi.ports().state;
128 }
129}