@memstack/core 0.4.0 → 0.5.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
@@ -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
- for (let i = 0; i < normalInputs.length; i += EMBEDDING_BATCH_SIZE) {
453
- const batch = normalInputs.slice(i, i + EMBEDDING_BATCH_SIZE);
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 > 2048) {
3636
- throw new MemStackError("VALIDATION_ERROR", `Max 2048 texts per batch, got ${texts.length}`);
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 > 96) {
3692
- throw new MemStackError("VALIDATION_ERROR", `Cohere max 96 texts per batch, got ${texts.length}`);
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;