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

115 lines
3.5 KiB
JavaScript

function normalizePolicyId(value) {
const raw = String(value ?? '').trim();
if (!raw) return null;
if (!/^[a-zA-Z0-9_-]+$/.test(raw)) {
throw new Error(`Invalid MEMORY_LANGGRAPH_POLICY_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 createLangGraphHttpClient({
baseUrl,
apiKey = null,
resolvePath = '/memory/resolve',
modelProviderKeyId = null,
model = null,
modelApiType = null,
fetchImpl = globalThis.fetch,
timeoutMs = 3000,
} = {}) {
const resolvedBaseUrl = String(baseUrl ?? '').trim();
if (!resolvedBaseUrl) throw new Error('createLangGraphHttpClient requires MEMORY_LANGGRAPH_URL');
if (typeof fetchImpl !== 'function') throw new Error('createLangGraphHttpClient requires fetch');
return {
async resolve(input = {}) {
const timeout = timeoutSignal(Number(timeoutMs));
try {
const response = await fetchImpl(joinUrl(resolvedBaseUrl, resolvePath), {
method: 'POST',
headers: {
'content-type': 'application/json',
...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
},
body: JSON.stringify({
user_id: input.userId,
session_id: input.sessionId,
query: input.query,
policy_id: input.policyId,
limit: input.limit,
model_provider_key_id: String(modelProviderKeyId ?? '').trim() || null,
model: String(model ?? '').trim() || null,
model_api_type: String(modelApiType ?? '').trim() || null,
}),
...(timeout?.signal ? { signal: timeout.signal } : {}),
});
if (!response?.ok) {
throw new Error(`LangGraph request failed: ${response?.status ?? 'unknown'}`);
}
return response.json?.() ?? {};
} finally {
timeout?.clear();
}
},
};
}
export function createLangGraphMemoryBackend({
enabled = false,
policyId = null,
client = null,
unavailableReason = null,
} = {}) {
const resolvedPolicyId = normalizePolicyId(policyId);
const hasClient = Boolean(client?.resolve);
const wired = Boolean(enabled && hasClient);
const reason = unavailableReason
?? (enabled ? 'client_not_configured' : 'not_configured');
return {
name: 'langgraph',
category: 'policy',
role: 'routing-reasoning-policy',
flag: 'MEMORY_LANGGRAPH_ENABLED',
unavailableReason: reason,
policyId: resolvedPolicyId,
isAvailable() {
return Boolean(wired);
},
getUnavailableReason() {
return this.isAvailable() ? null : reason;
},
async resolve(input = {}) {
if (!this.isAvailable()) {
return { memories: [], semanticMemories: [], behaviorSummary: null, activeGoals: [] };
}
const payload = await client.resolve({ ...input, policyId: resolvedPolicyId });
return {
memories: Array.isArray(payload?.memories) ? payload.memories : [],
semanticMemories: Array.isArray(payload?.semanticMemories) ? payload.semanticMemories : [],
behaviorSummary: payload?.behaviorSummary ?? null,
activeGoals: Array.isArray(payload?.activeGoals) ? payload.activeGoals : [],
};
},
};
}