115 lines
3.5 KiB
JavaScript
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 : [],
|
|
};
|
|
},
|
|
};
|
|
}
|