opencode-session-rs#
Read opencode session storage with typed Rust APIs, an indexed metadata layer, and lazy file loading.
What this crate does#
- Detects opencode storage paths (
$XDG_DATA_HOME/opencode/storageby default). - Builds an in-memory index of projects, sessions, messages, and parts.
- Resolves session/message/part leaves as memory-mapped spans before hydration.
- Lets you run workflows at different levels: index-only, reference tree, or hydrated tree.
The primary high-level entrypoint is SessionMaterializer.
Install#
[dependencies]
opencode-session = "0.1"
Quick start#
use opencode_session::{SessionId, SessionMaterializer};
use std::str::FromStr;
fn main() -> opencode_session::Result<()> {
let materializer = SessionMaterializer::new()?;
for project_id in materializer.project_ids() {
let session_ids = materializer.session_ids_for_project(project_id);
println!("project={project_id} sessions={}", session_ids.len());
}
let session_id = SessionId::from_str("ses_3975b29b7ffeDyjus9LjxKUoeX")?;
let tree = materializer.load_session_tree(&session_id)?;
println!("loaded {} messages", tree.messages.len());
Ok(())
}
API surfaces#
1. Structural index access#
Use this when you want fast metadata queries without loading full JSON payloads.
let materializer = SessionMaterializer::new()?;
let session_meta = materializer.session_meta(&session_id);
let message_ids = materializer.message_ids_for_session(&session_id);
2. Piece-by-piece loading#
Use this when you want to deconstruct control flow and run explicit stages.
let message_ids = materializer.message_ids_for_session(&session_id);
for message_id in message_ids {
let message = materializer.load_message(message_id)?;
let parts = materializer.load_parts_for_message(message_id)?;
println!("message={} parts={}", message.id(), parts.len());
}
3. Full tree materialization#
Use this when you want one call returning session + messages + parts.
let tree = materializer.load_session_tree(&session_id)?;
println!("session={} messages={}", tree.session.info.id, tree.messages.len());
4. Incremental index construction#
Use this when you want to control indexing scope (for example, one project at a time).
use opencode_session::{SessionIndex, StoragePaths};
let paths = StoragePaths::detect()?;
let mut builder = SessionIndex::builder(paths);
builder.index_project("05fc47d0307a3740bf2ac963190b7c5b029b6ab1")?;
let index = builder.finish();
println!("indexed sessions={}", index.session_count());
5. Decomposed flow execution (Bon builder)#
Use staged planning + resolve + hydrate when you want full control and zero-copy leaves.
use opencode_session::{SessionFlowOptions, SessionId, SessionMaterializer};
use std::str::FromStr;
let materializer = SessionMaterializer::new()?;
let session_id = SessionId::from_str("ses_3975b29b7ffeDyjus9LjxKUoeX")?;
let options = SessionFlowOptions::builder()
.session_id(session_id)
.part_limit_per_message(5)
.build();
let plan = materializer.plan_session_tree(&options)?;
let ref_tree = materializer.resolve_session_tree(&plan.value)?;
let hydrated = materializer.hydrate_session_tree(&ref_tree.value)?;
// zero-copy leaf access before hydration
let first_message = &ref_tree.value.session.messages[0];
let raw_bytes = first_message.span.as_bytes();
println!(
"planned_messages={} hydrated_messages={} first_message_bytes={}",
plan.value.messages.len(),
hydrated.value.messages.len(),
raw_bytes.len(),
);
Core types#
SessionIndex: metadata graph (projects -> sessions -> messages -> parts)SessionMaterializer: high-level indexed read APISessionLoader: lower-level loader overFileReaderFileReader: path-based typed JSON reads + listing helpersMappedSpan: file-backed byte range leaf for zero-copy tree stagesMappedFileCache: memory-mapped file cache (papaya registry)
Notes#
- This crate is pre-1.0 and API shaping is active.
- Backward compatibility between minor revisions is not guaranteed yet.
watchandwatch-fallbackfeatures are reserved for live change tracking integration.- Registry internals use
papayaconcurrent hash maps for index and mmap caches.