UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

150 lines (149 loc) • 7.78 kB
import { a as getNodeSqliteKysely, o as iterateSqliteQuerySync, r as executeSqliteQuerySync, t as compileSqliteQueryBindings } from "./kysely-sync-K7ftY-g4.mjs"; import { n as decodeMemoryEmbedding, r as encodeMemoryEmbedding } from "./embedding-vector-DA_9H0FL.mjs"; import "./memory-core-host-engine-storage-g5_Iw_VB.mjs"; import "./sqlite-worker-runtime-BFi6Nryt.mjs"; import fs from "node:fs"; //#region extensions/memory-core/src/memory/manager-embedding-cache.ts /** Require a finite, nonempty vector compatible with the active embedding dimensions. */ function isValidMemoryEmbedding(embedding, dimensions) { return Array.isArray(embedding) && embedding.length > 0 && (dimensions === void 0 || embedding.length === dimensions) && embedding.every((coordinate) => typeof coordinate === "number" && Number.isFinite(coordinate)); } function loadMemoryEmbeddingCache(params) { if (!params.enabled || params.providerIdentities.length === 0 || params.hashes.length === 0) return /* @__PURE__ */ new Map(); const unresolved = new Set(params.hashes.filter(Boolean)); if (unresolved.size === 0) return /* @__PURE__ */ new Map(); const db = getNodeSqliteKysely(params.db); const out = /* @__PURE__ */ new Map(); const batchSize = 400; for (const identity of params.providerIdentities) { if (unresolved.size === 0) break; const hashes = [...unresolved]; for (let start = 0; start < hashes.length; start += batchSize) { const batch = hashes.slice(start, start + batchSize); const query = db.selectFrom("memory_embedding_cache").select(["hash", "embedding"]).select((eb) => eb.or([eb("dims", "is", null), eb("dims", "=", eb(eb.fn("length", ["embedding"]), "/", eb.lit(8)))]).as("dimensions_match")).where("provider", "=", identity.provider).where("model", "=", identity.model).where("provider_key", "=", identity.providerKey).where("hash", "in", batch); for (const row of iterateSqliteQuerySync(params.db, query)) { const embedding = decodeMemoryEmbedding(row.embedding); out.set(row.hash, row.dimensions_match && isValidMemoryEmbedding(embedding) ? embedding : []); unresolved.delete(row.hash); } } } return out; } function countMemoryEmbeddingCache(database) { const db = getNodeSqliteKysely(database); return executeSqliteQuerySync(database, db.selectFrom("memory_embedding_cache").select((eb) => eb.fn.countAll().as("count"))).rows[0].count; } /** The caller holds the write transaction; another purge may have reduced the cache. */ function pruneMemoryEmbeddingCache(database, maxEntries) { const excess = countMemoryEmbeddingCache(database) - maxEntries; if (excess <= 0) return; deleteOldestMemoryEmbeddingCacheRows(database, Math.min(excess, 100)); } function deleteOldestMemoryEmbeddingCacheRows(database, limit) { const db = getNodeSqliteKysely(database); executeSqliteQuerySync(database, db.deleteFrom("memory_embedding_cache").where("rowid", "in", db.selectFrom("memory_embedding_cache").select("rowid").orderBy("updated_at", "asc").orderBy("rowid", "asc").limit(limit))); } /** Discard ambiguous vector spaces without removing unrelated provider caches or index rows. */ function clearMemoryEmbeddingCacheIdentities(database, identities) { const db = getNodeSqliteKysely(database); for (const identity of identities) executeSqliteQuerySync(database, db.deleteFrom("memory_embedding_cache").where("provider", "=", identity.provider).where("model", "=", identity.model).where("provider_key", "=", identity.providerKey)); } function prepareMemoryEmbeddingCacheUpsert(db) { const { compiled, bind } = compileSqliteQueryBindings((parameter) => getNodeSqliteKysely(db).insertInto("memory_embedding_cache").values({ provider: parameter((row) => row.provider), model: parameter((row) => row.model), provider_key: parameter((row) => row.provider_key), hash: parameter((row) => row.hash), embedding: parameter((row) => row.embedding), dims: parameter((row) => row.dims), updated_at: parameter((row) => row.updated_at) }).onConflict((conflict) => conflict.columns([ "provider", "model", "provider_key", "hash" ]).doUpdateSet((eb) => ({ embedding: eb.ref("excluded.embedding"), dims: eb.ref("excluded.dims"), updated_at: eb.ref("excluded.updated_at") })))); const statement = db.prepare(compiled.sql); statement.setReadBigInts(true); return (row) => statement.run(...bind(row)); } function upsertMemoryEmbeddingCache(params) { const provider = params.provider; if (!params.enabled || !provider || !params.providerKey) return; const lastRows = /* @__PURE__ */ new Map(); let row = 0; for (const entry of params.entries()) lastRows.set(entry.hash, row++); const uniqueRows = [...lastRows].toSorted((left, right) => left[1] - right[1]); const maxEntries = typeof params.maxEntries === "number" && Number.isFinite(params.maxEntries) && params.maxEntries > 0 ? Math.floor(params.maxEntries) : void 0; const retainedRows = maxEntries === void 0 ? uniqueRows : uniqueRows.slice(-maxEntries); if (retainedRows.length === 0) return; if (maxEntries !== void 0) reserveMemoryEmbeddingCacheCapacity({ db: params.db, provider, providerKey: params.providerKey, hashes: retainedRows.map(([hash]) => hash), maxEntries }); const now = params.now ?? Date.now(); const upsert = prepareMemoryEmbeddingCacheUpsert(params.db); const retained = new Set(retainedRows.map(([, index]) => index)); row = 0; for (const entry of params.entries()) { if (!retained.has(row++)) continue; const embedding = entry.embedding ?? []; upsert({ provider: provider.id, model: provider.model, provider_key: params.providerKey, hash: entry.hash, embedding: encodeMemoryEmbedding(embedding), dims: embedding.length, updated_at: now }); } } function reserveMemoryEmbeddingCacheCapacity(params) { const db = getNodeSqliteKysely(params.db); for (let start = 0; start < params.hashes.length; start += 400) executeSqliteQuerySync(params.db, db.deleteFrom("memory_embedding_cache").where("provider", "=", params.provider.id).where("model", "=", params.provider.model).where("provider_key", "=", params.providerKey).where("hash", "in", params.hashes.slice(start, start + 400))); const retainedCapacity = params.maxEntries - params.hashes.length; const excess = countMemoryEmbeddingCache(params.db) - retainedCapacity; if (excess > 0) deleteOldestMemoryEmbeddingCacheRows(params.db, excess); } function collectMemoryCachedEmbeddings(params) { const embeddings = Array.from({ length: params.chunks.length }, () => []); const missing = []; for (let index = 0; index < params.chunks.length; index += 1) { const chunk = params.chunks[index]; const hit = chunk?.hash ? params.cached.get(chunk.hash) : void 0; if (hit && hit.length > 0) embeddings[index] = hit; else if (chunk) missing.push({ index, chunk }); } return { embeddings, missing }; } //#endregion //#region extensions/memory-core/src/memory/manager-shadow-task.ts function readMemoryShadowIdentity(filename) { const info = fs.statSync(filename, { bigint: true }); if (!info.isFile()) throw new Error("Memory reindex shadow is not a regular file"); return { device: String(info.dev), inode: String(info.ino) }; } function assertMemoryShadowIdentity(filename, expected) { const actual = readMemoryShadowIdentity(filename); if (actual.device !== expected.device || actual.inode !== expected.inode) throw new Error("Memory reindex shadow file changed during its owned lifetime"); } //#endregion export { countMemoryEmbeddingCache as a, pruneMemoryEmbeddingCache as c, collectMemoryCachedEmbeddings as i, upsertMemoryEmbeddingCache as l, readMemoryShadowIdentity as n, isValidMemoryEmbedding as o, clearMemoryEmbeddingCacheIdentities as r, loadMemoryEmbeddingCache as s, assertMemoryShadowIdentity as t };