#!/usr/bin/env node import { createMemoryV2Runtime } from '../memory-v2-runtime.mjs'; const BACKENDS = new Set([ 'qdrant', 'weaviate', 'mem0', 'letta', 'neo4j', 'redis-streams', 'langgraph', ]); function usage() { return ` Usage: node scripts/smoke-memory-v2-external.mjs --backend [options] Runs a Memory V2 external backend smoke through the real runtime wiring. Options: --backend qdrant | weaviate | mem0 | letta | neo4j | redis-streams | langgraph --operation resolve | write | compact. Default resolves per backend. --query Resolve query. Default: memory-chain smoke --limit Resolve limit. Default: 1 --vector Optional query embedding CSV for resolve. --user-id Smoke user id. Default: memory-v2-smoke-user --session-id Smoke session id. Default: memory-v2-smoke-session -h, --help Show this help. `.trim(); } function parseVector(value) { if (value == null || value === '') return null; const vector = String(value).split(',').map((item) => Number(item.trim())); if (!vector.length || vector.some((item) => !Number.isFinite(item))) { throw new Error('--vector must be a comma-separated list of numbers'); } return vector; } function defaultOperation(backend) { if (backend === 'mem0' || backend === 'redis-streams') return 'write'; return 'resolve'; } export function parseMemoryV2ExternalSmokeArgs(argv = []) { const options = { backend: null, operation: null, query: 'memory-chain smoke', limit: 1, vector: null, userId: 'memory-v2-smoke-user', sessionId: 'memory-v2-smoke-session', help: false, }; for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; switch (arg) { case '--backend': options.backend = argv[++i] ?? ''; break; case '--operation': options.operation = argv[++i] ?? ''; break; case '--query': options.query = argv[++i] ?? ''; break; case '--limit': options.limit = Number(argv[++i]); break; case '--vector': options.vector = parseVector(argv[++i]); break; case '--user-id': options.userId = argv[++i] ?? ''; break; case '--session-id': options.sessionId = argv[++i] ?? ''; break; case '-h': case '--help': options.help = true; break; default: throw new Error(`Unknown argument: ${arg}`); } } if (options.help) return options; if (!BACKENDS.has(options.backend)) { throw new Error(`--backend must be one of: ${[...BACKENDS].join(', ')}`); } options.operation ||= defaultOperation(options.backend); if (!['resolve', 'write', 'compact'].includes(options.operation)) { throw new Error('--operation must be resolve, write, or compact'); } if (!options.query) throw new Error('--query requires a value'); if (!Number.isInteger(options.limit) || options.limit < 1) { throw new Error('--limit requires a positive integer'); } if (!options.userId) throw new Error('--user-id requires a value'); if (!options.sessionId) throw new Error('--session-id requires a value'); return options; } function createSyntheticLegacyMemoryService() { return { async listMemories() { return []; }, async saveAndAnalyze() { return { saved: 0, analyzed: 0, memories: 0 }; }, async analyzeUser() { return { analyzed: 0, memories: 0 }; }, }; } async function runOperation(memory, options) { const input = { userId: options.userId, sessionId: options.sessionId, query: options.query, embedding: options.vector, limit: options.limit, messages: [{ role: 'user', text: options.query, content: options.query, }], eventType: 'memory-v2.smoke', }; if (options.operation === 'resolve') return memory.resolve(input); if (options.operation === 'write') return memory.write(input); return memory.compact(input); } export async function runMemoryV2ExternalSmokeCli({ argv = process.argv.slice(2), env = process.env, stdout = process.stdout, fetchImpl = globalThis.fetch, importRedis = (specifier) => import(specifier), importModule = (specifier) => import(specifier), } = {}) { const options = parseMemoryV2ExternalSmokeArgs(argv); if (options.help) { stdout.write(`${usage()}\n`); return { ok: true, mode: 'help' }; } const memory = await createMemoryV2Runtime({ legacyMemoryService: createSyntheticLegacyMemoryService(), env: { ...env, MEMORY_ENABLED: '1', MEMORY_BACKEND: options.backend, }, logger: console, fetchImpl, importRedis, importModule, }); try { const result = await runOperation(memory, options); const backend = memory.getStatus().backends.find((item) => item.name === options.backend); const report = { ok: result?.ok !== false, checkedAt: new Date().toISOString(), backend: options.backend, operation: options.operation, selectedBackend: memory.getStatus().selectedBackend, backendAvailable: backend?.available ?? false, backendReason: backend?.reason ?? null, result, }; stdout.write(`${JSON.stringify(report, null, 2)}\n`); return report; } finally { await memory.close?.(); } } if (import.meta.url === `file://${process.argv[1]}`) { runMemoryV2ExternalSmokeCli().then((result) => { process.exitCode = result.ok ? 0 : 1; }).catch((err) => { console.error(err instanceof Error ? err.message : err); process.exitCode = 1; }); }