opencode-mem 2.11.8 → 2.11.10

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.
Files changed (35) hide show
  1. package/README.md +8 -12
  2. package/dist/config.d.ts +1 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +2 -0
  5. package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -1
  6. package/dist/services/ai/providers/anthropic-messages.js +1 -0
  7. package/dist/services/ai/providers/base-provider.d.ts +1 -0
  8. package/dist/services/ai/providers/base-provider.d.ts.map +1 -1
  9. package/dist/services/api-handlers.js +6 -6
  10. package/dist/services/cleanup-service.js +1 -1
  11. package/dist/services/client.js +1 -1
  12. package/dist/services/deduplication-service.js +1 -1
  13. package/dist/services/migration-service.js +3 -3
  14. package/dist/services/sqlite/shard-manager.d.ts +1 -1
  15. package/dist/services/sqlite/shard-manager.d.ts.map +1 -1
  16. package/dist/services/sqlite/shard-manager.js +12 -1
  17. package/dist/services/sqlite/vector-search.d.ts +8 -4
  18. package/dist/services/sqlite/vector-search.d.ts.map +1 -1
  19. package/dist/services/sqlite/vector-search.js +107 -44
  20. package/dist/services/vector-backends/backend-factory.d.ts +3 -0
  21. package/dist/services/vector-backends/backend-factory.d.ts.map +1 -0
  22. package/dist/services/vector-backends/backend-factory.js +104 -0
  23. package/dist/services/vector-backends/exact-scan-backend.d.ts +39 -0
  24. package/dist/services/vector-backends/exact-scan-backend.d.ts.map +1 -0
  25. package/dist/services/vector-backends/exact-scan-backend.js +63 -0
  26. package/dist/services/vector-backends/types.d.ts +51 -0
  27. package/dist/services/vector-backends/types.d.ts.map +1 -0
  28. package/dist/services/vector-backends/types.js +1 -0
  29. package/dist/services/vector-backends/usearch-backend.d.ts +47 -0
  30. package/dist/services/vector-backends/usearch-backend.d.ts.map +1 -0
  31. package/dist/services/vector-backends/usearch-backend.js +174 -0
  32. package/package.json +3 -3
  33. package/dist/services/sqlite/hnsw-index.d.ts +0 -37
  34. package/dist/services/sqlite/hnsw-index.d.ts.map +0 -1
  35. package/dist/services/sqlite/hnsw-index.js +0 -235
@@ -1,235 +0,0 @@
1
- import { mkdirSync, existsSync, writeFileSync, unlinkSync, readdirSync } from "node:fs";
2
- import { join, dirname, basename } from "node:path";
3
- import { log } from "../logger.js";
4
- import { CONFIG } from "../../config.js";
5
- let HNSWLib = null;
6
- async function loadHNSWLib() {
7
- if (!HNSWLib) {
8
- // hnswlib-wasm is compiled with Emscripten -sENVIRONMENT=web and requires
9
- // a browser-like global. This monkey-patch allows it to load in Node.js/Bun.
10
- if (typeof globalThis.window === "undefined") {
11
- globalThis.window = globalThis;
12
- }
13
- const { loadHnswlib } = await import("hnswlib-wasm");
14
- HNSWLib = await loadHnswlib();
15
- }
16
- return HNSWLib;
17
- }
18
- export class HNSWIndex {
19
- index = null;
20
- idMap = new Map();
21
- reverseMap = new Map();
22
- nextId = 0;
23
- dimensions;
24
- indexPath;
25
- maxElements = 50000;
26
- initialized = false;
27
- constructor(dimensions, indexPath) {
28
- this.dimensions = dimensions;
29
- this.indexPath = indexPath;
30
- }
31
- async ensureInitialized() {
32
- if (this.initialized)
33
- return;
34
- const hnsw = await loadHNSWLib();
35
- const dir = dirname(this.indexPath);
36
- if (!existsSync(dir)) {
37
- mkdirSync(dir, { recursive: true });
38
- }
39
- // hnswlib-wasm uses Emscripten MEMFS (in-memory virtual FS) and has no
40
- // exported FS API for bridging to real filesystem. HNSW indexes are kept
41
- // purely in-memory and rebuilt from SQLite vectors on process restart.
42
- // Constructor requires 3 args: (spaceName, numDimensions, autoSaveFilename)
43
- this.index = new hnsw.HierarchicalNSW("cosine", this.dimensions, "");
44
- // initIndex requires 4 args: (maxElements, m, efConstruction, randomSeed)
45
- this.index.initIndex(this.maxElements, 16, 200, 100);
46
- this.initialized = true;
47
- log("HNSW index initialized (in-memory)", {
48
- path: this.indexPath,
49
- dimensions: this.dimensions,
50
- });
51
- }
52
- async insert(id, vector) {
53
- await this.ensureInitialized();
54
- if (this.reverseMap.has(id)) {
55
- const internalId = this.reverseMap.get(id);
56
- this.index.markDelete(internalId);
57
- }
58
- const internalId = this.nextId++;
59
- // hnswlib-wasm addPoint requires 3 args: (point, label, replaceDeleted)
60
- this.index.addPoint(vector, internalId, false);
61
- this.idMap.set(internalId, id);
62
- this.reverseMap.set(id, internalId);
63
- await this.save();
64
- }
65
- async insertBatch(items) {
66
- await this.ensureInitialized();
67
- for (const item of items) {
68
- if (this.reverseMap.has(item.id)) {
69
- const internalId = this.reverseMap.get(item.id);
70
- this.index.markDelete(internalId);
71
- }
72
- const internalId = this.nextId++;
73
- // hnswlib-wasm addPoint requires 3 args: (point, label, replaceDeleted)
74
- this.index.addPoint(item.vector, internalId, false);
75
- this.idMap.set(internalId, item.id);
76
- this.reverseMap.set(item.id, internalId);
77
- }
78
- await this.save();
79
- }
80
- async search(queryVector, k) {
81
- await this.ensureInitialized();
82
- try {
83
- // hnswlib-wasm searchKnn requires 3 args: (queryPoint, numNeighbors, filter)
84
- const actualK = Math.min(k, this.reverseMap.size);
85
- if (actualK === 0)
86
- return [];
87
- const results = this.index.searchKnn(queryVector, actualK, null);
88
- return results.neighbors
89
- .map((internalId, idx) => ({
90
- id: this.idMap.get(internalId) || "",
91
- distance: results.distances[idx] ?? 0,
92
- }))
93
- .filter((r) => r.id);
94
- }
95
- catch (error) {
96
- log("HNSW search error", { error: String(error) });
97
- return [];
98
- }
99
- }
100
- async delete(id) {
101
- await this.ensureInitialized();
102
- if (this.reverseMap.has(id)) {
103
- const internalId = this.reverseMap.get(id);
104
- this.index.markDelete(internalId);
105
- this.idMap.delete(internalId);
106
- this.reverseMap.delete(id);
107
- await this.save();
108
- }
109
- }
110
- async save() {
111
- if (!this.index)
112
- return;
113
- const dir = dirname(this.indexPath);
114
- if (!existsSync(dir)) {
115
- mkdirSync(dir, { recursive: true });
116
- }
117
- // Only persist id mapping (.meta file). HNSW index data lives in-memory
118
- // and is rebuilt from SQLite vectors on process restart.
119
- const metaPath = this.indexPath + ".meta";
120
- const meta = {
121
- nextId: this.nextId,
122
- idMap: Object.fromEntries(this.idMap),
123
- reverseMap: Object.fromEntries(this.reverseMap),
124
- };
125
- writeFileSync(metaPath, JSON.stringify(meta));
126
- }
127
- getCount() {
128
- return this.reverseMap.size;
129
- }
130
- isPopulated() {
131
- return this.reverseMap.size > 0;
132
- }
133
- }
134
- export class HNSWIndexManager {
135
- indexes = new Map();
136
- baseDir;
137
- constructor(baseDir) {
138
- this.baseDir = baseDir;
139
- if (!existsSync(baseDir)) {
140
- mkdirSync(baseDir, { recursive: true });
141
- }
142
- }
143
- getIndex(scope, scopeHash, shardIndex) {
144
- const key = `${scope}_${scopeHash}_${shardIndex}`;
145
- if (!this.indexes.has(key)) {
146
- const indexPath = join(this.baseDir, scope + "s", `${key}.hnsw`);
147
- this.indexes.set(key, new HNSWIndex(CONFIG.embeddingDimensions, indexPath));
148
- }
149
- return this.indexes.get(key);
150
- }
151
- getTagsIndex(scope, scopeHash, shardIndex) {
152
- const key = `${scope}_${scopeHash}_${shardIndex}_tags`;
153
- if (!this.indexes.has(key)) {
154
- const indexPath = join(this.baseDir, scope + "s", `${key}.hnsw`);
155
- this.indexes.set(key, new HNSWIndex(CONFIG.embeddingDimensions, indexPath));
156
- }
157
- return this.indexes.get(key);
158
- }
159
- async rebuildFromShard(db, scope, scopeHash, shardIndex) {
160
- const contentIndex = this.getIndex(scope, scopeHash, shardIndex);
161
- const tagsIndex = this.getTagsIndex(scope, scopeHash, shardIndex);
162
- const rows = db.prepare("SELECT id, vector, tags_vector FROM memories").all();
163
- const contentItems = [];
164
- const tagsItems = [];
165
- for (const row of rows) {
166
- if (row.vector) {
167
- const vector = new Float32Array(row.vector.buffer);
168
- contentItems.push({ id: row.id, vector });
169
- }
170
- if (row.tags_vector) {
171
- const tagsVector = new Float32Array(row.tags_vector.buffer);
172
- tagsItems.push({ id: row.id, vector: tagsVector });
173
- }
174
- }
175
- if (contentItems.length > 0) {
176
- await contentIndex.insertBatch(contentItems);
177
- }
178
- if (tagsItems.length > 0) {
179
- await tagsIndex.insertBatch(tagsItems);
180
- }
181
- log("HNSW indexes rebuilt", {
182
- scope,
183
- scopeHash,
184
- shardIndex,
185
- content: contentItems.length,
186
- tags: tagsItems.length,
187
- });
188
- }
189
- async deleteIndex(scope, scopeHash, shardIndex) {
190
- const contentKey = `${scope}_${scopeHash}_${shardIndex}`;
191
- const tagsKey = `${scope}_${scopeHash}_${shardIndex}_tags`;
192
- this.indexes.delete(contentKey);
193
- this.indexes.delete(tagsKey);
194
- for (const key of [contentKey, tagsKey]) {
195
- const indexPath = join(this.baseDir, scope + "s", `${key}.hnsw`);
196
- const metaPath = indexPath + ".meta";
197
- try {
198
- if (existsSync(indexPath))
199
- unlinkSync(indexPath);
200
- if (existsSync(metaPath))
201
- unlinkSync(metaPath);
202
- }
203
- catch (error) {
204
- log("Error deleting HNSW index files", { path: indexPath, error: String(error) });
205
- }
206
- }
207
- }
208
- async cleanupOrphanedIndexes(validKeys) {
209
- const scopeDirs = ["users", "projects"];
210
- for (const scopeDir of scopeDirs) {
211
- const dir = join(this.baseDir, scopeDir);
212
- if (!existsSync(dir))
213
- continue;
214
- const files = readdirSync(dir);
215
- for (const file of files) {
216
- if (file.endsWith(".hnsw")) {
217
- const key = basename(file, ".hnsw");
218
- if (!validKeys.has(key)) {
219
- const indexPath = join(dir, file);
220
- const metaPath = indexPath + ".meta";
221
- try {
222
- unlinkSync(indexPath);
223
- if (existsSync(metaPath))
224
- unlinkSync(metaPath);
225
- log("Removed orphaned HNSW index", { path: indexPath });
226
- }
227
- catch (error) {
228
- log("Error removing orphaned index", { path: indexPath, error: String(error) });
229
- }
230
- }
231
- }
232
- }
233
- }
234
- }
235
- }