From 88275535760027e061e525bf887baa936a9bdaef Mon Sep 17 00:00:00 2001 From: Saket Aryan Date: Thu, 16 Apr 2026 05:28:49 +0530 Subject: [PATCH] fix: adopt new v3 memory endpoints in Python + TS clients (#4856) --- mem0-ts/src/client/mem0.ts | 8 ++--- mem0-ts/src/client/mem0.types.ts | 7 +++++ .../src/client/tests/integration/crud.test.ts | 27 ++++++++++------- .../src/client/tests/integration/helpers.ts | 5 +--- .../client/tests/memoryClient.crud.test.ts | 18 +++++------ .../client/tests/memoryClient.search.test.ts | 4 +-- mem0/client/main.py | 30 +++++++------------ 7 files changed, 49 insertions(+), 50 deletions(-) diff --git a/mem0-ts/src/client/mem0.ts b/mem0-ts/src/client/mem0.ts index bda29c058..783d7f925 100644 --- a/mem0-ts/src/client/mem0.ts +++ b/mem0-ts/src/client/mem0.ts @@ -1,6 +1,7 @@ import axios from "axios"; import { AllUsers, + PaginatedMemories, ProjectOptions, Memory, MemoryHistory, @@ -221,7 +222,7 @@ export default class MemoryClient { this._captureEvent("add", [payloadKeys]); const response = await this._fetchWithErrorHandling( - `${this.host}/v3/memories/`, + `${this.host}/v3/memories/add/`, { method: "POST", headers: this.headers, @@ -284,7 +285,7 @@ export default class MemoryClient { ); } - async getAll(options?: GetAllMemoryOptions): Promise> { + async getAll(options?: GetAllMemoryOptions): Promise { // Reject top-level entity params - must use filters instead rejectTopLevelEntityParams(options as Record, "getAll"); @@ -293,12 +294,11 @@ export default class MemoryClient { this._captureEvent("get_all", [payloadKeys]); const { page, pageSize, filters, ...rest } = options ?? {}; const body: Record = { - output_format: "v1.1", ...camelToSnakeKeys(rest), ...(filters && { filters }), }; - let url = `${this.host}/v2/memories/`; + let url = `${this.host}/v3/memories/`; if (page && pageSize) { url += `?page=${page}&page_size=${pageSize}`; } diff --git a/mem0-ts/src/client/mem0.types.ts b/mem0-ts/src/client/mem0.types.ts index af1f67a72..1dacce376 100644 --- a/mem0-ts/src/client/mem0.types.ts +++ b/mem0-ts/src/client/mem0.types.ts @@ -142,6 +142,13 @@ export interface AllUsers { previous: any; } +export interface PaginatedMemories { + count: number; + next: string | null; + previous: string | null; + results: Array; +} + export interface ProjectResponse { customInstructions?: string; customCategories?: string[]; diff --git a/mem0-ts/src/client/tests/integration/crud.test.ts b/mem0-ts/src/client/tests/integration/crud.test.ts index d3c8a703e..ee4e59324 100644 --- a/mem0-ts/src/client/tests/integration/crud.test.ts +++ b/mem0-ts/src/client/tests/integration/crud.test.ts @@ -124,13 +124,16 @@ describeIntegration("MemoryClient Integration — CRUD", () => { filters: { user_id: TEST_USER_ID }, }); - // v1.1 output_format returns { results: [...] } + // Paginated shape: { count, next, previous, results: [...] } + expect(response).toHaveProperty("count"); + expect(response).toHaveProperty("next"); + expect(response).toHaveProperty("previous"); expect(response).toHaveProperty("results"); - const memories = (response as any).results; - expect(Array.isArray(memories)).toBe(true); - expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length); + expect(typeof response.count).toBe("number"); + expect(Array.isArray(response.results)).toBe(true); + expect(response.results.length).toBeGreaterThanOrEqual(memoryIds.length); - for (const mem of memories) { + for (const mem of response.results) { expect(typeof mem.id).toBe("string"); expect(typeof mem.memory).toBe("string"); } @@ -143,9 +146,12 @@ describeIntegration("MemoryClient Integration — CRUD", () => { pageSize: 1, }); - // Paginated response is an object with results array expect(page1).toBeDefined(); - expect(page1).toHaveProperty("results"); + expect(page1).toHaveProperty("count"); + expect(page1).toHaveProperty("next"); + expect(page1).toHaveProperty("previous"); + expect(Array.isArray(page1.results)).toBe(true); + expect(page1.results.length).toBeLessThanOrEqual(1); }); }); @@ -206,11 +212,10 @@ describeIntegration("MemoryClient Integration — CRUD", () => { filters: { user_id: `nonexistent-user-${randomUUID()}` }, }); - // v1.1 output_format returns { results: [...] } expect(response).toHaveProperty("results"); - const memories = (response as any).results; - expect(Array.isArray(memories)).toBe(true); - expect(memories.length).toBe(0); + expect(Array.isArray(response.results)).toBe(true); + expect(response.results.length).toBe(0); + expect(response.count).toBe(0); }); test("deleteAll for non-existent user does not throw", async () => { diff --git a/mem0-ts/src/client/tests/integration/helpers.ts b/mem0-ts/src/client/tests/integration/helpers.ts index 84279375a..56a173534 100644 --- a/mem0-ts/src/client/tests/integration/helpers.ts +++ b/mem0-ts/src/client/tests/integration/helpers.ts @@ -70,10 +70,7 @@ export async function waitForMemories( const response = await withRetry(() => client.getAll({ filters: { user_id: userId } }), ); - // v1.1 output_format returns { results: [...] } - const memories = Array.isArray(response) - ? response - : ((response as any)?.results ?? []); + const memories = response.results ?? []; if (memories.length >= minCount) { return memories; } diff --git a/mem0-ts/src/client/tests/memoryClient.crud.test.ts b/mem0-ts/src/client/tests/memoryClient.crud.test.ts index ecbbf92c3..80bdcfa01 100644 --- a/mem0-ts/src/client/tests/memoryClient.crud.test.ts +++ b/mem0-ts/src/client/tests/memoryClient.crud.test.ts @@ -21,33 +21,33 @@ installConsoleSuppression(); // ─── add() ─────────────────────────────────────────────── describe("MemoryClient - add()", () => { - test("sends POST to /v3/memories/", async () => { + test("sends POST to /v3/memories/add/", async () => { const extra = new Map(); - extra.set("/v3/memories/", { status: 200, body: [createMockMemory()] }); + extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] }); const mock = setupMockFetch(extra); const client = new MemoryClient({ apiKey: TEST_API_KEY }); await client.add([{ role: "user", content: "Hello" }], { userId: "u1" }); - expect(findFetchCall(mock, "/v3/memories/", "POST")).toBeDefined(); + expect(findFetchCall(mock, "/v3/memories/add/", "POST")).toBeDefined(); }); test("includes messages in request body", async () => { const messages = [{ role: "user" as const, content: "Hello, I am Alex" }]; const extra = new Map(); - extra.set("/v3/memories/", { status: 200, body: [createMockMemory()] }); + extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] }); const mock = setupMockFetch(extra); const client = new MemoryClient({ apiKey: TEST_API_KEY }); await client.add(messages, { userId: "u1" }); - const call = findFetchCall(mock, "/v3/memories/", "POST"); + const call = findFetchCall(mock, "/v3/memories/add/", "POST"); expect(getFetchBody(call!).messages).toEqual(messages); }); test("includes user_id in request body", async () => { const extra = new Map(); - extra.set("/v3/memories/", { status: 200, body: [createMockMemory()] }); + extra.set("/v3/memories/add/", { status: 200, body: [createMockMemory()] }); const mock = setupMockFetch(extra); const client = new MemoryClient({ apiKey: TEST_API_KEY }); @@ -55,19 +55,19 @@ describe("MemoryClient - add()", () => { user_id: "user_1", }); - const call = findFetchCall(mock, "/v3/memories/", "POST"); + const call = findFetchCall(mock, "/v3/memories/add/", "POST"); expect(getFetchBody(call!).user_id).toBe("user_1"); }); test("sends empty messages array without crashing", async () => { const extra = new Map(); - extra.set("/v3/memories/", { status: 200, body: [] }); + extra.set("/v3/memories/add/", { status: 200, body: [] }); const mock = setupMockFetch(extra); const client = new MemoryClient({ apiKey: TEST_API_KEY }); await client.add([], { userId: "u1" }); - const call = findFetchCall(mock, "/v3/memories/", "POST"); + const call = findFetchCall(mock, "/v3/memories/add/", "POST"); expect(getFetchBody(call!).messages).toEqual([]); }); }); diff --git a/mem0-ts/src/client/tests/memoryClient.search.test.ts b/mem0-ts/src/client/tests/memoryClient.search.test.ts index b411c7e05..649b53563 100644 --- a/mem0-ts/src/client/tests/memoryClient.search.test.ts +++ b/mem0-ts/src/client/tests/memoryClient.search.test.ts @@ -254,7 +254,7 @@ describe("MemoryClient - getAll() entity param rejection", () => { test("accepts filters with user_id", async () => { const extra = new Map(); - extra.set("/v2/memories/", { + extra.set("/v3/memories/", { status: 200, body: { results: [] }, }); @@ -262,6 +262,6 @@ describe("MemoryClient - getAll() entity param rejection", () => { const client = new MemoryClient({ apiKey: TEST_API_KEY }); await client.getAll({ filters: { user_id: "u1" } }); - expect(findFetchCall(mock, "/v2/memories/", "POST")).toBeDefined(); + expect(findFetchCall(mock, "/v3/memories/", "POST")).toBeDefined(); }); }); diff --git a/mem0/client/main.py b/mem0/client/main.py index 98690d934..34db1c782 100644 --- a/mem0/client/main.py +++ b/mem0/client/main.py @@ -167,7 +167,7 @@ class MemoryClient: kwargs = self._prepare_params(kwargs) payload = self._prepare_payload(messages, kwargs) - response = self.client.post("/v3/memories/", json=payload) + response = self.client.post("/v3/memories/add/", json=payload) response.raise_for_status() if "metadata" in kwargs: del kwargs["metadata"] @@ -207,7 +207,7 @@ class MemoryClient: **kwargs: Optional parameters for filtering (filters, page, page_size). Returns: - A dictionary containing memories in v1.1 format: {"results": [...]} + A paginated dict: {"count": int, "next": str | None, "previous": str | None, "results": [...]} Raises: ValidationError: If the input data is invalid. @@ -233,9 +233,9 @@ class MemoryClient: "page": params.pop("page"), "page_size": params.pop("page_size"), } - response = self.client.post("/v2/memories/", json=params, params=query_params) + response = self.client.post("/v3/memories/", json=params, params=query_params) else: - response = self.client.post("/v2/memories/", json=params) + response = self.client.post("/v3/memories/", json=params) response.raise_for_status() if "metadata" in kwargs: del kwargs["metadata"] @@ -247,12 +247,7 @@ class MemoryClient: "sync_type": "sync", }, ) - result = response.json() - - # Ensure v1.1 format (wrap raw list if needed) - if isinstance(result, list): - return {"results": result} - return result + return response.json() @api_error_handler def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]: @@ -1095,7 +1090,7 @@ class AsyncMemoryClient: kwargs = self._prepare_params(kwargs) payload = self._prepare_payload(messages, kwargs) - response = await self.async_client.post("/v3/memories/", json=payload) + response = await self.async_client.post("/v3/memories/add/", json=payload) response.raise_for_status() if "metadata" in kwargs: del kwargs["metadata"] @@ -1119,7 +1114,7 @@ class AsyncMemoryClient: **kwargs: Optional parameters for filtering (filters, page, page_size). Returns: - A dictionary containing memories in v1.1 format: {"results": [...]} + A paginated dict: {"count": int, "next": str | None, "previous": str | None, "results": [...]} Raises: ValidationError: If the input data is invalid. @@ -1145,9 +1140,9 @@ class AsyncMemoryClient: "page": params.pop("page"), "page_size": params.pop("page_size"), } - response = await self.async_client.post("/v2/memories/", json=params, params=query_params) + response = await self.async_client.post("/v3/memories/", json=params, params=query_params) else: - response = await self.async_client.post("/v2/memories/", json=params) + response = await self.async_client.post("/v3/memories/", json=params) response.raise_for_status() if "metadata" in kwargs: del kwargs["metadata"] @@ -1159,12 +1154,7 @@ class AsyncMemoryClient: "sync_type": "async", }, ) - result = response.json() - - # Ensure v1.1 format (wrap raw list if needed) - if isinstance(result, list): - return {"results": result} - return result + return response.json() @api_error_handler async def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]: