1use std::collections::HashMap;
13
14use serde::{Deserialize, Serialize};
15
16use crate::client::Client;
17use crate::error::Result;
18use crate::pagination::Paginated;
19
20use super::MANAGED_AGENTS_BETA;
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
28#[non_exhaustive]
29pub struct MemoryStore {
30 pub id: String,
32 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
34 pub ty: Option<String>,
35 pub name: String,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub description: Option<String>,
40 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
42 pub metadata: HashMap<String, String>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub created_at: Option<String>,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub updated_at: Option<String>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub archived_at: Option<String>,
52}
53
54#[derive(Debug, Clone, Serialize)]
56#[non_exhaustive]
57pub struct CreateMemoryStoreRequest {
58 pub name: String,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub description: Option<String>,
63}
64
65impl CreateMemoryStoreRequest {
66 #[must_use]
68 pub fn new(name: impl Into<String>) -> Self {
69 Self {
70 name: name.into(),
71 description: None,
72 }
73 }
74
75 #[must_use]
77 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
78 self.description = Some(desc.into());
79 self
80 }
81}
82
83#[derive(Debug, Clone, Default, Serialize)]
85#[non_exhaustive]
86pub struct UpdateMemoryStoreRequest {
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub name: Option<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub description: Option<String>,
93}
94
95#[derive(Debug, Clone, Default)]
97#[non_exhaustive]
98pub struct ListMemoryStoresParams {
99 pub after: Option<String>,
101 pub before: Option<String>,
103 pub limit: Option<u32>,
105 pub include_archived: Option<bool>,
107}
108
109impl ListMemoryStoresParams {
110 fn to_query(&self) -> Vec<(&'static str, String)> {
111 let mut q = Vec::new();
112 if let Some(a) = &self.after {
113 q.push(("after", a.clone()));
114 }
115 if let Some(b) = &self.before {
116 q.push(("before", b.clone()));
117 }
118 if let Some(l) = self.limit {
119 q.push(("limit", l.to_string()));
120 }
121 if let Some(ia) = self.include_archived {
122 q.push(("include_archived", ia.to_string()));
123 }
124 q
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
134#[non_exhaustive]
135pub struct Memory {
136 pub id: String,
138 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
141 pub ty: Option<String>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub memory_store_id: Option<String>,
145 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub memory_version_id: Option<String>,
148 pub path: String,
150 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub content: Option<String>,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub content_sha256: Option<String>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub content_size_bytes: Option<u64>,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub created_at: Option<String>,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub updated_at: Option<String>,
166}
167
168#[derive(Debug, Clone, Serialize)]
170#[non_exhaustive]
171pub struct CreateMemoryRequest {
172 pub path: String,
174 pub content: String,
176}
177
178impl CreateMemoryRequest {
179 #[must_use]
181 pub fn new(path: impl Into<String>, content: impl Into<String>) -> Self {
182 Self {
183 path: path.into(),
184 content: content.into(),
185 }
186 }
187}
188
189#[derive(Debug, Clone, Serialize)]
191#[serde(tag = "type", rename_all = "snake_case")]
192#[non_exhaustive]
193pub enum MemoryPrecondition {
194 ContentSha256 {
196 content_sha256: String,
198 },
199}
200
201#[derive(Debug, Clone, Default, Serialize)]
203#[non_exhaustive]
204pub struct UpdateMemoryRequest {
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub content: Option<String>,
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub path: Option<String>,
211 #[serde(skip_serializing_if = "Option::is_none")]
214 pub precondition: Option<MemoryPrecondition>,
215}
216
217#[derive(Debug, Clone, Default)]
219#[non_exhaustive]
220pub struct ListMemoriesParams {
221 pub path_prefix: Option<String>,
223 pub order_by: Option<String>,
225 pub depth: Option<u32>,
227 pub after: Option<String>,
229 pub limit: Option<u32>,
231}
232
233impl ListMemoriesParams {
234 fn to_query(&self) -> Vec<(&'static str, String)> {
235 let mut q = Vec::new();
236 if let Some(p) = &self.path_prefix {
237 q.push(("path_prefix", p.clone()));
238 }
239 if let Some(o) = &self.order_by {
240 q.push(("order_by", o.clone()));
241 }
242 if let Some(d) = self.depth {
243 q.push(("depth", d.to_string()));
244 }
245 if let Some(a) = &self.after {
246 q.push(("after", a.clone()));
247 }
248 if let Some(l) = self.limit {
249 q.push(("limit", l.to_string()));
250 }
251 q
252 }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
261#[serde(rename_all = "lowercase")]
262#[non_exhaustive]
263pub enum MemoryVersionOperation {
264 Created,
266 Modified,
268 Deleted,
270}
271
272#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
275#[serde(tag = "type", rename_all = "snake_case")]
276#[non_exhaustive]
277pub enum MemoryActor {
278 SessionActor {
280 session_id: String,
282 },
283 ApiActor {
285 api_key_id: String,
287 },
288 UserActor {
290 user_id: String,
292 },
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
297#[non_exhaustive]
298pub struct MemoryVersion {
299 pub id: String,
301 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
303 pub ty: Option<String>,
304 #[serde(default, skip_serializing_if = "Option::is_none")]
306 pub memory_store_id: Option<String>,
307 #[serde(default, skip_serializing_if = "Option::is_none")]
309 pub memory_id: Option<String>,
310 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub operation: Option<MemoryVersionOperation>,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
315 pub path: Option<String>,
316 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub content: Option<String>,
320 #[serde(default, skip_serializing_if = "Option::is_none")]
322 pub content_size_bytes: Option<u64>,
323 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub content_sha256: Option<String>,
326 #[serde(default, skip_serializing_if = "Option::is_none")]
328 pub created_by: Option<MemoryActor>,
329 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub created_at: Option<String>,
332 #[serde(default, skip_serializing_if = "Option::is_none")]
334 pub redacted_at: Option<String>,
335 #[serde(default, skip_serializing_if = "Option::is_none")]
337 pub redacted_by: Option<MemoryActor>,
338}
339
340#[derive(Debug, Clone, Default)]
342#[non_exhaustive]
343pub struct ListMemoryVersionsParams {
344 pub memory_id: Option<String>,
346 pub after: Option<String>,
348 pub limit: Option<u32>,
350}
351
352impl ListMemoryVersionsParams {
353 fn to_query(&self) -> Vec<(&'static str, String)> {
354 let mut q = Vec::new();
355 if let Some(m) = &self.memory_id {
356 q.push(("memory_id", m.clone()));
357 }
358 if let Some(a) = &self.after {
359 q.push(("after", a.clone()));
360 }
361 if let Some(l) = self.limit {
362 q.push(("limit", l.to_string()));
363 }
364 q
365 }
366}
367
368pub struct MemoryStores<'a> {
374 client: &'a Client,
375}
376
377impl<'a> MemoryStores<'a> {
378 pub(crate) fn new(client: &'a Client) -> Self {
379 Self { client }
380 }
381
382 pub async fn create(&self, request: CreateMemoryStoreRequest) -> Result<MemoryStore> {
384 let body = &request;
385 self.client
386 .execute_with_retry(
387 || {
388 self.client
389 .request_builder(reqwest::Method::POST, "/v1/memory_stores")
390 .json(body)
391 },
392 &[MANAGED_AGENTS_BETA],
393 )
394 .await
395 }
396
397 pub async fn retrieve(&self, store_id: &str) -> Result<MemoryStore> {
399 let path = format!("/v1/memory_stores/{store_id}");
400 self.client
401 .execute_with_retry(
402 || self.client.request_builder(reqwest::Method::GET, &path),
403 &[MANAGED_AGENTS_BETA],
404 )
405 .await
406 }
407
408 pub async fn update(
410 &self,
411 store_id: &str,
412 request: UpdateMemoryStoreRequest,
413 ) -> Result<MemoryStore> {
414 let path = format!("/v1/memory_stores/{store_id}");
415 let body = &request;
416 self.client
417 .execute_with_retry(
418 || {
419 self.client
420 .request_builder(reqwest::Method::POST, &path)
421 .json(body)
422 },
423 &[MANAGED_AGENTS_BETA],
424 )
425 .await
426 }
427
428 pub async fn list(&self, params: ListMemoryStoresParams) -> Result<Paginated<MemoryStore>> {
430 let query = params.to_query();
431 self.client
432 .execute_with_retry(
433 || {
434 let mut req = self
435 .client
436 .request_builder(reqwest::Method::GET, "/v1/memory_stores");
437 for (k, v) in &query {
438 req = req.query(&[(k, v)]);
439 }
440 req
441 },
442 &[MANAGED_AGENTS_BETA],
443 )
444 .await
445 }
446
447 pub async fn archive(&self, store_id: &str) -> Result<MemoryStore> {
450 let path = format!("/v1/memory_stores/{store_id}/archive");
451 self.client
452 .execute_with_retry(
453 || self.client.request_builder(reqwest::Method::POST, &path),
454 &[MANAGED_AGENTS_BETA],
455 )
456 .await
457 }
458
459 pub async fn delete(&self, store_id: &str) -> Result<()> {
463 let path = format!("/v1/memory_stores/{store_id}");
464 let _: serde_json::Value = self
465 .client
466 .execute_with_retry(
467 || self.client.request_builder(reqwest::Method::DELETE, &path),
468 &[MANAGED_AGENTS_BETA],
469 )
470 .await?;
471 Ok(())
472 }
473
474 #[must_use]
476 pub fn memories(&self, store_id: impl Into<String>) -> Memories<'_> {
477 Memories {
478 client: self.client,
479 store_id: store_id.into(),
480 }
481 }
482
483 #[must_use]
485 pub fn memory_versions(&self, store_id: impl Into<String>) -> MemoryVersions<'_> {
486 MemoryVersions {
487 client: self.client,
488 store_id: store_id.into(),
489 }
490 }
491}
492
493pub struct Memories<'a> {
495 client: &'a Client,
496 store_id: String,
497}
498
499impl Memories<'_> {
500 pub async fn create(&self, request: CreateMemoryRequest) -> Result<Memory> {
502 let path = format!("/v1/memory_stores/{}/memories", self.store_id);
503 let body = &request;
504 self.client
505 .execute_with_retry(
506 || {
507 self.client
508 .request_builder(reqwest::Method::POST, &path)
509 .json(body)
510 },
511 &[MANAGED_AGENTS_BETA],
512 )
513 .await
514 }
515
516 pub async fn retrieve(&self, memory_id: &str) -> Result<Memory> {
518 let path = format!("/v1/memory_stores/{}/memories/{memory_id}", self.store_id);
519 self.client
520 .execute_with_retry(
521 || self.client.request_builder(reqwest::Method::GET, &path),
522 &[MANAGED_AGENTS_BETA],
523 )
524 .await
525 }
526
527 pub async fn update(&self, memory_id: &str, request: UpdateMemoryRequest) -> Result<Memory> {
531 let path = format!("/v1/memory_stores/{}/memories/{memory_id}", self.store_id);
532 let body = &request;
533 self.client
534 .execute_with_retry(
535 || {
536 self.client
537 .request_builder(reqwest::Method::POST, &path)
538 .json(body)
539 },
540 &[MANAGED_AGENTS_BETA],
541 )
542 .await
543 }
544
545 pub async fn list(&self, params: ListMemoriesParams) -> Result<Paginated<Memory>> {
547 let path = format!("/v1/memory_stores/{}/memories", self.store_id);
548 let query = params.to_query();
549 self.client
550 .execute_with_retry(
551 || {
552 let mut req = self.client.request_builder(reqwest::Method::GET, &path);
553 for (k, v) in &query {
554 req = req.query(&[(k, v)]);
555 }
556 req
557 },
558 &[MANAGED_AGENTS_BETA],
559 )
560 .await
561 }
562
563 pub async fn delete(&self, memory_id: &str) -> Result<()> {
565 let path = format!("/v1/memory_stores/{}/memories/{memory_id}", self.store_id);
566 let _: serde_json::Value = self
567 .client
568 .execute_with_retry(
569 || self.client.request_builder(reqwest::Method::DELETE, &path),
570 &[MANAGED_AGENTS_BETA],
571 )
572 .await?;
573 Ok(())
574 }
575}
576
577pub struct MemoryVersions<'a> {
579 client: &'a Client,
580 store_id: String,
581}
582
583impl MemoryVersions<'_> {
584 pub async fn list(&self, params: ListMemoryVersionsParams) -> Result<Paginated<MemoryVersion>> {
586 let path = format!("/v1/memory_stores/{}/memory_versions", self.store_id);
587 let query = params.to_query();
588 self.client
589 .execute_with_retry(
590 || {
591 let mut req = self.client.request_builder(reqwest::Method::GET, &path);
592 for (k, v) in &query {
593 req = req.query(&[(k, v)]);
594 }
595 req
596 },
597 &[MANAGED_AGENTS_BETA],
598 )
599 .await
600 }
601
602 pub async fn retrieve(&self, version_id: &str) -> Result<MemoryVersion> {
604 let path = format!(
605 "/v1/memory_stores/{}/memory_versions/{version_id}",
606 self.store_id
607 );
608 self.client
609 .execute_with_retry(
610 || self.client.request_builder(reqwest::Method::GET, &path),
611 &[MANAGED_AGENTS_BETA],
612 )
613 .await
614 }
615
616 pub async fn redact(&self, version_id: &str) -> Result<MemoryVersion> {
621 let path = format!(
622 "/v1/memory_stores/{}/memory_versions/{version_id}/redact",
623 self.store_id
624 );
625 self.client
626 .execute_with_retry(
627 || {
628 self.client
629 .request_builder(reqwest::Method::POST, &path)
630 .json(&serde_json::json!({}))
631 },
632 &[MANAGED_AGENTS_BETA],
633 )
634 .await
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641 use pretty_assertions::assert_eq;
642 use serde_json::json;
643 use wiremock::matchers::{body_partial_json, method, path};
644 use wiremock::{Mock, MockServer, ResponseTemplate};
645
646 fn client_for(mock: &MockServer) -> Client {
647 Client::builder()
648 .api_key("sk-ant-test")
649 .base_url(mock.uri())
650 .build()
651 .unwrap()
652 }
653
654 #[tokio::test]
655 async fn create_memory_store_round_trips() {
656 let mock = MockServer::start().await;
657 Mock::given(method("POST"))
658 .and(path("/v1/memory_stores"))
659 .and(body_partial_json(json!({
660 "name": "User Preferences",
661 "description": "Per-user preferences."
662 })))
663 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
664 "id": "memstore_01",
665 "type": "memory_store",
666 "name": "User Preferences",
667 "description": "Per-user preferences."
668 })))
669 .mount(&mock)
670 .await;
671
672 let client = client_for(&mock);
673 let req = CreateMemoryStoreRequest::new("User Preferences")
674 .with_description("Per-user preferences.");
675 let s = client
676 .managed_agents()
677 .memory_stores()
678 .create(req)
679 .await
680 .unwrap();
681 assert_eq!(s.id, "memstore_01");
682 }
683
684 #[tokio::test]
685 async fn create_memory_under_store() {
686 let mock = MockServer::start().await;
687 Mock::given(method("POST"))
688 .and(path("/v1/memory_stores/memstore_01/memories"))
689 .and(body_partial_json(json!({
690 "path": "/preferences/formatting.md",
691 "content": "Always use tabs."
692 })))
693 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
694 "id": "mem_01",
695 "type": "file",
696 "path": "/preferences/formatting.md",
697 "content": "Always use tabs.",
698 "content_sha256": "abc123"
699 })))
700 .mount(&mock)
701 .await;
702
703 let client = client_for(&mock);
704 let req = CreateMemoryRequest::new("/preferences/formatting.md", "Always use tabs.");
705 let m = client
706 .managed_agents()
707 .memory_stores()
708 .memories("memstore_01")
709 .create(req)
710 .await
711 .unwrap();
712 assert_eq!(m.id, "mem_01");
713 assert_eq!(m.content_sha256.as_deref(), Some("abc123"));
714 }
715
716 #[tokio::test]
717 async fn update_memory_with_content_sha256_precondition() {
718 let mock = MockServer::start().await;
719 Mock::given(method("POST"))
720 .and(path("/v1/memory_stores/memstore_01/memories/mem_01"))
721 .and(body_partial_json(json!({
722 "content": "CORRECTED",
723 "precondition": {"type": "content_sha256", "content_sha256": "abc123"}
724 })))
725 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
726 "id": "mem_01",
727 "path": "/preferences/formatting.md",
728 "content": "CORRECTED",
729 "content_sha256": "def456"
730 })))
731 .mount(&mock)
732 .await;
733
734 let client = client_for(&mock);
735 let req = UpdateMemoryRequest {
736 content: Some("CORRECTED".into()),
737 path: None,
738 precondition: Some(MemoryPrecondition::ContentSha256 {
739 content_sha256: "abc123".into(),
740 }),
741 };
742 let m = client
743 .managed_agents()
744 .memory_stores()
745 .memories("memstore_01")
746 .update("mem_01", req)
747 .await
748 .unwrap();
749 assert_eq!(m.content_sha256.as_deref(), Some("def456"));
750 }
751
752 #[tokio::test]
753 async fn list_memories_passes_path_prefix_query() {
754 let mock = MockServer::start().await;
755 Mock::given(method("GET"))
756 .and(path("/v1/memory_stores/memstore_01/memories"))
757 .and(wiremock::matchers::query_param(
758 "path_prefix",
759 "/preferences/",
760 ))
761 .and(wiremock::matchers::query_param("depth", "2"))
762 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
763 "data": [
764 {"id": "mem_01", "type": "file", "path": "/preferences/formatting.md"}
765 ],
766 "has_more": false
767 })))
768 .mount(&mock)
769 .await;
770
771 let client = client_for(&mock);
772 let page = client
773 .managed_agents()
774 .memory_stores()
775 .memories("memstore_01")
776 .list(ListMemoriesParams {
777 path_prefix: Some("/preferences/".into()),
778 depth: Some(2),
779 ..Default::default()
780 })
781 .await
782 .unwrap();
783 assert_eq!(page.data.len(), 1);
784 }
785
786 #[tokio::test]
787 async fn retrieve_memory_store_returns_typed_record() {
788 let mock = MockServer::start().await;
789 Mock::given(method("GET"))
790 .and(path("/v1/memory_stores/memstore_01"))
791 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
792 "id": "memstore_01",
793 "type": "memory_store",
794 "name": "Prefs"
795 })))
796 .mount(&mock)
797 .await;
798 let client = client_for(&mock);
799 let s = client
800 .managed_agents()
801 .memory_stores()
802 .retrieve("memstore_01")
803 .await
804 .unwrap();
805 assert_eq!(s.id, "memstore_01");
806 }
807
808 #[tokio::test]
809 async fn update_memory_store_patches_name_and_description() {
810 let mock = MockServer::start().await;
811 Mock::given(method("POST"))
812 .and(path("/v1/memory_stores/memstore_01"))
813 .and(wiremock::matchers::body_partial_json(json!({
814 "name": "Renamed",
815 "description": "New desc."
816 })))
817 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
818 "id": "memstore_01",
819 "name": "Renamed",
820 "description": "New desc."
821 })))
822 .mount(&mock)
823 .await;
824 let client = client_for(&mock);
825 let s = client
826 .managed_agents()
827 .memory_stores()
828 .update(
829 "memstore_01",
830 UpdateMemoryStoreRequest {
831 name: Some("Renamed".into()),
832 description: Some("New desc.".into()),
833 },
834 )
835 .await
836 .unwrap();
837 assert_eq!(s.name, "Renamed");
838 }
839
840 #[tokio::test]
841 async fn list_memory_stores_passes_include_archived_query() {
842 let mock = MockServer::start().await;
843 Mock::given(method("GET"))
844 .and(path("/v1/memory_stores"))
845 .and(wiremock::matchers::query_param("include_archived", "true"))
846 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
847 "data": [{"id": "memstore_01", "name": "Prefs", "archived_at": "2026-04-30T12:00:00Z"}],
848 "has_more": false
849 })))
850 .mount(&mock)
851 .await;
852 let client = client_for(&mock);
853 let page = client
854 .managed_agents()
855 .memory_stores()
856 .list(ListMemoryStoresParams {
857 include_archived: Some(true),
858 ..Default::default()
859 })
860 .await
861 .unwrap();
862 assert_eq!(page.data.len(), 1);
863 assert!(page.data[0].archived_at.is_some());
864 }
865
866 #[tokio::test]
867 async fn archive_memory_store_posts_to_archive_subpath() {
868 let mock = MockServer::start().await;
869 Mock::given(method("POST"))
870 .and(path("/v1/memory_stores/memstore_01/archive"))
871 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
872 "id": "memstore_01",
873 "name": "Prefs",
874 "archived_at": "2026-04-30T12:00:00Z"
875 })))
876 .mount(&mock)
877 .await;
878 let client = client_for(&mock);
879 let s = client
880 .managed_agents()
881 .memory_stores()
882 .archive("memstore_01")
883 .await
884 .unwrap();
885 assert!(s.archived_at.is_some());
886 }
887
888 #[tokio::test]
889 async fn delete_memory_store_returns_unit() {
890 let mock = MockServer::start().await;
891 Mock::given(method("DELETE"))
892 .and(path("/v1/memory_stores/memstore_01"))
893 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
894 .mount(&mock)
895 .await;
896 let client = client_for(&mock);
897 client
898 .managed_agents()
899 .memory_stores()
900 .delete("memstore_01")
901 .await
902 .unwrap();
903 }
904
905 #[tokio::test]
906 async fn retrieve_memory_returns_full_content() {
907 let mock = MockServer::start().await;
908 Mock::given(method("GET"))
909 .and(path("/v1/memory_stores/memstore_01/memories/mem_01"))
910 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
911 "id": "mem_01",
912 "type": "file",
913 "path": "/notes.md",
914 "content": "Hello.",
915 "content_sha256": "abc"
916 })))
917 .mount(&mock)
918 .await;
919 let client = client_for(&mock);
920 let m = client
921 .managed_agents()
922 .memory_stores()
923 .memories("memstore_01")
924 .retrieve("mem_01")
925 .await
926 .unwrap();
927 assert_eq!(m.content.as_deref(), Some("Hello."));
928 }
929
930 #[tokio::test]
931 async fn delete_memory_returns_unit() {
932 let mock = MockServer::start().await;
933 Mock::given(method("DELETE"))
934 .and(path("/v1/memory_stores/memstore_01/memories/mem_01"))
935 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
936 .mount(&mock)
937 .await;
938 let client = client_for(&mock);
939 client
940 .managed_agents()
941 .memory_stores()
942 .memories("memstore_01")
943 .delete("mem_01")
944 .await
945 .unwrap();
946 }
947
948 #[tokio::test]
949 async fn retrieve_memory_version_includes_content() {
950 let mock = MockServer::start().await;
951 Mock::given(method("GET"))
952 .and(path(
953 "/v1/memory_stores/memstore_01/memory_versions/memver_01",
954 ))
955 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
956 "id": "memver_01",
957 "memory_id": "mem_01",
958 "operation": "created",
959 "path": "/notes.md",
960 "content": "Original."
961 })))
962 .mount(&mock)
963 .await;
964 let client = client_for(&mock);
965 let v = client
966 .managed_agents()
967 .memory_stores()
968 .memory_versions("memstore_01")
969 .retrieve("memver_01")
970 .await
971 .unwrap();
972 assert_eq!(v.content.as_deref(), Some("Original."));
973 assert_eq!(v.operation, Some(MemoryVersionOperation::Created));
974 }
975
976 #[tokio::test]
977 async fn redact_memory_version_posts_to_redact_subpath() {
978 let mock = MockServer::start().await;
979 Mock::given(method("POST"))
980 .and(path(
981 "/v1/memory_stores/memstore_01/memory_versions/memver_01/redact",
982 ))
983 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
984 "id": "memver_01",
985 "operation": "deleted",
986 "redacted_at": "2026-04-30T12:00:00Z",
987 "redacted_by": {"type": "api_actor", "api_key_id": "ak_01"}
988 })))
989 .mount(&mock)
990 .await;
991
992 let client = client_for(&mock);
993 let v = client
994 .managed_agents()
995 .memory_stores()
996 .memory_versions("memstore_01")
997 .redact("memver_01")
998 .await
999 .unwrap();
1000 assert_eq!(v.redacted_at.as_deref(), Some("2026-04-30T12:00:00Z"));
1001 match v.redacted_by.unwrap() {
1002 MemoryActor::ApiActor { api_key_id } => assert_eq!(api_key_id, "ak_01"),
1003 _ => panic!("expected ApiActor"),
1004 }
1005 }
1006
1007 #[tokio::test]
1008 async fn list_memory_versions_filters_by_memory_id() {
1009 let mock = MockServer::start().await;
1010 Mock::given(method("GET"))
1011 .and(path("/v1/memory_stores/memstore_01/memory_versions"))
1012 .and(wiremock::matchers::query_param("memory_id", "mem_01"))
1013 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1014 "data": [
1015 {"id": "memver_02", "operation": "modified"},
1016 {"id": "memver_01", "operation": "created"}
1017 ],
1018 "has_more": false
1019 })))
1020 .mount(&mock)
1021 .await;
1022
1023 let client = client_for(&mock);
1024 let page = client
1025 .managed_agents()
1026 .memory_stores()
1027 .memory_versions("memstore_01")
1028 .list(ListMemoryVersionsParams {
1029 memory_id: Some("mem_01".into()),
1030 ..Default::default()
1031 })
1032 .await
1033 .unwrap();
1034 assert_eq!(page.data.len(), 2);
1035 }
1036
1037 #[test]
1038 fn memory_actor_round_trips_all_three_variants() {
1039 for (actor, expected) in [
1040 (
1041 MemoryActor::SessionActor {
1042 session_id: "sesn_x".into(),
1043 },
1044 json!({"type": "session_actor", "session_id": "sesn_x"}),
1045 ),
1046 (
1047 MemoryActor::ApiActor {
1048 api_key_id: "ak_x".into(),
1049 },
1050 json!({"type": "api_actor", "api_key_id": "ak_x"}),
1051 ),
1052 (
1053 MemoryActor::UserActor {
1054 user_id: "user_x".into(),
1055 },
1056 json!({"type": "user_actor", "user_id": "user_x"}),
1057 ),
1058 ] {
1059 let v = serde_json::to_value(&actor).unwrap();
1060 assert_eq!(v, expected);
1061 let parsed: MemoryActor = serde_json::from_value(v).unwrap();
1062 assert_eq!(parsed, actor);
1063 }
1064 }
1065
1066 #[test]
1067 fn memory_version_operation_round_trips_lowercase() {
1068 for (op, wire) in [
1069 (MemoryVersionOperation::Created, "created"),
1070 (MemoryVersionOperation::Modified, "modified"),
1071 (MemoryVersionOperation::Deleted, "deleted"),
1072 ] {
1073 let v = serde_json::to_value(op).unwrap();
1074 assert_eq!(v, json!(wire));
1075 let parsed: MemoryVersionOperation = serde_json::from_value(v).unwrap();
1076 assert_eq!(parsed, op);
1077 }
1078 }
1079}