use super::SessionsHook;
use crabllm_core::Provider;
use runtime::sessions::{SearchOptions, SessionHit, WindowItem};
use schemars::JsonSchema;
use serde::Deserialize;
use wcore::ToolDispatch;
#[derive(Deserialize, JsonSchema)]
pub struct SearchSessions {
pub query: String,
pub limit: Option<usize>,
pub context_before: Option<usize>,
pub context_after: Option<usize>,
pub agent: Option<String>,
pub sender: Option<String>,
}
impl<P: Provider + 'static> SessionsHook<P> {
pub(super) async fn handle_search_sessions(
&self,
call: ToolDispatch,
) -> Result<String, String> {
let input: SearchSessions =
serde_json::from_str(&call.args).map_err(|e| format!("invalid arguments: {e}"))?;
let shared = self
.runtime
.get()
.ok_or_else(|| "search_sessions: runtime not initialized".to_owned())?;
let rt = shared.read().await.clone();
let opts = SearchOptions {
limit: input.limit.unwrap_or(5),
context_before: input.context_before.unwrap_or(4),
context_after: input.context_after.unwrap_or(4),
agent_filter: input.agent,
sender_filter: input.sender,
};
let hits = rt.search_sessions(&input.query, &opts);
Ok(format_hits(&hits))
}
}
fn format_hits(hits: &[SessionHit]) -> String {
if hits.is_empty() {
return "no sessions found".to_owned();
}
hits.iter()
.map(format_hit)
.collect::<Vec<_>>()
.join("\n---\n")
}
fn format_hit(hit: &SessionHit) -> String {
let handle = hit.session_handle.as_str();
let title = if hit.title.is_empty() {
"(untitled)".to_owned()
} else {
hit.title.clone()
};
let header = format!(
"## {title}\nsession: {handle} · agent: {agent} · sender: {sender}\nupdated: {updated} · matched message #{idx}",
agent = hit.agent,
sender = hit.sender,
updated = hit.updated_at,
idx = hit.msg_idx,
);
let body = hit
.window
.iter()
.map(format_item)
.collect::<Vec<_>>()
.join("\n");
format!("{header}\n{body}")
}
fn format_item(item: &WindowItem) -> String {
let role = role_label(item);
let trunc = if item.truncated { " …" } else { "" };
format!(
"- [{role} #{idx}] {snippet}{trunc}",
role = role,
idx = item.msg_idx,
snippet = item.snippet,
trunc = trunc,
)
}
fn role_label(item: &WindowItem) -> String {
use wcore::model::Role;
match item.role {
Role::User => "user".to_owned(),
Role::Assistant if item.tool_name.is_some() => format!(
"tool-call:{}",
item.tool_name.as_deref().unwrap_or_default()
),
Role::Assistant => "assistant".to_owned(),
Role::Tool => format!("tool:{}", item.tool_name.as_deref().unwrap_or_default()),
Role::System => "system".to_owned(),
_ => "other".to_owned(),
}
}