fix: adopt new v3 memory endpoints in Python + TS clients (#4856)

This commit is contained in:
Saket Aryan
2026-04-16 05:28:49 +05:30
committed by GitHub
parent c8e20a9bb5
commit 8827553576
7 changed files with 49 additions and 50 deletions
+4 -4
View File
@@ -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<Array<Memory>> {
async getAll(options?: GetAllMemoryOptions): Promise<PaginatedMemories> {
// Reject top-level entity params - must use filters instead
rejectTopLevelEntityParams(options as Record<string, any>, "getAll");
@@ -293,12 +294,11 @@ export default class MemoryClient {
this._captureEvent("get_all", [payloadKeys]);
const { page, pageSize, filters, ...rest } = options ?? {};
const body: Record<string, any> = {
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}`;
}
+7
View File
@@ -142,6 +142,13 @@ export interface AllUsers {
previous: any;
}
export interface PaginatedMemories {
count: number;
next: string | null;
previous: string | null;
results: Array<Memory>;
}
export interface ProjectResponse {
customInstructions?: string;
customCategories?: string[];
@@ -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 () => {
@@ -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;
}
@@ -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<string, { status: number; body: unknown }>();
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<string, { status: number; body: unknown }>();
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<string, { status: number; body: unknown }>();
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<string, { status: number; body: unknown }>();
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([]);
});
});
@@ -254,7 +254,7 @@ describe("MemoryClient - getAll() entity param rejection", () => {
test("accepts filters with user_id", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
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();
});
});
+10 -20
View File
@@ -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]: