Merge Memory V2 runtime facade
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
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 : [],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user