Merge Memory V2 runtime facade
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
const ALLOWED_OPERATIONS = ['resolve', 'write', 'compact'];
|
||||
const ALLOWED_PLUGIN_CATEGORIES = new Set([
|
||||
'semantic',
|
||||
'extraction',
|
||||
'lifecycle',
|
||||
'behavior',
|
||||
'policy',
|
||||
]);
|
||||
|
||||
function check(name, ok, message = null, details = {}) {
|
||||
return { name, ok: Boolean(ok), ...(message ? { message } : {}), ...details };
|
||||
}
|
||||
|
||||
function isValidName(value) {
|
||||
return /^[a-z][a-z0-9-]*$/.test(String(value ?? ''));
|
||||
}
|
||||
|
||||
function supports(backend, operation) {
|
||||
return typeof backend?.[operation] === 'function';
|
||||
}
|
||||
|
||||
function safeAvailable(backend) {
|
||||
try {
|
||||
return backend?.isAvailable?.() !== false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function inspectMemoryV2Backend(backend) {
|
||||
const name = String(backend?.name ?? '').trim();
|
||||
const available = safeAvailable(backend);
|
||||
const support = Object.fromEntries(
|
||||
ALLOWED_OPERATIONS.map((operation) => [operation, supports(backend, operation)]),
|
||||
);
|
||||
const checks = [
|
||||
check('name_present', Boolean(name), 'Backend name is required'),
|
||||
check('name_format', isValidName(name), 'Backend name must be lowercase kebab-case', {
|
||||
backendName: name,
|
||||
}),
|
||||
check(
|
||||
'has_operation',
|
||||
Object.values(support).some(Boolean),
|
||||
'Backend must implement at least one Memory V2 operation',
|
||||
{ supports: support },
|
||||
),
|
||||
];
|
||||
|
||||
if (!available) {
|
||||
const reason = typeof backend?.getUnavailableReason === 'function'
|
||||
? backend.getUnavailableReason()
|
||||
: backend?.unavailableReason;
|
||||
checks.push(check(
|
||||
'unavailable_reason',
|
||||
Boolean(reason),
|
||||
'Unavailable backend must expose a reason',
|
||||
{ reason: reason ?? null },
|
||||
));
|
||||
}
|
||||
|
||||
if (backend?.category != null) {
|
||||
checks.push(check(
|
||||
'category_known',
|
||||
ALLOWED_PLUGIN_CATEGORIES.has(String(backend.category)),
|
||||
'Plugin backend category is unknown',
|
||||
{ category: String(backend.category) },
|
||||
));
|
||||
}
|
||||
|
||||
if (backend?.flag != null) {
|
||||
checks.push(check(
|
||||
'flag_format',
|
||||
/^[A-Z0-9_]+$/.test(String(backend.flag)),
|
||||
'Backend feature flag must be SCREAMING_SNAKE_CASE',
|
||||
{ flag: String(backend.flag) },
|
||||
));
|
||||
}
|
||||
|
||||
return {
|
||||
ok: checks.every((item) => item.ok),
|
||||
name,
|
||||
available,
|
||||
supports: support,
|
||||
category: backend?.category ?? null,
|
||||
role: backend?.role ?? null,
|
||||
flag: backend?.flag ?? null,
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
||||
export function validateMemoryV2BackendSet(backends = []) {
|
||||
const inspected = backends.map((backend) => inspectMemoryV2Backend(backend));
|
||||
const names = inspected.map((item) => item.name).filter(Boolean);
|
||||
const duplicateNames = names.filter((name, index) => names.indexOf(name) !== index);
|
||||
const checks = [
|
||||
check('backend_list_present', Array.isArray(backends), 'Backends must be an array'),
|
||||
check('backend_list_non_empty', inspected.length > 0, 'At least one backend is required'),
|
||||
check('backend_names_unique', duplicateNames.length === 0, 'Backend names must be unique', {
|
||||
duplicateNames: [...new Set(duplicateNames)],
|
||||
}),
|
||||
check(
|
||||
'legacy_backend_present',
|
||||
names.includes('legacy-conversation-memory'),
|
||||
'Legacy conversation memory backend must remain registered',
|
||||
),
|
||||
];
|
||||
|
||||
return {
|
||||
ok: checks.every((item) => item.ok) && inspected.every((item) => item.ok),
|
||||
checkedAt: new Date().toISOString(),
|
||||
summary: {
|
||||
backendCount: inspected.length,
|
||||
availableBackends: inspected.filter((item) => item.available).map((item) => item.name),
|
||||
operationSupport: Object.fromEntries(
|
||||
ALLOWED_OPERATIONS.map((operation) => [
|
||||
operation,
|
||||
inspected.filter((item) => item.supports[operation]).map((item) => item.name),
|
||||
]),
|
||||
),
|
||||
},
|
||||
checks,
|
||||
backends: inspected,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user