fix: adopt new v3 memory endpoints in Python + TS clients (#4856)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {
|
import {
|
||||||
AllUsers,
|
AllUsers,
|
||||||
|
PaginatedMemories,
|
||||||
ProjectOptions,
|
ProjectOptions,
|
||||||
Memory,
|
Memory,
|
||||||
MemoryHistory,
|
MemoryHistory,
|
||||||
@@ -221,7 +222,7 @@ export default class MemoryClient {
|
|||||||
this._captureEvent("add", [payloadKeys]);
|
this._captureEvent("add", [payloadKeys]);
|
||||||
|
|
||||||
const response = await this._fetchWithErrorHandling(
|
const response = await this._fetchWithErrorHandling(
|
||||||
`${this.host}/v3/memories/`,
|
`${this.host}/v3/memories/add/`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: this.headers,
|
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
|
// Reject top-level entity params - must use filters instead
|
||||||
rejectTopLevelEntityParams(options as Record<string, any>, "getAll");
|
rejectTopLevelEntityParams(options as Record<string, any>, "getAll");
|
||||||
|
|
||||||
@@ -293,12 +294,11 @@ export default class MemoryClient {
|
|||||||
this._captureEvent("get_all", [payloadKeys]);
|
this._captureEvent("get_all", [payloadKeys]);
|
||||||
const { page, pageSize, filters, ...rest } = options ?? {};
|
const { page, pageSize, filters, ...rest } = options ?? {};
|
||||||
const body: Record<string, any> = {
|
const body: Record<string, any> = {
|
||||||
output_format: "v1.1",
|
|
||||||
...camelToSnakeKeys(rest),
|
...camelToSnakeKeys(rest),
|
||||||
...(filters && { filters }),
|
...(filters && { filters }),
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = `${this.host}/v2/memories/`;
|
let url = `${this.host}/v3/memories/`;
|
||||||
if (page && pageSize) {
|
if (page && pageSize) {
|
||||||
url += `?page=${page}&page_size=${pageSize}`;
|
url += `?page=${page}&page_size=${pageSize}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,13 @@ export interface AllUsers {
|
|||||||
previous: any;
|
previous: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PaginatedMemories {
|
||||||
|
count: number;
|
||||||
|
next: string | null;
|
||||||
|
previous: string | null;
|
||||||
|
results: Array<Memory>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProjectResponse {
|
export interface ProjectResponse {
|
||||||
customInstructions?: string;
|
customInstructions?: string;
|
||||||
customCategories?: string[];
|
customCategories?: string[];
|
||||||
|
|||||||
@@ -124,13 +124,16 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
|||||||
filters: { user_id: TEST_USER_ID },
|
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");
|
expect(response).toHaveProperty("results");
|
||||||
const memories = (response as any).results;
|
expect(typeof response.count).toBe("number");
|
||||||
expect(Array.isArray(memories)).toBe(true);
|
expect(Array.isArray(response.results)).toBe(true);
|
||||||
expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length);
|
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.id).toBe("string");
|
||||||
expect(typeof mem.memory).toBe("string");
|
expect(typeof mem.memory).toBe("string");
|
||||||
}
|
}
|
||||||
@@ -143,9 +146,12 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
|||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Paginated response is an object with results array
|
|
||||||
expect(page1).toBeDefined();
|
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()}` },
|
filters: { user_id: `nonexistent-user-${randomUUID()}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
// v1.1 output_format returns { results: [...] }
|
|
||||||
expect(response).toHaveProperty("results");
|
expect(response).toHaveProperty("results");
|
||||||
const memories = (response as any).results;
|
expect(Array.isArray(response.results)).toBe(true);
|
||||||
expect(Array.isArray(memories)).toBe(true);
|
expect(response.results.length).toBe(0);
|
||||||
expect(memories.length).toBe(0);
|
expect(response.count).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("deleteAll for non-existent user does not throw", async () => {
|
test("deleteAll for non-existent user does not throw", async () => {
|
||||||
|
|||||||
@@ -70,10 +70,7 @@ export async function waitForMemories(
|
|||||||
const response = await withRetry(() =>
|
const response = await withRetry(() =>
|
||||||
client.getAll({ filters: { user_id: userId } }),
|
client.getAll({ filters: { user_id: userId } }),
|
||||||
);
|
);
|
||||||
// v1.1 output_format returns { results: [...] }
|
const memories = response.results ?? [];
|
||||||
const memories = Array.isArray(response)
|
|
||||||
? response
|
|
||||||
: ((response as any)?.results ?? []);
|
|
||||||
if (memories.length >= minCount) {
|
if (memories.length >= minCount) {
|
||||||
return memories;
|
return memories;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,33 +21,33 @@ installConsoleSuppression();
|
|||||||
// ─── add() ───────────────────────────────────────────────
|
// ─── add() ───────────────────────────────────────────────
|
||||||
|
|
||||||
describe("MemoryClient - 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 }>();
|
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 mock = setupMockFetch(extra);
|
||||||
|
|
||||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
await client.add([{ role: "user", content: "Hello" }], { userId: "u1" });
|
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 () => {
|
test("includes messages in request body", async () => {
|
||||||
const messages = [{ role: "user" as const, content: "Hello, I am Alex" }];
|
const messages = [{ role: "user" as const, content: "Hello, I am Alex" }];
|
||||||
const extra = new Map<string, { status: number; body: unknown }>();
|
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 mock = setupMockFetch(extra);
|
||||||
|
|
||||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
await client.add(messages, { userId: "u1" });
|
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);
|
expect(getFetchBody(call!).messages).toEqual(messages);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("includes user_id in request body", async () => {
|
test("includes user_id in request body", async () => {
|
||||||
const extra = new Map<string, { status: number; body: unknown }>();
|
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 mock = setupMockFetch(extra);
|
||||||
|
|
||||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
@@ -55,19 +55,19 @@ describe("MemoryClient - add()", () => {
|
|||||||
user_id: "user_1",
|
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");
|
expect(getFetchBody(call!).user_id).toBe("user_1");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sends empty messages array without crashing", async () => {
|
test("sends empty messages array without crashing", async () => {
|
||||||
const extra = new Map<string, { status: number; body: unknown }>();
|
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 mock = setupMockFetch(extra);
|
||||||
|
|
||||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
await client.add([], { userId: "u1" });
|
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([]);
|
expect(getFetchBody(call!).messages).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ describe("MemoryClient - getAll() entity param rejection", () => {
|
|||||||
|
|
||||||
test("accepts filters with user_id", async () => {
|
test("accepts filters with user_id", async () => {
|
||||||
const extra = new Map<string, { status: number; body: unknown }>();
|
const extra = new Map<string, { status: number; body: unknown }>();
|
||||||
extra.set("/v2/memories/", {
|
extra.set("/v3/memories/", {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: { results: [] },
|
body: { results: [] },
|
||||||
});
|
});
|
||||||
@@ -262,6 +262,6 @@ describe("MemoryClient - getAll() entity param rejection", () => {
|
|||||||
|
|
||||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||||
await client.getAll({ filters: { user_id: "u1" } });
|
await client.getAll({ filters: { user_id: "u1" } });
|
||||||
expect(findFetchCall(mock, "/v2/memories/", "POST")).toBeDefined();
|
expect(findFetchCall(mock, "/v3/memories/", "POST")).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-20
@@ -167,7 +167,7 @@ class MemoryClient:
|
|||||||
|
|
||||||
kwargs = self._prepare_params(kwargs)
|
kwargs = self._prepare_params(kwargs)
|
||||||
payload = self._prepare_payload(messages, 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()
|
response.raise_for_status()
|
||||||
if "metadata" in kwargs:
|
if "metadata" in kwargs:
|
||||||
del kwargs["metadata"]
|
del kwargs["metadata"]
|
||||||
@@ -207,7 +207,7 @@ class MemoryClient:
|
|||||||
**kwargs: Optional parameters for filtering (filters, page, page_size).
|
**kwargs: Optional parameters for filtering (filters, page, page_size).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dictionary containing memories in v1.1 format: {"results": [...]}
|
A paginated dict: {"count": int, "next": str | None, "previous": str | None, "results": [...]}
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValidationError: If the input data is invalid.
|
ValidationError: If the input data is invalid.
|
||||||
@@ -233,9 +233,9 @@ class MemoryClient:
|
|||||||
"page": params.pop("page"),
|
"page": params.pop("page"),
|
||||||
"page_size": params.pop("page_size"),
|
"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:
|
else:
|
||||||
response = self.client.post("/v2/memories/", json=params)
|
response = self.client.post("/v3/memories/", json=params)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
if "metadata" in kwargs:
|
if "metadata" in kwargs:
|
||||||
del kwargs["metadata"]
|
del kwargs["metadata"]
|
||||||
@@ -247,12 +247,7 @@ class MemoryClient:
|
|||||||
"sync_type": "sync",
|
"sync_type": "sync",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
result = response.json()
|
return response.json()
|
||||||
|
|
||||||
# Ensure v1.1 format (wrap raw list if needed)
|
|
||||||
if isinstance(result, list):
|
|
||||||
return {"results": result}
|
|
||||||
return result
|
|
||||||
|
|
||||||
@api_error_handler
|
@api_error_handler
|
||||||
def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
||||||
@@ -1095,7 +1090,7 @@ class AsyncMemoryClient:
|
|||||||
|
|
||||||
kwargs = self._prepare_params(kwargs)
|
kwargs = self._prepare_params(kwargs)
|
||||||
payload = self._prepare_payload(messages, 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()
|
response.raise_for_status()
|
||||||
if "metadata" in kwargs:
|
if "metadata" in kwargs:
|
||||||
del kwargs["metadata"]
|
del kwargs["metadata"]
|
||||||
@@ -1119,7 +1114,7 @@ class AsyncMemoryClient:
|
|||||||
**kwargs: Optional parameters for filtering (filters, page, page_size).
|
**kwargs: Optional parameters for filtering (filters, page, page_size).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dictionary containing memories in v1.1 format: {"results": [...]}
|
A paginated dict: {"count": int, "next": str | None, "previous": str | None, "results": [...]}
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValidationError: If the input data is invalid.
|
ValidationError: If the input data is invalid.
|
||||||
@@ -1145,9 +1140,9 @@ class AsyncMemoryClient:
|
|||||||
"page": params.pop("page"),
|
"page": params.pop("page"),
|
||||||
"page_size": params.pop("page_size"),
|
"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:
|
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()
|
response.raise_for_status()
|
||||||
if "metadata" in kwargs:
|
if "metadata" in kwargs:
|
||||||
del kwargs["metadata"]
|
del kwargs["metadata"]
|
||||||
@@ -1159,12 +1154,7 @@ class AsyncMemoryClient:
|
|||||||
"sync_type": "async",
|
"sync_type": "async",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
result = response.json()
|
return response.json()
|
||||||
|
|
||||||
# Ensure v1.1 format (wrap raw list if needed)
|
|
||||||
if isinstance(result, list):
|
|
||||||
return {"results": result}
|
|
||||||
return result
|
|
||||||
|
|
||||||
@api_error_handler
|
@api_error_handler
|
||||||
async def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
async def search(self, query: str, options: Optional[SearchMemoryOptions] = None, **kwargs) -> Dict[str, Any]:
|
||||||
|
|||||||
Reference in New Issue
Block a user