Skip to main content

toolpath_pi/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod derive;
4pub mod error;
5pub mod io;
6pub mod paths;
7pub mod project;
8pub mod provider;
9pub mod reader;
10pub mod types;
11
12pub use derive::{derive_graph, derive_path, derive_project};
13pub use error::{PiError, Result};
14pub use paths::PathResolver;
15pub use provider::session_to_view;
16pub use reader::{PiSession, SessionMeta};
17pub use toolpath_convo::DeriveConfig;
18pub use types::{
19    AgentMessage, ContentBlock, CostBreakdown, Entry, EntryBase, SessionHeader, Usage,
20};
21
22use toolpath_convo::ConversationView;
23
24/// High-level interface for reading Pi sessions.
25#[derive(Debug, Clone, Default)]
26pub struct PiConvo {
27    resolver: PathResolver,
28}
29
30impl PiConvo {
31    /// Build a manager with the default resolver (`~/.pi/agent/sessions/`).
32    pub fn new() -> Self {
33        Self {
34            resolver: PathResolver::new(),
35        }
36    }
37
38    /// Build a manager with a custom resolver (useful for tests).
39    pub fn with_resolver(resolver: PathResolver) -> Self {
40        Self { resolver }
41    }
42
43    /// Access the underlying resolver.
44    pub fn resolver(&self) -> &PathResolver {
45        &self.resolver
46    }
47
48    /// Whether the Pi sessions directory exists on disk.
49    pub fn exists(&self) -> bool {
50        self.resolver.sessions_dir().exists()
51    }
52
53    /// List known project paths (decoded cwd values).
54    pub fn list_projects(&self) -> Result<Vec<String>> {
55        io::list_projects(&self.resolver)
56    }
57
58    /// List session metadata for a project, newest first.
59    pub fn list_sessions(&self, project: &str) -> Result<Vec<SessionMeta>> {
60        io::list_sessions(&self.resolver, project)
61    }
62
63    /// Read a specific session by ID.
64    pub fn read_session(&self, project: &str, session_id: &str) -> Result<PiSession> {
65        reader::read_session(&self.resolver, project, session_id)
66    }
67
68    /// Read the most recently active session for a project, if any.
69    pub fn most_recent_session(&self, project: &str) -> Result<Option<PiSession>> {
70        let mut metas = self.list_sessions(project)?;
71        if let Some(meta) = metas.drain(..).next() {
72            let session = self.read_session(project, &meta.id)?;
73            Ok(Some(session))
74        } else {
75            Ok(None)
76        }
77    }
78
79    /// Convert a Pi session into a provider-agnostic `ConversationView`.
80    pub fn to_view(&self, session: &PiSession) -> ConversationView {
81        provider::session_to_view(session)
82    }
83
84    /// Read all sessions for a project.
85    pub fn read_all_sessions(&self, project: &str) -> Result<Vec<PiSession>> {
86        let metas = self.list_sessions(project)?;
87        let mut sessions = Vec::new();
88        for meta in metas {
89            match self.read_session(project, &meta.id) {
90                Ok(s) => sessions.push(s),
91                Err(e) => {
92                    eprintln!("Warning: failed to read Pi session {}: {}", meta.id, e);
93                }
94            }
95        }
96        Ok(sessions)
97    }
98}