Files
memind/scripts/smoke-memory-v2-external.mjs
T
2026-07-03 09:46:03 +08:00

193 lines
5.5 KiB
JavaScript

#!/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 <name> [options]
Runs a Memory V2 external backend smoke through the real runtime wiring.
Options:
--backend <name> qdrant | weaviate | mem0 | letta | neo4j | redis-streams | langgraph
--operation <name> resolve | write | compact. Default resolves per backend.
--query <text> Resolve query. Default: memory-chain smoke
--limit <number> Resolve limit. Default: 1
--vector <csv> Optional query embedding CSV for resolve.
--user-id <id> Smoke user id. Default: memory-v2-smoke-user
--session-id <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;
});
}