Files
memind/memory-v2-mem0.mjs
2026-07-03 09:46:03 +08:00

150 lines
4.7 KiB
JavaScript

function normalizeProjectId(value) {
const raw = String(value ?? '').trim();
if (!raw) return null;
if (!/^[a-zA-Z0-9_-]+$/.test(raw)) {
throw new Error(`Invalid MEMORY_MEM0_PROJECT_ID: ${raw}`);
}
return raw;
}
function timeoutSignal(timeoutMs) {
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return null;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
return {
signal: controller.signal,
clear() {
clearTimeout(timeout);
},
};
}
function joinUrl(baseUrl, path) {
return `${String(baseUrl).replace(/\/$/, '')}/${String(path).replace(/^\//, '')}`;
}
export function createMem0HttpClient({
apiKey,
projectId = null,
baseUrl = 'https://api.mem0.ai',
writePath = '/v1/memories',
compactPath = '/v1/memories/compact',
modelProviderKeyId = null,
model = null,
modelApiType = null,
fetchImpl = globalThis.fetch,
timeoutMs = 3000,
} = {}) {
const resolvedApiKey = String(apiKey ?? '').trim();
if (!resolvedApiKey) throw new Error('createMem0HttpClient requires MEMORY_MEM0_API_KEY');
if (typeof fetchImpl !== 'function') throw new Error('createMem0HttpClient requires fetch');
const resolvedProjectId = normalizeProjectId(projectId);
async function postJson(path, body) {
const timeout = timeoutSignal(Number(timeoutMs));
try {
const response = await fetchImpl(joinUrl(baseUrl, path), {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${resolvedApiKey}`,
},
body: JSON.stringify(body),
...(timeout?.signal ? { signal: timeout.signal } : {}),
});
if (!response?.ok) {
throw new Error(`Mem0 request failed: ${response?.status ?? 'unknown'}`);
}
return response.json?.() ?? {};
} finally {
timeout?.clear();
}
}
return {
async write(input = {}) {
const data = await postJson(writePath, {
user_id: input.userId,
session_id: input.sessionId,
messages: Array.isArray(input.messages) ? input.messages : [],
project_id: resolvedProjectId,
model_provider_key_id: String(modelProviderKeyId ?? '').trim() || null,
model: String(model ?? '').trim() || null,
model_api_type: String(modelApiType ?? '').trim() || null,
});
return {
saved: Number(data?.saved ?? data?.count ?? 1),
analyzed: Number(data?.analyzed ?? 1),
memories: Number(data?.memories ?? data?.memory_count ?? data?.count ?? 0),
};
},
async compact(input = {}) {
const data = await postJson(compactPath, {
user_id: input.userId,
project_id: resolvedProjectId,
model_provider_key_id: String(modelProviderKeyId ?? '').trim() || null,
model: String(model ?? '').trim() || null,
model_api_type: String(modelApiType ?? '').trim() || null,
});
return {
analyzed: Number(data?.analyzed ?? 1),
memories: Number(data?.memories ?? data?.memory_count ?? data?.count ?? 0),
};
},
};
}
export function createMem0MemoryBackend({
enabled = false,
apiKey = null,
projectId = null,
client = null,
unavailableReason = null,
} = {}) {
const hasApiKey = Boolean(String(apiKey ?? '').trim());
const resolvedProjectId = normalizeProjectId(projectId);
const configured = Boolean(enabled && hasApiKey);
const hasClient = Boolean(client?.write || client?.compact);
const wired = Boolean(configured && hasClient);
const reason = unavailableReason
?? (enabled
? (!hasApiKey ? 'api_key_not_configured' : 'client_not_configured')
: 'not_configured');
return {
name: 'mem0',
category: 'extraction',
role: 'automatic-memory-generation',
flag: 'MEMORY_MEM0_ENABLED',
unavailableReason: reason,
projectId: resolvedProjectId,
isAvailable() {
return Boolean(wired);
},
getUnavailableReason() {
return this.isAvailable() ? null : reason;
},
async write(input = {}) {
if (!this.isAvailable() || !client?.write) return { saved: 0, analyzed: 0, memories: 0 };
const result = await client.write({ ...input, projectId: resolvedProjectId });
return {
saved: Number(result?.saved ?? 0),
analyzed: Number(result?.analyzed ?? 0),
memories: Number(result?.memories ?? 0),
};
},
async compact(input = {}) {
if (!this.isAvailable() || !client?.compact) return { analyzed: 0, memories: 0 };
const result = await client.compact({ ...input, projectId: resolvedProjectId });
return {
analyzed: Number(result?.analyzed ?? 0),
memories: Number(result?.memories ?? 0),
};
},
};
}