chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mem0AddMemoriesTool } from '@/tools/mem0/add_memories'
|
||||
import type { Mem0AddMemoriesParams } from '@/tools/mem0/types'
|
||||
|
||||
describe('mem0AddMemoriesTool', () => {
|
||||
const buildBody = mem0AddMemoriesTool.request.body!
|
||||
const transformResponse = mem0AddMemoriesTool.transformResponse!
|
||||
|
||||
it('uses the v3 add memories endpoint', () => {
|
||||
expect(mem0AddMemoriesTool.request.url).toBe('https://api.mem0.ai/v3/memories/add/')
|
||||
expect(mem0AddMemoriesTool.request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('builds the documented add memories request body', () => {
|
||||
const body = buildBody({
|
||||
apiKey: 'test-key',
|
||||
userId: ' alice ',
|
||||
messages: [{ role: 'user', content: 'I like Sim.' }],
|
||||
})
|
||||
|
||||
expect(body).toEqual({
|
||||
messages: [{ role: 'user', content: 'I like Sim.' }],
|
||||
user_id: 'alice',
|
||||
})
|
||||
})
|
||||
|
||||
it('accepts JSON string messages from the block code input', () => {
|
||||
const params: Mem0AddMemoriesParams = {
|
||||
apiKey: 'test-key',
|
||||
userId: 'alice',
|
||||
messages: JSON.stringify([{ role: 'assistant', content: 'I will remember that.' }]),
|
||||
}
|
||||
|
||||
expect(buildBody(params)).toEqual({
|
||||
messages: [{ role: 'assistant', content: 'I will remember that.' }],
|
||||
user_id: 'alice',
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects unsupported message roles before building the request body', () => {
|
||||
expect(() =>
|
||||
buildBody({
|
||||
apiKey: 'test-key',
|
||||
userId: 'alice',
|
||||
messages: JSON.stringify([{ role: 'system', content: 'Remember this.' }]),
|
||||
})
|
||||
).toThrow('Each message must have role user or assistant and non-empty content')
|
||||
})
|
||||
|
||||
it('extracts queued processing fields from v3 responses', async () => {
|
||||
const result = await transformResponse(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
message: 'Memory processing has been queued for background execution',
|
||||
status: 'PENDING',
|
||||
event_id: 'evt-123',
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
output: {
|
||||
message: 'Memory processing has been queued for background execution',
|
||||
status: 'PENDING',
|
||||
event_id: 'evt-123',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
ADD_MEMORY_OUTPUT_PROPERTIES,
|
||||
type Mem0AddMemoriesParams,
|
||||
type Mem0AddMemoriesResponse,
|
||||
} from '@/tools/mem0/types'
|
||||
import { parseMem0Messages } from '@/tools/mem0/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Add Memories Tool
|
||||
* @see https://docs.mem0.ai/api-reference/memory/add-memories
|
||||
*/
|
||||
export const mem0AddMemoriesTool: ToolConfig<Mem0AddMemoriesParams, Mem0AddMemoriesResponse> = {
|
||||
id: 'mem0_add_memories',
|
||||
name: 'Add Memories',
|
||||
description: 'Add memories to Mem0 for persistent storage and retrieval',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID associated with the memory (e.g., "user_123", "alice@example.com")',
|
||||
},
|
||||
messages: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Array of message objects with role and content (e.g., [{"role": "user", "content": "Hello"}])',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Mem0 API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.mem0.ai/v3/memories/add/',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Token ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const messages = parseMem0Messages(params.messages)
|
||||
return {
|
||||
messages,
|
||||
user_id: params.userId.trim(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response): Promise<Mem0AddMemoriesResponse> => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: data.message ?? '',
|
||||
status: data.status ?? '',
|
||||
event_id: data.event_id ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
message: ADD_MEMORY_OUTPUT_PROPERTIES.message,
|
||||
status: ADD_MEMORY_OUTPUT_PROPERTIES.status,
|
||||
event_id: ADD_MEMORY_OUTPUT_PROPERTIES.event_id,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mem0GetMemoriesTool } from '@/tools/mem0/get_memories'
|
||||
|
||||
interface Mem0GetParams {
|
||||
apiKey: string
|
||||
userId?: string
|
||||
memoryId?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
page?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
describe('mem0GetMemoriesTool', () => {
|
||||
const buildUrl = mem0GetMemoriesTool.request.url as (params: Mem0GetParams) => string
|
||||
const buildMethod = mem0GetMemoriesTool.request.method as (params: Mem0GetParams) => string
|
||||
const buildBody = mem0GetMemoriesTool.request.body!
|
||||
const transformResponse = mem0GetMemoriesTool.transformResponse!
|
||||
|
||||
it('uses scoped v3 list memories requests', () => {
|
||||
const params = {
|
||||
apiKey: 'test-key',
|
||||
userId: 'user-123',
|
||||
page: 3,
|
||||
limit: 25,
|
||||
}
|
||||
|
||||
expect(buildUrl(params)).toBe('https://api.mem0.ai/v3/memories/')
|
||||
expect(buildMethod(params)).toBe('POST')
|
||||
expect(buildBody(params)).toEqual({
|
||||
filters: {
|
||||
user_id: 'user-123',
|
||||
},
|
||||
page: 3,
|
||||
page_size: 25,
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps date filters inside the scoped filter object', () => {
|
||||
const body = buildBody({
|
||||
apiKey: 'test-key',
|
||||
userId: 'user-123',
|
||||
startDate: '2026-01-01',
|
||||
endDate: '2026-01-31',
|
||||
})
|
||||
|
||||
expect(body).toEqual({
|
||||
filters: {
|
||||
user_id: 'user-123',
|
||||
created_at: {
|
||||
gte: '2026-01-01',
|
||||
lte: '2026-01-31',
|
||||
},
|
||||
},
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
})
|
||||
})
|
||||
|
||||
it('uses the single-memory endpoint for memoryId requests', () => {
|
||||
const params = {
|
||||
apiKey: 'test-key',
|
||||
userId: 'user-123',
|
||||
memoryId: 'mem/123',
|
||||
}
|
||||
|
||||
expect(buildUrl(params)).toBe('https://api.mem0.ai/v1/memories/mem%2F123/')
|
||||
expect(buildMethod(params)).toBe('GET')
|
||||
expect(buildBody(params)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('extracts memories from paginated v3 responses', async () => {
|
||||
const result = await transformResponse(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
count: 2,
|
||||
next: 'https://api.mem0.ai/v3/memories/?page=2&page_size=25',
|
||||
previous: null,
|
||||
results: [
|
||||
{ id: 'mem-1', memory: 'First memory.', user_id: 'user-123' },
|
||||
{ id: 'mem-2', memory: 'Second memory.', user_id: 'user-123' },
|
||||
],
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
expect(result.output).toEqual({
|
||||
memories: [
|
||||
{ id: 'mem-1', memory: 'First memory.', user_id: 'user-123' },
|
||||
{ id: 'mem-2', memory: 'Second memory.', user_id: 'user-123' },
|
||||
],
|
||||
ids: ['mem-1', 'mem-2'],
|
||||
count: 2,
|
||||
next: 'https://api.mem0.ai/v3/memories/?page=2&page_size=25',
|
||||
previous: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('extracts direct single memory responses without rewriting fields', async () => {
|
||||
const result = await transformResponse(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
id: 'mem-1',
|
||||
memory: 'Stored memory content.',
|
||||
created_at: '2026-01-01T00:00:00Z',
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
expect(result.output.memories).toEqual([
|
||||
{
|
||||
id: 'mem-1',
|
||||
memory: 'Stored memory content.',
|
||||
created_at: '2026-01-01T00:00:00Z',
|
||||
},
|
||||
])
|
||||
expect(result.output.ids).toEqual(['mem-1'])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,168 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { MEMORY_OUTPUT_PROPERTIES, type Mem0GetMemoriesParams } from '@/tools/mem0/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const getMemoriesFromResponse = (data: unknown): unknown[] => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (!isRecordLike(data)) return []
|
||||
if (Array.isArray(data.results)) return data.results
|
||||
if (isRecordLike(data.memory)) return [data.memory]
|
||||
if (data.id) return [data]
|
||||
return []
|
||||
}
|
||||
|
||||
const getMemoryId = (memory: unknown): string | undefined =>
|
||||
isRecordLike(memory) && typeof memory.id === 'string' ? memory.id : undefined
|
||||
|
||||
/**
|
||||
* Get Memories Tool
|
||||
* @see https://docs.mem0.ai/api-reference/memory/get-memories
|
||||
*/
|
||||
export const mem0GetMemoriesTool: ToolConfig<Mem0GetMemoriesParams> = {
|
||||
id: 'mem0_get_memories',
|
||||
name: 'Get Memories',
|
||||
description: 'Retrieve memories from Mem0 by ID or filter criteria',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID to retrieve memories for (e.g., "user_123", "alice@example.com")',
|
||||
},
|
||||
memoryId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Specific memory ID to retrieve (e.g., "mem_abc123")',
|
||||
},
|
||||
startDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start date for filtering by created_at (e.g., "2024-01-15")',
|
||||
},
|
||||
endDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End date for filtering by created_at (e.g., "2024-12-31")',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 10,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return (e.g., 10, 50, 100)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 1,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number to retrieve for paginated list results',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Mem0 API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const memoryId = typeof params.memoryId === 'string' ? params.memoryId.trim() : undefined
|
||||
if (memoryId) {
|
||||
return `https://api.mem0.ai/v1/memories/${encodeURIComponent(memoryId)}/`
|
||||
}
|
||||
return 'https://api.mem0.ai/v3/memories/'
|
||||
},
|
||||
method: (params) =>
|
||||
typeof params.memoryId === 'string' && params.memoryId.trim() ? 'GET' : 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Token ${params.apiKey}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
if (typeof params.memoryId === 'string' && params.memoryId.trim()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const filters: Record<string, unknown> = {
|
||||
user_id: params.userId?.trim(),
|
||||
}
|
||||
if (params.startDate || params.endDate) {
|
||||
const dateFilter: Record<string, unknown> = {}
|
||||
if (params.startDate) {
|
||||
dateFilter.gte = params.startDate
|
||||
}
|
||||
if (params.endDate) {
|
||||
dateFilter.lte = params.endDate
|
||||
}
|
||||
filters.created_at = dateFilter
|
||||
}
|
||||
|
||||
return {
|
||||
filters,
|
||||
page: Number(params.page ?? 1),
|
||||
page_size: Number(params.limit || 10),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
const memories = getMemoriesFromResponse(data)
|
||||
const ids = memories.map(getMemoryId).filter((id): id is string => Boolean(id))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
memories,
|
||||
ids,
|
||||
...(isRecordLike(data) && typeof data.count === 'number' ? { count: data.count } : {}),
|
||||
...(isRecordLike(data) && (typeof data.next === 'string' || data.next === null)
|
||||
? { next: data.next }
|
||||
: {}),
|
||||
...(isRecordLike(data) && (typeof data.previous === 'string' || data.previous === null)
|
||||
? { previous: data.previous }
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
memories: {
|
||||
type: 'array',
|
||||
description: 'Array of retrieved memory objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: MEMORY_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
ids: {
|
||||
type: 'array',
|
||||
description: 'Array of memory IDs that were retrieved',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Total number of memories matching the filters',
|
||||
optional: true,
|
||||
},
|
||||
next: {
|
||||
type: 'string',
|
||||
description: 'URL for the next page of results',
|
||||
optional: true,
|
||||
},
|
||||
previous: {
|
||||
type: 'string',
|
||||
description: 'URL for the previous page of results',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { mem0AddMemoriesTool } from '@/tools/mem0/add_memories'
|
||||
import { mem0GetMemoriesTool } from '@/tools/mem0/get_memories'
|
||||
import { mem0SearchMemoriesTool } from '@/tools/mem0/search_memories'
|
||||
|
||||
export { mem0AddMemoriesTool, mem0SearchMemoriesTool, mem0GetMemoriesTool }
|
||||
export * from '@/tools/mem0/types'
|
||||
export * from '@/tools/mem0/utils'
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mem0SearchMemoriesTool } from '@/tools/mem0/search_memories'
|
||||
|
||||
describe('mem0SearchMemoriesTool', () => {
|
||||
const buildBody = mem0SearchMemoriesTool.request.body!
|
||||
const transformResponse = mem0SearchMemoriesTool.transformResponse!
|
||||
|
||||
it('uses the v3 search endpoint', () => {
|
||||
expect(mem0SearchMemoriesTool.request.url).toBe('https://api.mem0.ai/v3/memories/search/')
|
||||
expect(mem0SearchMemoriesTool.request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('builds the documented search request body', () => {
|
||||
const body = buildBody({
|
||||
apiKey: 'test-key',
|
||||
userId: ' alice ',
|
||||
query: 'where does the user live?',
|
||||
limit: 20,
|
||||
})
|
||||
|
||||
expect(body).toEqual({
|
||||
query: 'where does the user live?',
|
||||
filters: {
|
||||
user_id: 'alice',
|
||||
},
|
||||
top_k: 20,
|
||||
})
|
||||
})
|
||||
|
||||
it('extracts results from v3 response envelopes', async () => {
|
||||
const result = await transformResponse(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
id: 'mem-1',
|
||||
memory: 'User lives in San Francisco.',
|
||||
user_id: 'alice',
|
||||
categories: ['location'],
|
||||
score: 0.82,
|
||||
created_at: '2026-01-15T10:30:00Z',
|
||||
updated_at: '2026-01-15T10:30:00Z',
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
expect(result.output).toEqual({
|
||||
searchResults: [
|
||||
{
|
||||
id: 'mem-1',
|
||||
memory: 'User lives in San Francisco.',
|
||||
user_id: 'alice',
|
||||
agent_id: undefined,
|
||||
app_id: undefined,
|
||||
run_id: undefined,
|
||||
hash: undefined,
|
||||
metadata: undefined,
|
||||
categories: ['location'],
|
||||
created_at: '2026-01-15T10:30:00Z',
|
||||
updated_at: '2026-01-15T10:30:00Z',
|
||||
score: 0.82,
|
||||
},
|
||||
],
|
||||
ids: ['mem-1'],
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,123 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import type { Mem0Response, Mem0SearchMemoriesParams } from '@/tools/mem0/types'
|
||||
import { SEARCH_RESULT_OUTPUT_PROPERTIES } from '@/tools/mem0/types'
|
||||
import type { JsonRecord } from '@/tools/mem0/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const getSearchResults = (data: unknown): JsonRecord[] => {
|
||||
if (!isRecordLike(data) || !Array.isArray(data.results)) return []
|
||||
return data.results.filter(isRecordLike)
|
||||
}
|
||||
|
||||
const getString = (value: unknown): string | undefined =>
|
||||
typeof value === 'string' ? value : undefined
|
||||
|
||||
const getStringArray = (value: unknown): string[] | undefined =>
|
||||
Array.isArray(value)
|
||||
? value.filter((item): item is string => typeof item === 'string')
|
||||
: undefined
|
||||
|
||||
const getNumber = (value: unknown, fallback = 0): number =>
|
||||
typeof value === 'number' ? value : fallback
|
||||
|
||||
/**
|
||||
* Search Memories Tool
|
||||
* @see https://docs.mem0.ai/api-reference/memory/search-memories
|
||||
*/
|
||||
export const mem0SearchMemoriesTool: ToolConfig<Mem0SearchMemoriesParams, Mem0Response> = {
|
||||
id: 'mem0_search_memories',
|
||||
name: 'Search Memories',
|
||||
description: 'Search for memories in Mem0 using semantic search',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID to search memories for (e.g., "user_123", "alice@example.com")',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search query to find relevant memories (e.g., "What are my favorite foods?")',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 10,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return (e.g., 10, 50, 100)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Mem0 API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.mem0.ai/v3/memories/search/',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Token ${params.apiKey}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
return {
|
||||
query: params.query,
|
||||
filters: {
|
||||
user_id: params.userId.trim(),
|
||||
},
|
||||
top_k: Number(params.limit || 10),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response): Promise<Mem0Response> => {
|
||||
const data = await response.json()
|
||||
const searchResults = getSearchResults(data).map((result) => ({
|
||||
id: getString(result.id) ?? '',
|
||||
memory: getString(result.memory) ?? '',
|
||||
user_id: getString(result.user_id),
|
||||
agent_id: getString(result.agent_id),
|
||||
app_id: getString(result.app_id),
|
||||
run_id: getString(result.run_id),
|
||||
hash: getString(result.hash),
|
||||
metadata: isRecordLike(result.metadata) ? result.metadata : undefined,
|
||||
categories: getStringArray(result.categories),
|
||||
created_at: getString(result.created_at),
|
||||
updated_at: getString(result.updated_at),
|
||||
score: getNumber(result.score),
|
||||
}))
|
||||
const ids = searchResults.map((result) => result.id).filter(Boolean)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
searchResults,
|
||||
ids,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
searchResults: {
|
||||
type: 'array',
|
||||
description: 'Array of search results with memory data and similarity scores',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: SEARCH_RESULT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
ids: {
|
||||
type: 'array',
|
||||
description: 'Array of memory IDs found in the search results',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface Mem0Message {
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface Mem0AddMemoriesParams {
|
||||
userId: string
|
||||
messages: Mem0Message[] | string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface Mem0SearchMemoriesParams {
|
||||
userId: string
|
||||
query: string
|
||||
limit?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface Mem0GetMemoriesParams {
|
||||
userId?: string
|
||||
memoryId?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
page?: number
|
||||
limit?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface Mem0AddMemoriesResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
status: string
|
||||
event_id: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared output property definitions for Mem0 API responses.
|
||||
* Based on official Mem0 REST API documentation.
|
||||
* @see https://docs.mem0.ai/api-reference
|
||||
*/
|
||||
|
||||
/**
|
||||
* Output definition for queued add-memory operations.
|
||||
* @see https://docs.mem0.ai/api-reference/memory/add-memories
|
||||
*/
|
||||
export const ADD_MEMORY_OUTPUT_PROPERTIES = {
|
||||
message: { type: 'string', description: 'Status message for the queued memory processing job' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Processing status returned by Mem0',
|
||||
},
|
||||
event_id: {
|
||||
type: 'string',
|
||||
description: 'Event ID for polling memory processing status',
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete add memory object output definition
|
||||
*/
|
||||
export const ADD_MEMORY_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Queued memory processing job returned from add operation',
|
||||
properties: ADD_MEMORY_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for memory objects returned by get operations.
|
||||
* Get responses include full memory details with timestamps and ownership info.
|
||||
* @see https://docs.mem0.ai/api-reference/memory/get-memories
|
||||
*/
|
||||
export const MEMORY_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'Unique identifier for the memory' },
|
||||
memory: { type: 'string', description: 'The content of the memory' },
|
||||
user_id: { type: 'string', description: 'User ID associated with this memory', optional: true },
|
||||
agent_id: { type: 'string', description: 'Agent ID associated with this memory', optional: true },
|
||||
app_id: { type: 'string', description: 'App ID associated with this memory', optional: true },
|
||||
run_id: {
|
||||
type: 'string',
|
||||
description: 'Run/session ID associated with this memory',
|
||||
optional: true,
|
||||
},
|
||||
hash: { type: 'string', description: 'Hash of the memory content', optional: true },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Custom metadata associated with the memory',
|
||||
optional: true,
|
||||
},
|
||||
categories: {
|
||||
type: 'json',
|
||||
description: 'Auto-assigned categories for the memory',
|
||||
optional: true,
|
||||
},
|
||||
created_at: { type: 'string', description: 'ISO 8601 timestamp when the memory was created' },
|
||||
updated_at: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 timestamp when the memory was last updated',
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete memory object output definition
|
||||
*/
|
||||
export const MEMORY_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Memory object with full details including timestamps and ownership',
|
||||
properties: MEMORY_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for search result objects returned by search operations.
|
||||
* Search responses include similarity score in addition to memory details.
|
||||
* @see https://docs.mem0.ai/api-reference/memory/search-memories
|
||||
*/
|
||||
export const SEARCH_RESULT_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'Unique identifier for the memory' },
|
||||
memory: { type: 'string', description: 'The content of the memory' },
|
||||
user_id: { type: 'string', description: 'User ID associated with this memory', optional: true },
|
||||
agent_id: { type: 'string', description: 'Agent ID associated with this memory', optional: true },
|
||||
app_id: { type: 'string', description: 'App ID associated with this memory', optional: true },
|
||||
run_id: {
|
||||
type: 'string',
|
||||
description: 'Run/session ID associated with this memory',
|
||||
optional: true,
|
||||
},
|
||||
hash: { type: 'string', description: 'Hash of the memory content', optional: true },
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Custom metadata associated with the memory',
|
||||
optional: true,
|
||||
},
|
||||
categories: {
|
||||
type: 'json',
|
||||
description: 'Auto-assigned categories for the memory',
|
||||
optional: true,
|
||||
},
|
||||
created_at: { type: 'string', description: 'ISO 8601 timestamp when the memory was created' },
|
||||
updated_at: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 timestamp when the memory was last updated',
|
||||
},
|
||||
score: { type: 'number', description: 'Similarity score from vector search' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete search result object output definition
|
||||
*/
|
||||
export const SEARCH_RESULT_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Search result with memory details and similarity score',
|
||||
properties: SEARCH_RESULT_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
export interface Mem0Response extends ToolResponse {
|
||||
output: {
|
||||
ids?: string[]
|
||||
memories?: Array<{
|
||||
id: string
|
||||
memory: string
|
||||
user_id?: string
|
||||
agent_id?: string
|
||||
app_id?: string
|
||||
run_id?: string
|
||||
hash?: string
|
||||
metadata?: Record<string, unknown>
|
||||
categories?: string[]
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}>
|
||||
count?: number
|
||||
next?: string | null
|
||||
previous?: string | null
|
||||
searchResults?: Array<{
|
||||
id: string
|
||||
memory: string
|
||||
user_id?: string
|
||||
agent_id?: string
|
||||
app_id?: string
|
||||
run_id?: string
|
||||
hash?: string
|
||||
metadata?: Record<string, unknown>
|
||||
categories?: string[]
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
score: number
|
||||
}>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { toError } from '@sim/utils/errors'
|
||||
import type { Mem0Message } from '@/tools/mem0/types'
|
||||
|
||||
export type JsonRecord = Record<string, unknown>
|
||||
|
||||
function isMem0Message(value: unknown): value is Mem0Message {
|
||||
return (
|
||||
value !== null &&
|
||||
typeof value === 'object' &&
|
||||
'role' in value &&
|
||||
'content' in value &&
|
||||
(value.role === 'user' || value.role === 'assistant') &&
|
||||
typeof value.content === 'string' &&
|
||||
value.content.length > 0
|
||||
)
|
||||
}
|
||||
|
||||
export function parseMem0Messages(value: unknown): Mem0Message[] {
|
||||
let messages: unknown
|
||||
try {
|
||||
messages = typeof value === 'string' ? JSON.parse(value) : value
|
||||
} catch (error) {
|
||||
throw new Error(`Messages must be valid JSON: ${toError(error).message}`)
|
||||
}
|
||||
|
||||
if (!Array.isArray(messages) || messages.length === 0) {
|
||||
throw new Error('Messages must be a non-empty array')
|
||||
}
|
||||
|
||||
const validMessages: Mem0Message[] = []
|
||||
for (const message of messages) {
|
||||
if (!isMem0Message(message)) {
|
||||
throw new Error('Each message must have role user or assistant and non-empty content')
|
||||
}
|
||||
validMessages.push(message)
|
||||
}
|
||||
|
||||
return validMessages
|
||||
}
|
||||
Reference in New Issue
Block a user