use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::types::{
AgentFeedbackSummary, EdgeType, FeedbackHealthResponse, FeedbackHistoryResponse,
FeedbackResponse, FeedbackSignal, GraphExport, GraphLinkRequest, GraphLinkResponse,
GraphOptions, GraphPath, MemoryFeedbackBody, MemoryGraph, MemoryImportancePatch,
};
use crate::DakeraClient;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum MemoryType {
#[default]
Episodic,
Semantic,
Procedural,
Working,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreMemoryRequest {
pub agent_id: String,
pub content: String,
#[serde(default)]
pub memory_type: MemoryType,
#[serde(default = "default_importance")]
pub importance: f32,
#[serde(default)]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl_seconds: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
}
fn default_importance() -> f32 {
0.5
}
impl StoreMemoryRequest {
pub fn new(agent_id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
agent_id: agent_id.into(),
content: content.into(),
memory_type: MemoryType::default(),
importance: 0.5,
tags: Vec::new(),
session_id: None,
metadata: None,
ttl_seconds: None,
expires_at: None,
}
}
pub fn with_type(mut self, memory_type: MemoryType) -> Self {
self.memory_type = memory_type;
self
}
pub fn with_importance(mut self, importance: f32) -> Self {
self.importance = importance.clamp(0.0, 1.0);
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
self.ttl_seconds = Some(ttl_seconds);
self
}
pub fn with_expires_at(mut self, expires_at: u64) -> Self {
self.expires_at = Some(expires_at);
self
}
}
#[derive(Debug, Clone, Serialize)]
pub struct StoreMemoryResponse {
pub memory_id: String,
pub agent_id: String,
pub namespace: String,
pub embedding_time_ms: Option<u64>,
}
impl<'de> serde::Deserialize<'de> for StoreMemoryResponse {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
use serde::de::Error;
let val = serde_json::Value::deserialize(deserializer)?;
if let Some(memory) = val.get("memory") {
let memory_id = memory
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| D::Error::missing_field("memory.id"))?
.to_string();
let agent_id = memory
.get("agent_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let namespace = memory
.get("namespace")
.and_then(|v| v.as_str())
.unwrap_or("default")
.to_string();
let embedding_time_ms = val.get("embedding_time_ms").and_then(|v| v.as_u64());
return Ok(Self {
memory_id,
agent_id,
namespace,
embedding_time_ms,
});
}
let memory_id = val
.get("memory_id")
.and_then(|v| v.as_str())
.ok_or_else(|| D::Error::missing_field("memory_id"))?
.to_string();
let agent_id = val
.get("agent_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let namespace = val
.get("namespace")
.and_then(|v| v.as_str())
.unwrap_or("default")
.to_string();
Ok(Self {
memory_id,
agent_id,
namespace,
embedding_time_ms: None,
})
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum FusionStrategy {
#[default]
Rrf,
#[serde(rename = "minmax")]
MinMax,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RoutingMode {
Auto,
Vector,
Bm25,
Hybrid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecallRequest {
pub agent_id: String,
pub query: String,
#[serde(default = "default_top_k")]
pub top_k: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_type: Option<MemoryType>,
#[serde(default)]
pub min_importance: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub include_associated: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub associated_memories_cap: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub associated_memories_depth: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub associated_memories_min_weight: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub since: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub until: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub routing: Option<RoutingMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rerank: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fusion: Option<FusionStrategy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vector_weight: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iterations: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub neighborhood: Option<bool>,
}
fn default_top_k() -> usize {
5
}
impl RecallRequest {
pub fn new(agent_id: impl Into<String>, query: impl Into<String>) -> Self {
Self {
agent_id: agent_id.into(),
query: query.into(),
top_k: 5,
memory_type: None,
min_importance: 0.0,
session_id: None,
tags: Vec::new(),
include_associated: false,
associated_memories_cap: None,
associated_memories_depth: None,
associated_memories_min_weight: None,
since: None,
until: None,
routing: None,
rerank: None,
fusion: None,
vector_weight: None,
iterations: None,
neighborhood: None,
}
}
pub fn with_top_k(mut self, top_k: usize) -> Self {
self.top_k = top_k;
self
}
pub fn with_type(mut self, memory_type: MemoryType) -> Self {
self.memory_type = Some(memory_type);
self
}
pub fn with_min_importance(mut self, min: f32) -> Self {
self.min_importance = min;
self
}
pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn with_associated(mut self) -> Self {
self.include_associated = true;
self
}
pub fn with_associated_cap(mut self, cap: u32) -> Self {
self.include_associated = true;
self.associated_memories_cap = Some(cap);
self
}
pub fn with_since(mut self, since: impl Into<String>) -> Self {
self.since = Some(since.into());
self
}
pub fn with_until(mut self, until: impl Into<String>) -> Self {
self.until = Some(until.into());
self
}
pub fn with_routing(mut self, routing: RoutingMode) -> Self {
self.routing = Some(routing);
self
}
pub fn with_rerank(mut self, rerank: bool) -> Self {
self.rerank = Some(rerank);
self
}
pub fn with_associated_depth(mut self, depth: u8) -> Self {
self.include_associated = true;
self.associated_memories_depth = Some(depth);
self
}
pub fn with_associated_min_weight(mut self, weight: f32) -> Self {
self.associated_memories_min_weight = Some(weight);
self
}
pub fn with_fusion(mut self, fusion: FusionStrategy) -> Self {
self.fusion = Some(fusion);
self
}
pub fn with_vector_weight(mut self, weight: f32) -> Self {
self.vector_weight = Some(weight);
self
}
pub fn with_iterations(mut self, iterations: u8) -> Self {
self.iterations = Some(iterations);
self
}
pub fn with_neighborhood(mut self, neighborhood: bool) -> Self {
self.neighborhood = Some(neighborhood);
self
}
}
#[derive(Debug, Clone, Serialize)]
pub struct RecalledMemory {
pub id: String,
pub content: String,
pub memory_type: MemoryType,
pub importance: f32,
pub score: f32,
#[serde(default)]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
pub created_at: u64,
pub last_accessed_at: u64,
pub access_count: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub depth: Option<u8>,
}
impl<'de> serde::Deserialize<'de> for RecalledMemory {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
use serde::de::Error as _;
let val = serde_json::Value::deserialize(deserializer)?;
let score = val
.get("score")
.and_then(|v| v.as_f64())
.or_else(|| val.get("weighted_score").and_then(|v| v.as_f64()))
.unwrap_or(0.0) as f32;
let mem = val.get("memory").unwrap_or(&val);
let id = mem
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| D::Error::missing_field("id"))?
.to_string();
let content = mem
.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| D::Error::missing_field("content"))?
.to_string();
let memory_type: MemoryType = mem
.get("memory_type")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or(MemoryType::Episodic);
let importance = mem
.get("importance")
.and_then(|v| v.as_f64())
.unwrap_or(0.5) as f32;
let tags: Vec<String> = mem
.get("tags")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
let session_id = mem
.get("session_id")
.and_then(|v| v.as_str())
.map(String::from);
let metadata = mem.get("metadata").cloned().filter(|v| !v.is_null());
let created_at = mem.get("created_at").and_then(|v| v.as_u64()).unwrap_or(0);
let last_accessed_at = mem
.get("last_accessed_at")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let access_count = mem
.get("access_count")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u32;
let depth = mem.get("depth").and_then(|v| v.as_u64()).map(|v| v as u8);
Ok(Self {
id,
content,
memory_type,
importance,
score,
tags,
session_id,
metadata,
created_at,
last_accessed_at,
access_count,
depth,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecallResponse {
pub memories: Vec<RecalledMemory>,
#[serde(default)]
pub total_found: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub associated_memories: Option<Vec<RecalledMemory>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForgetRequest {
pub agent_id: String,
#[serde(default)]
pub memory_ids: Vec<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before_timestamp: Option<u64>,
}
impl ForgetRequest {
pub fn by_ids(agent_id: impl Into<String>, ids: Vec<String>) -> Self {
Self {
agent_id: agent_id.into(),
memory_ids: ids,
tags: Vec::new(),
session_id: None,
before_timestamp: None,
}
}
pub fn by_tags(agent_id: impl Into<String>, tags: Vec<String>) -> Self {
Self {
agent_id: agent_id.into(),
memory_ids: Vec::new(),
tags,
session_id: None,
before_timestamp: None,
}
}
pub fn by_session(agent_id: impl Into<String>, session_id: impl Into<String>) -> Self {
Self {
agent_id: agent_id.into(),
memory_ids: Vec::new(),
tags: Vec::new(),
session_id: Some(session_id.into()),
before_timestamp: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForgetResponse {
pub deleted_count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionStartRequest {
pub agent_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub id: String,
pub agent_id: String,
pub started_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub ended_at: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(default)]
pub memory_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionEndRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionStartResponse {
pub session: Session,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionEndResponse {
pub session: Session,
pub memory_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateMemoryRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_type: Option<MemoryType>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateImportanceRequest {
pub memory_ids: Vec<String>,
pub importance: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ConsolidationConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub algorithm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_samples: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub eps: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsolidationLogEntry {
pub step: String,
pub memories_before: usize,
pub memories_after: usize,
pub duration_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ConsolidateRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub threshold: Option<f32>,
#[serde(default)]
pub dry_run: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<ConsolidationConfig>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ConsolidateResponse {
pub consolidated_count: usize,
pub removed_count: usize,
#[serde(default)]
pub new_memories: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub log: Vec<ConsolidationLogEntry>,
}
impl<'de> serde::Deserialize<'de> for ConsolidateResponse {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
let val = serde_json::Value::deserialize(deserializer)?;
let removed = val
.get("memories_removed")
.and_then(|v| v.as_u64())
.or_else(|| val.get("removed_count").and_then(|v| v.as_u64()))
.or_else(|| val.get("consolidated_count").and_then(|v| v.as_u64()))
.unwrap_or(0) as usize;
let source_ids: Vec<String> = val
.get("source_memory_ids")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
Ok(Self {
consolidated_count: removed,
removed_count: removed,
new_memories: source_ids,
log: vec![],
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryImportResponse {
pub imported_count: usize,
pub skipped_count: usize,
#[serde(default)]
pub errors: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryExportResponse {
pub data: Vec<serde_json::Value>,
pub format: String,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
pub id: String,
pub event_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub namespace: Option<String>,
pub timestamp: u64,
#[serde(default)]
pub details: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditListResponse {
pub events: Vec<AuditEvent>,
pub total: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditExportResponse {
pub data: String,
pub format: String,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AuditQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractionResult {
pub entities: Vec<serde_json::Value>,
pub provider: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
pub duration_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractionProviderInfo {
pub name: String,
pub available: bool,
#[serde(default)]
pub models: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ExtractProvidersResponse {
List(Vec<ExtractionProviderInfo>),
Object {
providers: Vec<ExtractionProviderInfo>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RotateEncryptionKeyRequest {
pub new_key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub namespace: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RotateEncryptionKeyResponse {
pub rotated: usize,
pub skipped: usize,
#[serde(default)]
pub namespaces: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeedbackRequest {
pub memory_id: String,
pub feedback: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub relevance_score: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LegacyFeedbackResponse {
pub status: String,
pub updated_importance: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BatchMemoryFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_importance: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_importance: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_after: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_before: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_type: Option<MemoryType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
}
impl BatchMemoryFilter {
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = Some(tags);
self
}
pub fn with_min_importance(mut self, min: f32) -> Self {
self.min_importance = Some(min);
self
}
pub fn with_max_importance(mut self, max: f32) -> Self {
self.max_importance = Some(max);
self
}
pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchRecallRequest {
pub agent_id: String,
#[serde(default)]
pub filter: BatchMemoryFilter,
#[serde(default = "default_batch_limit")]
pub limit: usize,
}
fn default_batch_limit() -> usize {
100
}
impl BatchRecallRequest {
pub fn new(agent_id: impl Into<String>) -> Self {
Self {
agent_id: agent_id.into(),
filter: BatchMemoryFilter::default(),
limit: 100,
}
}
pub fn with_filter(mut self, filter: BatchMemoryFilter) -> Self {
self.filter = filter;
self
}
pub fn with_limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchRecallResponse {
pub memories: Vec<RecalledMemory>,
pub total: usize,
pub filtered: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchForgetRequest {
pub agent_id: String,
pub filter: BatchMemoryFilter,
}
impl BatchForgetRequest {
pub fn new(agent_id: impl Into<String>, filter: BatchMemoryFilter) -> Self {
Self {
agent_id: agent_id.into(),
filter,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchForgetResponse {
pub deleted_count: usize,
}
impl DakeraClient {
pub async fn store_memory(&self, request: StoreMemoryRequest) -> Result<StoreMemoryResponse> {
let url = format!("{}/v1/memory/store", self.base_url);
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn recall(&self, request: RecallRequest) -> Result<RecallResponse> {
let url = format!("{}/v1/memory/recall", self.base_url);
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn recall_simple(
&self,
agent_id: &str,
query: &str,
top_k: usize,
) -> Result<RecallResponse> {
self.recall(RecallRequest::new(agent_id, query).with_top_k(top_k))
.await
}
pub async fn get_memory(&self, memory_id: &str) -> Result<RecalledMemory> {
let url = format!("{}/v1/memory/get/{}", self.base_url, memory_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn forget(&self, request: ForgetRequest) -> Result<ForgetResponse> {
let url = format!("{}/v1/memory/forget", self.base_url);
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn search_memories(&self, request: RecallRequest) -> Result<RecallResponse> {
let url = format!("{}/v1/memory/search", self.base_url);
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn update_memory(
&self,
agent_id: &str,
memory_id: &str,
request: UpdateMemoryRequest,
) -> Result<StoreMemoryResponse> {
let url = format!(
"{}/v1/agents/{}/memories/{}",
self.base_url, agent_id, memory_id
);
let response = self.client.put(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn update_importance(
&self,
agent_id: &str,
request: UpdateImportanceRequest,
) -> Result<serde_json::Value> {
let url = format!(
"{}/v1/agents/{}/memories/importance",
self.base_url, agent_id
);
let response = self.client.put(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn consolidate(
&self,
agent_id: &str,
request: ConsolidateRequest,
) -> Result<ConsolidateResponse> {
let url = format!("{}/v1/memory/consolidate", self.base_url);
let mut body = serde_json::to_value(&request)?;
body["agent_id"] = serde_json::Value::String(agent_id.to_string());
let response = self.client.post(&url).json(&body).send().await?;
self.handle_response(response).await
}
pub async fn memory_feedback(
&self,
agent_id: &str,
request: FeedbackRequest,
) -> Result<LegacyFeedbackResponse> {
let url = format!("{}/v1/agents/{}/memories/feedback", self.base_url, agent_id);
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn feedback_memory(
&self,
memory_id: &str,
agent_id: &str,
signal: FeedbackSignal,
) -> Result<FeedbackResponse> {
let url = format!("{}/v1/memories/{}/feedback", self.base_url, memory_id);
let body = MemoryFeedbackBody {
agent_id: agent_id.to_string(),
signal,
};
let response = self.client.post(&url).json(&body).send().await?;
self.handle_response(response).await
}
pub async fn get_memory_feedback_history(
&self,
memory_id: &str,
) -> Result<FeedbackHistoryResponse> {
let url = format!("{}/v1/memories/{}/feedback", self.base_url, memory_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn get_agent_feedback_summary(&self, agent_id: &str) -> Result<AgentFeedbackSummary> {
let url = format!("{}/v1/agents/{}/feedback/summary", self.base_url, agent_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn patch_memory_importance(
&self,
memory_id: &str,
agent_id: &str,
importance: f32,
) -> Result<FeedbackResponse> {
let url = format!("{}/v1/memories/{}/importance", self.base_url, memory_id);
let body = MemoryImportancePatch {
agent_id: agent_id.to_string(),
importance,
};
let response = self.client.patch(&url).json(&body).send().await?;
self.handle_response(response).await
}
pub async fn get_feedback_health(&self, agent_id: &str) -> Result<FeedbackHealthResponse> {
let url = format!("{}/v1/feedback/health?agent_id={}", self.base_url, agent_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn memory_graph(
&self,
memory_id: &str,
options: GraphOptions,
) -> Result<MemoryGraph> {
let mut url = format!("{}/v1/memories/{}/graph", self.base_url, memory_id);
let depth = options.depth.unwrap_or(1);
url.push_str(&format!("?depth={}", depth));
if let Some(types) = &options.types {
let type_strs: Vec<String> = types
.iter()
.map(|t| {
serde_json::to_value(t)
.unwrap()
.as_str()
.unwrap_or("")
.to_string()
})
.collect();
if !type_strs.is_empty() {
url.push_str(&format!("&types={}", type_strs.join(",")));
}
}
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn memory_path(&self, source_id: &str, target_id: &str) -> Result<GraphPath> {
let url = format!(
"{}/v1/memories/{}/path?target={}",
self.base_url,
source_id,
urlencoding::encode(target_id)
);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn memory_link(
&self,
source_id: &str,
target_id: &str,
edge_type: EdgeType,
) -> Result<GraphLinkResponse> {
let url = format!("{}/v1/memories/{}/links", self.base_url, source_id);
let request = GraphLinkRequest {
target_id: target_id.to_string(),
edge_type,
};
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn agent_graph_export(&self, agent_id: &str, format: &str) -> Result<GraphExport> {
let url = format!(
"{}/v1/agents/{}/graph/export?format={}",
self.base_url, agent_id, format
);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn start_session(&self, agent_id: &str) -> Result<Session> {
let url = format!("{}/v1/sessions/start", self.base_url);
let request = SessionStartRequest {
agent_id: agent_id.to_string(),
metadata: None,
};
let response = self.client.post(&url).json(&request).send().await?;
let resp: SessionStartResponse = self.handle_response(response).await?;
Ok(resp.session)
}
pub async fn start_session_with_metadata(
&self,
agent_id: &str,
metadata: serde_json::Value,
) -> Result<Session> {
let url = format!("{}/v1/sessions/start", self.base_url);
let request = SessionStartRequest {
agent_id: agent_id.to_string(),
metadata: Some(metadata),
};
let response = self.client.post(&url).json(&request).send().await?;
let resp: SessionStartResponse = self.handle_response(response).await?;
Ok(resp.session)
}
pub async fn end_session(
&self,
session_id: &str,
summary: Option<String>,
) -> Result<SessionEndResponse> {
let url = format!("{}/v1/sessions/{}/end", self.base_url, session_id);
let request = SessionEndRequest { summary };
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn get_session(&self, session_id: &str) -> Result<Session> {
let url = format!("{}/v1/sessions/{}", self.base_url, session_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn list_sessions(&self, agent_id: &str) -> Result<Vec<Session>> {
let url = format!("{}/v1/sessions?agent_id={}", self.base_url, agent_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn session_memories(&self, session_id: &str) -> Result<RecallResponse> {
let url = format!("{}/v1/sessions/{}/memories", self.base_url, session_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn batch_recall(&self, request: BatchRecallRequest) -> Result<BatchRecallResponse> {
let url = format!("{}/v1/memories/recall/batch", self.base_url);
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn batch_forget(&self, request: BatchForgetRequest) -> Result<BatchForgetResponse> {
let url = format!("{}/v1/memories/forget/batch", self.base_url);
let response = self.client.delete(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn import_memories(
&self,
data: serde_json::Value,
format: &str,
agent_id: Option<&str>,
namespace: Option<&str>,
) -> Result<MemoryImportResponse> {
let mut body = serde_json::json!({"data": data, "format": format});
if let Some(aid) = agent_id {
body["agent_id"] = serde_json::Value::String(aid.to_string());
}
if let Some(ns) = namespace {
body["namespace"] = serde_json::Value::String(ns.to_string());
}
let url = format!("{}/v1/import", self.base_url);
let response = self.client.post(&url).json(&body).send().await?;
self.handle_response(response).await
}
pub async fn export_memories(
&self,
format: &str,
agent_id: Option<&str>,
namespace: Option<&str>,
limit: Option<u32>,
) -> Result<MemoryExportResponse> {
let mut params = vec![("format", format.to_string())];
if let Some(aid) = agent_id {
params.push(("agent_id", aid.to_string()));
}
if let Some(ns) = namespace {
params.push(("namespace", ns.to_string()));
}
if let Some(l) = limit {
params.push(("limit", l.to_string()));
}
let url = format!("{}/v1/export", self.base_url);
let response = self.client.get(&url).query(¶ms).send().await?;
self.handle_response(response).await
}
pub async fn list_audit_events(&self, query: AuditQuery) -> Result<AuditListResponse> {
let url = format!("{}/v1/audit", self.base_url);
let response = self.client.get(&url).query(&query).send().await?;
self.handle_response(response).await
}
pub async fn stream_audit_events(
&self,
agent_id: Option<&str>,
event_type: Option<&str>,
) -> Result<tokio::sync::mpsc::Receiver<Result<crate::events::DakeraEvent>>> {
let mut params: Vec<(&str, String)> = Vec::new();
if let Some(aid) = agent_id {
params.push(("agent_id", aid.to_string()));
}
if let Some(et) = event_type {
params.push(("event_type", et.to_string()));
}
let base = format!("{}/v1/audit/stream", self.base_url);
let url = if params.is_empty() {
base
} else {
let qs = params
.iter()
.map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
.collect::<Vec<_>>()
.join("&");
format!("{}?{}", base, qs)
};
self.stream_sse(url).await
}
pub async fn export_audit(
&self,
format: &str,
agent_id: Option<&str>,
event_type: Option<&str>,
from_ts: Option<u64>,
to_ts: Option<u64>,
) -> Result<AuditExportResponse> {
let mut body = serde_json::json!({"format": format});
if let Some(aid) = agent_id {
body["agent_id"] = serde_json::Value::String(aid.to_string());
}
if let Some(et) = event_type {
body["event_type"] = serde_json::Value::String(et.to_string());
}
if let Some(f) = from_ts {
body["from"] = serde_json::Value::Number(f.into());
}
if let Some(t) = to_ts {
body["to"] = serde_json::Value::Number(t.into());
}
let url = format!("{}/v1/audit/export", self.base_url);
let response = self.client.post(&url).json(&body).send().await?;
self.handle_response(response).await
}
pub async fn extract_text(
&self,
text: &str,
namespace: Option<&str>,
provider: Option<&str>,
model: Option<&str>,
) -> Result<ExtractionResult> {
let mut body = serde_json::json!({"text": text});
if let Some(ns) = namespace {
body["namespace"] = serde_json::Value::String(ns.to_string());
}
if let Some(p) = provider {
body["provider"] = serde_json::Value::String(p.to_string());
}
if let Some(m) = model {
body["model"] = serde_json::Value::String(m.to_string());
}
let url = format!("{}/v1/extract", self.base_url);
let response = self.client.post(&url).json(&body).send().await?;
self.handle_response(response).await
}
pub async fn list_extract_providers(&self) -> Result<Vec<ExtractionProviderInfo>> {
let url = format!("{}/v1/extract/providers", self.base_url);
let response = self.client.get(&url).send().await?;
let result: ExtractProvidersResponse = self.handle_response(response).await?;
Ok(match result {
ExtractProvidersResponse::List(v) => v,
ExtractProvidersResponse::Object { providers } => providers,
})
}
pub async fn configure_namespace_extractor(
&self,
namespace: &str,
provider: &str,
model: Option<&str>,
) -> Result<serde_json::Value> {
let mut body = serde_json::json!({"provider": provider});
if let Some(m) = model {
body["model"] = serde_json::Value::String(m.to_string());
}
let url = format!(
"{}/v1/namespaces/{}/extractor",
self.base_url,
urlencoding::encode(namespace)
);
let response = self.client.patch(&url).json(&body).send().await?;
self.handle_response(response).await
}
pub async fn rotate_encryption_key(
&self,
new_key: &str,
namespace: Option<&str>,
) -> Result<RotateEncryptionKeyResponse> {
let body = RotateEncryptionKeyRequest {
new_key: new_key.to_string(),
namespace: namespace.map(|s| s.to_string()),
};
let url = format!("{}/v1/admin/encryption/rotate-key", self.base_url);
let response = self.client.post(&url).json(&body).send().await?;
self.handle_response(response).await
}
}