@ppagent/memory 0.1.2 → 0.3.1

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.
@@ -0,0 +1,585 @@
1
+ // src/db/provider.resolver.ts
2
+ import * as fs2 from "node:fs";
3
+ import * as path2 from "node:path";
4
+
5
+ // src/db/providers/lancedb.provider.ts
6
+ async function lancedbAvailable() {
7
+ try {
8
+ await import("@lancedb/lancedb");
9
+ return true;
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+ function esc(value) {
15
+ return String(value).replace(/'/g, "''");
16
+ }
17
+ function literal(value) {
18
+ if (typeof value === "string") return `'${esc(value)}'`;
19
+ return String(value);
20
+ }
21
+ function filterToSql(filter) {
22
+ if (!filter || filter.length === 0) return void 0;
23
+ const parts = filter.map((c) => {
24
+ switch (c.op) {
25
+ case "eq":
26
+ return `${c.field} = ${literal(c.value)}`;
27
+ case "gt":
28
+ return `${c.field} > ${literal(c.value)}`;
29
+ case "gte":
30
+ return `${c.field} >= ${literal(c.value)}`;
31
+ case "in":
32
+ if (c.values.length === 0) return "1 = 0";
33
+ return `${c.field} IN (${c.values.map((v) => literal(v)).join(", ")})`;
34
+ case "jsonContains": {
35
+ const jsonValue = typeof c.value === "string" ? `"${c.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : String(c.value);
36
+ return `${c.field} LIKE '%${esc(`"${c.key}":${jsonValue}`)}%'`;
37
+ }
38
+ }
39
+ });
40
+ return parts.join(" AND ");
41
+ }
42
+ var LanceDbProvider = class {
43
+ kind = "lancedb";
44
+ path;
45
+ lancedb;
46
+ conn;
47
+ tables = /* @__PURE__ */ new Map();
48
+ defs = /* @__PURE__ */ new Map();
49
+ /**
50
+ * 空表无法创建 btree 标量索引(LanceDB 限制),新建的表记录在此,
51
+ * 首次写入后补建索引。
52
+ */
53
+ pendingIndexTables = /* @__PURE__ */ new Set();
54
+ constructor(opts) {
55
+ this.path = opts.path;
56
+ }
57
+ async init(tables) {
58
+ this.lancedb = await import("@lancedb/lancedb");
59
+ const arrow = await import("apache-arrow");
60
+ this.conn = await this.lancedb.connect(this.path);
61
+ const existing = await this.conn.tableNames();
62
+ for (const def of tables) {
63
+ this.defs.set(def.name, def);
64
+ if (existing.includes(def.name)) {
65
+ const table = await this.conn.openTable(def.name);
66
+ this.tables.set(def.name, table);
67
+ await this.ensureColumns(table, def);
68
+ } else {
69
+ const table = await this.conn.createEmptyTable(def.name, this.buildSchema(arrow, def));
70
+ this.tables.set(def.name, table);
71
+ this.pendingIndexTables.add(def.name);
72
+ }
73
+ await this.ensureIndexes(def.name);
74
+ }
75
+ }
76
+ buildSchema(arrow, def) {
77
+ const { Field, FixedSizeList, Float32, Int32, Int64, Schema, Utf8 } = arrow;
78
+ const fields = def.columns.map((col) => {
79
+ switch (col.type) {
80
+ case "text":
81
+ return new Field(col.name, new Utf8(), col.nullable ?? false);
82
+ case "int":
83
+ return new Field(col.name, new Int32(), col.nullable ?? false);
84
+ case "long":
85
+ return new Field(col.name, new Int64(), col.nullable ?? false);
86
+ case "vector": {
87
+ if (!def.vectorDimension) {
88
+ throw new Error(`Table ${def.name} has vector column but no vectorDimension`);
89
+ }
90
+ return new Field(
91
+ col.name,
92
+ new FixedSizeList(def.vectorDimension, new Field("item", new Float32(), false)),
93
+ col.nullable ?? false
94
+ );
95
+ }
96
+ }
97
+ });
98
+ return new Schema(fields);
99
+ }
100
+ /**
101
+ * schema 演进:为存量表补齐缺失的 nullable 列(如 messages.parts、topics.title)。
102
+ * 旧版本不支持 addColumns 时忽略;读取端映射需自带 ?? 兜底。
103
+ */
104
+ async ensureColumns(table, def) {
105
+ try {
106
+ const schema = await table.schema();
107
+ const existing = new Set(
108
+ (schema.fields ?? []).map((f) => f.name)
109
+ );
110
+ const missing = def.columns.filter((c) => c.nullable && !existing.has(c.name));
111
+ if (missing.length === 0) return;
112
+ await table.addColumns(
113
+ missing.map((c) => ({
114
+ name: c.name,
115
+ valueSql: c.type === "text" ? "CAST(NULL AS STRING)" : "CAST(NULL AS BIGINT)"
116
+ }))
117
+ );
118
+ } catch {
119
+ }
120
+ }
121
+ /**
122
+ * 按需补齐索引:先经 listIndices 判存在,缺失的列才 createIndex(且 replace:false)。
123
+ * createIndex 默认 replace:true 会在每次启动时全量重建索引并提交新表版本——
124
+ * 这正是历史上「启动越来越慢 + _versions 目录膨胀」的根源,绝不可回退到无条件 createIndex。
125
+ */
126
+ async ensureIndexes(tableName) {
127
+ const table = this.mustTable(tableName);
128
+ const def = this.defs.get(tableName);
129
+ let indexed;
130
+ try {
131
+ indexed = new Set((await table.listIndices()).flatMap((i) => i.columns));
132
+ } catch {
133
+ indexed = /* @__PURE__ */ new Set();
134
+ }
135
+ for (const spec of def.indexes) {
136
+ if (spec.kind === "vector") continue;
137
+ if (indexed.has(spec.column)) continue;
138
+ try {
139
+ await table.createIndex(
140
+ spec.column,
141
+ spec.kind === "fts" ? { config: this.lancedb.Index.fts(), replace: false } : { replace: false }
142
+ );
143
+ } catch {
144
+ }
145
+ }
146
+ }
147
+ mustTable(name) {
148
+ const table = this.tables.get(name);
149
+ if (!table) throw new Error(`Table not initialized: ${name}`);
150
+ return table;
151
+ }
152
+ async add(table, rows) {
153
+ if (rows.length === 0) return;
154
+ await this.mustTable(table).add(rows);
155
+ if (this.pendingIndexTables.has(table)) {
156
+ await this.ensureIndexes(table);
157
+ this.pendingIndexTables.delete(table);
158
+ }
159
+ }
160
+ async update(table, values, filter) {
161
+ const where = filterToSql(filter);
162
+ if (!where) throw new Error(`update on ${table} requires a non-empty filter`);
163
+ await this.mustTable(table).update({
164
+ values,
165
+ where
166
+ });
167
+ }
168
+ async deleteWhere(table, filter) {
169
+ const where = filterToSql(filter);
170
+ if (!where) throw new Error(`deleteWhere on ${table} requires a non-empty filter`);
171
+ await this.mustTable(table).delete(where);
172
+ }
173
+ async query(table, opts) {
174
+ const q = this.mustTable(table).query();
175
+ const where = filterToSql(opts?.filter);
176
+ if (where) q.where(where);
177
+ if (opts?.select) q.select(opts.select);
178
+ if (opts?.orderBy?.length) {
179
+ q.orderBy(opts.orderBy.map((o) => ({ columnName: o.column, ascending: o.ascending })));
180
+ }
181
+ if (opts?.limit !== void 0) q.limit(opts.limit);
182
+ return await q.toArray();
183
+ }
184
+ async vectorSearch(table, vector, opts) {
185
+ const q = this.mustTable(table).vectorSearch(vector).limit(opts.limit);
186
+ const where = filterToSql(opts.filter);
187
+ if (where) q.where(where);
188
+ const rows = await q.toArray();
189
+ return rows.map((r) => ({ ...r, _distance: Number(r._distance) }));
190
+ }
191
+ async ftsSearch(table, query, opts) {
192
+ const q = this.mustTable(table).query().fullTextSearch(query).limit(opts.limit);
193
+ const where = filterToSql(opts.filter);
194
+ if (where) q.where(where);
195
+ const rows = await q.toArray();
196
+ return rows.map((r) => ({ ...r, _score: Number(r._score ?? 0) }));
197
+ }
198
+ async count(table, filter) {
199
+ return this.mustTable(table).countRows(filterToSql(filter));
200
+ }
201
+ /**
202
+ * 存储压实:逐表执行碎片合并 + 清理 retentionMs 之前的历史版本。
203
+ * 嵌入式场景下 LanceDB 不会自动做这件事,长期运行后版本/碎片无限累积会显著拖慢启动与查询。
204
+ */
205
+ async optimize(retentionMs) {
206
+ const cutoff = new Date(Date.now() - Math.max(0, retentionMs));
207
+ const results = [];
208
+ for (const [name, table] of this.tables) {
209
+ try {
210
+ const stats = await table.optimize({ cleanupOlderThan: cutoff });
211
+ results.push({
212
+ table: name,
213
+ fragmentsRemoved: stats.compaction.fragmentsRemoved,
214
+ fragmentsAdded: stats.compaction.fragmentsAdded,
215
+ filesRemoved: stats.compaction.filesRemoved,
216
+ oldVersionsRemoved: stats.prune.oldVersionsRemoved,
217
+ bytesRemoved: Number(stats.prune.bytesRemoved)
218
+ });
219
+ } catch (err) {
220
+ console.warn(`[LanceDbProvider] optimize table "${name}" failed:`, err);
221
+ }
222
+ }
223
+ return results;
224
+ }
225
+ capabilities() {
226
+ return { fts: true, ann: false, optimize: true };
227
+ }
228
+ async close() {
229
+ try {
230
+ this.conn?.close();
231
+ } catch {
232
+ }
233
+ this.tables.clear();
234
+ }
235
+ };
236
+
237
+ // src/db/providers/sqlite.provider.ts
238
+ import Database from "better-sqlite3";
239
+ import * as sqliteVec from "sqlite-vec";
240
+ import { cut_for_search } from "jieba-wasm";
241
+ import * as path from "node:path";
242
+ import * as fs from "node:fs";
243
+ function tokenizeForIndex(text) {
244
+ if (!text) return "";
245
+ return cut_for_search(text, true).map((t) => t.trim()).filter(Boolean).join(" ");
246
+ }
247
+ function tokenizeForQuery(text) {
248
+ const tokens = cut_for_search(text, true).map((t) => t.trim()).filter(Boolean);
249
+ return tokens.map((t) => `"${t.replace(/"/g, '""')}"`).join(" OR ");
250
+ }
251
+ function escLike(s) {
252
+ return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
253
+ }
254
+ function filterToWhere(filter, columnPrefix = "") {
255
+ if (!filter || filter.length === 0) return { sql: "", params: [] };
256
+ const parts = [];
257
+ const params = [];
258
+ for (const c of filter) {
259
+ const col = `${columnPrefix}"${c.field}"`;
260
+ switch (c.op) {
261
+ case "eq":
262
+ parts.push(`${col} = ?`);
263
+ params.push(c.value);
264
+ break;
265
+ case "gt":
266
+ parts.push(`${col} > ?`);
267
+ params.push(c.value);
268
+ break;
269
+ case "gte":
270
+ parts.push(`${col} >= ?`);
271
+ params.push(c.value);
272
+ break;
273
+ case "in":
274
+ if (c.values.length === 0) {
275
+ parts.push("0 = 1");
276
+ } else {
277
+ parts.push(`${col} IN (${c.values.map(() => "?").join(", ")})`);
278
+ params.push(...c.values);
279
+ }
280
+ break;
281
+ case "jsonContains": {
282
+ const jsonValue = typeof c.value === "string" ? `"${c.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : String(c.value);
283
+ parts.push(`${col} LIKE ? ESCAPE '\\'`);
284
+ params.push(`%${escLike(`"${c.key}":${jsonValue}`)}%`);
285
+ break;
286
+ }
287
+ }
288
+ }
289
+ return { sql: parts.join(" AND "), params };
290
+ }
291
+ function vectorToBlob(v) {
292
+ return Buffer.from(new Float32Array(v).buffer);
293
+ }
294
+ function blobToVector(b) {
295
+ if (Array.isArray(b)) return b;
296
+ const buf = b;
297
+ return Array.from(new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4));
298
+ }
299
+ var SqliteProvider = class {
300
+ kind = "sqlite";
301
+ dbPath;
302
+ db;
303
+ defs = /* @__PURE__ */ new Map();
304
+ /** 表名 → fts 列名列表(无 fts 索引的表不在此 map) */
305
+ ftsColumns = /* @__PURE__ */ new Map();
306
+ constructor(opts) {
307
+ this.dbPath = opts.path;
308
+ }
309
+ async init(tables) {
310
+ fs.mkdirSync(path.dirname(this.dbPath), { recursive: true });
311
+ this.db = new Database(this.dbPath);
312
+ sqliteVec.load(this.db);
313
+ this.db.pragma("journal_mode = WAL");
314
+ this.db.pragma("synchronous = NORMAL");
315
+ for (const def of tables) {
316
+ this.defs.set(def.name, def);
317
+ this.createOrMigrateTable(def);
318
+ }
319
+ }
320
+ createOrMigrateTable(def) {
321
+ const colSql = (c) => {
322
+ const type = c.type === "text" ? "TEXT" : c.type === "vector" ? "BLOB" : "INTEGER";
323
+ return `"${c.name}" ${type}`;
324
+ };
325
+ this.db.exec(
326
+ `CREATE TABLE IF NOT EXISTS "${def.name}" (${def.columns.map(colSql).join(", ")})`
327
+ );
328
+ const existing = new Set(
329
+ this.db.pragma(`table_info("${def.name}")`).map((r) => r.name)
330
+ );
331
+ for (const c of def.columns) {
332
+ if (!existing.has(c.name)) {
333
+ this.db.exec(`ALTER TABLE "${def.name}" ADD COLUMN ${colSql(c)}`);
334
+ }
335
+ }
336
+ for (const idx of def.indexes) {
337
+ if (idx.kind === "scalar") {
338
+ this.db.exec(
339
+ `CREATE INDEX IF NOT EXISTS "idx_${def.name}_${idx.column}" ON "${def.name}"("${idx.column}")`
340
+ );
341
+ }
342
+ }
343
+ const ftsCols = def.indexes.filter((i) => i.kind === "fts").map((i) => i.column);
344
+ if (ftsCols.length > 0) {
345
+ this.ftsColumns.set(def.name, ftsCols);
346
+ this.db.exec(
347
+ `CREATE VIRTUAL TABLE IF NOT EXISTS "${def.name}_fts" USING fts5(${ftsCols.map((c) => `"${c}"`).join(", ")})`
348
+ );
349
+ }
350
+ }
351
+ mustDef(table) {
352
+ const def = this.defs.get(table);
353
+ if (!def) throw new Error(`Table not initialized: ${table}`);
354
+ return def;
355
+ }
356
+ async add(table, rows) {
357
+ if (rows.length === 0) return;
358
+ const def = this.mustDef(table);
359
+ const cols = def.columns.map((c) => c.name);
360
+ const insert = this.db.prepare(
361
+ `INSERT INTO "${table}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`
362
+ );
363
+ const ftsCols = this.ftsColumns.get(table);
364
+ const insertFts = ftsCols ? this.db.prepare(
365
+ `INSERT INTO "${table}_fts" (rowid, ${ftsCols.map((c) => `"${c}"`).join(", ")}) VALUES (${[
366
+ "?",
367
+ ...ftsCols.map(() => "?")
368
+ ].join(", ")})`
369
+ ) : null;
370
+ const encode = (colName, row) => {
371
+ const def0 = def.columns.find((c) => c.name === colName);
372
+ const v = row[colName];
373
+ if (v === void 0 || v === null) return null;
374
+ if (def0.type === "vector") return vectorToBlob(v);
375
+ return v;
376
+ };
377
+ this.db.transaction(() => {
378
+ for (const row of rows) {
379
+ const info = insert.run(...cols.map((c) => encode(c, row)));
380
+ if (insertFts && ftsCols) {
381
+ insertFts.run(
382
+ info.lastInsertRowid,
383
+ ...ftsCols.map((c) => tokenizeForIndex(String(row[c] ?? "")))
384
+ );
385
+ }
386
+ }
387
+ })();
388
+ }
389
+ async update(table, values, filter) {
390
+ const def = this.mustDef(table);
391
+ const where = filterToWhere(filter);
392
+ if (!where.sql) throw new Error(`update on ${table} requires a non-empty filter`);
393
+ const cols = Object.keys(values);
394
+ const encode = (colName) => {
395
+ const cd = def.columns.find((c) => c.name === colName);
396
+ const v = values[colName];
397
+ if (v === void 0 || v === null) return null;
398
+ if (cd?.type === "vector") return vectorToBlob(v);
399
+ return v;
400
+ };
401
+ const ftsCols = this.ftsColumns.get(table);
402
+ const touchedFts = ftsCols?.filter((c) => cols.includes(c)) ?? [];
403
+ this.db.transaction(() => {
404
+ this.db.prepare(
405
+ `UPDATE "${table}" SET ${cols.map((c) => `"${c}" = ?`).join(", ")} WHERE ${where.sql}`
406
+ ).run(...cols.map(encode), ...where.params);
407
+ if (touchedFts.length > 0) {
408
+ const rowids = this.db.prepare(`SELECT rowid FROM "${table}" WHERE ${where.sql}`).all(...where.params);
409
+ const upd = this.db.prepare(
410
+ `UPDATE "${table}_fts" SET ${touchedFts.map((c) => `"${c}" = ?`).join(", ")} WHERE rowid = ?`
411
+ );
412
+ for (const { rowid } of rowids) {
413
+ upd.run(...touchedFts.map((c) => tokenizeForIndex(String(values[c] ?? ""))), rowid);
414
+ }
415
+ }
416
+ })();
417
+ }
418
+ async deleteWhere(table, filter) {
419
+ this.mustDef(table);
420
+ const where = filterToWhere(filter);
421
+ if (!where.sql) throw new Error(`deleteWhere on ${table} requires a non-empty filter`);
422
+ const hasFts = this.ftsColumns.has(table);
423
+ this.db.transaction(() => {
424
+ if (hasFts) {
425
+ this.db.prepare(
426
+ `DELETE FROM "${table}_fts" WHERE rowid IN (SELECT rowid FROM "${table}" WHERE ${where.sql})`
427
+ ).run(...where.params);
428
+ }
429
+ this.db.prepare(`DELETE FROM "${table}" WHERE ${where.sql}`).run(...where.params);
430
+ })();
431
+ }
432
+ async query(table, opts) {
433
+ const def = this.mustDef(table);
434
+ const select = opts?.select?.length ? opts.select.map((c) => `"${c}"`).join(", ") : "*";
435
+ const where = filterToWhere(opts?.filter);
436
+ let sql = `SELECT ${select} FROM "${table}"`;
437
+ if (where.sql) sql += ` WHERE ${where.sql}`;
438
+ if (opts?.orderBy?.length) {
439
+ sql += ` ORDER BY ${opts.orderBy.map((o) => `"${o.column}" ${o.ascending ? "ASC" : "DESC"}`).join(", ")}`;
440
+ }
441
+ if (opts?.limit !== void 0) sql += ` LIMIT ${Math.max(0, Math.floor(opts.limit))}`;
442
+ const rows = this.db.prepare(sql).all(...where.params);
443
+ return rows.map((r) => this.decodeRow(def, r));
444
+ }
445
+ async vectorSearch(table, vector, opts) {
446
+ const def = this.mustDef(table);
447
+ if (!def.vectorDimension) throw new Error(`Table ${table} has no vector column`);
448
+ const where = filterToWhere(opts.filter);
449
+ let sql = `SELECT *, vec_distance_l2("vector", ?) AS _distance FROM "${table}"`;
450
+ if (where.sql) sql += ` WHERE ${where.sql}`;
451
+ sql += ` ORDER BY _distance ASC LIMIT ${Math.max(0, Math.floor(opts.limit))}`;
452
+ const rows = this.db.prepare(sql).all(vectorToBlob(vector), ...where.params);
453
+ return rows.map((r) => {
454
+ const decoded = this.decodeRow(def, r);
455
+ const d = Number(r._distance);
456
+ decoded._distance = d * d;
457
+ return decoded;
458
+ });
459
+ }
460
+ async ftsSearch(table, query, opts) {
461
+ const def = this.mustDef(table);
462
+ if (!this.ftsColumns.has(table)) return [];
463
+ const match = tokenizeForQuery(query);
464
+ if (!match) return [];
465
+ const where = filterToWhere(opts.filter, `"${table}".`);
466
+ let sql = `SELECT "${table}".*, -bm25("${table}_fts") AS _score FROM "${table}_fts" JOIN "${table}" ON "${table}".rowid = "${table}_fts".rowid WHERE "${table}_fts" MATCH ?`;
467
+ if (where.sql) sql += ` AND ${where.sql}`;
468
+ sql += ` ORDER BY bm25("${table}_fts") LIMIT ${Math.max(0, Math.floor(opts.limit))}`;
469
+ const rows = this.db.prepare(sql).all(match, ...where.params);
470
+ return rows.map((r) => {
471
+ const decoded = this.decodeRow(def, r);
472
+ decoded._score = Number(r._score);
473
+ return decoded;
474
+ });
475
+ }
476
+ async count(table, filter) {
477
+ this.mustDef(table);
478
+ const where = filterToWhere(filter);
479
+ let sql = `SELECT count(*) AS c FROM "${table}"`;
480
+ if (where.sql) sql += ` WHERE ${where.sql}`;
481
+ const row = this.db.prepare(sql).get(...where.params);
482
+ return Number(row.c);
483
+ }
484
+ /** SQLite 无版本概念:checkpoint WAL + 查询计划统计刷新,返回空结果集 */
485
+ async optimize(_retentionMs) {
486
+ try {
487
+ this.db.pragma("wal_checkpoint(TRUNCATE)");
488
+ this.db.pragma("optimize");
489
+ } catch (err) {
490
+ console.warn("[SqliteProvider] optimize failed:", err);
491
+ }
492
+ return [];
493
+ }
494
+ capabilities() {
495
+ return { fts: true, ann: false, optimize: false };
496
+ }
497
+ async close() {
498
+ try {
499
+ this.db?.close();
500
+ } catch {
501
+ }
502
+ }
503
+ decodeRow(def, r) {
504
+ const out = { ...r };
505
+ for (const c of def.columns) {
506
+ if (c.type === "vector" && out[c.name] != null) {
507
+ out[c.name] = blobToVector(out[c.name]);
508
+ }
509
+ }
510
+ return out;
511
+ }
512
+ };
513
+
514
+ // src/db/provider.resolver.ts
515
+ function markerPath(config) {
516
+ return path2.join(path2.dirname(config.lancedbPath), ".memory-provider");
517
+ }
518
+ function readMarker(config) {
519
+ try {
520
+ const v = fs2.readFileSync(markerPath(config), "utf-8").trim();
521
+ if (v === "lancedb" || v === "sqlite") return v;
522
+ } catch {
523
+ }
524
+ return void 0;
525
+ }
526
+ function writeMarker(config, kind) {
527
+ try {
528
+ fs2.mkdirSync(path2.dirname(markerPath(config)), { recursive: true });
529
+ fs2.writeFileSync(markerPath(config), kind, "utf-8");
530
+ } catch (err) {
531
+ console.warn("[memory] \u5199\u5165 provider marker \u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u8FD0\u884C\uFF09:", err);
532
+ }
533
+ }
534
+ function lanceDataExists(config) {
535
+ try {
536
+ return fs2.readdirSync(config.lancedbPath).some((f) => f.endsWith(".lance"));
537
+ } catch {
538
+ return false;
539
+ }
540
+ }
541
+ async function resolveProviderKind(config) {
542
+ const requested = config.provider;
543
+ if (requested !== "auto") {
544
+ const marker2 = readMarker(config);
545
+ if (marker2 && marker2 !== requested) {
546
+ console.warn(
547
+ `[memory] \u663E\u5F0F\u914D\u7F6E provider=${requested} \u4E0E\u6B64\u524D\u4F7F\u7528\u7684 ${marker2} \u4E0D\u4E00\u81F4\uFF0C\u4E24\u4E2A\u540E\u7AEF\u7684\u6570\u636E\u4E92\u4E0D\u76F8\u901A\uFF0C\u5982\u9700\u4FDD\u7559\u65E7\u6570\u636E\u8BF7\u5148\u5BFC\u51FA\u8FC1\u79FB\u3002`
548
+ );
549
+ }
550
+ writeMarker(config, requested);
551
+ return requested;
552
+ }
553
+ const detected = await lancedbAvailable() ? "lancedb" : "sqlite";
554
+ const marker = readMarker(config);
555
+ if (!marker) {
556
+ if (detected === "sqlite" && lanceDataExists(config)) {
557
+ console.warn(
558
+ `[memory] \u68C0\u6D4B\u5230\u5B58\u91CF LanceDB \u6570\u636E\uFF08${config.lancedbPath}\uFF09\uFF0C\u4F46\u672C\u673A\u65E0\u6CD5\u52A0\u8F7D @lancedb/lancedb \u539F\u751F\u7ED1\u5B9A\uFF08\u5982 Intel Mac \u65E0\u9884\u7F16\u8BD1\u4E8C\u8FDB\u5236\uFF09\uFF0C\u5C06\u6539\u7528 sqlite \u540E\u7AEF\u4ECE\u7A7A\u5E93\u5F00\u59CB\u3002\u65E7\u6570\u636E\u4E0D\u4F1A\u4E22\u5931\u4F46\u5728\u672C\u673A\u4E0D\u53EF\u8BFB\uFF0C\u53EF\u5728\u652F\u6301 lancedb \u7684\u673A\u5668\u4E0A\u5BFC\u51FA\u8FC1\u79FB\u3002`
559
+ );
560
+ }
561
+ writeMarker(config, detected);
562
+ return detected;
563
+ }
564
+ if (marker === detected) return detected;
565
+ if (marker === "sqlite") {
566
+ console.info(
567
+ "[memory] \u672C\u673A\u53EF\u52A0\u8F7D lancedb\uFF0C\u4F46\u6570\u636E\u76EE\u5F55\u6B64\u524D\u4F7F\u7528 sqlite \u540E\u7AEF\uFF0C\u4E3A\u4FDD\u6570\u636E\u8FDE\u7EED\u6027\u7EE7\u7EED\u4F7F\u7528 sqlite\u3002\u5982\u9700\u5207\u6362\u8BF7\u663E\u5F0F\u914D\u7F6E provider \u5E76\u81EA\u884C\u8FC1\u79FB\u6570\u636E\u3002"
568
+ );
569
+ return "sqlite";
570
+ }
571
+ throw new Error(
572
+ `[memory] \u6570\u636E\u76EE\u5F55\u6B64\u524D\u4F7F\u7528 lancedb \u540E\u7AEF\uFF0C\u4F46\u672C\u673A\u65E0\u6CD5\u52A0\u8F7D @lancedb/lancedb \u539F\u751F\u7ED1\u5B9A\uFF08Intel Mac / musl \u7B49\u65E0\u9884\u7F16\u8BD1\u4E8C\u8FDB\u5236\u7684\u5E73\u53F0\uFF09\u3002\u4E3A\u907F\u514D\u9759\u9ED8\u6253\u5F00\u7A7A\u5E93\u9020\u6210\u300C\u8BB0\u5FC6\u4E22\u5931\u300D\u7684\u5047\u8C61\uFF0C\u5DF2\u4E2D\u6B62\u542F\u52A8\u3002\u53EF\u9009\u5904\u7406\uFF1A1) \u5728\u652F\u6301 lancedb \u7684\u673A\u5668\u4E0A\u5BFC\u51FA\u6570\u636E\u540E\u8FC1\u79FB\uFF1B2) \u663E\u5F0F\u914D\u7F6E provider="sqlite" \u4ECE\u7A7A\u5E93\u5F00\u59CB\uFF1B3) \u4FEE\u590D lancedb \u5B89\u88C5\uFF08\u68C0\u67E5 Node \u67B6\u6784\u4E0E optional dependencies\uFF09\u3002`
573
+ );
574
+ }
575
+ async function createProvider(config) {
576
+ const kind = await resolveProviderKind(config);
577
+ if (kind === "lancedb") {
578
+ return new LanceDbProvider({ path: config.lancedbPath });
579
+ }
580
+ return new SqliteProvider({ path: config.sqlitePath });
581
+ }
582
+ export {
583
+ createProvider,
584
+ resolveProviderKind
585
+ };