Skip to main content

dakera_client/
memory.rs

1//! Memory-oriented client methods for Dakera AI Agent Memory Platform
2//!
3//! Provides high-level methods for storing, recalling, and managing
4//! agent memories and sessions through the Dakera API.
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::Result;
9use crate::types::{
10    AgentFeedbackSummary, EdgeType, FeedbackHealthResponse, FeedbackHistoryResponse,
11    FeedbackResponse, FeedbackSignal, GraphExport, GraphLinkRequest, GraphLinkResponse,
12    GraphOptions, GraphPath, MemoryFeedbackBody, MemoryGraph, MemoryImportancePatch,
13};
14use crate::DakeraClient;
15
16// ============================================================================
17// Memory Types (client-side)
18// ============================================================================
19
20/// Memory type classification
21#[derive(Debug, Clone, Serialize, Deserialize, Default)]
22pub enum MemoryType {
23    #[default]
24    Episodic,
25    Semantic,
26    Procedural,
27    Working,
28}
29
30/// Store a memory request
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct StoreMemoryRequest {
33    pub agent_id: String,
34    pub content: String,
35    #[serde(default)]
36    pub memory_type: MemoryType,
37    #[serde(default = "default_importance")]
38    pub importance: f32,
39    #[serde(default)]
40    pub tags: Vec<String>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub session_id: Option<String>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub metadata: Option<serde_json::Value>,
45    /// Optional TTL in seconds. The memory is hard-deleted after this many
46    /// seconds from creation.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub ttl_seconds: Option<u64>,
49    /// Optional explicit expiry as a Unix timestamp (seconds). Takes precedence
50    /// over `ttl_seconds` when both are set. The memory is hard-deleted by the
51    /// decay engine on expiry (DECAY-3).
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub expires_at: Option<u64>,
54}
55
56fn default_importance() -> f32 {
57    0.5
58}
59
60impl StoreMemoryRequest {
61    /// Create a new store memory request
62    pub fn new(agent_id: impl Into<String>, content: impl Into<String>) -> Self {
63        Self {
64            agent_id: agent_id.into(),
65            content: content.into(),
66            memory_type: MemoryType::default(),
67            importance: 0.5,
68            tags: Vec::new(),
69            session_id: None,
70            metadata: None,
71            ttl_seconds: None,
72            expires_at: None,
73        }
74    }
75
76    /// Set memory type
77    pub fn with_type(mut self, memory_type: MemoryType) -> Self {
78        self.memory_type = memory_type;
79        self
80    }
81
82    /// Set importance score
83    pub fn with_importance(mut self, importance: f32) -> Self {
84        self.importance = importance.clamp(0.0, 1.0);
85        self
86    }
87
88    /// Set tags
89    pub fn with_tags(mut self, tags: Vec<String>) -> Self {
90        self.tags = tags;
91        self
92    }
93
94    /// Set session ID
95    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
96        self.session_id = Some(session_id.into());
97        self
98    }
99
100    /// Set metadata
101    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
102        self.metadata = Some(metadata);
103        self
104    }
105
106    /// Set TTL in seconds. The memory is hard-deleted after this many seconds
107    /// from creation.
108    pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
109        self.ttl_seconds = Some(ttl_seconds);
110        self
111    }
112
113    /// Set an explicit expiry Unix timestamp (seconds). Takes precedence over
114    /// `ttl_seconds` when both are set (DECAY-3).
115    pub fn with_expires_at(mut self, expires_at: u64) -> Self {
116        self.expires_at = Some(expires_at);
117        self
118    }
119}
120
121/// Stored memory response
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct StoreMemoryResponse {
124    pub memory_id: String,
125    pub agent_id: String,
126    pub namespace: String,
127}
128
129/// Recall memories request
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct RecallRequest {
132    pub agent_id: String,
133    pub query: String,
134    #[serde(default = "default_top_k")]
135    pub top_k: usize,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub memory_type: Option<MemoryType>,
138    #[serde(default)]
139    pub min_importance: f32,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub session_id: Option<String>,
142    #[serde(default)]
143    pub tags: Vec<String>,
144    /// COG-2: traverse KG depth-1 from recalled memories and include
145    /// associatively linked memories in the response (default: false)
146    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
147    pub include_associated: bool,
148    /// COG-2: max associated memories to return (default: 10, max: 10)
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub associated_memories_cap: Option<u32>,
151}
152
153fn default_top_k() -> usize {
154    5
155}
156
157impl RecallRequest {
158    /// Create a new recall request
159    pub fn new(agent_id: impl Into<String>, query: impl Into<String>) -> Self {
160        Self {
161            agent_id: agent_id.into(),
162            query: query.into(),
163            top_k: 5,
164            memory_type: None,
165            min_importance: 0.0,
166            session_id: None,
167            tags: Vec::new(),
168            include_associated: false,
169            associated_memories_cap: None,
170        }
171    }
172
173    /// Set number of results
174    pub fn with_top_k(mut self, top_k: usize) -> Self {
175        self.top_k = top_k;
176        self
177    }
178
179    /// Filter by memory type
180    pub fn with_type(mut self, memory_type: MemoryType) -> Self {
181        self.memory_type = Some(memory_type);
182        self
183    }
184
185    /// Set minimum importance threshold
186    pub fn with_min_importance(mut self, min: f32) -> Self {
187        self.min_importance = min;
188        self
189    }
190
191    /// Filter by session
192    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
193        self.session_id = Some(session_id.into());
194        self
195    }
196
197    /// Filter by tags
198    pub fn with_tags(mut self, tags: Vec<String>) -> Self {
199        self.tags = tags;
200        self
201    }
202
203    /// COG-2: include KG depth-1 associated memories in the response
204    pub fn with_associated(mut self) -> Self {
205        self.include_associated = true;
206        self
207    }
208
209    /// COG-2: set max associated memories cap (default: 10, max: 10)
210    pub fn with_associated_cap(mut self, cap: u32) -> Self {
211        self.include_associated = true;
212        self.associated_memories_cap = Some(cap);
213        self
214    }
215}
216
217/// A recalled memory
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct RecalledMemory {
220    pub id: String,
221    pub content: String,
222    pub memory_type: MemoryType,
223    pub importance: f32,
224    pub score: f32,
225    #[serde(default)]
226    pub tags: Vec<String>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub session_id: Option<String>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub metadata: Option<serde_json::Value>,
231    pub created_at: u64,
232    pub last_accessed_at: u64,
233    pub access_count: u32,
234}
235
236/// Recall response
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct RecallResponse {
239    pub memories: Vec<RecalledMemory>,
240    pub total_found: usize,
241    /// COG-2: KG depth-1 associated memories (only present when include_associated was true)
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub associated_memories: Option<Vec<RecalledMemory>>,
244}
245
246/// Forget (delete) memories request
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct ForgetRequest {
249    pub agent_id: String,
250    #[serde(default)]
251    pub memory_ids: Vec<String>,
252    #[serde(default)]
253    pub tags: Vec<String>,
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub session_id: Option<String>,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub before_timestamp: Option<u64>,
258}
259
260impl ForgetRequest {
261    /// Forget specific memories by ID
262    pub fn by_ids(agent_id: impl Into<String>, ids: Vec<String>) -> Self {
263        Self {
264            agent_id: agent_id.into(),
265            memory_ids: ids,
266            tags: Vec::new(),
267            session_id: None,
268            before_timestamp: None,
269        }
270    }
271
272    /// Forget memories with specific tags
273    pub fn by_tags(agent_id: impl Into<String>, tags: Vec<String>) -> Self {
274        Self {
275            agent_id: agent_id.into(),
276            memory_ids: Vec::new(),
277            tags,
278            session_id: None,
279            before_timestamp: None,
280        }
281    }
282
283    /// Forget all memories in a session
284    pub fn by_session(agent_id: impl Into<String>, session_id: impl Into<String>) -> Self {
285        Self {
286            agent_id: agent_id.into(),
287            memory_ids: Vec::new(),
288            tags: Vec::new(),
289            session_id: Some(session_id.into()),
290            before_timestamp: None,
291        }
292    }
293}
294
295/// Forget response
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct ForgetResponse {
298    pub deleted_count: u64,
299}
300
301/// Session start request
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct SessionStartRequest {
304    pub agent_id: String,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub metadata: Option<serde_json::Value>,
307}
308
309/// Session information
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct Session {
312    pub id: String,
313    pub agent_id: String,
314    pub started_at: u64,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub ended_at: Option<u64>,
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub summary: Option<String>,
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub metadata: Option<serde_json::Value>,
321}
322
323/// Session end request
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct SessionEndRequest {
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub summary: Option<String>,
328}
329
330/// Request to update a memory
331#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct UpdateMemoryRequest {
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub content: Option<String>,
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub metadata: Option<serde_json::Value>,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub memory_type: Option<MemoryType>,
339}
340
341/// Request to update memory importance
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct UpdateImportanceRequest {
344    pub memory_ids: Vec<String>,
345    pub importance: f32,
346}
347
348/// DBSCAN algorithm config for adaptive consolidation (CE-6).
349#[derive(Debug, Clone, Serialize, Deserialize, Default)]
350pub struct ConsolidationConfig {
351    /// Clustering algorithm: `"dbscan"` (default) or `"greedy"`.
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub algorithm: Option<String>,
354    /// Minimum cluster samples for DBSCAN.
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub min_samples: Option<u32>,
357    /// Epsilon distance parameter for DBSCAN.
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub eps: Option<f32>,
360}
361
362/// One step in the consolidation execution log (CE-6).
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct ConsolidationLogEntry {
365    pub step: String,
366    pub memories_before: usize,
367    pub memories_after: usize,
368    pub duration_ms: f64,
369}
370
371/// Request to consolidate memories
372#[derive(Debug, Clone, Serialize, Deserialize, Default)]
373pub struct ConsolidateRequest {
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub memory_type: Option<String>,
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub threshold: Option<f32>,
378    #[serde(default)]
379    pub dry_run: bool,
380    /// Optional DBSCAN algorithm configuration (CE-6).
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub config: Option<ConsolidationConfig>,
383}
384
385/// Response from consolidation
386#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct ConsolidateResponse {
388    pub consolidated_count: usize,
389    pub removed_count: usize,
390    pub new_memories: Vec<String>,
391    /// Step-by-step consolidation log (CE-6, optional).
392    #[serde(default, skip_serializing_if = "Vec::is_empty")]
393    pub log: Vec<ConsolidationLogEntry>,
394}
395
396// ============================================================================
397// DX-1: Memory Import / Export
398// ============================================================================
399
400/// Response from `POST /v1/import` (DX-1).
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct MemoryImportResponse {
403    pub imported_count: usize,
404    pub skipped_count: usize,
405    #[serde(default)]
406    pub errors: Vec<String>,
407}
408
409/// Response from `GET /v1/export` (DX-1).
410#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct MemoryExportResponse {
412    pub data: Vec<serde_json::Value>,
413    pub format: String,
414    pub count: usize,
415}
416
417// ============================================================================
418// OBS-1: Business-Event Audit Log
419// ============================================================================
420
421/// A single business-event entry from the audit log (OBS-1).
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct AuditEvent {
424    pub id: String,
425    pub event_type: String,
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub agent_id: Option<String>,
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub namespace: Option<String>,
430    pub timestamp: u64,
431    #[serde(default)]
432    pub details: serde_json::Value,
433}
434
435/// Response from `GET /v1/audit` (OBS-1).
436#[derive(Debug, Clone, Serialize, Deserialize)]
437pub struct AuditListResponse {
438    pub events: Vec<AuditEvent>,
439    pub total: usize,
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub cursor: Option<String>,
442}
443
444/// Response from `POST /v1/audit/export` (OBS-1).
445#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct AuditExportResponse {
447    pub data: String,
448    pub format: String,
449    pub count: usize,
450}
451
452/// Query parameters for the audit log (OBS-1).
453#[derive(Debug, Clone, Serialize, Deserialize, Default)]
454pub struct AuditQuery {
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub agent_id: Option<String>,
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub event_type: Option<String>,
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub from: Option<u64>,
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub to: Option<u64>,
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub limit: Option<u32>,
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub cursor: Option<String>,
467}
468
469// ============================================================================
470// EXT-1: External Extraction Providers
471// ============================================================================
472
473/// Result from `POST /v1/extract` (EXT-1).
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct ExtractionResult {
476    pub entities: Vec<serde_json::Value>,
477    pub provider: String,
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub model: Option<String>,
480    pub duration_ms: f64,
481}
482
483/// Metadata for an available extraction provider (EXT-1).
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct ExtractionProviderInfo {
486    pub name: String,
487    pub available: bool,
488    #[serde(default)]
489    pub models: Vec<String>,
490}
491
492/// Response from `GET /v1/extract/providers` (EXT-1).
493#[derive(Debug, Clone, Serialize, Deserialize)]
494#[serde(untagged)]
495pub enum ExtractProvidersResponse {
496    List(Vec<ExtractionProviderInfo>),
497    Object {
498        providers: Vec<ExtractionProviderInfo>,
499    },
500}
501
502// ============================================================================
503// SEC-3: AES-256-GCM Encryption Key Rotation
504// ============================================================================
505
506/// Request body for `POST /v1/admin/encryption/rotate-key` (SEC-3).
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct RotateEncryptionKeyRequest {
509    /// New passphrase or 64-char hex key to rotate to.
510    pub new_key: String,
511    /// If set, rotate only memories in this namespace. Omit to rotate all.
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub namespace: Option<String>,
514}
515
516/// Response from `POST /v1/admin/encryption/rotate-key` (SEC-3).
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct RotateEncryptionKeyResponse {
519    pub rotated: usize,
520    pub skipped: usize,
521    #[serde(default)]
522    pub namespaces: Vec<String>,
523}
524
525/// Request for memory feedback
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct FeedbackRequest {
528    pub memory_id: String,
529    pub feedback: String,
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub relevance_score: Option<f32>,
532}
533
534/// Response from legacy feedback endpoint (POST /v1/agents/:id/memories/feedback)
535#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct LegacyFeedbackResponse {
537    pub status: String,
538    pub updated_importance: Option<f32>,
539}
540
541// ============================================================================
542// CE-2: Batch Recall / Forget Types
543// ============================================================================
544
545/// Filter predicates for batch memory operations (CE-2).
546///
547/// All fields are optional.  For [`BatchForgetRequest`] at least one must be
548/// set (server-side safety guard).
549#[derive(Debug, Clone, Serialize, Deserialize, Default)]
550pub struct BatchMemoryFilter {
551    /// Restrict to memories that carry **all** listed tags.
552    #[serde(skip_serializing_if = "Option::is_none")]
553    pub tags: Option<Vec<String>>,
554    /// Minimum importance (inclusive).
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub min_importance: Option<f32>,
557    /// Maximum importance (inclusive).
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub max_importance: Option<f32>,
560    /// Only memories created at or after this Unix timestamp (seconds).
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub created_after: Option<u64>,
563    /// Only memories created before or at this Unix timestamp (seconds).
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub created_before: Option<u64>,
566    /// Restrict to a specific memory type.
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub memory_type: Option<MemoryType>,
569    /// Restrict to memories from a specific session.
570    #[serde(skip_serializing_if = "Option::is_none")]
571    pub session_id: Option<String>,
572}
573
574impl BatchMemoryFilter {
575    /// Convenience: filter by tags.
576    pub fn with_tags(mut self, tags: Vec<String>) -> Self {
577        self.tags = Some(tags);
578        self
579    }
580
581    /// Convenience: filter by minimum importance.
582    pub fn with_min_importance(mut self, min: f32) -> Self {
583        self.min_importance = Some(min);
584        self
585    }
586
587    /// Convenience: filter by maximum importance.
588    pub fn with_max_importance(mut self, max: f32) -> Self {
589        self.max_importance = Some(max);
590        self
591    }
592
593    /// Convenience: filter by session.
594    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
595        self.session_id = Some(session_id.into());
596        self
597    }
598}
599
600/// Request body for `POST /v1/memories/recall/batch`.
601#[derive(Debug, Clone, Serialize, Deserialize)]
602pub struct BatchRecallRequest {
603    /// Agent whose memory namespace to search.
604    pub agent_id: String,
605    /// Filter predicates to apply.
606    #[serde(default)]
607    pub filter: BatchMemoryFilter,
608    /// Maximum number of results to return (default: 100).
609    #[serde(default = "default_batch_limit")]
610    pub limit: usize,
611}
612
613fn default_batch_limit() -> usize {
614    100
615}
616
617impl BatchRecallRequest {
618    /// Create a new batch recall request for an agent.
619    pub fn new(agent_id: impl Into<String>) -> Self {
620        Self {
621            agent_id: agent_id.into(),
622            filter: BatchMemoryFilter::default(),
623            limit: 100,
624        }
625    }
626
627    /// Set filter predicates.
628    pub fn with_filter(mut self, filter: BatchMemoryFilter) -> Self {
629        self.filter = filter;
630        self
631    }
632
633    /// Set result limit.
634    pub fn with_limit(mut self, limit: usize) -> Self {
635        self.limit = limit;
636        self
637    }
638}
639
640/// Response from `POST /v1/memories/recall/batch`.
641#[derive(Debug, Clone, Serialize, Deserialize)]
642pub struct BatchRecallResponse {
643    pub memories: Vec<RecalledMemory>,
644    /// Total memories in the agent namespace.
645    pub total: usize,
646    /// Number of memories that passed the filter.
647    pub filtered: usize,
648}
649
650/// Request body for `DELETE /v1/memories/forget/batch`.
651#[derive(Debug, Clone, Serialize, Deserialize)]
652pub struct BatchForgetRequest {
653    /// Agent whose memory namespace to purge from.
654    pub agent_id: String,
655    /// Filter predicates — **at least one must be set** (server safety guard).
656    pub filter: BatchMemoryFilter,
657}
658
659impl BatchForgetRequest {
660    /// Create a new batch forget request with the given filter.
661    pub fn new(agent_id: impl Into<String>, filter: BatchMemoryFilter) -> Self {
662        Self {
663            agent_id: agent_id.into(),
664            filter,
665        }
666    }
667}
668
669/// Response from `DELETE /v1/memories/forget/batch`.
670#[derive(Debug, Clone, Serialize, Deserialize)]
671pub struct BatchForgetResponse {
672    pub deleted_count: usize,
673}
674
675// ============================================================================
676// Memory Client Methods
677// ============================================================================
678
679impl DakeraClient {
680    // ========================================================================
681    // Memory Operations
682    // ========================================================================
683
684    /// Store a memory for an agent
685    ///
686    /// # Example
687    ///
688    /// ```rust,no_run
689    /// use dakera_client::{DakeraClient, memory::StoreMemoryRequest};
690    ///
691    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
692    /// let client = DakeraClient::new("http://localhost:3000")?;
693    ///
694    /// let request = StoreMemoryRequest::new("agent-1", "The user prefers dark mode")
695    ///     .with_importance(0.8)
696    ///     .with_tags(vec!["preferences".to_string()]);
697    ///
698    /// let response = client.store_memory(request).await?;
699    /// println!("Stored memory: {}", response.memory_id);
700    /// # Ok(())
701    /// # }
702    /// ```
703    pub async fn store_memory(&self, request: StoreMemoryRequest) -> Result<StoreMemoryResponse> {
704        let url = format!("{}/v1/memory/store", self.base_url);
705        let response = self.client.post(&url).json(&request).send().await?;
706        self.handle_response(response).await
707    }
708
709    /// Recall memories by semantic query
710    ///
711    /// # Example
712    ///
713    /// ```rust,no_run
714    /// use dakera_client::{DakeraClient, memory::RecallRequest};
715    ///
716    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
717    /// let client = DakeraClient::new("http://localhost:3000")?;
718    ///
719    /// let request = RecallRequest::new("agent-1", "user preferences")
720    ///     .with_top_k(10);
721    ///
722    /// let response = client.recall(request).await?;
723    /// for memory in response.memories {
724    ///     println!("{}: {} (score: {})", memory.id, memory.content, memory.score);
725    /// }
726    /// # Ok(())
727    /// # }
728    /// ```
729    pub async fn recall(&self, request: RecallRequest) -> Result<RecallResponse> {
730        let url = format!("{}/v1/memory/recall", self.base_url);
731        let response = self.client.post(&url).json(&request).send().await?;
732        self.handle_response(response).await
733    }
734
735    /// Simple recall with just agent_id and query (convenience method)
736    pub async fn recall_simple(
737        &self,
738        agent_id: &str,
739        query: &str,
740        top_k: usize,
741    ) -> Result<RecallResponse> {
742        self.recall(RecallRequest::new(agent_id, query).with_top_k(top_k))
743            .await
744    }
745
746    /// Get a specific memory by ID
747    pub async fn get_memory(&self, memory_id: &str) -> Result<RecalledMemory> {
748        let url = format!("{}/v1/memory/get/{}", self.base_url, memory_id);
749        let response = self.client.get(&url).send().await?;
750        self.handle_response(response).await
751    }
752
753    /// Forget (delete) memories
754    pub async fn forget(&self, request: ForgetRequest) -> Result<ForgetResponse> {
755        let url = format!("{}/v1/memory/forget", self.base_url);
756        let response = self.client.post(&url).json(&request).send().await?;
757        self.handle_response(response).await
758    }
759
760    /// Search memories with advanced filters
761    pub async fn search_memories(&self, request: RecallRequest) -> Result<RecallResponse> {
762        let url = format!("{}/v1/memory/search", self.base_url);
763        let response = self.client.post(&url).json(&request).send().await?;
764        self.handle_response(response).await
765    }
766
767    /// Update an existing memory
768    pub async fn update_memory(
769        &self,
770        agent_id: &str,
771        memory_id: &str,
772        request: UpdateMemoryRequest,
773    ) -> Result<StoreMemoryResponse> {
774        let url = format!(
775            "{}/v1/agents/{}/memories/{}",
776            self.base_url, agent_id, memory_id
777        );
778        let response = self.client.put(&url).json(&request).send().await?;
779        self.handle_response(response).await
780    }
781
782    /// Update importance of memories
783    pub async fn update_importance(
784        &self,
785        agent_id: &str,
786        request: UpdateImportanceRequest,
787    ) -> Result<serde_json::Value> {
788        let url = format!(
789            "{}/v1/agents/{}/memories/importance",
790            self.base_url, agent_id
791        );
792        let response = self.client.put(&url).json(&request).send().await?;
793        self.handle_response(response).await
794    }
795
796    /// Consolidate memories for an agent
797    pub async fn consolidate(
798        &self,
799        agent_id: &str,
800        request: ConsolidateRequest,
801    ) -> Result<ConsolidateResponse> {
802        let url = format!(
803            "{}/v1/agents/{}/memories/consolidate",
804            self.base_url, agent_id
805        );
806        let response = self.client.post(&url).json(&request).send().await?;
807        self.handle_response(response).await
808    }
809
810    /// Submit feedback on a memory recall
811    pub async fn memory_feedback(
812        &self,
813        agent_id: &str,
814        request: FeedbackRequest,
815    ) -> Result<LegacyFeedbackResponse> {
816        let url = format!("{}/v1/agents/{}/memories/feedback", self.base_url, agent_id);
817        let response = self.client.post(&url).json(&request).send().await?;
818        self.handle_response(response).await
819    }
820
821    // ========================================================================
822    // Memory Feedback Loop — INT-1
823    // ========================================================================
824
825    /// Submit upvote/downvote/flag feedback on a memory (INT-1).
826    ///
827    /// # Arguments
828    /// * `memory_id` – The memory to give feedback on.
829    /// * `agent_id` – The agent that owns the memory.
830    /// * `signal` – [`FeedbackSignal`] value: `Upvote`, `Downvote`, or `Flag`.
831    ///
832    /// # Example
833    /// ```no_run
834    /// # use dakera_client::{DakeraClient, FeedbackSignal};
835    /// # async fn example(client: &DakeraClient) -> dakera_client::Result<()> {
836    /// let resp = client.feedback_memory("mem-abc", "agent-1", FeedbackSignal::Upvote).await?;
837    /// println!("new importance: {}", resp.new_importance);
838    /// # Ok(()) }
839    /// ```
840    pub async fn feedback_memory(
841        &self,
842        memory_id: &str,
843        agent_id: &str,
844        signal: FeedbackSignal,
845    ) -> Result<FeedbackResponse> {
846        let url = format!("{}/v1/memories/{}/feedback", self.base_url, memory_id);
847        let body = MemoryFeedbackBody {
848            agent_id: agent_id.to_string(),
849            signal,
850        };
851        let response = self.client.post(&url).json(&body).send().await?;
852        self.handle_response(response).await
853    }
854
855    /// Get the full feedback history for a memory (INT-1).
856    pub async fn get_memory_feedback_history(
857        &self,
858        memory_id: &str,
859    ) -> Result<FeedbackHistoryResponse> {
860        let url = format!("{}/v1/memories/{}/feedback", self.base_url, memory_id);
861        let response = self.client.get(&url).send().await?;
862        self.handle_response(response).await
863    }
864
865    /// Get aggregate feedback counts and health score for an agent (INT-1).
866    pub async fn get_agent_feedback_summary(&self, agent_id: &str) -> Result<AgentFeedbackSummary> {
867        let url = format!("{}/v1/agents/{}/feedback/summary", self.base_url, agent_id);
868        let response = self.client.get(&url).send().await?;
869        self.handle_response(response).await
870    }
871
872    /// Directly override a memory's importance score (INT-1).
873    ///
874    /// # Arguments
875    /// * `memory_id` – The memory to update.
876    /// * `agent_id` – The agent that owns the memory.
877    /// * `importance` – New importance value (0.0–1.0).
878    pub async fn patch_memory_importance(
879        &self,
880        memory_id: &str,
881        agent_id: &str,
882        importance: f32,
883    ) -> Result<FeedbackResponse> {
884        let url = format!("{}/v1/memories/{}/importance", self.base_url, memory_id);
885        let body = MemoryImportancePatch {
886            agent_id: agent_id.to_string(),
887            importance,
888        };
889        let response = self.client.patch(&url).json(&body).send().await?;
890        self.handle_response(response).await
891    }
892
893    /// Get overall feedback health score for an agent (INT-1).
894    ///
895    /// The health score is the mean importance of all non-expired memories (0.0–1.0).
896    /// A higher score indicates a healthier, more relevant memory store.
897    pub async fn get_feedback_health(&self, agent_id: &str) -> Result<FeedbackHealthResponse> {
898        let url = format!("{}/v1/feedback/health?agent_id={}", self.base_url, agent_id);
899        let response = self.client.get(&url).send().await?;
900        self.handle_response(response).await
901    }
902
903    // ========================================================================
904    // Memory Knowledge Graph Operations (CE-5 / SDK-9)
905    // ========================================================================
906
907    /// Traverse the knowledge graph from a memory node.
908    ///
909    /// Requires CE-5 (Memory Knowledge Graph) on the server.
910    ///
911    /// # Arguments
912    /// * `memory_id` – Root memory ID to start traversal from.
913    /// * `options` – Traversal options (depth, edge type filters).
914    ///
915    /// # Example
916    /// ```no_run
917    /// # use dakera_client::{DakeraClient, GraphOptions};
918    /// # async fn example(client: &DakeraClient) -> dakera_client::Result<()> {
919    /// let graph = client.memory_graph("mem-abc", GraphOptions::new().depth(2)).await?;
920    /// println!("{} nodes, {} edges", graph.nodes.len(), graph.edges.len());
921    /// # Ok(()) }
922    /// ```
923    pub async fn memory_graph(
924        &self,
925        memory_id: &str,
926        options: GraphOptions,
927    ) -> Result<MemoryGraph> {
928        let mut url = format!("{}/v1/memories/{}/graph", self.base_url, memory_id);
929        let depth = options.depth.unwrap_or(1);
930        url.push_str(&format!("?depth={}", depth));
931        if let Some(types) = &options.types {
932            let type_strs: Vec<String> = types
933                .iter()
934                .map(|t| {
935                    serde_json::to_value(t)
936                        .unwrap()
937                        .as_str()
938                        .unwrap_or("")
939                        .to_string()
940                })
941                .collect();
942            if !type_strs.is_empty() {
943                url.push_str(&format!("&types={}", type_strs.join(",")));
944            }
945        }
946        let response = self.client.get(&url).send().await?;
947        self.handle_response(response).await
948    }
949
950    /// Find the shortest path between two memories in the knowledge graph.
951    ///
952    /// Requires CE-5 (Memory Knowledge Graph) on the server.
953    ///
954    /// # Example
955    /// ```no_run
956    /// # use dakera_client::DakeraClient;
957    /// # async fn example(client: &DakeraClient) -> dakera_client::Result<()> {
958    /// let path = client.memory_path("mem-abc", "mem-xyz").await?;
959    /// println!("{} hops: {:?}", path.hops, path.path);
960    /// # Ok(()) }
961    /// ```
962    pub async fn memory_path(&self, source_id: &str, target_id: &str) -> Result<GraphPath> {
963        let url = format!(
964            "{}/v1/memories/{}/path?target={}",
965            self.base_url,
966            source_id,
967            urlencoding::encode(target_id)
968        );
969        let response = self.client.get(&url).send().await?;
970        self.handle_response(response).await
971    }
972
973    /// Create an explicit edge between two memories.
974    ///
975    /// Requires CE-5 (Memory Knowledge Graph) on the server.
976    ///
977    /// # Example
978    /// ```no_run
979    /// # use dakera_client::{DakeraClient, EdgeType};
980    /// # async fn example(client: &DakeraClient) -> dakera_client::Result<()> {
981    /// let resp = client.memory_link("mem-abc", "mem-xyz", EdgeType::LinkedBy).await?;
982    /// println!("Created edge: {}", resp.edge.id);
983    /// # Ok(()) }
984    /// ```
985    pub async fn memory_link(
986        &self,
987        source_id: &str,
988        target_id: &str,
989        edge_type: EdgeType,
990    ) -> Result<GraphLinkResponse> {
991        let url = format!("{}/v1/memories/{}/links", self.base_url, source_id);
992        let request = GraphLinkRequest {
993            target_id: target_id.to_string(),
994            edge_type,
995        };
996        let response = self.client.post(&url).json(&request).send().await?;
997        self.handle_response(response).await
998    }
999
1000    /// Export the full knowledge graph for an agent.
1001    ///
1002    /// Requires CE-5 (Memory Knowledge Graph) on the server.
1003    ///
1004    /// # Arguments
1005    /// * `agent_id` – Agent whose graph to export.
1006    /// * `format` – Export format: `"json"` (default), `"graphml"`, or `"csv"`.
1007    pub async fn agent_graph_export(&self, agent_id: &str, format: &str) -> Result<GraphExport> {
1008        let url = format!(
1009            "{}/v1/agents/{}/graph/export?format={}",
1010            self.base_url, agent_id, format
1011        );
1012        let response = self.client.get(&url).send().await?;
1013        self.handle_response(response).await
1014    }
1015
1016    // ========================================================================
1017    // Session Operations
1018    // ========================================================================
1019
1020    /// Start a new session for an agent
1021    pub async fn start_session(&self, agent_id: &str) -> Result<Session> {
1022        let url = format!("{}/v1/sessions/start", self.base_url);
1023        let request = SessionStartRequest {
1024            agent_id: agent_id.to_string(),
1025            metadata: None,
1026        };
1027        let response = self.client.post(&url).json(&request).send().await?;
1028        self.handle_response(response).await
1029    }
1030
1031    /// Start a session with metadata
1032    pub async fn start_session_with_metadata(
1033        &self,
1034        agent_id: &str,
1035        metadata: serde_json::Value,
1036    ) -> Result<Session> {
1037        let url = format!("{}/v1/sessions/start", self.base_url);
1038        let request = SessionStartRequest {
1039            agent_id: agent_id.to_string(),
1040            metadata: Some(metadata),
1041        };
1042        let response = self.client.post(&url).json(&request).send().await?;
1043        self.handle_response(response).await
1044    }
1045
1046    /// End a session, optionally with a summary
1047    pub async fn end_session(&self, session_id: &str, summary: Option<String>) -> Result<Session> {
1048        let url = format!("{}/v1/sessions/{}/end", self.base_url, session_id);
1049        let request = SessionEndRequest { summary };
1050        let response = self.client.post(&url).json(&request).send().await?;
1051        self.handle_response(response).await
1052    }
1053
1054    /// Get a session by ID
1055    pub async fn get_session(&self, session_id: &str) -> Result<Session> {
1056        let url = format!("{}/v1/sessions/{}", self.base_url, session_id);
1057        let response = self.client.get(&url).send().await?;
1058        self.handle_response(response).await
1059    }
1060
1061    /// List sessions for an agent
1062    pub async fn list_sessions(&self, agent_id: &str) -> Result<Vec<Session>> {
1063        let url = format!("{}/v1/sessions?agent_id={}", self.base_url, agent_id);
1064        let response = self.client.get(&url).send().await?;
1065        self.handle_response(response).await
1066    }
1067
1068    /// Get memories in a session
1069    pub async fn session_memories(&self, session_id: &str) -> Result<RecallResponse> {
1070        let url = format!("{}/v1/sessions/{}/memories", self.base_url, session_id);
1071        let response = self.client.get(&url).send().await?;
1072        self.handle_response(response).await
1073    }
1074
1075    // ========================================================================
1076    // CE-2: Batch Recall / Forget
1077    // ========================================================================
1078
1079    /// Bulk-recall memories using filter predicates (CE-2).
1080    ///
1081    /// Uses `POST /v1/memories/recall/batch` — no embedding required.
1082    ///
1083    /// # Example
1084    ///
1085    /// ```rust,no_run
1086    /// use dakera_client::{DakeraClient, memory::{BatchRecallRequest, BatchMemoryFilter}};
1087    ///
1088    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1089    /// let client = DakeraClient::new("http://localhost:3000")?;
1090    ///
1091    /// let filter = BatchMemoryFilter::default().with_min_importance(0.7);
1092    /// let req = BatchRecallRequest::new("agent-1").with_filter(filter).with_limit(50);
1093    /// let resp = client.batch_recall(req).await?;
1094    /// println!("Found {} memories", resp.filtered);
1095    /// # Ok(())
1096    /// # }
1097    /// ```
1098    pub async fn batch_recall(&self, request: BatchRecallRequest) -> Result<BatchRecallResponse> {
1099        let url = format!("{}/v1/memories/recall/batch", self.base_url);
1100        let response = self.client.post(&url).json(&request).send().await?;
1101        self.handle_response(response).await
1102    }
1103
1104    /// Bulk-delete memories using filter predicates (CE-2).
1105    ///
1106    /// Uses `DELETE /v1/memories/forget/batch`.  The server requires at least
1107    /// one filter predicate to be set as a safety guard.
1108    ///
1109    /// # Example
1110    ///
1111    /// ```rust,no_run
1112    /// use dakera_client::{DakeraClient, memory::{BatchForgetRequest, BatchMemoryFilter}};
1113    ///
1114    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1115    /// let client = DakeraClient::new("http://localhost:3000")?;
1116    ///
1117    /// let filter = BatchMemoryFilter::default().with_min_importance(0.0).with_max_importance(0.2);
1118    /// let resp = client.batch_forget(BatchForgetRequest::new("agent-1", filter)).await?;
1119    /// println!("Deleted {} memories", resp.deleted_count);
1120    /// # Ok(())
1121    /// # }
1122    /// ```
1123    pub async fn batch_forget(&self, request: BatchForgetRequest) -> Result<BatchForgetResponse> {
1124        let url = format!("{}/v1/memories/forget/batch", self.base_url);
1125        let response = self.client.delete(&url).json(&request).send().await?;
1126        self.handle_response(response).await
1127    }
1128
1129    // ========================================================================
1130    // DX-1: Memory Import / Export
1131    // ========================================================================
1132
1133    /// Import memories from an external format (DX-1).
1134    ///
1135    /// Supported formats: `"jsonl"`, `"mem0"`, `"zep"`, `"csv"`.
1136    ///
1137    /// ```no_run
1138    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1139    /// let client = dakera_client::DakeraClient::new("http://localhost:3000")?;
1140    /// let data = serde_json::json!([{"content": "hello", "agent_id": "agent-1"}]);
1141    /// let resp = client.import_memories(data, "jsonl", None, None).await?;
1142    /// println!("Imported {} memories", resp.imported_count);
1143    /// # Ok(())
1144    /// # }
1145    /// ```
1146    pub async fn import_memories(
1147        &self,
1148        data: serde_json::Value,
1149        format: &str,
1150        agent_id: Option<&str>,
1151        namespace: Option<&str>,
1152    ) -> Result<MemoryImportResponse> {
1153        let mut body = serde_json::json!({"data": data, "format": format});
1154        if let Some(aid) = agent_id {
1155            body["agent_id"] = serde_json::Value::String(aid.to_string());
1156        }
1157        if let Some(ns) = namespace {
1158            body["namespace"] = serde_json::Value::String(ns.to_string());
1159        }
1160        let url = format!("{}/v1/import", self.base_url);
1161        let response = self.client.post(&url).json(&body).send().await?;
1162        self.handle_response(response).await
1163    }
1164
1165    /// Export memories in a portable format (DX-1).
1166    ///
1167    /// Supported formats: `"jsonl"`, `"mem0"`, `"zep"`, `"csv"`.
1168    pub async fn export_memories(
1169        &self,
1170        format: &str,
1171        agent_id: Option<&str>,
1172        namespace: Option<&str>,
1173        limit: Option<u32>,
1174    ) -> Result<MemoryExportResponse> {
1175        let mut params = vec![("format", format.to_string())];
1176        if let Some(aid) = agent_id {
1177            params.push(("agent_id", aid.to_string()));
1178        }
1179        if let Some(ns) = namespace {
1180            params.push(("namespace", ns.to_string()));
1181        }
1182        if let Some(l) = limit {
1183            params.push(("limit", l.to_string()));
1184        }
1185        let url = format!("{}/v1/export", self.base_url);
1186        let response = self.client.get(&url).query(&params).send().await?;
1187        self.handle_response(response).await
1188    }
1189
1190    // ========================================================================
1191    // OBS-1: Business-Event Audit Log
1192    // ========================================================================
1193
1194    /// List paginated audit log entries (OBS-1).
1195    pub async fn list_audit_events(&self, query: AuditQuery) -> Result<AuditListResponse> {
1196        let url = format!("{}/v1/audit", self.base_url);
1197        let response = self.client.get(&url).query(&query).send().await?;
1198        self.handle_response(response).await
1199    }
1200
1201    /// Stream live audit events via SSE (OBS-1).
1202    ///
1203    /// Returns a [`tokio::sync::mpsc::Receiver`] that yields [`DakeraEvent`] results.
1204    pub async fn stream_audit_events(
1205        &self,
1206        agent_id: Option<&str>,
1207        event_type: Option<&str>,
1208    ) -> Result<tokio::sync::mpsc::Receiver<Result<crate::events::DakeraEvent>>> {
1209        let mut params: Vec<(&str, String)> = Vec::new();
1210        if let Some(aid) = agent_id {
1211            params.push(("agent_id", aid.to_string()));
1212        }
1213        if let Some(et) = event_type {
1214            params.push(("event_type", et.to_string()));
1215        }
1216        let base = format!("{}/v1/audit/stream", self.base_url);
1217        let url = if params.is_empty() {
1218            base
1219        } else {
1220            let qs = params
1221                .iter()
1222                .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1223                .collect::<Vec<_>>()
1224                .join("&");
1225            format!("{}?{}", base, qs)
1226        };
1227        self.stream_sse(url).await
1228    }
1229
1230    /// Bulk-export audit log entries (OBS-1).
1231    pub async fn export_audit(
1232        &self,
1233        format: &str,
1234        agent_id: Option<&str>,
1235        event_type: Option<&str>,
1236        from_ts: Option<u64>,
1237        to_ts: Option<u64>,
1238    ) -> Result<AuditExportResponse> {
1239        let mut body = serde_json::json!({"format": format});
1240        if let Some(aid) = agent_id {
1241            body["agent_id"] = serde_json::Value::String(aid.to_string());
1242        }
1243        if let Some(et) = event_type {
1244            body["event_type"] = serde_json::Value::String(et.to_string());
1245        }
1246        if let Some(f) = from_ts {
1247            body["from"] = serde_json::Value::Number(f.into());
1248        }
1249        if let Some(t) = to_ts {
1250            body["to"] = serde_json::Value::Number(t.into());
1251        }
1252        let url = format!("{}/v1/audit/export", self.base_url);
1253        let response = self.client.post(&url).json(&body).send().await?;
1254        self.handle_response(response).await
1255    }
1256
1257    // ========================================================================
1258    // EXT-1: External Extraction Providers
1259    // ========================================================================
1260
1261    /// Extract entities from text using a pluggable provider (EXT-1).
1262    ///
1263    /// Provider hierarchy: per-request > namespace default > GLiNER (bundled).
1264    /// Supported providers: `"gliner"`, `"openai"`, `"anthropic"`, `"openrouter"`, `"ollama"`.
1265    pub async fn extract_text(
1266        &self,
1267        text: &str,
1268        namespace: Option<&str>,
1269        provider: Option<&str>,
1270        model: Option<&str>,
1271    ) -> Result<ExtractionResult> {
1272        let mut body = serde_json::json!({"text": text});
1273        if let Some(ns) = namespace {
1274            body["namespace"] = serde_json::Value::String(ns.to_string());
1275        }
1276        if let Some(p) = provider {
1277            body["provider"] = serde_json::Value::String(p.to_string());
1278        }
1279        if let Some(m) = model {
1280            body["model"] = serde_json::Value::String(m.to_string());
1281        }
1282        let url = format!("{}/v1/extract", self.base_url);
1283        let response = self.client.post(&url).json(&body).send().await?;
1284        self.handle_response(response).await
1285    }
1286
1287    /// List available extraction providers and their models (EXT-1).
1288    pub async fn list_extract_providers(&self) -> Result<Vec<ExtractionProviderInfo>> {
1289        let url = format!("{}/v1/extract/providers", self.base_url);
1290        let response = self.client.get(&url).send().await?;
1291        let result: ExtractProvidersResponse = self.handle_response(response).await?;
1292        Ok(match result {
1293            ExtractProvidersResponse::List(v) => v,
1294            ExtractProvidersResponse::Object { providers } => providers,
1295        })
1296    }
1297
1298    /// Set the default extraction provider for a namespace (EXT-1).
1299    pub async fn configure_namespace_extractor(
1300        &self,
1301        namespace: &str,
1302        provider: &str,
1303        model: Option<&str>,
1304    ) -> Result<serde_json::Value> {
1305        let mut body = serde_json::json!({"provider": provider});
1306        if let Some(m) = model {
1307            body["model"] = serde_json::Value::String(m.to_string());
1308        }
1309        let url = format!(
1310            "{}/v1/namespaces/{}/extractor",
1311            self.base_url,
1312            urlencoding::encode(namespace)
1313        );
1314        let response = self.client.patch(&url).json(&body).send().await?;
1315        self.handle_response(response).await
1316    }
1317
1318    // =========================================================================
1319    // SEC-3: AES-256-GCM Encryption Key Rotation
1320    // =========================================================================
1321
1322    /// Re-encrypt all memory content blobs with a new AES-256-GCM key (SEC-3).
1323    ///
1324    /// After this call the new key is active in the running process.
1325    /// The operator must update `DAKERA_ENCRYPTION_KEY` and restart to make
1326    /// the rotation durable across restarts.
1327    ///
1328    /// Requires Admin scope.
1329    ///
1330    /// # Arguments
1331    /// * `new_key` - New passphrase or 64-char hex key.
1332    /// * `namespace` - If `Some`, rotate only this namespace. `None` rotates all.
1333    pub async fn rotate_encryption_key(
1334        &self,
1335        new_key: &str,
1336        namespace: Option<&str>,
1337    ) -> Result<RotateEncryptionKeyResponse> {
1338        let body = RotateEncryptionKeyRequest {
1339            new_key: new_key.to_string(),
1340            namespace: namespace.map(|s| s.to_string()),
1341        };
1342        let url = format!("{}/v1/admin/encryption/rotate-key", self.base_url);
1343        let response = self.client.post(&url).json(&body).send().await?;
1344        self.handle_response(response).await
1345    }
1346}