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:
Kartik
2026-04-12 02:40:54 +05:30
committed by GitHub
co-authored by Claude Opus 4.6
parent e44b46ef2e
commit 9d6b79a14e
45 changed files with 292 additions and 1927 deletions
+38 -36
View File
@@ -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;
+44 -48
View File
@@ -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);
});
+49
View File
@@ -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),
]),
);
}