1use serde::{Deserialize, Serialize};
4
5use crate::error::Result;
6use crate::memory::{RecalledMemory, Session};
7use crate::types::{
8 AgentConsolidateResponse, AgentConsolidationConfig, AgentConsolidationLogEntry,
9 ConsolidationConfigPatch,
10};
11use crate::DakeraClient;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AgentSummary {
20 pub agent_id: String,
21 pub memory_count: i64,
22 pub session_count: i64,
23 pub active_sessions: i64,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct AgentStats {
29 pub agent_id: String,
30 pub total_memories: i64,
31 #[serde(default)]
32 pub memories_by_type: std::collections::HashMap<String, i64>,
33 pub total_sessions: i64,
34 pub active_sessions: i64,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub avg_importance: Option<f32>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub oldest_memory_at: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub newest_memory_at: Option<String>,
41}
42
43impl DakeraClient {
48 pub async fn list_agents(&self) -> Result<Vec<AgentSummary>> {
50 let url = format!("{}/v1/agents", self.base_url);
51 let response = self.client.get(&url).send().await?;
52 self.handle_response(response).await
53 }
54
55 pub async fn agent_memories(
57 &self,
58 agent_id: &str,
59 memory_type: Option<&str>,
60 limit: Option<u32>,
61 ) -> Result<Vec<RecalledMemory>> {
62 let mut url = format!("{}/v1/agents/{}/memories", self.base_url, agent_id);
63 let mut params = Vec::new();
64 if let Some(t) = memory_type {
65 params.push(format!("memory_type={}", t));
66 }
67 if let Some(l) = limit {
68 params.push(format!("limit={}", l));
69 }
70 if !params.is_empty() {
71 url.push('?');
72 url.push_str(¶ms.join("&"));
73 }
74
75 let response = self.client.get(&url).send().await?;
76 self.handle_response(response).await
77 }
78
79 pub async fn agent_stats(&self, agent_id: &str) -> Result<AgentStats> {
81 let url = format!("{}/v1/agents/{}/stats", self.base_url, agent_id);
82 let response = self.client.get(&url).send().await?;
83 self.handle_response(response).await
84 }
85
86 pub async fn subscribe_agent_events(
115 &self,
116 agent_id: &str,
117 tags: Option<Vec<String>>,
118 ) -> crate::error::Result<
119 tokio::sync::mpsc::Receiver<crate::error::Result<crate::events::MemoryEvent>>,
120 > {
121 let (tx, rx) = tokio::sync::mpsc::channel(64);
122 let client = self.clone();
123 let agent_id = agent_id.to_owned();
124
125 tokio::spawn(async move {
126 loop {
127 match client.stream_memory_events().await {
128 Err(_) => {
129 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
130 continue;
131 }
132 Ok(mut inner_rx) => {
133 while let Some(result) = inner_rx.recv().await {
134 match result {
135 Err(e) => {
136 let _ = tx.send(Err(e)).await;
138 break;
139 }
140 Ok(event) => {
141 if event.event_type == "connected" {
142 continue;
143 }
144 if event.agent_id != agent_id {
145 continue;
146 }
147 if let Some(ref filter_tags) = tags {
148 let event_tags = event.tags.as_deref().unwrap_or(&[]);
149 if !filter_tags.iter().any(|t| event_tags.contains(t)) {
150 continue;
151 }
152 }
153 if tx.send(Ok(event)).await.is_err() {
154 return; }
156 }
157 }
158 }
159 }
160 }
161 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
163 }
164 });
165
166 Ok(rx)
167 }
168
169 pub async fn agent_sessions(
171 &self,
172 agent_id: &str,
173 active_only: Option<bool>,
174 limit: Option<u32>,
175 ) -> Result<Vec<Session>> {
176 let mut url = format!("{}/v1/agents/{}/sessions", self.base_url, agent_id);
177 let mut params = Vec::new();
178 if let Some(active) = active_only {
179 params.push(format!("active_only={}", active));
180 }
181 if let Some(l) = limit {
182 params.push(format!("limit={}", l));
183 }
184 if !params.is_empty() {
185 url.push('?');
186 url.push_str(¶ms.join("&"));
187 }
188
189 let response = self.client.get(&url).send().await?;
190 self.handle_response(response).await
191 }
192
193 pub async fn wake_up(
206 &self,
207 agent_id: &str,
208 top_n: Option<u32>,
209 min_importance: Option<f32>,
210 ) -> Result<WakeUpResponse> {
211 let mut url = format!("{}/v1/agents/{}/wake-up", self.base_url, agent_id);
212 let mut params = Vec::new();
213 if let Some(n) = top_n {
214 params.push(format!("top_n={}", n));
215 }
216 if let Some(mi) = min_importance {
217 params.push(format!("min_importance={}", mi));
218 }
219 if !params.is_empty() {
220 url.push('?');
221 url.push_str(¶ms.join("&"));
222 }
223
224 let response = self.client.get(&url).send().await?;
225 self.handle_response(response).await
226 }
227
228 pub async fn compress(&self, agent_id: &str) -> Result<CompressResponse> {
236 let url = format!("{}/v1/agents/{}/compress", self.base_url, agent_id);
237 let response = self.client.post(&url).send().await?;
238 self.handle_response(response).await
239 }
240
241 pub async fn compress_agent(&self, agent_id: &str) -> Result<CompressResponse> {
243 self.compress(agent_id).await
244 }
245
246 #[tracing::instrument(skip(self))]
248 pub async fn consolidate_agent(&self, agent_id: &str) -> Result<AgentConsolidateResponse> {
249 let url = format!("{}/v1/agents/{}/consolidate", self.base_url, agent_id);
250 let response = self.client.post(&url).send().await?;
251 self.handle_response(response).await
252 }
253
254 #[tracing::instrument(skip(self))]
256 pub async fn get_consolidation_log(
257 &self,
258 agent_id: &str,
259 ) -> Result<Vec<AgentConsolidationLogEntry>> {
260 let url = format!("{}/v1/agents/{}/consolidation/log", self.base_url, agent_id);
261 let response = self.client.get(&url).send().await?;
262 self.handle_response(response).await
263 }
264
265 #[tracing::instrument(skip(self, patch))]
267 pub async fn patch_consolidation_config(
268 &self,
269 agent_id: &str,
270 patch: ConsolidationConfigPatch,
271 ) -> Result<AgentConsolidationConfig> {
272 let url = format!(
273 "{}/v1/agents/{}/consolidation/config",
274 self.base_url, agent_id
275 );
276 let response = self.client.patch(&url).json(&patch).send().await?;
277 self.handle_response(response).await
278 }
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct Memory {
288 pub id: String,
290 pub content: String,
292 pub memory_type: String,
294 pub importance: f32,
296 #[serde(skip_serializing_if = "Option::is_none")]
298 pub metadata: Option<serde_json::Value>,
299 #[serde(skip_serializing_if = "Option::is_none")]
301 pub created_at: Option<String>,
302 #[serde(skip_serializing_if = "Option::is_none")]
304 pub updated_at: Option<String>,
305 #[serde(skip_serializing_if = "Option::is_none")]
307 pub access_count: Option<i64>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct WakeUpResponse {
316 pub agent_id: String,
318 pub memories: Vec<Memory>,
320 pub total_available: i64,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct CompressResponse {
337 pub agent_id: String,
339 #[serde(default)]
341 pub memories_scanned: i64,
342 #[serde(default, alias = "removed_count")]
344 pub originals_deprecated: i64,
345 #[serde(default)]
347 pub clusters_found: i64,
348 #[serde(default)]
350 pub summaries_created: i64,
351 #[serde(default, skip_serializing_if = "Vec::is_empty")]
353 pub deprecated_ids: Vec<String>,
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub duration_ms: Option<f64>,
357}