agentmux_mcp/tools/
history.rs1use super::*;
7
8pub(super) async fn call(name: &str, arguments: &Value, cx: &ToolCtx<'_>) -> Result<String> {
9 let ToolCtx { local_url, auth_key, client, .. } = *cx;
10 match name {
11 "SearchHistory" => {
12 let slug = std::env::var("AGENTMUX_AGENT_ID").unwrap_or_default();
18 let query = arguments
19 .get("query")
20 .and_then(|v| v.as_str())
21 .unwrap_or_default()
22 .to_string();
23 let tool = arguments.get("tool").and_then(|v| v.as_str());
24 if query.trim().is_empty() && tool.is_none() {
25 anyhow::bail!(
26 "query must be non-empty unless `tool` is given (an empty query with no \
27 tool filter would match everything and return a truncated firehose)"
28 );
29 }
30
31 if local_url.is_empty() || auth_key.is_empty() {
32 anyhow::bail!(
33 "AGENTMUX_LOCAL_URL and AGENTMUX_AUTH_KEY must be set. \
34 Is this agent pane opened via AgentMux?"
35 );
36 }
37
38 let url = format!(
39 "{}/agentmux/reactive/history/search",
40 local_url.trim_end_matches('/')
41 );
42 let mut query_params: Vec<(&str, String)> = vec![("query", query)];
43 if !slug.trim().is_empty() {
44 query_params.push(("agent", slug));
45 }
46 if let Some(t) = tool {
47 query_params.push(("tool", t.to_string()));
48 }
49 if let Some(r) = arguments.get("role").and_then(|v| v.as_str()) {
50 query_params.push(("role", r.to_string()));
51 }
52 for (key, arg) in [
53 ("since", "since"),
54 ("until", "until"),
55 ("max_sessions", "max_sessions"),
56 ("limit", "limit"),
57 ] {
58 if let Some(n) = arguments.get(arg).and_then(|v| v.as_i64()) {
59 query_params.push((key, n.to_string()));
60 }
61 }
62 if arguments.get("include_inferred").and_then(|v| v.as_bool()) == Some(true) {
63 query_params.push(("include_inferred", "true".to_string()));
64 }
65
66 let body = srv_get_json(client, &url, auth_key, &query_params, "history search").await?;
67 Ok(serde_json::to_string_pretty(&body).unwrap_or_else(|_| body.to_string()))
68 }
69 "GetAgentTranscript" => {
70 let agent = arguments
71 .get("agent")
72 .and_then(|v| v.as_str())
73 .filter(|s| !s.is_empty())
74 .ok_or_else(|| anyhow::anyhow!("missing required parameter: agent"))?;
75 let max_lines = arguments.get("max_lines").and_then(|v| v.as_u64());
76
77 if local_url.is_empty() || auth_key.is_empty() {
78 anyhow::bail!(
79 "AGENTMUX_LOCAL_URL and AGENTMUX_AUTH_KEY must be set. \
80 Is this agent pane opened via AgentMux?"
81 );
82 }
83
84 let url = format!(
85 "{}/agentmux/reactive/transcript",
86 local_url.trim_end_matches('/')
87 );
88 let mut query: Vec<(&str, String)> = vec![("agent", agent.to_string())];
89 if let Some(n) = max_lines {
90 query.push(("max_lines", n.to_string()));
91 }
92
93 let resp = client
94 .get(&url)
95 .header(AUTH_KEY_HEADER, auth_key)
96 .query(&query)
97 .send()
98 .await
99 .map_err(|e| anyhow::anyhow!("request failed: {e}"))?;
100
101 if !resp.status().is_success() {
102 let status = resp.status();
103 let text = resp.text().await.unwrap_or_default();
104 anyhow::bail!("transcript fetch failed: HTTP {status} — {text}");
105 }
106
107 let result: Value = resp
108 .json()
109 .await
110 .map_err(|e| anyhow::anyhow!("response parse failed: {e}"))?;
111
112 Ok(serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string()))
113 }
114 "ListConversations" => {
115 if local_url.is_empty() || auth_key.is_empty() {
116 anyhow::bail!(
117 "AGENTMUX_LOCAL_URL and AGENTMUX_AUTH_KEY must be set. \
118 Is this agent pane opened via AgentMux?"
119 );
120 }
121
122 let url = format!(
123 "{}/api/v1/muxspect/conversations",
124 local_url.trim_end_matches('/')
125 );
126 let resp = client
127 .get(&url)
128 .header(AUTH_KEY_HEADER, auth_key)
129 .send()
130 .await
131 .map_err(|e| anyhow::anyhow!("request failed: {e}"))?;
132
133 if !resp.status().is_success() {
134 let status = resp.status();
135 let text = resp.text().await.unwrap_or_default();
136 anyhow::bail!("conversations fetch failed: HTTP {status} — {text}");
137 }
138
139 let result: Value = resp
140 .json()
141 .await
142 .map_err(|e| anyhow::anyhow!("response parse failed: {e}"))?;
143
144 Ok(serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string()))
145 }
146 _ => Err(not_in_family()),
147 }
148}