fix(sdk): removing deprecating param from our sdk and docs changes with it (#4740)

This commit is contained in:
Kartik
2026-04-12 00:34:58 +05:30
committed by GitHub
parent 3882af7450
commit e44b46ef2e
138 changed files with 1186 additions and 2840 deletions
+7 -2
View File
@@ -3,14 +3,17 @@ import type * as MemoryTypes from "./mem0.types";
// Re-export all types from mem0.types
export type {
MemoryOptions,
EntityOptions,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
ProjectOptions,
Memory,
MemoryHistory,
MemoryUpdateBody,
ProjectResponse,
PromptUpdatePayload,
SearchOptions,
Webhook,
WebhookCreatePayload,
WebhookUpdatePayload,
@@ -19,6 +22,8 @@ export type {
AllUsers,
User,
FeedbackPayload,
CreateMemoryExportPayload,
GetMemoryExportPayload,
} from "./mem0.types";
// Re-export enums as values (not type-only)
+57 -205
View File
@@ -4,11 +4,13 @@ import {
ProjectOptions,
Memory,
MemoryHistory,
MemoryOptions,
AddMemoryOptions,
SearchMemoryOptions,
GetAllMemoryOptions,
DeleteAllMemoryOptions,
MemoryUpdateBody,
ProjectResponse,
PromptUpdatePayload,
SearchOptions,
Webhook,
WebhookCreatePayload,
WebhookUpdatePayload,
@@ -30,19 +32,13 @@ class APIError extends Error {
interface ClientOptions {
apiKey: string;
host?: string;
organizationName?: string;
projectName?: string;
organizationId?: string;
projectId?: string;
}
export default class MemoryClient {
apiKey: string;
host: string;
organizationName: string | null;
projectName: string | null;
organizationId: string | number | null;
projectId: string | number | null;
private organizationId: string | number | null;
private projectId: string | number | null;
headers: Record<string, string>;
client: any;
telemetryId: string;
@@ -59,35 +55,11 @@ export default class MemoryClient {
}
}
_validateOrgProject(): void {
// Check for organizationName/projectName pair
if (
(this.organizationName === null && this.projectName !== null) ||
(this.organizationName !== null && this.projectName === null)
) {
console.warn(
"Warning: Both organizationName and projectName must be provided together when using either. This will be removed from version 1.0.40. Note that organizationName/projectName are being deprecated in favor of organizationId/projectId.",
);
}
// Check for organizationId/projectId pair
if (
(this.organizationId === null && this.projectId !== null) ||
(this.organizationId !== null && this.projectId === null)
) {
console.warn(
"Warning: Both organizationId and projectId must be provided together when using either. This will be removed from version 1.0.40.",
);
}
}
constructor(options: ClientOptions) {
this.apiKey = options.apiKey;
this.host = options.host || "https://api.mem0.ai";
this.organizationName = options.organizationName || null;
this.projectName = options.projectName || null;
this.organizationId = options.organizationId || null;
this.projectId = options.projectId || null;
this.organizationId = null;
this.projectId = null;
this.headers = {
Authorization: `Token ${this.apiKey}`,
@@ -101,28 +73,19 @@ export default class MemoryClient {
});
this._validateApiKey();
// Initialize with a temporary ID that will be updated
this.telemetryId = "";
// Initialize the client
this._initializeClient();
}
private async _initializeClient() {
try {
// Generate telemetry ID
await this.ping();
if (!this.telemetryId) {
this.telemetryId = generateHash(this.apiKey);
}
this._validateOrgProject();
// Capture initialization event
captureClientEvent("init", this, {
api_version: "v1",
client_type: "MemoryClient",
}).catch((error: any) => {
console.error("Failed to capture event:", error);
@@ -163,13 +126,16 @@ export default class MemoryClient {
return jsonResponse;
}
_preparePayload(messages: Array<Message>, options: MemoryOptions): object {
_preparePayload(
messages: Array<Message>,
options: Record<string, any>,
): object {
const payload: any = {};
payload.messages = messages;
return { ...payload, ...options };
}
_prepareParams(options: MemoryOptions): object {
_prepareParams(options: Record<string, any>): object {
return Object.fromEntries(
Object.entries(options).filter(([_, v]) => v != null),
);
@@ -197,9 +163,8 @@ export default class MemoryClient {
const { org_id, project_id, user_email } = response;
// Only update if values are actually present
if (org_id && !this.organizationId) this.organizationId = org_id;
if (project_id && !this.projectId) this.projectId = project_id;
if (org_id) this.organizationId = org_id;
if (project_id) this.projectId = project_id;
if (user_email) this.telemetryId = user_email;
} catch (error: any) {
// Pass through structured exceptions and APIError
@@ -215,30 +180,11 @@ export default class MemoryClient {
async add(
messages: Array<Message>,
options: MemoryOptions & Record<string, any> = {},
options: AddMemoryOptions & Record<string, any> = {},
): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
if (options.api_version) {
options.version = options.api_version.toString() || "v2";
}
const payload = this._preparePayload(messages, options);
// get payload keys whose value is not null or undefined
const payloadKeys = Object.keys(payload);
this._captureEvent("add", [payloadKeys]);
@@ -276,7 +222,6 @@ export default class MemoryClient {
}
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payload: Record<string, any> = {};
if (text !== undefined) payload.text = text;
if (metadata !== undefined) payload.metadata = metadata;
@@ -307,87 +252,53 @@ export default class MemoryClient {
);
}
async getAll(options?: SearchOptions): Promise<Array<Memory>> {
async getAll(options?: GetAllMemoryOptions): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_all", [payloadKeys]);
const { api_version, page, page_size, ...otherOptions } = options ?? {};
if (this.organizationName != null && this.projectName != null) {
otherOptions.org_name = this.organizationName;
otherOptions.project_name = this.projectName;
}
let appendedParams = "";
let paginated_response = false;
const { page, page_size, ...rest } = options ?? {};
const body: Record<string, any> = {
output_format: "v1.1",
...rest,
};
let url = `${this.host}/v2/memories/`;
if (page && page_size) {
appendedParams += `page=${page}&page_size=${page_size}`;
paginated_response = true;
url += `?page=${page}&page_size=${page_size}`;
}
if (this.organizationId != null && this.projectId != null) {
otherOptions.org_id = this.organizationId;
otherOptions.project_id = this.projectId;
if (otherOptions.org_name) delete otherOptions.org_name;
if (otherOptions.project_name) delete otherOptions.project_name;
}
if (api_version === "v2") {
let url = paginated_response
? `${this.host}/v2/memories/?${appendedParams}`
: `${this.host}/v2/memories/`;
return this._fetchWithErrorHandling(url, {
method: "POST",
headers: this.headers,
body: JSON.stringify(otherOptions),
});
} else {
// @ts-ignore
const params = new URLSearchParams(this._prepareParams(otherOptions));
const url = paginated_response
? `${this.host}/v1/memories/?${params}&${appendedParams}`
: `${this.host}/v1/memories/?${params}`;
return this._fetchWithErrorHandling(url, {
headers: this.headers,
});
}
const response = await this._fetchWithErrorHandling(url, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
});
// Unwrap v1.1 format: { results: [...] } → [...]
return Array.isArray(response) ? response : (response?.results ?? response);
}
async search(
query: string,
options?: SearchOptions & Record<string, any>,
options?: SearchMemoryOptions,
): Promise<Array<Memory>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("search", [payloadKeys]);
const { api_version, ...otherOptions } = options ?? {};
const payload = { query, ...otherOptions };
if (this.organizationName != null && this.projectName != null) {
payload.org_name = this.organizationName;
payload.project_name = this.projectName;
}
const payload: Record<string, any> = {
query,
output_format: "v1.1",
...(options ?? {}),
};
if (this.organizationId != null && this.projectId != null) {
payload.org_id = this.organizationId;
payload.project_id = this.projectId;
if (payload.org_name) delete payload.org_name;
if (payload.project_name) delete payload.project_name;
}
const endpoint =
api_version === "v2" ? "/v2/memories/search/" : "/v1/memories/search/";
const response = await this._fetchWithErrorHandling(
`${this.host}${endpoint}`,
`${this.host}/v2/memories/search/`,
{
method: "POST",
headers: this.headers,
body: JSON.stringify(payload),
},
);
return response;
// Unwrap v1.1 format: { results: [...] } → [...]
return Array.isArray(response) ? response : (response?.results ?? response);
}
async delete(memoryId: string): Promise<{ message: string }> {
@@ -402,23 +313,12 @@ export default class MemoryClient {
);
}
async deleteAll(options: MemoryOptions = {}): Promise<{ message: string }> {
async deleteAll(
options: DeleteAllMemoryOptions = {},
): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("delete_all", [payloadKeys]);
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
// @ts-ignore
const params = new URLSearchParams(this._prepareParams(options));
const response = await this._fetchWithErrorHandling(
@@ -443,31 +343,20 @@ export default class MemoryClient {
return response;
}
async users(): Promise<AllUsers> {
async users(options?: {
page?: number;
page_size?: number;
}): Promise<AllUsers> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
this._captureEvent("users", []);
const options: MemoryOptions = {};
if (this.organizationName != null && this.projectName != null) {
options.org_name = this.organizationName;
options.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
options.org_id = this.organizationId;
options.project_id = this.projectId;
if (options.org_name) delete options.org_name;
if (options.project_name) delete options.project_name;
}
// @ts-ignore
const params = new URLSearchParams(options);
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/entities/?${params}`,
{
headers: this.headers,
},
);
let url = `${this.host}/v1/entities/`;
const params: string[] = [];
if (options?.page) params.push(`page=${options.page}`);
if (options?.page_size) params.push(`page_size=${options.page_size}`);
if (params.length) url += `?${params.join("&")}`;
const response = await this._fetchWithErrorHandling(url, {
headers: this.headers,
});
return response;
}
@@ -502,7 +391,6 @@ export default class MemoryClient {
} = {},
): Promise<{ message: string }> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
let to_delete: Array<{ type: string; name: string }> = [];
const { user_id, agent_id, app_id, run_id } = params;
@@ -527,29 +415,9 @@ export default class MemoryClient {
throw new Error("No entities to delete");
}
const requestOptions: MemoryOptions = {};
if (this.organizationName != null && this.projectName != null) {
requestOptions.org_name = this.organizationName;
requestOptions.project_name = this.projectName;
}
if (this.organizationId != null && this.projectId != null) {
requestOptions.org_id = this.organizationId;
requestOptions.project_id = this.projectId;
if (requestOptions.org_name) delete requestOptions.org_name;
if (requestOptions.project_name) delete requestOptions.project_name;
}
// Delete each entity and handle errors
for (const entity of to_delete) {
try {
await this.client.delete(
`/v2/entities/${entity.type}/${entity.name}/`,
{
params: requestOptions,
},
);
await this.client.delete(`/v2/entities/${entity.type}/${entity.name}/`);
} catch (error: any) {
throw new APIError(
`Failed to delete ${entity.type} ${entity.name}: ${error.message}`,
@@ -558,13 +426,7 @@ export default class MemoryClient {
}
this._captureEvent("delete_users", [
{
user_id: user_id,
agent_id: agent_id,
app_id: app_id,
run_id: run_id,
sync_type: "sync",
},
{ user_id, agent_id, app_id, run_id, sync_type: "sync" },
]);
return {
@@ -612,7 +474,6 @@ export default class MemoryClient {
async getProject(options: ProjectOptions): Promise<ProjectResponse> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
const payloadKeys = Object.keys(options || {});
this._captureEvent("get_project", [payloadKeys]);
const { fields } = options;
@@ -639,7 +500,6 @@ export default class MemoryClient {
prompts: PromptUpdatePayload,
): Promise<Record<string, any>> {
if (this.telemetryId === "") await this.ping();
this._validateOrgProject();
this._captureEvent("update_project", []);
if (!(this.organizationId && this.projectId)) {
throw new Error(
@@ -748,15 +608,10 @@ export default class MemoryClient {
if (this.telemetryId === "") await this.ping();
this._captureEvent("create_memory_export", []);
// Return if missing filters or schema
if (!data.filters || !data.schema) {
throw new Error("Missing filters or schema");
}
// Add Org and Project ID
data.org_id = this.organizationId?.toString() || null;
data.project_id = this.projectId?.toString() || null;
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/`,
{
@@ -779,9 +634,6 @@ export default class MemoryClient {
throw new Error("Missing memory_export_id or filters");
}
data.org_id = this.organizationId?.toString() || "";
data.project_id = this.projectId?.toString() || "";
const response = await this._fetchWithErrorHandling(
`${this.host}/v1/exports/get/`,
{
+63 -76
View File
@@ -1,59 +1,70 @@
interface Common {
project_id?: string | null;
org_id?: string | null;
}
export interface MemoryOptions {
api_version?: API_VERSION | string;
version?: API_VERSION | string;
// ─── Entity Options (for add/delete — top-level identity) ───
export interface EntityOptions {
user_id?: string;
agent_id?: string;
app_id?: string;
run_id?: string;
}
// ─── Per-Method Options ─────────────────────────────────────
export interface AddMemoryOptions extends EntityOptions {
metadata?: Record<string, any>;
filters?: Record<string, any>;
org_name?: string | null; // Deprecated
project_name?: string | null; // Deprecated
org_id?: string | number | null;
project_id?: string | number | null;
infer?: boolean;
page?: number;
page_size?: number;
includes?: string;
excludes?: string;
enable_graph?: boolean;
start_date?: string;
end_date?: string;
custom_categories?: custom_categories[];
custom_instructions?: string;
timestamp?: number;
output_format?: string | OutputFormat;
async_mode?: boolean;
filter_memories?: boolean;
immutable?: boolean;
structured_data_schema?: Record<string, any>;
enable_graph?: boolean;
}
export interface SearchMemoryOptions {
filters?: Record<string, any>;
metadata?: Record<string, any>;
top_k?: number;
threshold?: number;
rerank?: boolean;
fields?: string[];
categories?: string[];
enable_graph?: boolean;
}
export interface GetAllMemoryOptions {
filters?: Record<string, any>;
page?: number;
page_size?: number;
start_date?: string;
end_date?: string;
categories?: string[];
enable_graph?: boolean;
}
export interface DeleteAllMemoryOptions extends EntityOptions {}
// ─── Project Options ────────────────────────────────────────
export interface ProjectOptions {
fields?: string[];
}
export enum OutputFormat {
V1 = "v1.0",
V1_1 = "v1.1",
}
export enum API_VERSION {
V1 = "v1",
V2 = "v2",
export interface PromptUpdatePayload {
custom_instructions?: string;
custom_categories?: custom_categories[];
retrieval_criteria?: any[];
enable_graph?: boolean;
version?: string;
memory_depth?: string | null;
usecase_setting?: string | number;
multilingual?: boolean;
[key: string]: any;
}
// ─── Enums ──────────────────────────────────────────────────
export enum Feedback {
POSITIVE = "POSITIVE",
NEGATIVE = "NEGATIVE",
VERY_NEGATIVE = "VERY_NEGATIVE",
}
// ─── Message Types ──────────────────────────────────────────
export interface MultiModalMessages {
type: "image_url";
image_url: {
@@ -68,30 +79,9 @@ export interface Messages {
export interface Message extends Messages {}
export interface MemoryHistory {
id: string;
memory_id: string;
input: Array<Messages>;
old_memory: string | null;
new_memory: string | null;
user_id: string;
categories: Array<string>;
event: Event | string;
created_at: Date;
updated_at: Date;
}
export interface SearchOptions extends MemoryOptions {
api_version?: API_VERSION | string;
limit?: number;
enable_graph?: boolean;
threshold?: number;
top_k?: number;
only_metadata_based_search?: boolean;
keyword_search?: boolean;
fields?: string[];
categories?: string[];
rerank?: boolean;
// ─── Response Types (reflect API shapes, unchanged) ─────────
export interface MemoryData {
memory: string;
}
enum Event {
@@ -101,10 +91,6 @@ enum Event {
NOOP = "NOOP",
}
export interface MemoryData {
memory: string;
}
export interface Memory {
id: string;
messages?: Array<Messages>;
@@ -125,6 +111,19 @@ export interface Memory {
run_id?: string | null;
}
export interface MemoryHistory {
id: string;
memory_id: string;
input: Array<Messages>;
old_memory: string | null;
new_memory: string | null;
user_id: string;
categories: Array<string>;
event: Event | string;
created_at: Date;
updated_at: Date;
}
export interface MemoryUpdateBody {
memoryId: string;
text: string;
@@ -157,20 +156,7 @@ interface custom_categories {
[key: string]: any;
}
export interface PromptUpdatePayload {
custom_instructions?: string;
custom_categories?: custom_categories[];
retrieval_criteria?: any[];
enable_graph?: boolean;
version?: string;
inclusion_prompt?: string;
exclusion_prompt?: string;
memory_depth?: string | null;
usecase_setting?: string | number;
multilingual?: boolean;
[key: string]: any;
}
// ─── Webhook Types ──────────────────────────────────────────
export enum WebhookEvent {
MEMORY_ADDED = "memory_add",
MEMORY_UPDATED = "memory_update",
@@ -202,19 +188,20 @@ export interface WebhookUpdatePayload {
eventTypes?: WebhookEvent[];
}
// ─── Feedback & Export Types ────────────────────────────────
export interface FeedbackPayload {
memory_id: string;
feedback?: Feedback | null;
feedback_reason?: string | null;
}
export interface CreateMemoryExportPayload extends Common {
export interface CreateMemoryExportPayload {
schema: Record<string, any>;
filters: Record<string, any>;
export_instructions?: string;
}
export interface GetMemoryExportPayload extends Common {
export interface GetMemoryExportPayload {
filters?: Record<string, any>;
memory_export_id?: string;
}
@@ -122,7 +122,9 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
// ─── Get all ──────────────────────────────────────────────
describe("get all memories", () => {
test("returns all memories for test user", async () => {
const memories = await client.getAll({ user_id: TEST_USER_ID });
const memories = await client.getAll({
filters: { user_id: TEST_USER_ID },
});
expect(Array.isArray(memories)).toBe(true);
expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length);
@@ -135,7 +137,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("returns paginated results with page and page_size", async () => {
const page1 = await client.getAll({
user_id: TEST_USER_ID,
filters: { user_id: TEST_USER_ID },
page: 1,
page_size: 1,
});
@@ -200,7 +202,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
test("getAll for non-existent user returns empty array", async () => {
const memories = await client.getAll({
user_id: `nonexistent-user-${randomUUID()}`,
filters: { user_id: `nonexistent-user-${randomUUID()}` },
});
expect(Array.isArray(memories)).toBe(true);
@@ -63,7 +63,9 @@ export async function waitForMemories(
maxRetries = 4,
): Promise<Memory[]> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const memories = await withRetry(() => client.getAll({ user_id: userId }));
const memories = await withRetry(() =>
client.getAll({ filters: { user_id: userId } }),
);
if (Array.isArray(memories) && memories.length >= minCount) {
return memories;
}
@@ -31,10 +31,10 @@ describeIntegration("MemoryClient Integration — Initialization", () => {
afterAll(() => cleanup());
test("client pings successfully and resolves org/project", async () => {
test("client pings successfully", async () => {
await client.ping();
expect(client.organizationId).toBeTruthy();
expect(client.projectId).toBeTruthy();
// org/project are now resolved internally from the API key
expect(client.telemetryId).toBeTruthy();
});
test("get with invalid ID throws ValidationError", async () => {
@@ -1,7 +1,7 @@
/**
* Integration tests: Search and history operations.
*
* Tests search v1, search v2, and memory history against the real API.
* Tests search, filtered search, and memory history against the real API.
*
* Run: MEM0_API_KEY=your-key npx jest search.test.ts --forceExit
*/
@@ -36,14 +36,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
cleanup();
});
// ─── Search v1 ────────────────────────────────────────────
describe("search v1", () => {
// ─── Search ─────────────────────────────────────────────
describe("search", () => {
test("searches memories by user_id and returns results with scores", async () => {
// Search index may lag behind listing index — poll until ready
const results = await waitForSearchResults(
client,
"What is my favorite color?",
{ user_id: TEST_USER_ID },
{ filters: { user_id: TEST_USER_ID } },
);
expect(Array.isArray(results)).toBe(true);
@@ -57,15 +57,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
});
});
// ─── Search v2 ────────────────────────────────────────────
describe("search v2", () => {
// ─── Search with filters ─────────────────────────────────
describe("search with filters", () => {
test("searches with OR filters and returns results", async () => {
const results = await waitForSearchResults(
client,
"What do you know about me?",
{
filters: { OR: [{ user_id: TEST_USER_ID }] },
api_version: "v2",
},
);
@@ -110,19 +109,19 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
describe("edge cases", () => {
test("search for non-existent user returns empty results", async () => {
const results = await client.search("anything", {
user_id: `nonexistent-user-${randomUUID()}`,
filters: { user_id: `nonexistent-user-${randomUUID()}` },
});
expect(Array.isArray(results)).toBe(true);
expect(results.length).toBe(0);
});
test("search with limit param does not throw", async () => {
test("search with top_k param does not throw", async () => {
const results = await client.search(
"Tell me about integration test user",
{
user_id: TEST_USER_ID,
limit: 1,
filters: { user_id: TEST_USER_ID },
top_k: 1,
},
);
@@ -1,15 +1,13 @@
/**
* MemoryClient unit tests — add, get, getAll, update, delete, deleteAll, history.
* MemoryClient unit tests — add, get, update, delete, deleteAll, history.
* Tests verify request construction, not mock response echo.
*/
import { MemoryClient } from "../mem0";
import type { Memory, MemoryHistory } from "../mem0.types";
import type { MemoryHistory } from "../mem0.types";
import {
createMockMemory,
createMockMemoryHistory,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import {
setupMockFetch,
@@ -61,40 +59,6 @@ describe("MemoryClient - add()", () => {
expect(getFetchBody(call!).user_id).toBe("user_1");
});
test("attaches org_id from constructor to payload", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/", "POST");
const body = getFetchBody(call!);
expect(body.org_id).toBe(TEST_ORG_ID);
});
test("attaches project_id from constructor to payload", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [createMockMemory()] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.add([{ role: "user", content: "test" }], { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/", "POST");
const body = getFetchBody(call!);
expect(body.project_id).toBe(TEST_PROJECT_ID);
});
test("sends empty messages array without crashing", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] });
@@ -142,67 +106,6 @@ describe("MemoryClient - get()", () => {
});
});
// ─── getAll() ────────────────────────────────────────────
describe("MemoryClient - getAll()", () => {
test("uses v2 POST endpoint when api_version=v2", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({ user_id: "u1", api_version: "v2" });
expect(findFetchCall(mock, "/v2/memories/", "POST")).toBeDefined();
});
test("uses v1 GET endpoint by default with user_id as query param", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({ user_id: "u1" });
const call = mock.mock.calls.find(
(c: [string, RequestInit]) =>
c[0].includes("/v1/memories/?") && !c[1]?.method,
);
expect(call).toBeDefined();
expect(call![0]).toContain("user_id=u1");
});
test("appends page and page_size to URL as query params", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getAll({
user_id: "u1",
api_version: "v2",
page: 2,
page_size: 25,
});
const call = mock.mock.calls.find((c: [string, RequestInit]) =>
c[0].includes("page="),
);
expect(call![0]).toContain("page=2");
expect(call![0]).toContain("page_size=25");
});
test("does not crash when called without options", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result: Memory[] = await client.getAll();
expect(Array.isArray(result)).toBe(true);
});
});
// ─── update() ────────────────────────────────────────────
describe("MemoryClient - update()", () => {
@@ -7,13 +7,7 @@ import {
ValidationError,
MemoryError,
} from "../../common/exceptions";
import {
createMockFetch,
TEST_API_KEY,
TEST_HOST,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { createMockFetch, TEST_API_KEY, TEST_HOST } from "./helpers";
import {
setupMockFetch,
installConsoleSuppression,
@@ -55,24 +49,6 @@ describe("MemoryClient - Initialization", () => {
expect(client.host).toBe(TEST_HOST);
});
test("sets organizationId from constructor", () => {
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
expect(client.organizationId).toBe(TEST_ORG_ID);
});
test("sets projectId from constructor", () => {
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
expect(client.projectId).toBe(TEST_PROJECT_ID);
});
test("sets Authorization header with Token prefix", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
expect(client.headers["Authorization"]).toBe(`Token ${TEST_API_KEY}`);
@@ -87,20 +63,6 @@ describe("MemoryClient - Initialization", () => {
// ─── Ping ────────────────────────────────────────────────
describe("MemoryClient - ping()", () => {
test("sets organizationId from ping response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
expect(client.organizationId).toBe(TEST_ORG_ID);
});
test("sets projectId from ping response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
expect(client.projectId).toBe(TEST_PROJECT_ID);
});
test("sets telemetryId from user_email in response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
@@ -108,28 +70,6 @@ describe("MemoryClient - ping()", () => {
expect(client.telemetryId).toBe("test@example.com");
});
test("preserves constructor organizationId over ping response", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: "my_org",
projectId: "my_proj",
});
await client.ping();
expect(client.organizationId).toBe("my_org");
});
test("preserves constructor projectId over ping response", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: "my_org",
projectId: "my_proj",
});
await client.ping();
expect(client.projectId).toBe("my_proj");
});
test("throws AuthenticationError on 401 response", async () => {
const { AuthenticationError } = await import("../../common/exceptions");
const responses = new Map<string, { status: number; body: unknown }>();
@@ -4,12 +4,7 @@
*/
import { MemoryClient } from "../mem0";
import { Feedback } from "../mem0.types";
import {
createMockFetch,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { createMockFetch, TEST_API_KEY } from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -22,7 +17,7 @@ installConsoleSuppression();
// ─── getProject() ───────────────────────────────────────
describe("MemoryClient - getProject()", () => {
test("throws when organizationId and projectId not set", async () => {
test("throws when organizationId and projectId not set (ping returns no org)", async () => {
const responses = new Map<string, { status: number; body: unknown }>();
responses.set("/v1/ping/", { status: 200, body: { status: "ok" } });
global.fetch = createMockFetch(responses);
@@ -47,11 +42,9 @@ describe("MemoryClient - getProject()", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
// org/project come from ping mock response
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
await client.getProject({ fields: ["custom_instructions"] });
const call = mock.mock.calls.find(
@@ -74,11 +67,8 @@ describe("MemoryClient - updateProject()", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
await client.updateProject({
custom_instructions: "Updated instructions",
});
@@ -95,11 +85,8 @@ describe("MemoryClient - updateProject()", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
await client.updateProject({
custom_instructions: "Updated instructions",
});
@@ -161,11 +148,7 @@ describe("MemoryClient - feedback()", () => {
describe("MemoryClient - Memory Exports", () => {
test("createMemoryExport throws when missing filters or schema", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(
client.createMemoryExport({
filters: null as never,
@@ -182,11 +165,7 @@ describe("MemoryClient - Memory Exports", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.createMemoryExport({
schema: { fields: ["memory", "user_id"] },
filters: { user_id: "u1" },
@@ -195,37 +174,9 @@ describe("MemoryClient - Memory Exports", () => {
expect(findFetchCall(mock, "/v1/exports/", "POST")).toBeDefined();
});
test("createMemoryExport attaches org_id and project_id to body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/exports/", {
status: 200,
body: { message: "Created", id: "exp_1" },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.createMemoryExport({
schema: { fields: ["memory"] },
filters: { user_id: "u1" },
});
const call = findFetchCall(mock, "/v1/exports/", "POST");
const body = getFetchBody(call!);
expect(body.org_id).toBe(TEST_ORG_ID);
expect(body.project_id).toBe(TEST_PROJECT_ID);
});
test("getMemoryExport throws when missing both id and filters", async () => {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.getMemoryExport({} as never)).rejects.toThrow(
"Missing memory_export_id or filters",
);
@@ -239,11 +190,7 @@ describe("MemoryClient - Memory Exports", () => {
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.getMemoryExport({ memory_export_id: "exp_123" });
expect(findFetchCall(mock, "/v1/exports/get/", "POST")).toBeDefined();
@@ -1,5 +1,5 @@
/**
* MemoryClient unit tests — search (v1/v2 routing, filters).
* MemoryClient unit tests — search (v2 default, filters).
* Tests verify request construction, not mock response echo.
*/
import { MemoryClient } from "../mem0";
@@ -15,60 +15,52 @@ import {
installConsoleSuppression();
describe("MemoryClient - search()", () => {
test("sends POST to /v1/memories/search/ by default", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", { user_id: "u1" });
expect(findFetchCall(mock, "/v1/memories/search/", "POST")).toBeDefined();
});
test("includes query in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
expect(getFetchBody(call!).query).toBe("What is my name?");
});
test("includes user_id in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("test", { user_id: "u1" });
const call = findFetchCall(mock, "/v1/memories/search/", "POST");
expect(getFetchBody(call!).user_id).toBe("u1");
});
test("uses /v2/memories/search/ when api_version=v2", async () => {
test("sends POST to /v2/memories/search/ by default", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("test", { user_id: "u1", api_version: "v2" });
await client.search("What is my name?", {
filters: { user_id: "u1" },
});
expect(findFetchCall(mock, "/v2/memories/search/", "POST")).toBeDefined();
});
test("passes filters through to the v2 API body", async () => {
test("includes query in request body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("What is my name?", {
filters: { user_id: "u1" },
});
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
expect(getFetchBody(call!).query).toBe("What is my name?");
});
test("passes filters through to the API body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("test", { filters: { user_id: "u1" } });
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
expect(getFetchBody(call!).filters).toEqual({ user_id: "u1" });
});
test("passes complex OR filters through to the API body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v2/memories/search/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.search("query", {
api_version: "v2",
filters: { OR: [{ user_id: "u1" }, { agent_id: "a1" }] },
});
@@ -81,7 +73,7 @@ describe("MemoryClient - search()", () => {
test("does not crash when called without options", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
extra.set("/v2/memories/search/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
@@ -91,12 +83,12 @@ describe("MemoryClient - search()", () => {
test("handles empty results array", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/search/", { status: 200, body: [] });
extra.set("/v2/memories/search/", { status: 200, body: [] });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
const result: Memory[] = await client.search("nonexistent query", {
user_id: "u1",
filters: { user_id: "u1" },
});
expect(result).toHaveLength(0);
});
@@ -1,15 +1,9 @@
/**
* MemoryClient unit tests — users, deleteUser, deleteUsers.
* MemoryClient unit tests — users, deleteUser.
* Tests verify entity type routing and request construction.
*/
import { MemoryClient } from "../mem0";
import {
createMockUser,
createMockAllUsers,
TEST_API_KEY,
TEST_ORG_ID,
TEST_PROJECT_ID,
} from "./helpers";
import { createMockUser, createMockAllUsers, TEST_API_KEY } from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -40,111 +34,6 @@ describe("MemoryClient - users()", () => {
});
});
// ─── deleteUsers() ──────────────────────────────────────
describe("MemoryClient - deleteUsers()", () => {
function createClientWithMockedAxios() {
setupMockFetch();
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const axiosDeleteMock = jest
.fn()
.mockResolvedValue({ data: { message: "Deleted" } });
client.client.delete = axiosDeleteMock;
return { client, axiosDeleteMock };
}
test("routes user_id to DELETE /v2/entities/user/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ user_id: "u1" });
expect(axiosDeleteMock).toHaveBeenCalledWith("/v2/entities/user/u1/", {
params: expect.objectContaining({
org_id: TEST_ORG_ID,
project_id: TEST_PROJECT_ID,
}),
});
});
test("routes agent_id to DELETE /v2/entities/agent/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ agent_id: "agent_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/agent/agent_1/",
expect.any(Object),
);
});
test("routes app_id to DELETE /v2/entities/app/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ app_id: "app_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/app/app_1/",
expect.any(Object),
);
});
test("routes run_id to DELETE /v2/entities/run/:name/", async () => {
const { client, axiosDeleteMock } = createClientWithMockedAxios();
await client.deleteUsers({ run_id: "run_1" });
expect(axiosDeleteMock).toHaveBeenCalledWith(
"/v2/entities/run/run_1/",
expect.any(Object),
);
});
test("returns 'Entity deleted successfully.' for single entity", async () => {
const { client } = createClientWithMockedAxios();
const result = await client.deleteUsers({ user_id: "u1" });
expect(result.message).toBe("Entity deleted successfully.");
});
test("returns 'All users, agents, apps and runs deleted.' when no params given", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/entities/", {
status: 200,
body: createMockAllUsers([createMockUser({ name: "u1", type: "user" })]),
});
setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
client.client.delete = jest
.fn()
.mockResolvedValue({ data: { message: "Deleted" } });
const result = await client.deleteUsers();
expect(result.message).toBe("All users, agents, apps and runs deleted.");
});
test("throws when no entities exist to delete", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/entities/", {
status: 200,
body: createMockAllUsers([]),
});
setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
client.client.delete = jest.fn();
await expect(client.deleteUsers()).rejects.toThrow("No entities to delete");
});
});
// ─── deleteUser() (deprecated) ──────────────────────────
describe("MemoryClient - deleteUser() (deprecated)", () => {
@@ -5,7 +5,7 @@
*/
import { MemoryClient } from "../mem0";
import { WebhookEvent } from "../mem0.types";
import { TEST_API_KEY, TEST_ORG_ID, TEST_PROJECT_ID } from "./helpers";
import { TEST_API_KEY } from "./helpers";
import {
setupMockFetch,
findFetchCall,
@@ -23,8 +23,6 @@ function webhookMock(extra?: Map<string, { status: number; body: unknown }>) {
function createClient() {
return new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
}
@@ -100,14 +98,6 @@ describe("MemoryClient - createWebhook", () => {
expect(body.eventTypes).toBeUndefined();
});
test("body does not contain projectId", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.projectId).toBeUndefined();
});
test("body does not contain webhookId", async () => {
const mock = await callCreate();
const body = getFetchBody(
@@ -173,22 +163,6 @@ describe("MemoryClient - updateWebhook", () => {
expect(body.eventTypes).toBeUndefined();
});
test("body does not contain project_id", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.project_id).toBeUndefined();
});
test("body does not contain projectId", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.projectId).toBeUndefined();
});
test("body does not contain webhookId", async () => {
const mock = await callUpdate();
const body = getFetchBody(