193 lines
5.5 KiB
JavaScript
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;
|
|
});
|
|
}
|