@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
82 lines • 3.97 kB
JavaScript
/** Whether `store` implements {@link LexicalVectorStore}. */
export function isLexicalVectorStore(store) {
return typeof store.searchText === 'function';
}
/**
* A destructive call was refused because its scope could not be trusted. Thrown by
* {@link VectorStore.removeWhere} — never for a filter that merely matched nothing (that is a legitimate
* result of `0`), only for one that would have deleted *more* than the caller plausibly meant.
*/
export class UnsafeRemovalError extends Error {
reason;
constructor(
/** Why the removal was refused. */
reason, message) {
super(message);
this.reason = reason;
this.name = 'UnsafeRemovalError';
}
}
/**
* Guard for {@link VectorStore.removeWhere}: refuse a filter that scopes nothing. Shared so all three
* stores refuse the same input with the same error, rather than each deciding for itself how much of the
* corpus an empty object means.
*/
export function assertRemovalFilter(filter) {
if (Object.keys(filter).length === 0) {
throw new UnsafeRemovalError('empty-filter', 'removeWhere refused an empty filter: it would delete every chunk in the store, which is far ' +
'more likely to be a filter built wrong than a deliberate request. For a deliberate wipe, ' +
'loop `remove` over `listDocumentIds()`, or drop the table/collection.');
}
}
/**
* Does this filter deny everything? True when any key carries an **empty array** — no value is a member
* of the empty set, so nothing can match (see {@link import('./filter.js').matchesFilter}).
*
* Every filter builder in this package already encodes that (`matchesFilter` returns false,
* `buildMetadataWhere` emits a `false` clause, `buildQdrantFilter` emits `any: []`), and the live specs
* confirm the backends agree. {@link VectorStore.removeWhere} checks it anyway and short-circuits before
* issuing anything: for the one operation where misreading a deny as "no filter" destroys the corpus, the
* deny should not depend on a backend's interpretation of an empty set.
*/
export function filterDeniesAll(filter) {
return Object.values(filter).some((value) => Array.isArray(value) && value.length === 0);
}
/**
* The keys of a `patch` that will actually be written — every key whose value is not `undefined`. Shared
* by all three stores so "an empty patch writes nothing and returns 0" means the same thing on each, and
* so none of them issues a round trip for a no-op.
*/
export function effectivePatchKeys(patch) {
return Object.keys(patch).filter((key) => patch[key] !== undefined);
}
/**
* Apply a {@link MetadataPatch} to one chunk's metadata, returning the new metadata object. The single
* definition of the merge, shared by every store that can do the merge in JS — so the semantics cannot
* drift between adapters. Pure: the input object is never mutated.
*
* Always returns an object (never `undefined`), so a patched chunk has metadata even if it had none
* before, and an all-deleting patch leaves `{}` rather than a hole. `{}` and absent metadata are
* indistinguishable to {@link import('./filter.js').matchesFilter} and to pgvector's `@>`, so this costs
* a caller nothing.
*/
export function applyMetadataPatch(metadata, patch) {
const next = { ...metadata };
for (const key of Object.keys(patch)) {
const value = patch[key];
if (value === undefined) {
continue; // JSON has no `undefined`; treat it as "key not present".
}
if (value === null) {
delete next[key]; // JSON Merge Patch: null REMOVES the key.
continue;
}
next[key] = value; // Replaced wholesale — shallow, so no recursive merge.
}
return next;
}
/** Collapse a chunk id (`${documentId}#<n>`) back to its source document id. */
export function documentIdOf(chunkId) {
return chunkId.replace(/#\d+$/, '');
}
//# sourceMappingURL=vector-store.js.map