bamboo_engine/server_tools/session_inspector/
mod.rs1use async_trait::async_trait;
2use serde_json::json;
3use std::sync::Arc;
4
5use bamboo_agent_core::storage::Storage;
6use bamboo_agent_core::tools::{Tool, ToolError, ToolExecutionContext, ToolResult};
7use bamboo_infrastructure::SessionStoreV2;
8
9mod args;
10mod handlers;
11mod helpers;
12
13use args::SessionInspectorArgs;
14
15pub struct SessionInspectorTool {
23 pub(super) session_store: Arc<SessionStoreV2>,
24 pub(super) storage: Arc<dyn Storage>,
25}
26
27impl SessionInspectorTool {
28 pub fn new(session_store: Arc<SessionStoreV2>, storage: Arc<dyn Storage>) -> Self {
29 Self {
30 session_store,
31 storage,
32 }
33 }
34
35 pub(super) async fn load_session(
36 &self,
37 session_id: &str,
38 ) -> Result<bamboo_agent_core::Session, ToolError> {
39 match self.storage.load_session(session_id).await {
40 Ok(Some(s)) => Ok(s),
41 Ok(None) => Err(ToolError::Execution(format!(
42 "session not found: {session_id}"
43 ))),
44 Err(e) => Err(ToolError::Execution(format!(
45 "failed to load session {session_id}: {e}"
46 ))),
47 }
48 }
49}
50
51#[async_trait]
52impl Tool for SessionInspectorTool {
53 fn name(&self) -> &str {
54 "session_history"
55 }
56
57 fn description(&self) -> &str {
58 "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."
59 }
60
61 fn parameters_schema(&self) -> serde_json::Value {
62 json!({
64 "type": "object",
65 "properties": {
66 "action": {
67 "type": "string",
68 "enum": ["list", "get_meta", "read_messages", "read_compressed_cache", "search"],
69 "description": "Which inspection action to perform."
70 },
71 "query": { "type": "string", "description": "Search string (list/search)." },
72 "kind": { "type": "string", "enum": ["root", "child"], "description": "Filter by session kind (list)." },
73 "pinned": { "type": "boolean", "description": "Filter pinned sessions (list)." },
74 "parent_session_id": { "type": "string", "description": "Filter child sessions by parent (list)." },
75 "root_session_id": { "type": "string", "description": "Filter by root session (list)." },
76 "created_by_schedule_id": { "type": "string", "description": "Filter sessions created by a schedule (list)." },
77 "limit": { "type": "number", "description": "Max items/messages to return (list/read_messages)." },
78 "offset": { "type": "number", "description": "Offset (list/read_messages)." },
79 "session_id": { "type": "string", "description": "Target session id (get_meta/read_messages)." },
80 "from_end": { "type": "boolean", "description": "Read from end (read_messages)." },
81 "truncate_chars": { "type": "number", "description": "Max chars per message (read_messages)." },
82 "include_system": { "type": "boolean" },
83 "include_tool": { "type": "boolean" },
84 "include_tool_calls": { "type": "boolean" },
85 "include_image_urls": { "type": "boolean" },
86 "include_summary": { "type": "boolean", "description": "Include cached conversation summary when available (read_compressed_cache)." },
87 "mode": { "type": "string", "enum": ["title", "tail_messages"] },
88 "max_sessions": { "type": "number" },
89 "tail_messages": { "type": "number" },
90 "case_sensitive": { "type": "boolean" },
91 "max_matches": { "type": "number" }
92 },
93 "required": ["action"]
94 })
95 }
96
97 async fn execute(&self, args: serde_json::Value) -> Result<ToolResult, ToolError> {
98 self.execute_with_context(args, ToolExecutionContext::none("tool_call"))
99 .await
100 }
101
102 async fn execute_with_context(
103 &self,
104 args: serde_json::Value,
105 ctx: ToolExecutionContext<'_>,
106 ) -> Result<ToolResult, ToolError> {
107 let _caller_session_id = ctx.session_id.ok_or_else(|| {
108 ToolError::Execution(
109 "session_history requires a session_id in tool context".to_string(),
110 )
111 })?;
112
113 let parsed: SessionInspectorArgs = serde_json::from_value(args).map_err(|e| {
114 ToolError::InvalidArguments(format!("Invalid session_history args: {e}"))
115 })?;
116
117 match parsed {
118 SessionInspectorArgs::List {
119 query,
120 kind,
121 pinned,
122 parent_session_id,
123 root_session_id,
124 created_by_schedule_id,
125 limit,
126 offset,
127 } => {
128 handlers::handle_list(
129 self,
130 query,
131 kind,
132 pinned,
133 parent_session_id,
134 root_session_id,
135 created_by_schedule_id,
136 limit,
137 offset,
138 )
139 .await
140 }
141
142 SessionInspectorArgs::GetMeta { session_id } => {
143 handlers::handle_get_meta(self, session_id).await
144 }
145
146 SessionInspectorArgs::ReadMessages {
147 session_id,
148 from_end,
149 offset,
150 limit,
151 truncate_chars,
152 include_system,
153 include_tool,
154 include_tool_calls,
155 include_image_urls,
156 } => {
157 handlers::handle_read_messages(
158 self,
159 session_id,
160 from_end,
161 offset,
162 limit,
163 truncate_chars,
164 include_system,
165 include_tool,
166 include_tool_calls,
167 include_image_urls,
168 )
169 .await
170 }
171
172 SessionInspectorArgs::ReadCompressedCache {
173 session_id,
174 offset,
175 limit,
176 truncate_chars,
177 include_summary,
178 } => {
179 handlers::handle_read_compressed_cache(
180 self,
181 session_id,
182 offset,
183 limit,
184 truncate_chars,
185 include_summary,
186 )
187 .await
188 }
189
190 SessionInspectorArgs::Search {
191 query,
192 mode,
193 max_sessions,
194 tail_messages,
195 case_sensitive,
196 max_matches,
197 } => {
198 handlers::handle_search(
199 self,
200 query,
201 mode,
202 max_sessions,
203 tail_messages,
204 case_sensitive,
205 max_matches,
206 )
207 .await
208 }
209 }
210 }
211}