@memstack/core 0.4.0 → 0.6.4
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/README.md +213 -49
- package/dist/index.cjs +268 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.js +268 -8
- package/dist/index.js.map +1 -1
- package/package.json +21 -18
package/dist/index.cjs
CHANGED
|
@@ -360,7 +360,6 @@ var ContextCompiler = class {
|
|
|
360
360
|
};
|
|
361
361
|
|
|
362
362
|
// src/memory/MemoryStore.ts
|
|
363
|
-
var EMBEDDING_BATCH_SIZE = 2048;
|
|
364
363
|
var MemoryStore = class {
|
|
365
364
|
storage;
|
|
366
365
|
embedding;
|
|
@@ -449,8 +448,9 @@ var MemoryStore = class {
|
|
|
449
448
|
let stored;
|
|
450
449
|
if (this.embedding && this.embedOnStore) {
|
|
451
450
|
const enrichedResults = [];
|
|
452
|
-
|
|
453
|
-
|
|
451
|
+
const batchSize = this.embedding.maxBatchSize ?? 2048;
|
|
452
|
+
for (let i = 0; i < normalInputs.length; i += batchSize) {
|
|
453
|
+
const batch = normalInputs.slice(i, i + batchSize);
|
|
454
454
|
const texts = batch.map((inp) => inp.content);
|
|
455
455
|
const embeddings = await this.embedding.embed(texts);
|
|
456
456
|
const enriched = batch.map((input, j) => ({ ...input, embedding: embeddings[j] }));
|
|
@@ -3438,6 +3438,264 @@ var MongoDBStorageAdapter = class {
|
|
|
3438
3438
|
}
|
|
3439
3439
|
};
|
|
3440
3440
|
|
|
3441
|
+
// src/adapters/storage/sqlite.ts
|
|
3442
|
+
function cosineSimilarity(a, b) {
|
|
3443
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
3444
|
+
const dims = Math.min(a.length, b.length);
|
|
3445
|
+
let dot = 0;
|
|
3446
|
+
let magA = 0;
|
|
3447
|
+
let magB = 0;
|
|
3448
|
+
for (let i = 0; i < dims; i++) {
|
|
3449
|
+
dot += a[i] * b[i];
|
|
3450
|
+
magA += a[i] * a[i];
|
|
3451
|
+
magB += b[i] * b[i];
|
|
3452
|
+
}
|
|
3453
|
+
const mag = Math.sqrt(magA) * Math.sqrt(magB);
|
|
3454
|
+
return mag === 0 ? 0 : dot / mag;
|
|
3455
|
+
}
|
|
3456
|
+
var SQLiteStorageAdapter = class {
|
|
3457
|
+
db;
|
|
3458
|
+
table;
|
|
3459
|
+
vectorDimensions;
|
|
3460
|
+
constructor(config) {
|
|
3461
|
+
if (!config.db) {
|
|
3462
|
+
throw configError("SQLiteStorageAdapter requires a better-sqlite3 database instance");
|
|
3463
|
+
}
|
|
3464
|
+
this.db = config.db;
|
|
3465
|
+
this.table = config.tableName ?? "memstack_memories";
|
|
3466
|
+
if (!/^[a-zA-Z0-9_]+$/.test(this.table)) {
|
|
3467
|
+
throw configError(`Invalid table name: "${this.table}". Use only alphanumeric characters and underscores.`);
|
|
3468
|
+
}
|
|
3469
|
+
this.vectorDimensions = config.vectorDimensions ?? 1536;
|
|
3470
|
+
}
|
|
3471
|
+
async initialize() {
|
|
3472
|
+
this.db.exec(`
|
|
3473
|
+
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
3474
|
+
id TEXT PRIMARY KEY,
|
|
3475
|
+
actor_id TEXT NOT NULL,
|
|
3476
|
+
memory_type TEXT NOT NULL DEFAULT 'interaction',
|
|
3477
|
+
content TEXT NOT NULL,
|
|
3478
|
+
importance REAL NOT NULL DEFAULT 0.5,
|
|
3479
|
+
emotional_valence REAL NOT NULL DEFAULT 0,
|
|
3480
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
3481
|
+
embedding TEXT,
|
|
3482
|
+
source_id TEXT,
|
|
3483
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
3484
|
+
expires_at TEXT,
|
|
3485
|
+
created_at TEXT NOT NULL,
|
|
3486
|
+
touched_at TEXT NOT NULL
|
|
3487
|
+
)
|
|
3488
|
+
`);
|
|
3489
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${this.table}_actor ON ${this.table}(actor_id)`);
|
|
3490
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${this.table}_created ON ${this.table}(created_at DESC)`);
|
|
3491
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_${this.table}_importance ON ${this.table}(importance DESC)`);
|
|
3492
|
+
}
|
|
3493
|
+
generateId() {
|
|
3494
|
+
return `mem_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`;
|
|
3495
|
+
}
|
|
3496
|
+
async store(input) {
|
|
3497
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3498
|
+
const id = input.id ?? this.generateId();
|
|
3499
|
+
this.db.prepare(
|
|
3500
|
+
`INSERT INTO ${this.table} (id, actor_id, memory_type, content, importance, emotional_valence, tags, embedding, source_id, metadata, expires_at, created_at, touched_at)
|
|
3501
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3502
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
3503
|
+
content = excluded.content,
|
|
3504
|
+
importance = excluded.importance,
|
|
3505
|
+
emotional_valence = excluded.emotional_valence,
|
|
3506
|
+
tags = excluded.tags,
|
|
3507
|
+
embedding = excluded.embedding,
|
|
3508
|
+
source_id = excluded.source_id,
|
|
3509
|
+
metadata = excluded.metadata,
|
|
3510
|
+
expires_at = excluded.expires_at,
|
|
3511
|
+
touched_at = excluded.touched_at`
|
|
3512
|
+
).run(
|
|
3513
|
+
id,
|
|
3514
|
+
input.actorId,
|
|
3515
|
+
input.memoryType ?? "interaction",
|
|
3516
|
+
input.content,
|
|
3517
|
+
input.importance ?? 0.5,
|
|
3518
|
+
input.emotionalValence ?? 0,
|
|
3519
|
+
JSON.stringify(input.tags ?? []),
|
|
3520
|
+
input.embedding ? JSON.stringify(input.embedding) : null,
|
|
3521
|
+
input.sourceId ?? null,
|
|
3522
|
+
JSON.stringify(input.metadata ?? {}),
|
|
3523
|
+
input.expiresAt?.toISOString() ?? null,
|
|
3524
|
+
now,
|
|
3525
|
+
now
|
|
3526
|
+
);
|
|
3527
|
+
return {
|
|
3528
|
+
id,
|
|
3529
|
+
actorId: input.actorId,
|
|
3530
|
+
memoryType: input.memoryType ?? "interaction",
|
|
3531
|
+
content: input.content,
|
|
3532
|
+
importance: input.importance ?? 0.5,
|
|
3533
|
+
emotionalValence: input.emotionalValence ?? 0,
|
|
3534
|
+
tags: input.tags ?? [],
|
|
3535
|
+
embedding: input.embedding,
|
|
3536
|
+
sourceId: input.sourceId,
|
|
3537
|
+
metadata: input.metadata ?? {},
|
|
3538
|
+
expiresAt: input.expiresAt,
|
|
3539
|
+
createdAt: new Date(now)
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
async storeBatch(inputs) {
|
|
3543
|
+
const results = [];
|
|
3544
|
+
for (const input of inputs) {
|
|
3545
|
+
results.push(await this.store(input));
|
|
3546
|
+
}
|
|
3547
|
+
return results;
|
|
3548
|
+
}
|
|
3549
|
+
async get(id) {
|
|
3550
|
+
const row = this.db.prepare(
|
|
3551
|
+
`SELECT * FROM ${this.table} WHERE id = ? AND (expires_at IS NULL OR expires_at > ?)`
|
|
3552
|
+
).get(id, (/* @__PURE__ */ new Date()).toISOString());
|
|
3553
|
+
if (!row) return null;
|
|
3554
|
+
return this._rowToMemory(row);
|
|
3555
|
+
}
|
|
3556
|
+
async delete(id) {
|
|
3557
|
+
const row = this.db.prepare(`SELECT id FROM ${this.table} WHERE id = ?`).get(id);
|
|
3558
|
+
if (!row) throw notFound("Memory", id);
|
|
3559
|
+
this.db.prepare(`DELETE FROM ${this.table} WHERE id = ?`).run(id);
|
|
3560
|
+
}
|
|
3561
|
+
async deleteMany(ids) {
|
|
3562
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
3563
|
+
const result = this.db.prepare(
|
|
3564
|
+
`DELETE FROM ${this.table} WHERE id IN (${placeholders})`
|
|
3565
|
+
).run(...ids);
|
|
3566
|
+
return result.changes;
|
|
3567
|
+
}
|
|
3568
|
+
async touch(id) {
|
|
3569
|
+
const row = this.db.prepare(`SELECT id FROM ${this.table} WHERE id = ?`).get(id);
|
|
3570
|
+
if (!row) throw notFound("Memory", id);
|
|
3571
|
+
this.db.prepare(`UPDATE ${this.table} SET touched_at = ? WHERE id = ?`).run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
3572
|
+
}
|
|
3573
|
+
async retrieve(query, embedding) {
|
|
3574
|
+
const conditions = ["(expires_at IS NULL OR expires_at > ?)"];
|
|
3575
|
+
const params = [(/* @__PURE__ */ new Date()).toISOString()];
|
|
3576
|
+
if (query.actorId) {
|
|
3577
|
+
conditions.push("actor_id = ?");
|
|
3578
|
+
params.push(query.actorId);
|
|
3579
|
+
}
|
|
3580
|
+
if (query.memoryTypes && query.memoryTypes.length > 0) {
|
|
3581
|
+
const placeholders = query.memoryTypes.map(() => "?").join(", ");
|
|
3582
|
+
conditions.push(`memory_type IN (${placeholders})`);
|
|
3583
|
+
params.push(...query.memoryTypes);
|
|
3584
|
+
}
|
|
3585
|
+
if (query.createdAfter) {
|
|
3586
|
+
conditions.push("created_at >= ?");
|
|
3587
|
+
params.push(query.createdAfter.toISOString());
|
|
3588
|
+
}
|
|
3589
|
+
if (query.createdBefore) {
|
|
3590
|
+
conditions.push("created_at <= ?");
|
|
3591
|
+
params.push(query.createdBefore.toISOString());
|
|
3592
|
+
}
|
|
3593
|
+
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
3594
|
+
const hasSemantic = (query.strategy === "semantic" || query.strategy === "hybrid") && embedding && embedding.length > 0;
|
|
3595
|
+
let orderBy;
|
|
3596
|
+
switch (query.strategy) {
|
|
3597
|
+
case "recent":
|
|
3598
|
+
orderBy = "touched_at DESC";
|
|
3599
|
+
break;
|
|
3600
|
+
case "important":
|
|
3601
|
+
orderBy = "importance DESC";
|
|
3602
|
+
break;
|
|
3603
|
+
case "hybrid":
|
|
3604
|
+
case "semantic":
|
|
3605
|
+
default:
|
|
3606
|
+
if (hasSemantic) {
|
|
3607
|
+
orderBy = "touched_at DESC";
|
|
3608
|
+
} else {
|
|
3609
|
+
orderBy = "importance DESC, touched_at DESC";
|
|
3610
|
+
}
|
|
3611
|
+
break;
|
|
3612
|
+
}
|
|
3613
|
+
const limit = query.limit ?? 10;
|
|
3614
|
+
const sql = `SELECT * FROM ${this.table} ${where} ORDER BY ${orderBy}`;
|
|
3615
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
3616
|
+
let results = rows;
|
|
3617
|
+
if (query.query) {
|
|
3618
|
+
const q = query.query.toLowerCase();
|
|
3619
|
+
results = results.filter((r) => r.content.toLowerCase().includes(q));
|
|
3620
|
+
}
|
|
3621
|
+
if (query.tags && query.tags.length > 0) {
|
|
3622
|
+
results = results.filter((r) => {
|
|
3623
|
+
const tags = JSON.parse(r.tags);
|
|
3624
|
+
return tags.some((t) => query.tags.includes(t));
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
if (hasSemantic && embedding) {
|
|
3628
|
+
const scored = results.filter((r) => r.embedding !== null).map((r) => {
|
|
3629
|
+
const emb = JSON.parse(r.embedding);
|
|
3630
|
+
const score = cosineSimilarity(embedding, emb);
|
|
3631
|
+
return { row: r, score };
|
|
3632
|
+
}).sort((a, b) => b.score - a.score);
|
|
3633
|
+
if (query.strategy === "hybrid") {
|
|
3634
|
+
scored.sort((a, b) => b.score + b.row.importance - (a.score + a.row.importance));
|
|
3635
|
+
}
|
|
3636
|
+
const topK = scored.slice(0, limit);
|
|
3637
|
+
for (const { row } of topK) {
|
|
3638
|
+
this.db.prepare(`UPDATE ${this.table} SET touched_at = ? WHERE id = ?`).run((/* @__PURE__ */ new Date()).toISOString(), row.id);
|
|
3639
|
+
}
|
|
3640
|
+
return topK.map(({ row }) => this._rowToMemory(row));
|
|
3641
|
+
}
|
|
3642
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3643
|
+
const limited = results.slice(0, limit);
|
|
3644
|
+
for (const row of limited) {
|
|
3645
|
+
this.db.prepare(`UPDATE ${this.table} SET touched_at = ? WHERE id = ?`).run(now, row.id);
|
|
3646
|
+
}
|
|
3647
|
+
return limited.map((r) => this._rowToMemory(r));
|
|
3648
|
+
}
|
|
3649
|
+
async count(filter) {
|
|
3650
|
+
const conditions = ["(expires_at IS NULL OR expires_at > ?)"];
|
|
3651
|
+
const params = [(/* @__PURE__ */ new Date()).toISOString()];
|
|
3652
|
+
if (filter?.actorId) {
|
|
3653
|
+
conditions.push("actor_id = ?");
|
|
3654
|
+
params.push(filter.actorId);
|
|
3655
|
+
}
|
|
3656
|
+
if (filter?.memoryType) {
|
|
3657
|
+
conditions.push("memory_type = ?");
|
|
3658
|
+
params.push(filter.memoryType);
|
|
3659
|
+
}
|
|
3660
|
+
if (filter?.minImportance !== void 0) {
|
|
3661
|
+
conditions.push("importance >= ?");
|
|
3662
|
+
params.push(filter.minImportance);
|
|
3663
|
+
}
|
|
3664
|
+
if (filter?.createdAfter) {
|
|
3665
|
+
conditions.push("created_at >= ?");
|
|
3666
|
+
params.push(filter.createdAfter.toISOString());
|
|
3667
|
+
}
|
|
3668
|
+
if (filter?.createdBefore) {
|
|
3669
|
+
conditions.push("created_at <= ?");
|
|
3670
|
+
params.push(filter.createdBefore.toISOString());
|
|
3671
|
+
}
|
|
3672
|
+
const where = conditions.join(" AND ");
|
|
3673
|
+
const row = this.db.prepare(
|
|
3674
|
+
`SELECT COUNT(*) as count FROM ${this.table} WHERE ${where}`
|
|
3675
|
+
).get(...params);
|
|
3676
|
+
return row?.count ?? 0;
|
|
3677
|
+
}
|
|
3678
|
+
async close() {
|
|
3679
|
+
this.db.close();
|
|
3680
|
+
}
|
|
3681
|
+
_rowToMemory(row) {
|
|
3682
|
+
return {
|
|
3683
|
+
id: row.id,
|
|
3684
|
+
actorId: row.actor_id,
|
|
3685
|
+
memoryType: row.memory_type,
|
|
3686
|
+
content: row.content,
|
|
3687
|
+
importance: row.importance,
|
|
3688
|
+
emotionalValence: row.emotional_valence,
|
|
3689
|
+
tags: JSON.parse(row.tags),
|
|
3690
|
+
embedding: row.embedding ? JSON.parse(row.embedding) : void 0,
|
|
3691
|
+
sourceId: row.source_id ?? void 0,
|
|
3692
|
+
metadata: JSON.parse(row.metadata),
|
|
3693
|
+
expiresAt: row.expires_at ? new Date(row.expires_at) : void 0,
|
|
3694
|
+
createdAt: new Date(row.created_at)
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
};
|
|
3698
|
+
|
|
3441
3699
|
// src/adapters/llm/openai.ts
|
|
3442
3700
|
var OpenAILLMAdapter = class {
|
|
3443
3701
|
apiKey;
|
|
@@ -3624,6 +3882,7 @@ var OpenAIEmbeddingAdapter = class {
|
|
|
3624
3882
|
baseURL;
|
|
3625
3883
|
model;
|
|
3626
3884
|
dimensions;
|
|
3885
|
+
maxBatchSize = 2048;
|
|
3627
3886
|
constructor(config) {
|
|
3628
3887
|
this.apiKey = config.apiKey;
|
|
3629
3888
|
this.baseURL = config.baseURL ?? "https://api.openai.com/v1";
|
|
@@ -3632,8 +3891,8 @@ var OpenAIEmbeddingAdapter = class {
|
|
|
3632
3891
|
}
|
|
3633
3892
|
async embed(texts) {
|
|
3634
3893
|
if (texts.length === 0) return [];
|
|
3635
|
-
if (texts.length >
|
|
3636
|
-
throw new MemStackError("VALIDATION_ERROR", `Max
|
|
3894
|
+
if (texts.length > this.maxBatchSize) {
|
|
3895
|
+
throw new MemStackError("VALIDATION_ERROR", `Max ${this.maxBatchSize} texts per batch, got ${texts.length}`);
|
|
3637
3896
|
}
|
|
3638
3897
|
try {
|
|
3639
3898
|
const response = await fetch(`${this.baseURL}/embeddings`, {
|
|
@@ -3680,6 +3939,7 @@ var CohereEmbeddingAdapter = class {
|
|
|
3680
3939
|
baseURL;
|
|
3681
3940
|
model;
|
|
3682
3941
|
dimensions;
|
|
3942
|
+
maxBatchSize = 96;
|
|
3683
3943
|
constructor(config) {
|
|
3684
3944
|
this.apiKey = config.apiKey;
|
|
3685
3945
|
this.baseURL = config.baseURL ?? "https://api.cohere.ai/v1";
|
|
@@ -3688,8 +3948,8 @@ var CohereEmbeddingAdapter = class {
|
|
|
3688
3948
|
}
|
|
3689
3949
|
async embed(texts) {
|
|
3690
3950
|
if (texts.length === 0) return [];
|
|
3691
|
-
if (texts.length >
|
|
3692
|
-
throw new MemStackError("VALIDATION_ERROR", `Cohere max
|
|
3951
|
+
if (texts.length > this.maxBatchSize) {
|
|
3952
|
+
throw new MemStackError("VALIDATION_ERROR", `Cohere max ${this.maxBatchSize} texts per batch, got ${texts.length}`);
|
|
3693
3953
|
}
|
|
3694
3954
|
try {
|
|
3695
3955
|
const response = await fetch(`${this.baseURL}/embed`, {
|
|
@@ -3932,6 +4192,7 @@ exports.OpenAILLMAdapter = OpenAILLMAdapter;
|
|
|
3932
4192
|
exports.PostgresStorageAdapter = PostgresStorageAdapter;
|
|
3933
4193
|
exports.QdrantStorageAdapter = QdrantStorageAdapter;
|
|
3934
4194
|
exports.RedisStorageAdapter = RedisStorageAdapter;
|
|
4195
|
+
exports.SQLiteStorageAdapter = SQLiteStorageAdapter;
|
|
3935
4196
|
exports.WeaviateStorageAdapter = WeaviateStorageAdapter;
|
|
3936
4197
|
exports.configError = configError;
|
|
3937
4198
|
exports.notFound = notFound;
|