@agentskit/memory 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  var core = require('@agentskit/core');
4
4
  var security = require('@agentskit/core/security');
5
+ var promises = require('fs/promises');
6
+ var path = require('path');
5
7
 
6
8
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
9
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
@@ -1367,14 +1369,453 @@ function wrapVectorMemoryWithRedaction(inner, options) {
1367
1369
  };
1368
1370
  }
1369
1371
 
1372
+ // src/kv-store-types.ts
1373
+ var isExpired = (entry, ttlSeconds, now) => {
1374
+ if (ttlSeconds === void 0) return false;
1375
+ return now - entry.insertedAt > ttlSeconds * 1e3;
1376
+ };
1377
+ var enforceMaxMessages = (map, maxMessages) => {
1378
+ if (maxMessages === void 0) return;
1379
+ while (map.size > maxMessages) {
1380
+ const oldest = map.keys().next().value;
1381
+ if (oldest === void 0) break;
1382
+ map.delete(oldest);
1383
+ }
1384
+ };
1385
+
1386
+ // src/kv-store-basic.ts
1387
+ var createInMemoryStore = (config) => {
1388
+ const store = /* @__PURE__ */ new Map();
1389
+ const now = () => Date.now();
1390
+ return {
1391
+ id: "in-memory",
1392
+ async get(key) {
1393
+ const entry = store.get(key);
1394
+ if (!entry) return void 0;
1395
+ if (isExpired(entry, config.ttlSeconds, now())) {
1396
+ store.delete(key);
1397
+ return void 0;
1398
+ }
1399
+ return entry.value;
1400
+ },
1401
+ async set(key, value) {
1402
+ store.set(key, { value, insertedAt: now() });
1403
+ enforceMaxMessages(store, config.maxMessages);
1404
+ }
1405
+ };
1406
+ };
1407
+ var createFileStore = (config) => {
1408
+ const path$1 = config.path;
1409
+ let cache;
1410
+ const load = async () => {
1411
+ if (cache) return cache;
1412
+ try {
1413
+ const parsed = JSON.parse(await promises.readFile(path$1, "utf8"));
1414
+ cache = new Map(Object.entries(parsed));
1415
+ } catch (err) {
1416
+ if (err.code === "ENOENT") cache = /* @__PURE__ */ new Map();
1417
+ else throw err;
1418
+ }
1419
+ return cache;
1420
+ };
1421
+ const persist = async (map) => {
1422
+ await promises.mkdir(path.dirname(path$1), { recursive: true });
1423
+ await promises.writeFile(path$1, JSON.stringify(Object.fromEntries(map), null, 2), { encoding: "utf8", mode: 384 });
1424
+ };
1425
+ return {
1426
+ id: `file:${path$1}`,
1427
+ async get(key) {
1428
+ const map = await load();
1429
+ const entry = map.get(key);
1430
+ if (!entry) return void 0;
1431
+ if (isExpired(entry, config.ttlSeconds, Date.now())) {
1432
+ map.delete(key);
1433
+ await persist(map);
1434
+ return void 0;
1435
+ }
1436
+ return entry.value;
1437
+ },
1438
+ async set(key, value) {
1439
+ const map = await load();
1440
+ map.set(key, { value, insertedAt: Date.now() });
1441
+ enforceMaxMessages(map, config.maxMessages);
1442
+ await persist(map);
1443
+ }
1444
+ };
1445
+ };
1446
+ var resolveLocalStorage = () => {
1447
+ const maybe = globalThis.localStorage;
1448
+ return maybe && typeof maybe.getItem === "function" && typeof maybe.setItem === "function" ? maybe : void 0;
1449
+ };
1450
+ var defaultLocalStoragePath = () => `${process.cwd()}/.agentskit/memory-localstorage.json`;
1451
+ var createLocalStorageStore = ({
1452
+ config,
1453
+ storage = resolveLocalStorage(),
1454
+ filePath = defaultLocalStoragePath()
1455
+ }) => {
1456
+ const key = config.key;
1457
+ let cache;
1458
+ const mapFromJson = (raw) => raw ? new Map(Object.entries(JSON.parse(raw))) : /* @__PURE__ */ new Map();
1459
+ const loadFromFile = async () => {
1460
+ if (cache) return cache;
1461
+ try {
1462
+ cache = mapFromJson(await promises.readFile(filePath, "utf8"));
1463
+ } catch (err) {
1464
+ if (err.code === "ENOENT") cache = /* @__PURE__ */ new Map();
1465
+ else throw err;
1466
+ }
1467
+ return cache;
1468
+ };
1469
+ const load = async () => storage ? mapFromJson(storage.getItem(key)) : loadFromFile();
1470
+ const persist = async (map) => {
1471
+ const raw = JSON.stringify(Object.fromEntries(map), null, 2);
1472
+ if (storage) {
1473
+ storage.setItem(key, raw);
1474
+ return;
1475
+ }
1476
+ await promises.mkdir(path.dirname(filePath), { recursive: true });
1477
+ await promises.writeFile(filePath, raw, { encoding: "utf8", mode: 384 });
1478
+ };
1479
+ return {
1480
+ id: storage ? `localstorage:${key}` : `localstorage-file:${filePath}:${key}`,
1481
+ async get(itemKey) {
1482
+ const map = await load();
1483
+ const entry = map.get(itemKey);
1484
+ if (!entry) return void 0;
1485
+ if (isExpired(entry, config.ttlSeconds, Date.now())) {
1486
+ map.delete(itemKey);
1487
+ await persist(map);
1488
+ return void 0;
1489
+ }
1490
+ return entry.value;
1491
+ },
1492
+ async set(itemKey, value) {
1493
+ const map = await load();
1494
+ map.set(itemKey, { value, insertedAt: Date.now() });
1495
+ enforceMaxMessages(map, config.maxMessages);
1496
+ await persist(map);
1497
+ }
1498
+ };
1499
+ };
1500
+
1501
+ // src/kv-store-sqlite.ts
1502
+ var createSqliteStore = ({ config, open }) => {
1503
+ const db = open(config.path);
1504
+ db.exec(
1505
+ `CREATE TABLE IF NOT EXISTS memory (
1506
+ key TEXT PRIMARY KEY,
1507
+ value TEXT NOT NULL,
1508
+ inserted_at INTEGER NOT NULL
1509
+ )`
1510
+ );
1511
+ const getStmt = db.prepare("SELECT value, inserted_at FROM memory WHERE key = ?");
1512
+ const setStmt = db.prepare(
1513
+ "INSERT INTO memory(key, value, inserted_at) VALUES(?, ?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value, inserted_at=excluded.inserted_at"
1514
+ );
1515
+ const delStmt = db.prepare("DELETE FROM memory WHERE key = ?");
1516
+ const oldestStmt = db.prepare("SELECT key FROM memory ORDER BY inserted_at ASC LIMIT 1");
1517
+ const countStmt = db.prepare("SELECT COUNT(*) as n FROM memory");
1518
+ const enforce = () => {
1519
+ if (config.maxMessages === void 0) return;
1520
+ const countResult = countStmt.get();
1521
+ let count = countResult ? countResult.n : 0;
1522
+ while (count > config.maxMessages) {
1523
+ const oldest = oldestStmt.get();
1524
+ if (!oldest) break;
1525
+ delStmt.run(oldest.key);
1526
+ count -= 1;
1527
+ }
1528
+ };
1529
+ return {
1530
+ id: `sqlite:${config.path}`,
1531
+ async get(key) {
1532
+ const row = getStmt.get(key);
1533
+ if (!row) return void 0;
1534
+ if (isExpired({ insertedAt: row.inserted_at }, config.ttlSeconds, Date.now())) {
1535
+ delStmt.run(key);
1536
+ return void 0;
1537
+ }
1538
+ return JSON.parse(row.value);
1539
+ },
1540
+ async set(key, value) {
1541
+ setStmt.run(key, JSON.stringify(value), Date.now());
1542
+ enforce();
1543
+ }
1544
+ };
1545
+ };
1546
+ var tryDefaultSqliteOpener = async () => {
1547
+ try {
1548
+ const moduleId = "better-sqlite3";
1549
+ const mod = await import(
1550
+ /* @vite-ignore */
1551
+ moduleId
1552
+ );
1553
+ const Ctor = mod.default;
1554
+ if (typeof Ctor !== "function") return void 0;
1555
+ return (path) => new Ctor(path);
1556
+ } catch {
1557
+ return void 0;
1558
+ }
1559
+ };
1560
+ var createRedisStore = ({ config, client }) => {
1561
+ const prefix = config.prefix;
1562
+ const namespaced = (key) => `${prefix}${key}`;
1563
+ const wrap = async (op, fn) => {
1564
+ try {
1565
+ return await fn();
1566
+ } catch (cause) {
1567
+ throw new core.MemoryError({
1568
+ code: "AK_MEMORY_REDIS_CONNECTION_FAILED",
1569
+ message: `createRedisStore.${op}: redis command failed \u2014 ${cause instanceof Error ? cause.message : String(cause)}`,
1570
+ cause: cause instanceof Error ? cause : void 0
1571
+ });
1572
+ }
1573
+ };
1574
+ const enforce = async () => {
1575
+ if (config.maxMessages === void 0) return;
1576
+ const keys = await wrap("enforce", () => Promise.resolve(client.keys(`${prefix}*`)));
1577
+ if (keys.length <= config.maxMessages) return;
1578
+ const envelopes = [];
1579
+ for (const fullKey of keys) {
1580
+ const raw = await wrap("enforce", () => Promise.resolve(client.get(fullKey)));
1581
+ if (raw === null) continue;
1582
+ envelopes.push({ key: fullKey, insertedAt: JSON.parse(raw).insertedAt });
1583
+ }
1584
+ envelopes.sort((a, b) => a.insertedAt - b.insertedAt);
1585
+ let excess = envelopes.length - config.maxMessages;
1586
+ for (const entry of envelopes) {
1587
+ if (excess <= 0) break;
1588
+ await wrap("enforce", () => Promise.resolve(client.del(entry.key)));
1589
+ excess -= 1;
1590
+ }
1591
+ };
1592
+ return {
1593
+ id: `redis:${prefix}`,
1594
+ async get(key) {
1595
+ const raw = await wrap("get", () => Promise.resolve(client.get(namespaced(key))));
1596
+ if (raw === null) return void 0;
1597
+ const envelope = JSON.parse(raw);
1598
+ if (isExpired({ value: envelope.value, insertedAt: envelope.insertedAt }, config.ttlSeconds, Date.now())) {
1599
+ await wrap("get", () => Promise.resolve(client.del(namespaced(key))));
1600
+ return void 0;
1601
+ }
1602
+ return envelope.value;
1603
+ },
1604
+ async set(key, value) {
1605
+ const payload = JSON.stringify({ value, insertedAt: Date.now() });
1606
+ const options = config.ttlSeconds === void 0 ? void 0 : { EX: config.ttlSeconds };
1607
+ await wrap("set", () => Promise.resolve(client.set(namespaced(key), payload, options)));
1608
+ await enforce();
1609
+ }
1610
+ };
1611
+ };
1612
+ var adaptIoredis = (io) => ({
1613
+ get: (key) => io.get(key),
1614
+ set: (key, value, options) => options?.EX === void 0 ? io.set(key, value) : io.set(key, value, "EX", options.EX),
1615
+ del: (key) => io.del(key),
1616
+ keys: (pattern) => io.keys(pattern)
1617
+ });
1618
+ var tryDefaultRedisClient = async (url) => {
1619
+ try {
1620
+ const moduleId = "redis";
1621
+ const mod = await import(
1622
+ /* @vite-ignore */
1623
+ moduleId
1624
+ );
1625
+ const createClient = mod.createClient;
1626
+ if (typeof createClient !== "function") return void 0;
1627
+ const client = createClient({ url });
1628
+ try {
1629
+ await client.connect();
1630
+ } catch (cause) {
1631
+ throw new core.MemoryError({
1632
+ code: "AK_MEMORY_REDIS_CONNECTION_FAILED",
1633
+ message: `tryDefaultRedisClient: redis connect() failed \u2014 ${cause instanceof Error ? cause.message : String(cause)}`,
1634
+ cause: cause instanceof Error ? cause : void 0
1635
+ });
1636
+ }
1637
+ return client;
1638
+ } catch (err) {
1639
+ if (err instanceof core.MemoryError) throw err;
1640
+ return void 0;
1641
+ }
1642
+ };
1643
+ var createVectorStore = ({
1644
+ config,
1645
+ vectorStore,
1646
+ embedder
1647
+ }) => {
1648
+ const collection = config.collection;
1649
+ const embedOne = async (text) => {
1650
+ const [vec] = await embedder.embed([text]);
1651
+ if (vec === void 0) {
1652
+ throw new core.MemoryError({
1653
+ code: "AK_MEMORY_VECTOR_EMBEDDER_REQUIRED",
1654
+ message: "createVectorStore: embedder returned no vector for the input text."
1655
+ });
1656
+ }
1657
+ return vec;
1658
+ };
1659
+ return {
1660
+ id: `vector:${config.provider}:${collection}`,
1661
+ async get(key) {
1662
+ const vec = await embedOne(key);
1663
+ const hits = await vectorStore.query(vec, 1, { __collection: collection, __key: key });
1664
+ const hit = hits[0];
1665
+ if (hit === void 0) return void 0;
1666
+ if (hit.metadata["__key"] !== key) return void 0;
1667
+ const insertedAt = hit.metadata["__insertedAt"];
1668
+ if (typeof insertedAt === "number" && isExpired({ insertedAt }, config.ttlSeconds, Date.now())) {
1669
+ return void 0;
1670
+ }
1671
+ return hit.metadata["__value"];
1672
+ },
1673
+ async set(key, value) {
1674
+ const vec = await embedOne(key);
1675
+ await vectorStore.upsert([
1676
+ {
1677
+ chunkId: `${collection}:${key}`,
1678
+ vec,
1679
+ metadata: { __collection: collection, __key: key, __value: value, __insertedAt: Date.now() }
1680
+ }
1681
+ ]);
1682
+ },
1683
+ async recall(query, k = 5) {
1684
+ const vec = await embedOne(query);
1685
+ const hits = await vectorStore.query(vec, k, { __collection: collection });
1686
+ const now = Date.now();
1687
+ const results = [];
1688
+ for (const hit of hits) {
1689
+ const insertedAt = hit.metadata["__insertedAt"];
1690
+ if (typeof insertedAt === "number" && isExpired({ insertedAt }, config.ttlSeconds, now)) {
1691
+ continue;
1692
+ }
1693
+ results.push(hit.metadata["__value"]);
1694
+ }
1695
+ return results;
1696
+ }
1697
+ };
1698
+ };
1699
+ var MemoryBackendNotImplementedError = class extends Error {
1700
+ constructor(backend) {
1701
+ super(
1702
+ `Memory backend "${backend}" is not implemented. Supported: "in-memory", "file", "sqlite", "localstorage", "redis", "vector".`
1703
+ );
1704
+ this.code = "MEMORY_BACKEND_NOT_IMPLEMENTED";
1705
+ this.backend = backend;
1706
+ }
1707
+ };
1708
+ var MEMORY_BACKEND_SUPPORT = {
1709
+ "in-memory": "supported",
1710
+ file: "supported",
1711
+ sqlite: "supported",
1712
+ redis: "supported",
1713
+ vector: "supported",
1714
+ localstorage: "supported"
1715
+ };
1716
+ var isMemoryBackendSupported = (backend) => MEMORY_BACKEND_SUPPORT[backend] === "supported";
1717
+ var createKvMemoryFromConfig = ({
1718
+ config,
1719
+ sqlite,
1720
+ localStorageFilePath,
1721
+ redis,
1722
+ vectorStore,
1723
+ embedder
1724
+ }) => {
1725
+ if (config.backend === "in-memory") return createInMemoryStore(config);
1726
+ if (config.backend === "file") return createFileStore(config);
1727
+ if (config.backend === "localstorage") {
1728
+ return createLocalStorageStore({
1729
+ config,
1730
+ ...localStorageFilePath ? { filePath: localStorageFilePath } : {}
1731
+ });
1732
+ }
1733
+ if (config.backend === "sqlite") {
1734
+ if (!sqlite) {
1735
+ throw new core.MemoryError({
1736
+ code: "AK_MEMORY_SQLITE_OPENER_REQUIRED",
1737
+ message: "createKvMemoryFromConfig: sqlite backend requires an `open` function (better-sqlite3), or call createKvMemoryFromConfigAuto which lazy-imports it."
1738
+ });
1739
+ }
1740
+ return createSqliteStore({ config, open: sqlite });
1741
+ }
1742
+ if (config.backend === "redis") {
1743
+ if (!redis) {
1744
+ throw new core.MemoryError({
1745
+ code: "AK_MEMORY_REDIS_CLIENT_REQUIRED",
1746
+ message: "createKvMemoryFromConfig: redis backend requires a `redis` client, or call createKvMemoryFromConfigAuto which lazy-imports it."
1747
+ });
1748
+ }
1749
+ return createRedisStore({ config, client: redis });
1750
+ }
1751
+ if (config.backend === "vector") {
1752
+ if (!vectorStore) {
1753
+ throw new core.MemoryError({
1754
+ code: "AK_MEMORY_VECTOR_STORE_REQUIRED",
1755
+ message: "createKvMemoryFromConfig: vector backend requires a `vectorStore`."
1756
+ });
1757
+ }
1758
+ if (!embedder) {
1759
+ throw new core.MemoryError({
1760
+ code: "AK_MEMORY_VECTOR_EMBEDDER_REQUIRED",
1761
+ message: "createKvMemoryFromConfig: vector backend requires an `embedder`."
1762
+ });
1763
+ }
1764
+ return createVectorStore({ config, vectorStore, embedder });
1765
+ }
1766
+ const exhausted = config;
1767
+ throw new MemoryBackendNotImplementedError(exhausted.backend);
1768
+ };
1769
+ var createKvMemoryFromConfigAuto = async (config) => {
1770
+ if (config.backend === "sqlite") {
1771
+ const sqlite = await tryDefaultSqliteOpener();
1772
+ if (!sqlite) {
1773
+ throw new core.MemoryError({
1774
+ code: "AK_MEMORY_PEER_MISSING",
1775
+ message: "createKvMemoryFromConfigAuto: sqlite backend needs `better-sqlite3` (pnpm add better-sqlite3)."
1776
+ });
1777
+ }
1778
+ return createKvMemoryFromConfig({ config, sqlite });
1779
+ }
1780
+ if (config.backend === "redis") {
1781
+ const redis = await tryDefaultRedisClient(config.url);
1782
+ if (!redis) {
1783
+ throw new core.MemoryError({
1784
+ code: "AK_MEMORY_PEER_MISSING",
1785
+ message: "createKvMemoryFromConfigAuto: redis backend needs `redis` (pnpm add redis)."
1786
+ });
1787
+ }
1788
+ return createKvMemoryFromConfig({ config, redis });
1789
+ }
1790
+ if (config.backend === "vector") {
1791
+ throw new core.MemoryError({
1792
+ code: "AK_MEMORY_VECTOR_STORE_REQUIRED",
1793
+ message: "createKvMemoryFromConfigAuto: vector backend requires an injected vectorStore + embedder."
1794
+ });
1795
+ }
1796
+ return createKvMemoryFromConfig({ config });
1797
+ };
1798
+
1799
+ exports.MEMORY_BACKEND_SUPPORT = MEMORY_BACKEND_SUPPORT;
1800
+ exports.MemoryBackendNotImplementedError = MemoryBackendNotImplementedError;
1801
+ exports.adaptIoredis = adaptIoredis;
1370
1802
  exports.chroma = chroma;
1371
1803
  exports.createEncryptedMemory = createEncryptedMemory;
1804
+ exports.createFileStore = createFileStore;
1372
1805
  exports.createHierarchicalMemory = createHierarchicalMemory;
1373
1806
  exports.createInMemoryGraph = createInMemoryGraph;
1374
1807
  exports.createInMemoryPersonalization = createInMemoryPersonalization;
1808
+ exports.createInMemoryStore = createInMemoryStore;
1809
+ exports.createKvMemoryFromConfig = createKvMemoryFromConfig;
1810
+ exports.createKvMemoryFromConfigAuto = createKvMemoryFromConfigAuto;
1811
+ exports.createLocalStorageStore = createLocalStorageStore;
1812
+ exports.createRedisStore = createRedisStore;
1813
+ exports.createSqliteStore = createSqliteStore;
1814
+ exports.createVectorStore = createVectorStore;
1375
1815
  exports.fileChatMemory = fileChatMemory;
1376
1816
  exports.fileVectorMemory = fileVectorMemory;
1377
1817
  exports.forgetSubject = forgetSubject;
1818
+ exports.isMemoryBackendSupported = isMemoryBackendSupported;
1378
1819
  exports.makeForgettable = makeForgettable;
1379
1820
  exports.matchesFilter = matchesFilter;
1380
1821
  exports.milvusVectorStore = milvusVectorStore;
@@ -1387,6 +1828,8 @@ exports.redisVectorMemory = redisVectorMemory;
1387
1828
  exports.renderProfileContext = renderProfileContext;
1388
1829
  exports.sqliteChatMemory = sqliteChatMemory;
1389
1830
  exports.supabaseVectorStore = supabaseVectorStore;
1831
+ exports.tryDefaultRedisClient = tryDefaultRedisClient;
1832
+ exports.tryDefaultSqliteOpener = tryDefaultSqliteOpener;
1390
1833
  exports.tursoChatMemory = tursoChatMemory;
1391
1834
  exports.upstashVector = upstashVector;
1392
1835
  exports.weaviateVectorStore = weaviateVectorStore;