551 lines
20 KiB
JavaScript
551 lines
20 KiB
JavaScript
import path from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { createLangGraphHttpClient, createLangGraphMemoryBackend } from './memory-v2-langgraph.mjs';
|
|
import { createLegacyMemoryBackend, createMemoryV2, resolveMemoryV2Policy } from './memory-v2.mjs';
|
|
import { createLettaHttpClient, createLettaMemoryBackend } from './memory-v2-letta.mjs';
|
|
import { createMemoryV2PluginBackends } from './memory-v2-plugin-backends.mjs';
|
|
import { createMem0HttpClient, createMem0MemoryBackend } from './memory-v2-mem0.mjs';
|
|
import { createNeo4jHttpClient, createNeo4jMemoryBackend } from './memory-v2-neo4j.mjs';
|
|
import { createPgvectorMemoryBackend } from './memory-v2-pgvector.mjs';
|
|
import { createQdrantHttpClient, createQdrantMemoryBackend } from './memory-v2-qdrant.mjs';
|
|
import { createRedisStreamsClient, createRedisStreamsMemoryBackend } from './memory-v2-redis-streams.mjs';
|
|
import { createWeaviateHttpClient, createWeaviateMemoryBackend } from './memory-v2-weaviate.mjs';
|
|
|
|
const DEFAULT_PGVECTOR_URL_ENV = 'MEMORY_PGVECTOR_DATABASE_URL';
|
|
|
|
function readFlag(env, key, fallback = false) {
|
|
const raw = env?.[key];
|
|
if (raw == null || raw === '') return fallback;
|
|
const normalized = String(raw).trim().toLowerCase();
|
|
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
return fallback;
|
|
}
|
|
|
|
function resolveModuleSpecifier(specifier) {
|
|
const value = String(specifier ?? '').trim();
|
|
if (!value) return null;
|
|
if (value.startsWith('.') || value.startsWith('/')) {
|
|
return pathToFileURL(path.resolve(value)).href;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
async function loadEmbeddingModule(moduleSpecifier, importModule) {
|
|
const resolved = resolveModuleSpecifier(moduleSpecifier);
|
|
if (!resolved) return null;
|
|
const imported = await importModule(resolved);
|
|
const fn = imported?.embedQuery ?? imported?.default;
|
|
if (typeof fn !== 'function') {
|
|
throw new Error(
|
|
`Memory V2 pgvector embedding module must export embedQuery or default: ${moduleSpecifier}`,
|
|
);
|
|
}
|
|
return fn;
|
|
}
|
|
|
|
async function createPgPool(connectionString, { importPg, max }) {
|
|
const imported = await importPg('pg');
|
|
const PgPool = imported?.Pool ?? imported?.default?.Pool;
|
|
if (typeof PgPool !== 'function') {
|
|
throw new Error('pg module does not export Pool');
|
|
}
|
|
return new PgPool({ connectionString, max });
|
|
}
|
|
|
|
export async function createMemoryV2Runtime({
|
|
legacyMemoryService = null,
|
|
env = process.env,
|
|
logger = console,
|
|
importPg = (specifier) => import(specifier),
|
|
importRedis = (specifier) => import(specifier),
|
|
importModule = (specifier) => import(specifier),
|
|
fetchImpl = globalThis.fetch,
|
|
lettaClient = null,
|
|
weaviateClient = null,
|
|
neo4jClient = null,
|
|
redisStreamsClient = null,
|
|
langGraphClient = null,
|
|
} = {}) {
|
|
const vectorEnabled = readFlag(env, 'MEMORY_VECTOR_ENABLED', false);
|
|
const pgvectorEnabled = readFlag(env, 'MEMORY_PGVECTOR_ENABLED', vectorEnabled);
|
|
const connectionString = String(env?.[DEFAULT_PGVECTOR_URL_ENV] ?? '').trim();
|
|
const embeddingModule = String(env?.MEMORY_PGVECTOR_EMBEDDING_MODULE ?? '').trim();
|
|
const backends = [createLegacyMemoryBackend(legacyMemoryService)];
|
|
const closers = [];
|
|
|
|
let pgvectorBackend = null;
|
|
if (pgvectorEnabled && connectionString) {
|
|
try {
|
|
const embedQuery = await loadEmbeddingModule(embeddingModule, importModule);
|
|
if (!embedQuery) {
|
|
pgvectorBackend = createPgvectorMemoryBackend({
|
|
enabled: false,
|
|
tableName: env?.MEMORY_PGVECTOR_TABLE,
|
|
unavailableReason: 'embedding_module_not_configured',
|
|
});
|
|
} else {
|
|
const pool = await createPgPool(connectionString, {
|
|
importPg,
|
|
max: Math.max(1, Number(env?.MEMORY_PGVECTOR_POOL_MAX ?? 5) || 5),
|
|
});
|
|
closers.push(() => pool.end?.());
|
|
pgvectorBackend = createPgvectorMemoryBackend({
|
|
pool,
|
|
enabled: pgvectorEnabled,
|
|
tableName: env?.MEMORY_PGVECTOR_TABLE,
|
|
embedQuery,
|
|
defaultLimit: Number(env?.MEMORY_PGVECTOR_LIMIT ?? 8) || 8,
|
|
});
|
|
}
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] pgvector backend unavailable: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
pgvectorBackend = createPgvectorMemoryBackend({
|
|
enabled: false,
|
|
unavailableReason: 'init_failed',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (pgvectorBackend) backends.push(pgvectorBackend);
|
|
let qdrantBackend = null;
|
|
const qdrantEnabled = readFlag(env, 'MEMORY_QDRANT_ENABLED', false);
|
|
const qdrantUrl = String(env?.MEMORY_QDRANT_URL ?? '').trim();
|
|
const qdrantCollection = String(env?.MEMORY_QDRANT_COLLECTION ?? '').trim();
|
|
const qdrantEmbeddingModule = String(env?.MEMORY_QDRANT_EMBEDDING_MODULE ?? '').trim();
|
|
if (qdrantEnabled || qdrantUrl || qdrantCollection) {
|
|
try {
|
|
const qdrantEmbedQuery = await loadEmbeddingModule(qdrantEmbeddingModule, importModule);
|
|
const qdrantClient = qdrantEnabled && qdrantUrl && qdrantEmbedQuery
|
|
? createQdrantHttpClient({
|
|
url: qdrantUrl,
|
|
apiKey: env?.MEMORY_QDRANT_API_KEY,
|
|
fetchImpl,
|
|
timeoutMs: Number(env?.MEMORY_QDRANT_TIMEOUT_MS ?? 3000) || 3000,
|
|
})
|
|
: null;
|
|
qdrantBackend = createQdrantMemoryBackend({
|
|
enabled: qdrantEnabled,
|
|
url: qdrantUrl,
|
|
collection: qdrantCollection || undefined,
|
|
client: qdrantClient,
|
|
embedQuery: qdrantEmbedQuery,
|
|
defaultLimit: Number(env?.MEMORY_QDRANT_LIMIT ?? 8) || 8,
|
|
});
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] qdrant backend unavailable: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
qdrantBackend = createQdrantMemoryBackend({
|
|
enabled: false,
|
|
unavailableReason: 'init_failed',
|
|
});
|
|
}
|
|
}
|
|
if (qdrantBackend) backends.push(qdrantBackend);
|
|
let weaviateBackend = null;
|
|
const weaviateEnabled = readFlag(env, 'MEMORY_WEAVIATE_ENABLED', false);
|
|
const weaviateUrl = String(env?.MEMORY_WEAVIATE_URL ?? '').trim();
|
|
const weaviateCollection = String(env?.MEMORY_WEAVIATE_COLLECTION ?? '').trim();
|
|
const weaviateEmbeddingModule = String(env?.MEMORY_WEAVIATE_EMBEDDING_MODULE ?? '').trim();
|
|
if (weaviateEnabled || weaviateUrl || weaviateCollection) {
|
|
try {
|
|
const weaviateEmbedQuery = await loadEmbeddingModule(weaviateEmbeddingModule, importModule);
|
|
const resolvedWeaviateClient = weaviateClient ?? (
|
|
weaviateEnabled && weaviateUrl && weaviateEmbedQuery
|
|
? createWeaviateHttpClient({
|
|
url: weaviateUrl,
|
|
apiKey: env?.MEMORY_WEAVIATE_API_KEY,
|
|
fetchImpl,
|
|
timeoutMs: Number(env?.MEMORY_WEAVIATE_TIMEOUT_MS ?? 3000) || 3000,
|
|
})
|
|
: null
|
|
);
|
|
weaviateBackend = createWeaviateMemoryBackend({
|
|
enabled: weaviateEnabled,
|
|
url: weaviateUrl,
|
|
collection: weaviateCollection || undefined,
|
|
client: resolvedWeaviateClient,
|
|
embedQuery: weaviateEmbedQuery,
|
|
defaultLimit: Number(env?.MEMORY_WEAVIATE_LIMIT ?? 8) || 8,
|
|
});
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] weaviate backend unavailable: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
weaviateBackend = createWeaviateMemoryBackend({
|
|
enabled: false,
|
|
unavailableReason: 'init_failed',
|
|
});
|
|
}
|
|
}
|
|
if (weaviateBackend) backends.push(weaviateBackend);
|
|
let mem0Backend = null;
|
|
const mem0Enabled = readFlag(env, 'MEMORY_MEM0_ENABLED', false);
|
|
const mem0ApiKey = String(env?.MEMORY_MEM0_API_KEY ?? '').trim();
|
|
const mem0ProjectId = String(env?.MEMORY_MEM0_PROJECT_ID ?? '').trim();
|
|
const mem0BaseUrl = String(env?.MEMORY_MEM0_BASE_URL ?? 'https://api.mem0.ai').trim();
|
|
const mem0ModelProviderKeyId = String(env?.MEMORY_MEM0_MODEL_PROVIDER_KEY_ID ?? '').trim();
|
|
const mem0Model = String(env?.MEMORY_MEM0_MODEL ?? '').trim();
|
|
const mem0ModelApiType = String(env?.MEMORY_MEM0_MODEL_API ?? '').trim();
|
|
if (mem0Enabled || mem0ApiKey || mem0ProjectId) {
|
|
try {
|
|
const mem0Client = mem0Enabled && mem0ApiKey
|
|
? createMem0HttpClient({
|
|
apiKey: mem0ApiKey,
|
|
projectId: mem0ProjectId || undefined,
|
|
baseUrl: mem0BaseUrl,
|
|
writePath: env?.MEMORY_MEM0_WRITE_PATH || undefined,
|
|
compactPath: env?.MEMORY_MEM0_COMPACT_PATH || undefined,
|
|
modelProviderKeyId: mem0ModelProviderKeyId || undefined,
|
|
model: mem0Model || undefined,
|
|
modelApiType: mem0ModelApiType || undefined,
|
|
fetchImpl,
|
|
timeoutMs: Number(env?.MEMORY_MEM0_TIMEOUT_MS ?? 3000) || 3000,
|
|
})
|
|
: null;
|
|
mem0Backend = createMem0MemoryBackend({
|
|
enabled: mem0Enabled,
|
|
apiKey: mem0ApiKey,
|
|
projectId: mem0ProjectId || undefined,
|
|
client: mem0Client,
|
|
});
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] mem0 backend unavailable: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
mem0Backend = createMem0MemoryBackend({
|
|
enabled: false,
|
|
unavailableReason: 'init_failed',
|
|
});
|
|
}
|
|
}
|
|
if (mem0Backend) backends.push(mem0Backend);
|
|
let lettaBackend = null;
|
|
const lettaEnabled = readFlag(env, 'MEMORY_LETTA_ENABLED', false);
|
|
const lettaApiKey = String(env?.MEMORY_LETTA_API_KEY ?? '').trim();
|
|
const lettaProjectId = String(env?.MEMORY_LETTA_PROJECT_ID ?? '').trim();
|
|
const lettaAgentId = String(env?.MEMORY_LETTA_AGENT_ID ?? '').trim();
|
|
const lettaBaseUrl = String(env?.MEMORY_LETTA_BASE_URL ?? 'https://api.letta.com').trim();
|
|
const lettaModelProviderKeyId = String(env?.MEMORY_LETTA_MODEL_PROVIDER_KEY_ID ?? '').trim();
|
|
const lettaModel = String(env?.MEMORY_LETTA_MODEL ?? '').trim();
|
|
const lettaModelApiType = String(env?.MEMORY_LETTA_MODEL_API ?? '').trim();
|
|
if (lettaEnabled || lettaApiKey || lettaProjectId || lettaAgentId) {
|
|
try {
|
|
const resolvedLettaClient = lettaClient ?? (
|
|
lettaEnabled && lettaApiKey && lettaAgentId
|
|
? createLettaHttpClient({
|
|
apiKey: lettaApiKey,
|
|
baseUrl: lettaBaseUrl,
|
|
agentId: lettaAgentId,
|
|
projectId: lettaProjectId || undefined,
|
|
resolvePath: env?.MEMORY_LETTA_RESOLVE_PATH || undefined,
|
|
writePath: env?.MEMORY_LETTA_WRITE_PATH || undefined,
|
|
compactPath: env?.MEMORY_LETTA_COMPACT_PATH || undefined,
|
|
modelProviderKeyId: lettaModelProviderKeyId || undefined,
|
|
model: lettaModel || undefined,
|
|
modelApiType: lettaModelApiType || undefined,
|
|
fetchImpl,
|
|
timeoutMs: Number(env?.MEMORY_LETTA_TIMEOUT_MS ?? 3000) || 3000,
|
|
})
|
|
: null
|
|
);
|
|
lettaBackend = createLettaMemoryBackend({
|
|
enabled: lettaEnabled,
|
|
apiKey: lettaApiKey,
|
|
projectId: lettaProjectId || undefined,
|
|
agentId: lettaAgentId || undefined,
|
|
client: resolvedLettaClient,
|
|
});
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] letta backend unavailable: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
lettaBackend = createLettaMemoryBackend({
|
|
enabled: false,
|
|
unavailableReason: 'init_failed',
|
|
});
|
|
}
|
|
}
|
|
if (lettaBackend) backends.push(lettaBackend);
|
|
let neo4jBackend = null;
|
|
const neo4jEnabled = readFlag(env, 'MEMORY_NEO4J_ENABLED', false);
|
|
const neo4jUri = String(env?.MEMORY_NEO4J_URI ?? '').trim();
|
|
const neo4jHttpUrl = String(env?.MEMORY_NEO4J_HTTP_URL ?? '').trim();
|
|
const neo4jUsername = String(env?.MEMORY_NEO4J_USER ?? '').trim();
|
|
const neo4jPassword = String(env?.MEMORY_NEO4J_PASSWORD ?? '').trim();
|
|
if (neo4jEnabled || neo4jUri || neo4jHttpUrl || neo4jUsername || neo4jPassword) {
|
|
try {
|
|
const resolvedNeo4jClient = neo4jClient ?? (
|
|
neo4jEnabled && neo4jHttpUrl && neo4jUsername && neo4jPassword
|
|
? createNeo4jHttpClient({
|
|
httpUrl: neo4jHttpUrl,
|
|
username: neo4jUsername,
|
|
password: neo4jPassword,
|
|
database: env?.MEMORY_NEO4J_DATABASE || 'neo4j',
|
|
fetchImpl,
|
|
timeoutMs: Number(env?.MEMORY_NEO4J_TIMEOUT_MS ?? 3000) || 3000,
|
|
})
|
|
: null
|
|
);
|
|
neo4jBackend = createNeo4jMemoryBackend({
|
|
enabled: neo4jEnabled,
|
|
uri: neo4jUri,
|
|
httpUrl: neo4jHttpUrl,
|
|
username: neo4jUsername,
|
|
password: neo4jPassword,
|
|
client: resolvedNeo4jClient,
|
|
});
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] neo4j backend unavailable: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
neo4jBackend = createNeo4jMemoryBackend({
|
|
enabled: false,
|
|
unavailableReason: 'init_failed',
|
|
});
|
|
}
|
|
}
|
|
if (neo4jBackend) backends.push(neo4jBackend);
|
|
let redisStreamsBackend = null;
|
|
const redisStreamsEnabled = readFlag(env, 'MEMORY_REDIS_STREAMS_ENABLED', false);
|
|
const redisStreamsUrl = String(env?.MEMORY_REDIS_STREAMS_URL ?? '').trim();
|
|
const redisStreamsStream = String(env?.MEMORY_REDIS_STREAMS_STREAM ?? '').trim();
|
|
if (redisStreamsEnabled || redisStreamsUrl || redisStreamsStream) {
|
|
try {
|
|
const resolvedRedisStreamsClient = redisStreamsClient ?? (
|
|
redisStreamsEnabled && redisStreamsUrl
|
|
? await createRedisStreamsClient({
|
|
url: redisStreamsUrl,
|
|
stream: redisStreamsStream || undefined,
|
|
importRedis,
|
|
})
|
|
: null
|
|
);
|
|
if (resolvedRedisStreamsClient?.close) closers.push(() => resolvedRedisStreamsClient.close());
|
|
redisStreamsBackend = createRedisStreamsMemoryBackend({
|
|
enabled: redisStreamsEnabled,
|
|
url: redisStreamsUrl,
|
|
stream: redisStreamsStream || undefined,
|
|
client: resolvedRedisStreamsClient,
|
|
});
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] redis-streams backend unavailable: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
redisStreamsBackend = createRedisStreamsMemoryBackend({
|
|
enabled: false,
|
|
unavailableReason: 'init_failed',
|
|
});
|
|
}
|
|
}
|
|
if (redisStreamsBackend) backends.push(redisStreamsBackend);
|
|
let langGraphBackend = null;
|
|
const langGraphEnabled = readFlag(env, 'MEMORY_LANGGRAPH_ENABLED', false);
|
|
const langGraphPolicyId = String(env?.MEMORY_LANGGRAPH_POLICY_ID ?? '').trim();
|
|
const langGraphUrl = String(env?.MEMORY_LANGGRAPH_URL ?? '').trim();
|
|
const langGraphModelProviderKeyId = String(env?.MEMORY_LANGGRAPH_MODEL_PROVIDER_KEY_ID ?? '').trim();
|
|
const langGraphModel = String(env?.MEMORY_LANGGRAPH_MODEL ?? '').trim();
|
|
const langGraphModelApiType = String(env?.MEMORY_LANGGRAPH_MODEL_API ?? '').trim();
|
|
if (langGraphEnabled || langGraphPolicyId || langGraphUrl) {
|
|
try {
|
|
const resolvedLangGraphClient = langGraphClient ?? (
|
|
langGraphEnabled && langGraphUrl
|
|
? createLangGraphHttpClient({
|
|
baseUrl: langGraphUrl,
|
|
apiKey: env?.MEMORY_LANGGRAPH_API_KEY,
|
|
resolvePath: env?.MEMORY_LANGGRAPH_RESOLVE_PATH || undefined,
|
|
modelProviderKeyId: langGraphModelProviderKeyId || undefined,
|
|
model: langGraphModel || undefined,
|
|
modelApiType: langGraphModelApiType || undefined,
|
|
fetchImpl,
|
|
timeoutMs: Number(env?.MEMORY_LANGGRAPH_TIMEOUT_MS ?? 3000) || 3000,
|
|
})
|
|
: null
|
|
);
|
|
langGraphBackend = createLangGraphMemoryBackend({
|
|
enabled: langGraphEnabled,
|
|
policyId: langGraphPolicyId || undefined,
|
|
client: resolvedLangGraphClient,
|
|
});
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] langgraph backend unavailable: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
langGraphBackend = createLangGraphMemoryBackend({
|
|
enabled: false,
|
|
unavailableReason: 'init_failed',
|
|
});
|
|
}
|
|
}
|
|
if (langGraphBackend) backends.push(langGraphBackend);
|
|
backends.push(...createMemoryV2PluginBackends({
|
|
exclude: [
|
|
...(pgvectorBackend ? ['pgvector'] : []),
|
|
...(qdrantBackend ? ['qdrant'] : []),
|
|
...(weaviateBackend ? ['weaviate'] : []),
|
|
...(mem0Backend ? ['mem0'] : []),
|
|
...(lettaBackend ? ['letta'] : []),
|
|
...(neo4jBackend ? ['neo4j'] : []),
|
|
...(redisStreamsBackend ? ['redis-streams'] : []),
|
|
...(langGraphBackend ? ['langgraph'] : []),
|
|
],
|
|
}));
|
|
|
|
const memory = createMemoryV2({
|
|
legacyMemoryService,
|
|
backends,
|
|
env,
|
|
logger,
|
|
});
|
|
|
|
memory.close = async () => {
|
|
const results = await Promise.allSettled(closers.map((close) => close()));
|
|
for (const result of results) {
|
|
if (result.status === 'rejected') {
|
|
logger?.warn?.(
|
|
`[memory-v2] close skipped: ${result.reason instanceof Error ? result.reason.message : result.reason}`,
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
return memory;
|
|
}
|
|
|
|
export async function createManagedMemoryV2Runtime({
|
|
legacyMemoryService = null,
|
|
configService = null,
|
|
env = process.env,
|
|
logger = console,
|
|
importPg = (specifier) => import(specifier),
|
|
importRedis = (specifier) => import(specifier),
|
|
importModule = (specifier) => import(specifier),
|
|
fetchImpl = globalThis.fetch,
|
|
lettaClient = null,
|
|
weaviateClient = null,
|
|
neo4jClient = null,
|
|
redisStreamsClient = null,
|
|
langGraphClient = null,
|
|
} = {}) {
|
|
let activeRuntime = null;
|
|
let activeFingerprint = null;
|
|
let activeMeta = { source: 'env', updatedAt: null, updatedBy: null, configError: null };
|
|
|
|
async function loadRuntimeState() {
|
|
if (!configService?.getRuntimeState) {
|
|
return {
|
|
source: 'env',
|
|
updatedAt: null,
|
|
updatedBy: null,
|
|
fingerprint: 'env-only',
|
|
effectiveEnv: { ...env },
|
|
configError: null,
|
|
};
|
|
}
|
|
try {
|
|
const state = await configService.getRuntimeState();
|
|
return {
|
|
source: state?.source ?? 'admin-db',
|
|
updatedAt: state?.updatedAt ?? null,
|
|
updatedBy: state?.updatedBy ?? null,
|
|
fingerprint: state?.fingerprint ?? `admin-db:${Date.now()}`,
|
|
effectiveEnv: { ...env, ...(state?.overrides ?? {}) },
|
|
configError: null,
|
|
};
|
|
} catch (err) {
|
|
logger?.warn?.(
|
|
`[memory-v2] admin config unavailable, using process env: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
return {
|
|
source: 'env-fallback',
|
|
updatedAt: null,
|
|
updatedBy: null,
|
|
fingerprint: 'env-fallback',
|
|
effectiveEnv: { ...env },
|
|
configError: err instanceof Error ? err.message : String(err),
|
|
};
|
|
}
|
|
}
|
|
|
|
async function ensureRuntime() {
|
|
const state = await loadRuntimeState();
|
|
if (activeRuntime && activeFingerprint === state.fingerprint) {
|
|
activeMeta = state;
|
|
return activeRuntime;
|
|
}
|
|
const nextRuntime = await createMemoryV2Runtime({
|
|
legacyMemoryService,
|
|
env: state.effectiveEnv,
|
|
logger,
|
|
importPg,
|
|
importRedis,
|
|
importModule,
|
|
fetchImpl,
|
|
lettaClient,
|
|
weaviateClient,
|
|
neo4jClient,
|
|
redisStreamsClient,
|
|
langGraphClient,
|
|
});
|
|
const previous = activeRuntime;
|
|
activeRuntime = nextRuntime;
|
|
activeFingerprint = state.fingerprint;
|
|
activeMeta = state;
|
|
if (previous?.close) {
|
|
await previous.close().catch((err) => {
|
|
logger?.warn?.(
|
|
`[memory-v2] previous runtime close skipped: ${err instanceof Error ? err.message : err}`,
|
|
);
|
|
});
|
|
}
|
|
return activeRuntime;
|
|
}
|
|
|
|
const managed = {
|
|
get policy() {
|
|
return activeRuntime?.policy ?? resolveMemoryV2Policy({ env });
|
|
},
|
|
|
|
async getStatus() {
|
|
const runtime = await ensureRuntime();
|
|
const status = await Promise.resolve(runtime.getStatus());
|
|
return {
|
|
...status,
|
|
configSource: activeMeta.source,
|
|
configUpdatedAt: activeMeta.updatedAt,
|
|
configUpdatedBy: activeMeta.updatedBy,
|
|
configError: activeMeta.configError,
|
|
};
|
|
},
|
|
|
|
async resolve(input = {}) {
|
|
const runtime = await ensureRuntime();
|
|
return runtime.resolve(input);
|
|
},
|
|
|
|
async write(input = {}) {
|
|
const runtime = await ensureRuntime();
|
|
return runtime.write(input);
|
|
},
|
|
|
|
async compact(input = {}) {
|
|
const runtime = await ensureRuntime();
|
|
return runtime.compact(input);
|
|
},
|
|
|
|
async close() {
|
|
const runtime = activeRuntime;
|
|
activeRuntime = null;
|
|
activeFingerprint = null;
|
|
if (runtime?.close) {
|
|
await runtime.close();
|
|
}
|
|
},
|
|
};
|
|
|
|
return managed;
|
|
}
|