insight-chat: ToolGateOpts + per-tool description rewrites

Tools whose backing tables are empty (calendar, location_history,
daily_summaries) drop out of the catalog so the LLM doesn't waste
iteration budget calling them only to receive "no results found".
Vision and apollo gates already existed; this generalizes the pattern.

search_messages gains start_ts/end_ts/contact_id filters (date filter
is a client-side post-filter; SMS-API only accepts contact_id natively
on the search endpoint).

Descriptions follow a consistent convention: one sentence (what +
when), param semantics, examples for tools with non-obvious param
choices. No more all-caps headers, no more identity-prescriptive
language inside descriptions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron Cordes
2026-05-07 14:56:58 -04:00
parent b02da0d0cc
commit f50d32667b
4 changed files with 437 additions and 278 deletions

View File

@@ -295,6 +295,47 @@ impl SmsApiClient {
Ok(data.results)
}
/// Same shape as `search_messages` but with optional `contact_id`. The
/// SMS-API endpoint accepts contact_id natively; date filtering is the
/// caller's responsibility (post-filter on the returned rows).
pub async fn search_messages_with_contact(
&self,
query: &str,
mode: &str,
limit: usize,
contact_id: Option<i64>,
) -> Result<Vec<SmsSearchHit>> {
let mut url = format!(
"{}/api/messages/search/?q={}&mode={}&limit={}",
self.base_url,
urlencoding::encode(query),
urlencoding::encode(mode),
limit
);
if let Some(cid) = contact_id {
url.push_str(&format!("&contact_id={}", cid));
}
let mut request = self.client.get(&url);
if let Some(token) = &self.token {
request = request.header("Authorization", format!("Bearer {}", token));
}
let response = request.send().await?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(anyhow::anyhow!(
"SMS search request failed: {} - {}",
status,
body
));
}
let data: SmsSearchResponse = response.json().await?;
Ok(data.results)
}
pub async fn summarize_context(
&self,
messages: &[SmsMessage],