use async_trait::async_trait;
use serde_json::json;
use std::sync::Arc;
use bamboo_agent_core::storage::Storage;
use bamboo_agent_core::tools::{Tool, ToolError, ToolExecutionContext, ToolResult};
use bamboo_infrastructure::SessionStoreV2;
mod args;
mod handlers;
mod helpers;
use args::SessionInspectorArgs;
pub struct SessionInspectorTool {
pub(super) session_store: Arc<SessionStoreV2>,
pub(super) storage: Arc<dyn Storage>,
}
impl SessionInspectorTool {
pub fn new(session_store: Arc<SessionStoreV2>, storage: Arc<dyn Storage>) -> Self {
Self {
session_store,
storage,
}
}
pub(super) async fn load_session(
&self,
session_id: &str,
) -> Result<bamboo_agent_core::Session, ToolError> {
match self.storage.load_session(session_id).await {
Ok(Some(s)) => Ok(s),
Ok(None) => Err(ToolError::Execution(format!(
"session not found: {session_id}"
))),
Err(e) => Err(ToolError::Execution(format!(
"failed to load session {session_id}: {e}"
))),
}
}
}
#[async_trait]
impl Tool for SessionInspectorTool {
fn name(&self) -> &str {
"session_history"
}
fn description(&self) -> &str {
"Read-only viewer over the local SQLite session history. Use this to list prior sessions, inspect metadata, read bounded message slices, read the compressed conversation cache, and full-text search prior conversation history before asking the user to repeat information. This is purely a read tool — it has no runtime control and cannot influence live sessions. Distinct from the `memory` tool, which manages durable cross-session knowledge."
}
fn parameters_schema(&self) -> serde_json::Value {
json!({
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["list", "get_meta", "read_messages", "read_compressed_cache", "search"],
"description": "Which inspection action to perform."
},
"query": { "type": "string", "description": "Search string (list/search)." },
"kind": { "type": "string", "enum": ["root", "child"], "description": "Filter by session kind (list)." },
"pinned": { "type": "boolean", "description": "Filter pinned sessions (list)." },
"parent_session_id": { "type": "string", "description": "Filter child sessions by parent (list)." },
"root_session_id": { "type": "string", "description": "Filter by root session (list)." },
"created_by_schedule_id": { "type": "string", "description": "Filter sessions created by a schedule (list)." },
"limit": { "type": "number", "description": "Max items/messages to return (list/read_messages)." },
"offset": { "type": "number", "description": "Offset (list/read_messages)." },
"session_id": { "type": "string", "description": "Target session id (get_meta/read_messages)." },
"from_end": { "type": "boolean", "description": "Read from end (read_messages)." },
"truncate_chars": { "type": "number", "description": "Max chars per message (read_messages)." },
"include_system": { "type": "boolean" },
"include_tool": { "type": "boolean" },
"include_tool_calls": { "type": "boolean" },
"include_image_urls": { "type": "boolean" },
"include_summary": { "type": "boolean", "description": "Include cached conversation summary when available (read_compressed_cache)." },
"mode": { "type": "string", "enum": ["title", "tail_messages"] },
"max_sessions": { "type": "number" },
"tail_messages": { "type": "number" },
"case_sensitive": { "type": "boolean" },
"max_matches": { "type": "number" }
},
"required": ["action"]
})
}
async fn execute(&self, args: serde_json::Value) -> Result<ToolResult, ToolError> {
self.execute_with_context(args, ToolExecutionContext::none("tool_call"))
.await
}
async fn execute_with_context(
&self,
args: serde_json::Value,
ctx: ToolExecutionContext<'_>,
) -> Result<ToolResult, ToolError> {
let _caller_session_id = ctx.session_id.ok_or_else(|| {
ToolError::Execution(
"session_history requires a session_id in tool context".to_string(),
)
})?;
let parsed: SessionInspectorArgs = serde_json::from_value(args).map_err(|e| {
ToolError::InvalidArguments(format!("Invalid session_history args: {e}"))
})?;
match parsed {
SessionInspectorArgs::List {
query,
kind,
pinned,
parent_session_id,
root_session_id,
created_by_schedule_id,
limit,
offset,
} => {
handlers::handle_list(
self,
query,
kind,
pinned,
parent_session_id,
root_session_id,
created_by_schedule_id,
limit,
offset,
)
.await
}
SessionInspectorArgs::GetMeta { session_id } => {
handlers::handle_get_meta(self, session_id).await
}
SessionInspectorArgs::ReadMessages {
session_id,
from_end,
offset,
limit,
truncate_chars,
include_system,
include_tool,
include_tool_calls,
include_image_urls,
} => {
handlers::handle_read_messages(
self,
session_id,
from_end,
offset,
limit,
truncate_chars,
include_system,
include_tool,
include_tool_calls,
include_image_urls,
)
.await
}
SessionInspectorArgs::ReadCompressedCache {
session_id,
offset,
limit,
truncate_chars,
include_summary,
} => {
handlers::handle_read_compressed_cache(
self,
session_id,
offset,
limit,
truncate_chars,
include_summary,
)
.await
}
SessionInspectorArgs::Search {
query,
mode,
max_sessions,
tail_messages,
case_sensitive,
max_matches,
} => {
handlers::handle_search(
self,
query,
mode,
max_sessions,
tail_messages,
case_sensitive,
max_matches,
)
.await
}
}
}
}