fix(sdk): removing the enable graph flag and switching from snake case to camel case for client ts sdk (#4776)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
e44b46ef2e
commit
9d6b79a14e
+38
-36
@@ -20,6 +20,7 @@ import {
|
||||
GetMemoryExportPayload,
|
||||
} from "./mem0.types";
|
||||
import { captureClientEvent, generateHash } from "./telemetry";
|
||||
import { camelToSnake, camelToSnakeKeys, snakeToCamelKeys } from "./utils";
|
||||
import { createExceptionFromResponse, MemoryError } from "../common/exceptions";
|
||||
|
||||
class APIError extends Error {
|
||||
@@ -123,7 +124,7 @@ export default class MemoryClient {
|
||||
throw createExceptionFromResponse(response.status, errorData);
|
||||
}
|
||||
const jsonResponse = await response.json();
|
||||
return jsonResponse;
|
||||
return snakeToCamelKeys(jsonResponse);
|
||||
}
|
||||
|
||||
_preparePayload(
|
||||
@@ -132,7 +133,7 @@ export default class MemoryClient {
|
||||
): object {
|
||||
const payload: any = {};
|
||||
payload.messages = messages;
|
||||
return { ...payload, ...options };
|
||||
return camelToSnakeKeys({ ...payload, ...options });
|
||||
}
|
||||
|
||||
_prepareParams(options: Record<string, any>): object {
|
||||
@@ -161,11 +162,11 @@ export default class MemoryClient {
|
||||
throw new APIError(response.message || "API Key is invalid");
|
||||
}
|
||||
|
||||
const { org_id, project_id, user_email } = response;
|
||||
const { orgId, projectId, userEmail } = response;
|
||||
|
||||
if (org_id) this.organizationId = org_id;
|
||||
if (project_id) this.projectId = project_id;
|
||||
if (user_email) this.telemetryId = user_email;
|
||||
if (orgId) this.organizationId = orgId;
|
||||
if (projectId) this.projectId = projectId;
|
||||
if (userEmail) this.telemetryId = userEmail;
|
||||
} catch (error: any) {
|
||||
// Pass through structured exceptions and APIError
|
||||
if (error instanceof MemoryError || error instanceof APIError) {
|
||||
@@ -256,15 +257,15 @@ export default class MemoryClient {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("get_all", [payloadKeys]);
|
||||
const { page, page_size, ...rest } = options ?? {};
|
||||
const { page, pageSize, ...rest } = options ?? {};
|
||||
const body: Record<string, any> = {
|
||||
output_format: "v1.1",
|
||||
...rest,
|
||||
...camelToSnakeKeys(rest),
|
||||
};
|
||||
|
||||
let url = `${this.host}/v2/memories/`;
|
||||
if (page && page_size) {
|
||||
url += `?page=${page}&page_size=${page_size}`;
|
||||
if (page && pageSize) {
|
||||
url += `?page=${page}&page_size=${pageSize}`;
|
||||
}
|
||||
|
||||
const response = await this._fetchWithErrorHandling(url, {
|
||||
@@ -286,7 +287,7 @@ export default class MemoryClient {
|
||||
const payload: Record<string, any> = {
|
||||
query,
|
||||
output_format: "v1.1",
|
||||
...(options ?? {}),
|
||||
...camelToSnakeKeys(options ?? {}),
|
||||
};
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
@@ -319,8 +320,9 @@ export default class MemoryClient {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
const payloadKeys = Object.keys(options || {});
|
||||
this._captureEvent("delete_all", [payloadKeys]);
|
||||
const snakeOptions = camelToSnakeKeys(this._prepareParams(options));
|
||||
// @ts-ignore
|
||||
const params = new URLSearchParams(this._prepareParams(options));
|
||||
const params = new URLSearchParams(snakeOptions);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/memories/?${params}`,
|
||||
{
|
||||
@@ -345,14 +347,14 @@ export default class MemoryClient {
|
||||
|
||||
async users(options?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<AllUsers> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._captureEvent("users", []);
|
||||
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 (options?.pageSize) params.push(`page_size=${options.pageSize}`);
|
||||
if (params.length) url += `?${params.join("&")}`;
|
||||
const response = await this._fetchWithErrorHandling(url, {
|
||||
headers: this.headers,
|
||||
@@ -384,25 +386,25 @@ export default class MemoryClient {
|
||||
|
||||
async deleteUsers(
|
||||
params: {
|
||||
user_id?: string;
|
||||
agent_id?: string;
|
||||
app_id?: string;
|
||||
run_id?: string;
|
||||
userId?: string;
|
||||
agentId?: string;
|
||||
appId?: string;
|
||||
runId?: string;
|
||||
} = {},
|
||||
): Promise<{ message: string }> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
|
||||
let to_delete: Array<{ type: string; name: string }> = [];
|
||||
const { user_id, agent_id, app_id, run_id } = params;
|
||||
const { userId, agentId, appId, runId } = params;
|
||||
|
||||
if (user_id) {
|
||||
to_delete = [{ type: "user", name: user_id }];
|
||||
} else if (agent_id) {
|
||||
to_delete = [{ type: "agent", name: agent_id }];
|
||||
} else if (app_id) {
|
||||
to_delete = [{ type: "app", name: app_id }];
|
||||
} else if (run_id) {
|
||||
to_delete = [{ type: "run", name: run_id }];
|
||||
if (userId) {
|
||||
to_delete = [{ type: "user", name: userId }];
|
||||
} else if (agentId) {
|
||||
to_delete = [{ type: "agent", name: agentId }];
|
||||
} else if (appId) {
|
||||
to_delete = [{ type: "app", name: appId }];
|
||||
} else if (runId) {
|
||||
to_delete = [{ type: "run", name: runId }];
|
||||
} else {
|
||||
const entities = await this.users();
|
||||
to_delete = entities.results.map((entity) => ({
|
||||
@@ -426,12 +428,12 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
this._captureEvent("delete_users", [
|
||||
{ user_id, agent_id, app_id, run_id, sync_type: "sync" },
|
||||
{ userId, agentId, appId, runId, sync_type: "sync" },
|
||||
]);
|
||||
|
||||
return {
|
||||
message:
|
||||
user_id || agent_id || app_id || run_id
|
||||
userId || agentId || appId || runId
|
||||
? "Entity deleted successfully."
|
||||
: "All users, agents, apps and runs deleted.",
|
||||
};
|
||||
@@ -485,7 +487,7 @@ export default class MemoryClient {
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
fields?.forEach((field) => params.append("fields", field));
|
||||
fields?.forEach((field) => params.append("fields", camelToSnake(field)));
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/api/v1/orgs/organizations/${this.organizationId}/projects/${this.projectId}/?${params.toString()}`,
|
||||
@@ -512,7 +514,7 @@ export default class MemoryClient {
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(prompts),
|
||||
body: JSON.stringify(camelToSnakeKeys(prompts)),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@@ -596,7 +598,7 @@ export default class MemoryClient {
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(camelToSnakeKeys(data)),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@@ -617,7 +619,7 @@ export default class MemoryClient {
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(camelToSnakeKeys(data)),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -630,8 +632,8 @@ export default class MemoryClient {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._captureEvent("get_memory_export", []);
|
||||
|
||||
if (!data.memory_export_id && !data.filters) {
|
||||
throw new Error("Missing memory_export_id or filters");
|
||||
if (!data.memoryExportId && !data.filters) {
|
||||
throw new Error("Missing memoryExportId or filters");
|
||||
}
|
||||
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
@@ -639,7 +641,7 @@ export default class MemoryClient {
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(camelToSnakeKeys(data)),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
|
||||
@@ -1,41 +1,38 @@
|
||||
// ─── Entity Options (for add/delete — top-level identity) ───
|
||||
export interface EntityOptions {
|
||||
user_id?: string;
|
||||
agent_id?: string;
|
||||
app_id?: string;
|
||||
run_id?: string;
|
||||
userId?: string;
|
||||
agentId?: string;
|
||||
appId?: string;
|
||||
runId?: string;
|
||||
}
|
||||
|
||||
// ─── Per-Method Options ─────────────────────────────────────
|
||||
export interface AddMemoryOptions extends EntityOptions {
|
||||
metadata?: Record<string, any>;
|
||||
infer?: boolean;
|
||||
custom_categories?: custom_categories[];
|
||||
custom_instructions?: string;
|
||||
customCategories?: custom_categories[];
|
||||
customInstructions?: string;
|
||||
timestamp?: number;
|
||||
structured_data_schema?: Record<string, any>;
|
||||
enable_graph?: boolean;
|
||||
structuredDataSchema?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface SearchMemoryOptions {
|
||||
filters?: Record<string, any>;
|
||||
metadata?: Record<string, any>;
|
||||
top_k?: number;
|
||||
topK?: 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;
|
||||
pageSize?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
categories?: string[];
|
||||
enable_graph?: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteAllMemoryOptions extends EntityOptions {}
|
||||
@@ -46,13 +43,12 @@ export interface ProjectOptions {
|
||||
}
|
||||
|
||||
export interface PromptUpdatePayload {
|
||||
custom_instructions?: string;
|
||||
custom_categories?: custom_categories[];
|
||||
retrieval_criteria?: any[];
|
||||
enable_graph?: boolean;
|
||||
customInstructions?: string;
|
||||
customCategories?: custom_categories[];
|
||||
retrievalCriteria?: any[];
|
||||
version?: string;
|
||||
memory_depth?: string | null;
|
||||
usecase_setting?: string | number;
|
||||
memoryDepth?: string | null;
|
||||
usecaseSetting?: string | number;
|
||||
multilingual?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
@@ -79,7 +75,7 @@ export interface Messages {
|
||||
|
||||
export interface Message extends Messages {}
|
||||
|
||||
// ─── Response Types (reflect API shapes, unchanged) ─────────
|
||||
// ─── Response Types (camelCase — converted from API snake_case) ─────
|
||||
export interface MemoryData {
|
||||
memory: string;
|
||||
}
|
||||
@@ -97,31 +93,31 @@ export interface Memory {
|
||||
event?: Event | string;
|
||||
data?: MemoryData | null;
|
||||
memory?: string;
|
||||
user_id?: string;
|
||||
userId?: string;
|
||||
hash?: string;
|
||||
categories?: Array<string>;
|
||||
created_at?: Date;
|
||||
updated_at?: Date;
|
||||
memory_type?: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
memoryType?: string;
|
||||
score?: number;
|
||||
metadata?: any | null;
|
||||
owner?: string | null;
|
||||
agent_id?: string | null;
|
||||
app_id?: string | null;
|
||||
run_id?: string | null;
|
||||
agentId?: string | null;
|
||||
appId?: string | null;
|
||||
runId?: string | null;
|
||||
}
|
||||
|
||||
export interface MemoryHistory {
|
||||
id: string;
|
||||
memory_id: string;
|
||||
memoryId: string;
|
||||
input: Array<Messages>;
|
||||
old_memory: string | null;
|
||||
new_memory: string | null;
|
||||
user_id: string;
|
||||
oldMemory: string | null;
|
||||
newMemory: string | null;
|
||||
userId: string;
|
||||
categories: Array<string>;
|
||||
event: Event | string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface MemoryUpdateBody {
|
||||
@@ -132,9 +128,9 @@ export interface MemoryUpdateBody {
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
total_memories: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
totalMemories: number;
|
||||
owner: string;
|
||||
type: string;
|
||||
}
|
||||
@@ -147,8 +143,8 @@ export interface AllUsers {
|
||||
}
|
||||
|
||||
export interface ProjectResponse {
|
||||
custom_instructions?: string;
|
||||
custom_categories?: string[];
|
||||
customInstructions?: string;
|
||||
customCategories?: string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -165,14 +161,14 @@ export enum WebhookEvent {
|
||||
}
|
||||
|
||||
export interface Webhook {
|
||||
webhook_id?: string;
|
||||
webhookId?: string;
|
||||
name: string;
|
||||
url: string;
|
||||
project?: string;
|
||||
created_at?: Date;
|
||||
updated_at?: Date;
|
||||
is_active?: boolean;
|
||||
event_types?: WebhookEvent[];
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
isActive?: boolean;
|
||||
eventTypes?: WebhookEvent[];
|
||||
}
|
||||
|
||||
export interface WebhookCreatePayload {
|
||||
@@ -190,18 +186,18 @@ export interface WebhookUpdatePayload {
|
||||
|
||||
// ─── Feedback & Export Types ────────────────────────────────
|
||||
export interface FeedbackPayload {
|
||||
memory_id: string;
|
||||
memoryId: string;
|
||||
feedback?: Feedback | null;
|
||||
feedback_reason?: string | null;
|
||||
feedbackReason?: string | null;
|
||||
}
|
||||
|
||||
export interface CreateMemoryExportPayload {
|
||||
schema: Record<string, any>;
|
||||
filters: Record<string, any>;
|
||||
export_instructions?: string;
|
||||
exportInstructions?: string;
|
||||
}
|
||||
|
||||
export interface GetMemoryExportPayload {
|
||||
filters?: Record<string, any>;
|
||||
memory_export_id?: string;
|
||||
memoryExportId?: string;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = await client.add(messages, { user_id: TEST_USER_ID });
|
||||
const result = await client.add(messages, { userId: TEST_USER_ID });
|
||||
|
||||
// API processes memories asynchronously — returns PENDING
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
@@ -60,7 +60,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
// Validate response shape
|
||||
for (const item of result) {
|
||||
expect(item).toHaveProperty("status");
|
||||
expect(item).toHaveProperty("event_id");
|
||||
expect(item).toHaveProperty("eventId");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const result = await client.add(messages, { user_id: TEST_USER_ID });
|
||||
const result = await client.add(messages, { userId: TEST_USER_ID });
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -103,17 +103,17 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
expect(memory.id).toBe(memoryId);
|
||||
expect(typeof memory.memory).toBe("string");
|
||||
expect(memory.memory!.length).toBeGreaterThan(0);
|
||||
expect(typeof memory.user_id).toBe("string");
|
||||
expect(typeof memory.userId).toBe("string");
|
||||
expect(
|
||||
memory.metadata === null || typeof memory.metadata === "object",
|
||||
).toBe(true);
|
||||
expect(
|
||||
Array.isArray(memory.categories) || memory.categories === null,
|
||||
).toBe(true);
|
||||
expect(new Date(memory.created_at || "").toString()).not.toBe(
|
||||
expect(new Date(memory.createdAt || "").toString()).not.toBe(
|
||||
"Invalid Date",
|
||||
);
|
||||
expect(new Date(memory.updated_at || "").toString()).not.toBe(
|
||||
expect(new Date(memory.updatedAt || "").toString()).not.toBe(
|
||||
"Invalid Date",
|
||||
);
|
||||
});
|
||||
@@ -123,7 +123,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
describe("get all memories", () => {
|
||||
test("returns all memories for test user", async () => {
|
||||
const memories = await client.getAll({
|
||||
filters: { user_id: TEST_USER_ID },
|
||||
filters: { userId: TEST_USER_ID },
|
||||
});
|
||||
|
||||
expect(Array.isArray(memories)).toBe(true);
|
||||
@@ -137,9 +137,9 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
|
||||
test("returns paginated results with page and page_size", async () => {
|
||||
const page1 = await client.getAll({
|
||||
filters: { user_id: TEST_USER_ID },
|
||||
filters: { userId: TEST_USER_ID },
|
||||
page: 1,
|
||||
page_size: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
|
||||
// Paginated response is an object with results array
|
||||
@@ -191,7 +191,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
},
|
||||
],
|
||||
{
|
||||
user_id: TEST_USER_ID,
|
||||
userId: TEST_USER_ID,
|
||||
metadata: { source: "integration-test", category: "preferences" },
|
||||
},
|
||||
);
|
||||
@@ -202,7 +202,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
|
||||
test("getAll for non-existent user returns empty array", async () => {
|
||||
const memories = await client.getAll({
|
||||
filters: { user_id: `nonexistent-user-${randomUUID()}` },
|
||||
filters: { userId: `nonexistent-user-${randomUUID()}` },
|
||||
});
|
||||
|
||||
expect(Array.isArray(memories)).toBe(true);
|
||||
@@ -211,7 +211,7 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
|
||||
test("deleteAll for non-existent user does not throw", async () => {
|
||||
const result = await client.deleteAll({
|
||||
user_id: `nonexistent-user-${randomUUID()}`,
|
||||
userId: `nonexistent-user-${randomUUID()}`,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
@@ -241,13 +241,13 @@ describeIntegration("MemoryClient Integration — CRUD", () => {
|
||||
// ─── Delete all + delete user ─────────────────────────────
|
||||
describe("cleanup operations", () => {
|
||||
test("deletes all memories for test user", async () => {
|
||||
const result = await client.deleteAll({ user_id: TEST_USER_ID });
|
||||
const result = await client.deleteAll({ userId: TEST_USER_ID });
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result.message).toBe("string");
|
||||
});
|
||||
|
||||
test("deletes the test user entity", async () => {
|
||||
const result = await client.deleteUsers({ user_id: TEST_USER_ID });
|
||||
const result = await client.deleteUsers({ userId: TEST_USER_ID });
|
||||
expect(result).toBeDefined();
|
||||
expect(result.message).toBe("Entity deleted successfully.");
|
||||
});
|
||||
|
||||
@@ -18,10 +18,10 @@ export default async function globalSetup() {
|
||||
// Full project wipe — all four filters set explicitly
|
||||
try {
|
||||
await client.deleteAll({
|
||||
user_id: "*",
|
||||
agent_id: "*",
|
||||
app_id: "*",
|
||||
run_id: "*",
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore — may 404 if no data exists
|
||||
|
||||
@@ -17,10 +17,10 @@ export default async function globalTeardown() {
|
||||
|
||||
try {
|
||||
await client.deleteAll({
|
||||
user_id: "*",
|
||||
agent_id: "*",
|
||||
app_id: "*",
|
||||
run_id: "*",
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function waitForMemories(
|
||||
): Promise<Memory[]> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
const memories = await withRetry(() =>
|
||||
client.getAll({ filters: { user_id: userId } }),
|
||||
client.getAll({ filters: { userId } }),
|
||||
);
|
||||
if (Array.isArray(memories) && memories.length >= minCount) {
|
||||
return memories;
|
||||
@@ -154,7 +154,7 @@ export async function seedTestMemories(
|
||||
"Nice to meet you! I'll remember that your favorite color is blue.",
|
||||
},
|
||||
],
|
||||
{ user_id: userId },
|
||||
{ userId },
|
||||
),
|
||||
);
|
||||
|
||||
@@ -170,7 +170,7 @@ export async function seedTestMemories(
|
||||
content: "Got it, you're a software engineer at Acme Corp!",
|
||||
},
|
||||
],
|
||||
{ user_id: userId },
|
||||
{ userId },
|
||||
),
|
||||
);
|
||||
|
||||
@@ -186,12 +186,12 @@ export async function cleanupTestUser(
|
||||
userId: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await client.deleteAll({ user_id: userId });
|
||||
await client.deleteAll({ userId });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
await client.deleteUsers({ user_id: userId });
|
||||
await client.deleteUsers({ userId });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -209,10 +209,10 @@ export async function fullProjectCleanup(client: MemoryClient): Promise<void> {
|
||||
// Delete all memories — all four filters set explicitly
|
||||
try {
|
||||
await client.deleteAll({
|
||||
user_id: "*",
|
||||
agent_id: "*",
|
||||
app_id: "*",
|
||||
run_id: "*",
|
||||
userId: "*",
|
||||
agentId: "*",
|
||||
appId: "*",
|
||||
runId: "*",
|
||||
});
|
||||
} catch {
|
||||
// ignore — may 404 if no data exists
|
||||
|
||||
@@ -67,16 +67,16 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
|
||||
expect(project).toBeDefined();
|
||||
expect(typeof project).toBe("object");
|
||||
expect("custom_instructions" in project).toBe(true);
|
||||
expect("customInstructions" in project).toBe(true);
|
||||
|
||||
originalInstructions = project.custom_instructions;
|
||||
originalInstructions = project.customInstructions;
|
||||
});
|
||||
|
||||
test("updates project custom_instructions via updateProject()", async () => {
|
||||
const testInstruction = `integration-test-${randomUUID().slice(0, 8)}`;
|
||||
|
||||
const result = await client.updateProject({
|
||||
custom_instructions: testInstruction,
|
||||
customInstructions: testInstruction,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
@@ -85,11 +85,11 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
const project = await client.getProject({
|
||||
fields: ["custom_instructions"],
|
||||
});
|
||||
expect(project.custom_instructions).toBe(testInstruction);
|
||||
expect(project.customInstructions).toBe(testInstruction);
|
||||
|
||||
// Restore original
|
||||
await client.updateProject({
|
||||
custom_instructions: originalInstructions || "",
|
||||
customInstructions: originalInstructions || "",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -120,26 +120,26 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
eventTypes: [WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED],
|
||||
}),
|
||||
);
|
||||
createdWebhookId = result.webhook_id!;
|
||||
expect(result.webhook_id).toBeDefined();
|
||||
createdWebhookId = result.webhookId!;
|
||||
expect(result.webhookId).toBeDefined();
|
||||
});
|
||||
|
||||
test("createWebhook returns the correct name", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(wh!.name).toBe(hookName);
|
||||
});
|
||||
|
||||
test("createWebhook returns the correct url", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(wh!.url).toBe(hookUrl);
|
||||
});
|
||||
|
||||
test("createWebhook returns the correct event_types", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const wh = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
expect(wh!.event_types?.sort()).toStrictEqual(
|
||||
const wh = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(wh!.eventTypes?.sort()).toStrictEqual(
|
||||
[WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED].sort(),
|
||||
);
|
||||
});
|
||||
@@ -152,14 +152,14 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
|
||||
test("getWebhooks includes the created webhook", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(found).toBeDefined();
|
||||
});
|
||||
|
||||
test("getWebhooks shows the webhook as active", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
expect(found!.is_active).toBe(true);
|
||||
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(found!.isActive).toBe(true);
|
||||
});
|
||||
|
||||
// ─── Update ────────────────────────────────────────────
|
||||
@@ -177,14 +177,14 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
|
||||
test("updateWebhook persists the new name", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const updated = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const updated = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(updated!.name).toBe(updatedName);
|
||||
});
|
||||
|
||||
test("updateWebhook persists the new event_types", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const updated = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
expect(updated!.event_types?.sort()).toStrictEqual(
|
||||
const updated = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(updated!.eventTypes?.sort()).toStrictEqual(
|
||||
[WebhookEvent.MEMORY_DELETED].sort(),
|
||||
);
|
||||
});
|
||||
@@ -199,7 +199,7 @@ describeIntegration("MemoryClient Integration — Users & Project", () => {
|
||||
|
||||
test("deleteWebhook removes the webhook from the list", async () => {
|
||||
const webhooks = await withRetry(() => client.getWebhooks());
|
||||
const found = webhooks.find((w) => w.webhook_id === createdWebhookId);
|
||||
const found = webhooks.find((w) => w.webhookId === createdWebhookId);
|
||||
expect(found).toBeUndefined();
|
||||
createdWebhookId = "";
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
const results = await waitForSearchResults(
|
||||
client,
|
||||
"What is my favorite color?",
|
||||
{ filters: { user_id: TEST_USER_ID } },
|
||||
{ filters: { userId: TEST_USER_ID } },
|
||||
);
|
||||
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
@@ -64,7 +64,7 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
client,
|
||||
"What do you know about me?",
|
||||
{
|
||||
filters: { OR: [{ user_id: TEST_USER_ID }] },
|
||||
filters: { OR: [{ userId: TEST_USER_ID }] },
|
||||
},
|
||||
);
|
||||
|
||||
@@ -89,15 +89,15 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
|
||||
const entry = history[0];
|
||||
expect(typeof entry.id).toBe("string");
|
||||
expect(typeof entry.memory_id).toBe("string");
|
||||
expect(typeof entry.memoryId).toBe("string");
|
||||
expect(["ADD", "UPDATE", "DELETE", "NOOP"]).toContain(entry.event);
|
||||
expect(new Date(entry.created_at).toString()).not.toBe("Invalid Date");
|
||||
expect(new Date(entry.updated_at).toString()).not.toBe("Invalid Date");
|
||||
expect(new Date(entry.createdAt).toString()).not.toBe("Invalid Date");
|
||||
expect(new Date(entry.updatedAt).toString()).not.toBe("Invalid Date");
|
||||
expect(
|
||||
entry.new_memory === null || typeof entry.new_memory === "string",
|
||||
entry.newMemory === null || typeof entry.newMemory === "string",
|
||||
).toBe(true);
|
||||
expect(
|
||||
entry.old_memory === null || typeof entry.old_memory === "string",
|
||||
entry.oldMemory === null || typeof entry.oldMemory === "string",
|
||||
).toBe(true);
|
||||
|
||||
const events = history.map((h) => h.event);
|
||||
@@ -109,7 +109,7 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
describe("edge cases", () => {
|
||||
test("search for non-existent user returns empty results", async () => {
|
||||
const results = await client.search("anything", {
|
||||
filters: { user_id: `nonexistent-user-${randomUUID()}` },
|
||||
filters: { userId: `nonexistent-user-${randomUUID()}` },
|
||||
});
|
||||
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
@@ -120,8 +120,8 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
const results = await client.search(
|
||||
"Tell me about integration test user",
|
||||
{
|
||||
filters: { user_id: TEST_USER_ID },
|
||||
top_k: 1,
|
||||
filters: { userId: TEST_USER_ID },
|
||||
topK: 1,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("MemoryClient - add()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.add([{ role: "user", content: "Hello" }], { user_id: "u1" });
|
||||
await client.add([{ role: "user", content: "Hello" }], { userId: "u1" });
|
||||
|
||||
expect(findFetchCall(mock, "/v1/memories/", "POST")).toBeDefined();
|
||||
});
|
||||
@@ -39,7 +39,7 @@ describe("MemoryClient - add()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.add(messages, { user_id: "u1" });
|
||||
await client.add(messages, { userId: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/", "POST");
|
||||
expect(getFetchBody(call!).messages).toEqual(messages);
|
||||
@@ -65,7 +65,7 @@ describe("MemoryClient - add()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.add([], { user_id: "u1" });
|
||||
await client.add([], { userId: "u1" });
|
||||
|
||||
const call = findFetchCall(mock, "/v1/memories/", "POST");
|
||||
expect(getFetchBody(call!).messages).toEqual([]);
|
||||
@@ -215,7 +215,7 @@ describe("MemoryClient - deleteAll()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.deleteAll({ user_id: "u1" });
|
||||
await client.deleteAll({ userId: "u1" });
|
||||
|
||||
const call = mock.mock.calls.find(
|
||||
(c: [string, RequestInit]) =>
|
||||
@@ -231,7 +231,7 @@ describe("MemoryClient - deleteAll()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.deleteAll({ user_id: "user@email.com" });
|
||||
await client.deleteAll({ userId: "user@email.com" });
|
||||
|
||||
const call = mock.mock.calls.find(
|
||||
(c: [string, RequestInit]) =>
|
||||
|
||||
@@ -111,9 +111,9 @@ describe("MemoryClient - feedback()", () => {
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.feedback({
|
||||
memory_id: "mem_123",
|
||||
memoryId: "mem_123",
|
||||
feedback: Feedback.POSITIVE,
|
||||
feedback_reason: "Very helpful",
|
||||
feedbackReason: "Very helpful",
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v1/feedback/", "POST");
|
||||
@@ -130,9 +130,9 @@ describe("MemoryClient - feedback()", () => {
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.feedback({
|
||||
memory_id: "mem_123",
|
||||
memoryId: "mem_123",
|
||||
feedback: Feedback.POSITIVE,
|
||||
feedback_reason: "Very helpful",
|
||||
feedbackReason: "Very helpful",
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v1/feedback/", "POST");
|
||||
@@ -178,7 +178,7 @@ describe("MemoryClient - Memory Exports", () => {
|
||||
setupMockFetch();
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await expect(client.getMemoryExport({} as never)).rejects.toThrow(
|
||||
"Missing memory_export_id or filters",
|
||||
"Missing memoryExportId or filters",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -191,7 +191,7 @@ describe("MemoryClient - Memory Exports", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.getMemoryExport({ memory_export_id: "exp_123" });
|
||||
await client.getMemoryExport({ memoryExportId: "exp_123" });
|
||||
|
||||
expect(findFetchCall(mock, "/v1/exports/get/", "POST")).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ describe("MemoryClient - search()", () => {
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("What is my name?", {
|
||||
filters: { user_id: "u1" },
|
||||
filters: { userId: "u1" },
|
||||
});
|
||||
|
||||
expect(findFetchCall(mock, "/v2/memories/search/", "POST")).toBeDefined();
|
||||
@@ -35,7 +35,7 @@ describe("MemoryClient - search()", () => {
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("What is my name?", {
|
||||
filters: { user_id: "u1" },
|
||||
filters: { userId: "u1" },
|
||||
});
|
||||
|
||||
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
|
||||
@@ -48,7 +48,7 @@ describe("MemoryClient - search()", () => {
|
||||
const mock = setupMockFetch(extra);
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
await client.search("test", { filters: { user_id: "u1" } });
|
||||
await client.search("test", { filters: { userId: "u1" } });
|
||||
|
||||
const call = findFetchCall(mock, "/v2/memories/search/", "POST");
|
||||
expect(getFetchBody(call!).filters).toEqual({ user_id: "u1" });
|
||||
@@ -88,7 +88,7 @@ describe("MemoryClient - search()", () => {
|
||||
|
||||
const client = new MemoryClient({ apiKey: TEST_API_KEY });
|
||||
const result: Memory[] = await client.search("nonexistent query", {
|
||||
filters: { user_id: "u1" },
|
||||
filters: { userId: "u1" },
|
||||
});
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Converts a camelCase string to snake_case.
|
||||
*/
|
||||
export function camelToSnake(str: string): string {
|
||||
// Skip all-uppercase keys (e.g. OR, AND, NOT — logical operators)
|
||||
if (str === str.toUpperCase()) return str;
|
||||
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a snake_case string to camelCase.
|
||||
*/
|
||||
function snakeToCamel(str: string): string {
|
||||
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts all keys of an object from camelCase to snake_case.
|
||||
* Used for converting user-facing camelCase params to API snake_case payloads.
|
||||
*/
|
||||
export function camelToSnakeKeys(obj: any): any {
|
||||
if (obj === null || obj === undefined || typeof obj !== "object") return obj;
|
||||
if (Array.isArray(obj)) return obj.map(camelToSnakeKeys);
|
||||
if (obj instanceof Date) return obj;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
camelToSnake(key),
|
||||
camelToSnakeKeys(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts all keys of an object from snake_case to camelCase.
|
||||
* Used for converting API snake_case responses to user-facing camelCase.
|
||||
*/
|
||||
export function snakeToCamelKeys(obj: any): any {
|
||||
if (obj === null || obj === undefined || typeof obj !== "object") return obj;
|
||||
if (Array.isArray(obj)) return obj.map(snakeToCamelKeys);
|
||||
if (obj instanceof Date) return obj;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
snakeToCamel(key),
|
||||
snakeToCamelKeys(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -26,15 +26,6 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
|
||||
modelProperties: undefined,
|
||||
},
|
||||
},
|
||||
enableGraph: false,
|
||||
graphStore: {
|
||||
provider: "neo4j",
|
||||
config: {
|
||||
url: process.env.NEO4J_URL || "neo4j://localhost:7687",
|
||||
username: process.env.NEO4J_USERNAME || "neo4j",
|
||||
password: process.env.NEO4J_PASSWORD || "password",
|
||||
},
|
||||
},
|
||||
historyStore: {
|
||||
provider: "sqlite",
|
||||
config: {
|
||||
|
||||
@@ -132,10 +132,9 @@ export class ConfigManager {
|
||||
userConfig.historyStore?.config?.historyDbPath ||
|
||||
DEFAULT_MEMORY_CONFIG.historyStore?.config?.historyDbPath,
|
||||
customInstructions: userConfig.customInstructions,
|
||||
graphStore: {
|
||||
...DEFAULT_MEMORY_CONFIG.graphStore,
|
||||
...userConfig.graphStore,
|
||||
},
|
||||
graphStore: userConfig.graphStore
|
||||
? { ...userConfig.graphStore }
|
||||
: undefined,
|
||||
historyStore: (() => {
|
||||
const defaultHistoryStore = DEFAULT_MEMORY_CONFIG.historyStore!;
|
||||
const historyProvider =
|
||||
@@ -158,7 +157,6 @@ export class ConfigManager {
|
||||
})(),
|
||||
disableHistory:
|
||||
userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory,
|
||||
enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph,
|
||||
};
|
||||
|
||||
// Validate the merged config
|
||||
|
||||
@@ -47,7 +47,6 @@ export class Memory {
|
||||
private collectionName: string | undefined;
|
||||
private apiVersion: string;
|
||||
private graphMemory?: MemoryGraph;
|
||||
private enableGraph: boolean;
|
||||
telemetryId: string;
|
||||
private _initPromise: Promise<void>;
|
||||
private _initError?: Error;
|
||||
@@ -79,11 +78,10 @@ export class Memory {
|
||||
|
||||
this.collectionName = this.config.vectorStore.config.collectionName;
|
||||
this.apiVersion = this.config.version || "v1.0";
|
||||
this.enableGraph = this.config.enableGraph || false;
|
||||
this.telemetryId = "anonymous";
|
||||
|
||||
// Initialize graph memory if configured
|
||||
if (this.enableGraph && this.config.graphStore) {
|
||||
// Initialize graph memory if graphStore is configured
|
||||
if (this.config.graphStore) {
|
||||
this.graphMemory = new MemoryGraph(this.config);
|
||||
}
|
||||
|
||||
@@ -159,7 +157,6 @@ export class Memory {
|
||||
api_version: this.apiVersion,
|
||||
client_type: "Memory",
|
||||
collection_name: this.collectionName,
|
||||
enable_graph: this.enableGraph,
|
||||
});
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
|
||||
expect(cfg.historyStore!.provider).toBe("sqlite");
|
||||
expect(cfg.historyStore!.config.historyDbPath).toBe("memory.db");
|
||||
expect(cfg.disableHistory).toBe(false);
|
||||
expect(cfg.enableGraph).toBe(false);
|
||||
expect(cfg.graphStore).toBeUndefined();
|
||||
});
|
||||
|
||||
it("workaround: explicit historyStore still works (existing user pattern)", () => {
|
||||
@@ -106,7 +106,6 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
|
||||
|
||||
it("graphStore config passes through unchanged", () => {
|
||||
const cfg = ConfigManager.mergeConfig({
|
||||
enableGraph: true,
|
||||
graphStore: {
|
||||
provider: "neo4j",
|
||||
config: {
|
||||
@@ -116,7 +115,6 @@ describe("backward compat: ConfigManager.mergeConfig", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(cfg.enableGraph).toBe(true);
|
||||
expect(cfg.graphStore!.config.url).toBe("neo4j://custom:7687");
|
||||
});
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ export interface MemoryConfig {
|
||||
historyDbPath?: string;
|
||||
customInstructions?: string;
|
||||
graphStore?: GraphStoreConfig;
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
|
||||
export interface MemoryItem {
|
||||
@@ -149,7 +148,6 @@ export const MemoryConfigSchema = z.object({
|
||||
}),
|
||||
historyDbPath: z.string().optional(),
|
||||
customInstructions: z.string().optional(),
|
||||
enableGraph: z.boolean().optional(),
|
||||
graphStore: z
|
||||
.object({
|
||||
provider: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user