@h-ai/vecdb 1.0.0-beta.2 → 1.0.0-beta.21

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.js CHANGED
@@ -38,7 +38,8 @@ var en_US_default = {
38
38
  vecdb_configDatabaseRequired: "Database name is required",
39
39
  vecdb_sharedBackendRequired: "Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.",
40
40
  vecdb_chromaEndpointInvalid: "Chroma requires an HTTP(S) root endpoint without credentials, path prefix, query or fragment",
41
- vecdb_chromaTokenInvalid: "Chroma Bearer token must contain only printable ASCII characters without whitespace"
41
+ vecdb_chromaTokenInvalid: "Chroma Bearer token must contain only printable ASCII characters without whitespace",
42
+ vecdb_collectionMetricMissing: "Collection distance metric is missing; restore its vector index or column metadata"
42
43
  };
43
44
 
44
45
  // messages/ja-JP.json
@@ -71,7 +72,8 @@ var ja_JP_default = {
71
72
  vecdb_configDatabaseRequired: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u540D\u306F\u5FC5\u9808\u3067\u3059",
72
73
  vecdb_sharedBackendRequired: "\u672C\u756A\u74B0\u5883\u306B\u306F\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u304C\u5FC5\u8981\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u3068\u30E1\u30E2\u30EA\u30FC\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u306F\u958B\u767A\u74B0\u5883\u307E\u305F\u306F\u660E\u793A\u7684\u306A E2E \u30C6\u30B9\u30C8\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002",
73
74
  vecdb_chromaEndpointInvalid: "Chroma \u306B\u306F HTTP(S) \u306E\u30EB\u30FC\u30C8\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u8A8D\u8A3C\u60C5\u5831\u3001\u30D1\u30B9\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u3001\u30AF\u30A8\u30EA\u3001\u30D5\u30E9\u30B0\u30E1\u30F3\u30C8\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093",
74
- vecdb_chromaTokenInvalid: "Chroma Bearer \u30C8\u30FC\u30AF\u30F3\u306B\u306F\u7A7A\u767D\u3092\u542B\u307E\u306A\u3044\u8868\u793A\u53EF\u80FD\u306A ASCII \u6587\u5B57\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059"
75
+ vecdb_chromaTokenInvalid: "Chroma Bearer \u30C8\u30FC\u30AF\u30F3\u306B\u306F\u7A7A\u767D\u3092\u542B\u307E\u306A\u3044\u8868\u793A\u53EF\u80FD\u306A ASCII \u6587\u5B57\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059",
76
+ vecdb_collectionMetricMissing: "\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u306E\u8DDD\u96E2\u5C3A\u5EA6\u304C\u3042\u308A\u307E\u305B\u3093\u3002\u30D9\u30AF\u30C8\u30EB\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u307E\u305F\u306F\u5217\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u5FA9\u5143\u3057\u3066\u304F\u3060\u3055\u3044"
75
77
  };
76
78
 
77
79
  // messages/zh-CN.json
@@ -104,7 +106,8 @@ var zh_CN_default = {
104
106
  vecdb_configDatabaseRequired: "\u6570\u636E\u5E93\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
105
107
  vecdb_sharedBackendRequired: "\u751F\u4EA7\u6A21\u5F0F\u9700\u8981\u8DE8\u8282\u70B9\u5171\u4EAB\u540E\u7AEF\uFF1B\u672C\u673A\u6587\u4EF6\u6216\u5185\u5B58\u540E\u7AEF\u4EC5\u7528\u4E8E\u5F00\u53D1\u548C\u663E\u5F0F\u7AEF\u5230\u7AEF\u6D4B\u8BD5\u3002",
106
108
  vecdb_chromaEndpointInvalid: "Chroma \u4EC5\u652F\u6301 HTTP(S) \u6839\u7AEF\u70B9\uFF0C\u4E0D\u5F97\u5305\u542B\u51ED\u636E\u3001\u8DEF\u5F84\u524D\u7F00\u3001query \u6216 fragment",
107
- vecdb_chromaTokenInvalid: "Chroma Bearer token \u4EC5\u652F\u6301\u65E0\u7A7A\u767D\u7684\u53EF\u6253\u5370 ASCII \u5B57\u7B26"
109
+ vecdb_chromaTokenInvalid: "Chroma Bearer token \u4EC5\u652F\u6301\u65E0\u7A7A\u767D\u7684\u53EF\u6253\u5370 ASCII \u5B57\u7B26",
110
+ vecdb_collectionMetricMissing: "\u96C6\u5408\u7F3A\u5C11\u6709\u6548\u8DDD\u79BB\u7B97\u6CD5\u5143\u6570\u636E\uFF0C\u8BF7\u6838\u5BF9\u5E76\u6062\u590D\u5411\u91CF\u7D22\u5F15\u6216\u5217\u6CE8\u91CA"
108
111
  };
109
112
 
110
113
  // src/vecdb-i18n.ts
@@ -178,13 +181,17 @@ var ChromaConfigSchema = z.object({
178
181
  } catch {
179
182
  return false;
180
183
  }
181
- }, { error: () => vecdbM("vecdb_chromaEndpointInvalid") }).optional(),
184
+ }, {
185
+ error: () => vecdbM("vecdb_chromaEndpointInvalid")
186
+ }).optional(),
182
187
  /** 主机(默认 localhost) */
183
188
  host: z.string().default("localhost"),
184
189
  /** 端口(默认 8000) */
185
190
  port: z.number().int().min(1).max(65535).default(8e3),
186
191
  /** 可选 Bearer token,发送 Authorization 请求头;用于采用此协议的自托管服务/网关,不是 Chroma Cloud 配置。空串表示未配置。 */
187
- apiKey: z.string().regex(/^[\x21-\x7E]*$/, { error: () => vecdbM("vecdb_chromaTokenInvalid") }).optional(),
192
+ apiKey: z.string().regex(/^[\x21-\x7E]*$/, {
193
+ error: () => vecdbM("vecdb_chromaTokenInvalid")
194
+ }).optional(),
188
195
  /** 拉起本地服务的可执行命令(默认 'chroma',可指定绝对路径) */
189
196
  serverCommand: z.string().default("chroma"),
190
197
  /** 拉起本地服务的就绪等待超时(毫秒,默认 30000) */
@@ -216,6 +223,13 @@ var VecdbErrorInfo = {
216
223
  SERIALIZATION_FAILED: "014:500"
217
224
  };
218
225
  var HaiVecdbError = core.error.buildHaiErrorsDef("vecdb", VecdbErrorInfo);
226
+ function vectorScoreFromDistance(distance, metric) {
227
+ if (metric === "cosine")
228
+ return Math.max(0, Math.min(1, 1 - distance / 2));
229
+ if (metric === "dot")
230
+ return 1 / (1 + Math.exp(distance));
231
+ return 1 / (1 + Math.max(0, distance));
232
+ }
219
233
  function stringifyOperationPayload(payload, maxLength) {
220
234
  if (maxLength <= 0) {
221
235
  return "";
@@ -250,13 +264,21 @@ function logVecdbOperation(ctx, category, operation, payload) {
250
264
  function errorMsgFromCode(def, errorStr) {
251
265
  switch (def) {
252
266
  case HaiVecdbError.DELETE_FAILED:
253
- return vecdbM("vecdb_deleteFailed", { params: { error: errorStr } });
267
+ return vecdbM("vecdb_deleteFailed", {
268
+ params: { error: errorStr }
269
+ });
254
270
  case HaiVecdbError.INSERT_FAILED:
255
- return vecdbM("vecdb_insertFailed", { params: { error: errorStr } });
271
+ return vecdbM("vecdb_insertFailed", {
272
+ params: { error: errorStr }
273
+ });
256
274
  case HaiVecdbError.UPDATE_FAILED:
257
- return vecdbM("vecdb_updateFailed", { params: { error: errorStr } });
275
+ return vecdbM("vecdb_updateFailed", {
276
+ params: { error: errorStr }
277
+ });
258
278
  default:
259
- return vecdbM("vecdb_queryFailed", { params: { error: errorStr } });
279
+ return vecdbM("vecdb_queryFailed", {
280
+ params: { error: errorStr }
281
+ });
260
282
  }
261
283
  }
262
284
  async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
@@ -276,7 +298,11 @@ function createBaseCollectionOps(ctx, driver) {
276
298
  if (!Number.isInteger(options.dimension) || options.dimension <= 0) {
277
299
  return err(
278
300
  HaiVecdbError.DIMENSION_MISMATCH,
279
- vecdbM("vecdb_invalidDimension", { params: { dimension: String(options.dimension) } })
301
+ vecdbM("vecdb_invalidDimension", {
302
+ params: {
303
+ dimension: String(options.dimension)
304
+ }
305
+ })
280
306
  );
281
307
  }
282
308
  logVecdbOperation(ctx, "write", "collection.create", { name, options });
@@ -413,9 +439,7 @@ function fromChromaSpace(space) {
413
439
  return "cosine";
414
440
  }
415
441
  function distanceToScore(distance, metric) {
416
- if (metric === "cosine")
417
- return Math.max(0, Math.min(1, 1 - distance));
418
- return 1 / (1 + Math.max(0, distance));
442
+ return vectorScoreFromDistance(metric === "dot" ? distance - 1 : distance, metric);
419
443
  }
420
444
  function buildWhere(filter) {
421
445
  if (!filter)
@@ -423,7 +447,9 @@ function buildWhere(filter) {
423
447
  const entries = Object.entries(filter);
424
448
  if (entries.length === 0)
425
449
  return void 0;
426
- const where = entries.length === 1 ? { [entries[0][0]]: entries[0][1] } : { $and: entries.map(([key, value]) => ({ [key]: value })) };
450
+ const where = entries.length === 1 ? { [entries[0][0]]: entries[0][1] } : {
451
+ $and: entries.map(([key, value]) => ({ [key]: value }))
452
+ };
427
453
  return where;
428
454
  }
429
455
  function createChromaProvider() {
@@ -442,19 +468,33 @@ function createChromaProvider() {
442
468
  if (!isChromaModule(mod)) {
443
469
  const error = new Error("chromadb driver module does not export ChromaClient");
444
470
  logger.error("Failed to load Chroma driver", { error: error.message });
445
- return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "chromadb" } }), error);
471
+ return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
472
+ params: { driver: "chromadb" }
473
+ }), error);
446
474
  }
447
475
  return ok(mod);
448
476
  } catch (error) {
449
477
  if (isMissingModuleError(error)) {
450
- logger.error("Failed to load Chroma driver", { error: error instanceof Error ? error.message : String(error) });
451
- return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "chromadb" } }), error);
478
+ logger.error("Failed to load Chroma driver", {
479
+ error: error instanceof Error ? error.message : String(error)
480
+ });
481
+ return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
482
+ params: { driver: "chromadb" }
483
+ }), error);
452
484
  }
453
485
  throw error;
454
486
  }
455
487
  }
456
488
  async function startEmbeddedServer(chromaConfig) {
457
- const args = ["run", "--path", chromaConfig.path ?? "./data/chroma", "--host", chromaConfig.host, "--port", String(chromaConfig.port)];
489
+ const args = [
490
+ "run",
491
+ "--path",
492
+ chromaConfig.path ?? "./data/chroma",
493
+ "--host",
494
+ chromaConfig.host,
495
+ "--port",
496
+ String(chromaConfig.port)
497
+ ];
458
498
  logger.info("Starting embedded Chroma server", { command: chromaConfig.serverCommand, host: chromaConfig.host, port: chromaConfig.port });
459
499
  spawnFailed = false;
460
500
  const proc = spawn(chromaConfig.serverCommand, args, { stdio: "ignore" });
@@ -533,7 +573,16 @@ function createChromaProvider() {
533
573
  logger.info("Chroma connection closed");
534
574
  return ok(void 0);
535
575
  } catch (cause) {
536
- return err({ ...HaiVecdbError.CONNECTION_FAILED, message: vecdbM("vecdb_closeFailed", { params: { error: cause instanceof Error ? cause.message : String(cause) } }), cause, ext: { cleanupPending: true } });
576
+ return err({
577
+ ...HaiVecdbError.CONNECTION_FAILED,
578
+ message: vecdbM("vecdb_closeFailed", {
579
+ params: {
580
+ error: cause instanceof Error ? cause.message : String(cause)
581
+ }
582
+ }),
583
+ cause,
584
+ ext: { cleanupPending: true }
585
+ });
537
586
  }
538
587
  })().finally(() => {
539
588
  closing = null;
@@ -567,21 +616,36 @@ function createChromaProvider() {
567
616
  return null;
568
617
  return err(
569
618
  HaiVecdbError.DIMENSION_MISMATCH,
570
- vecdbM("vecdb_dimensionMismatch", { params: { expected: String(expected), actual: String(actual) } })
619
+ vecdbM("vecdb_dimensionMismatch", {
620
+ params: {
621
+ expected: String(expected),
622
+ actual: String(actual)
623
+ }
624
+ })
571
625
  );
572
626
  }
573
- const ctx = { isConnected: () => client !== null, logger, operationLog: () => config?.operationLog };
627
+ const ctx = {
628
+ isConnected: () => client !== null,
629
+ logger,
630
+ operationLog: () => config?.operationLog
631
+ };
574
632
  const collectionDriver = {
575
633
  async create(name, options) {
576
634
  logger.debug("Creating collection", { name, dimension: options.dimension, metric: options.metric });
577
635
  const existing = await client.listCollections();
578
636
  const exists = existing.some((item) => item.name === name);
579
- if (exists)
580
- return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", { params: { name } }));
637
+ if (exists) {
638
+ return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
639
+ params: { name }
640
+ }));
641
+ }
581
642
  const metric = options.metric ?? config?.metric ?? "cosine";
582
643
  await client.getOrCreateCollection({
583
644
  name,
584
- metadata: { [SPACE_METADATA_KEY]: toChromaSpace(metric), [DIMENSION_METADATA_KEY]: options.dimension },
645
+ metadata: {
646
+ [SPACE_METADATA_KEY]: toChromaSpace(metric),
647
+ [DIMENSION_METADATA_KEY]: options.dimension
648
+ },
585
649
  embeddingFunction: NOOP_EMBEDDING_FUNCTION
586
650
  });
587
651
  logger.info("Collection created", { name, dimension: options.dimension, metric });
@@ -591,8 +655,11 @@ function createChromaProvider() {
591
655
  logger.debug("Dropping collection", { name });
592
656
  const existing = await client.listCollections();
593
657
  const exists = existing.some((item) => item.name === name);
594
- if (!exists)
595
- return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
658
+ if (!exists) {
659
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
660
+ params: { name }
661
+ }));
662
+ }
596
663
  await client.deleteCollection({ name });
597
664
  logger.info("Collection dropped", { name });
598
665
  return ok(void 0);
@@ -605,8 +672,11 @@ function createChromaProvider() {
605
672
  async info(name) {
606
673
  logger.debug("Getting collection info", { name });
607
674
  const existing = await client.listCollections();
608
- if (!existing.some((item) => item.name === name))
609
- return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
675
+ if (!existing.some((item) => item.name === name)) {
676
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
677
+ params: { name }
678
+ }));
679
+ }
610
680
  const collection = await getCollection(name);
611
681
  const count = await collection.count();
612
682
  return ok({
@@ -676,7 +746,11 @@ function createChromaProvider() {
676
746
  if (validation)
677
747
  return validation;
678
748
  const metric = fromChromaSpace(target.metadata?.[SPACE_METADATA_KEY]);
679
- const response = await target.query({ queryEmbeddings: [vector], nResults: topK, where: buildWhere(options?.filter) });
749
+ const response = await target.query({
750
+ queryEmbeddings: [vector],
751
+ nResults: topK,
752
+ where: buildWhere(options?.filter)
753
+ });
680
754
  const ids = response.ids[0] ?? [];
681
755
  const distances = response.distances?.[0] ?? [];
682
756
  const metadatas = response.metadatas?.[0] ?? [];
@@ -705,8 +779,11 @@ function createChromaProvider() {
705
779
  return {
706
780
  name: "chroma",
707
781
  async connect(cfg) {
708
- if (cfg.type !== "chroma")
709
- return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", { params: { type: cfg.type } }));
782
+ if (cfg.type !== "chroma") {
783
+ return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
784
+ params: { type: cfg.type }
785
+ }));
786
+ }
710
787
  if (serverProcess || client || closing) {
711
788
  const cleaned = await closeResources();
712
789
  if (!cleaned.success)
@@ -724,7 +801,11 @@ function createChromaProvider() {
724
801
  if (embedded)
725
802
  await startEmbeddedServer(chromaConfig);
726
803
  const endpoint = chromaConfig.url ? new URL(chromaConfig.url) : void 0;
727
- const clientParams = endpoint ? { host: endpoint.hostname, port: Number(endpoint.port || (endpoint.protocol === "https:" ? 443 : 80)), ssl: endpoint.protocol === "https:" } : { host: chromaConfig.host, port: chromaConfig.port, ssl: false };
804
+ const clientParams = endpoint ? {
805
+ host: endpoint.hostname,
806
+ port: Number(endpoint.port || (endpoint.protocol === "https:" ? 443 : 80)),
807
+ ssl: endpoint.protocol === "https:"
808
+ } : { host: chromaConfig.host, port: chromaConfig.port, ssl: false };
728
809
  if (chromaConfig.apiKey)
729
810
  clientParams.headers = { Authorization: `Bearer ${chromaConfig.apiKey}` };
730
811
  clientParams.fetchOptions = { signal: startupAbort.signal };
@@ -736,14 +817,28 @@ function createChromaProvider() {
736
817
  throw new Error("Chroma child exited before readiness was confirmed");
737
818
  client = chromaClient;
738
819
  config = chromaConfig;
739
- logger.info("Chroma connected", { embedded, host: clientParams.host, port: clientParams.port, ssl: clientParams.ssl });
820
+ logger.info("Chroma connected", {
821
+ embedded,
822
+ host: clientParams.host,
823
+ port: clientParams.port,
824
+ ssl: clientParams.ssl
825
+ });
740
826
  return ok(void 0);
741
827
  } catch (error) {
742
828
  startupAbort.abort();
743
829
  const cleanup = await closeResources();
744
- logger.error("Failed to connect to Chroma", { error: error instanceof Error ? error.message : String(error) });
745
- const failure = err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", { params: { error: String(error) } }), error);
746
- return !failure.success && !cleanup.success ? err({ ...failure.error, ext: { cleanupPending: true, cleanupError: cleanup.error } }) : failure;
830
+ logger.error("Failed to connect to Chroma", {
831
+ error: error instanceof Error ? error.message : String(error)
832
+ });
833
+ const failure = err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
834
+ params: {
835
+ error: String(error)
836
+ }
837
+ }), error);
838
+ return !failure.success && !cleanup.success ? err({
839
+ ...failure.error,
840
+ ext: { cleanupPending: true, cleanupError: cleanup.error }
841
+ }) : failure;
747
842
  } finally {
748
843
  clearTimeout(startupTimer);
749
844
  }
@@ -789,19 +884,26 @@ function createLancedbProvider() {
789
884
  return ok(mod);
790
885
  } catch (error) {
791
886
  logger2.error("Failed to load @lancedb/lancedb", { error });
792
- return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "@lancedb/lancedb" } }), error);
887
+ return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
888
+ params: { driver: "@lancedb/lancedb" }
889
+ }), error);
793
890
  }
794
891
  }
795
892
  async function withCollection(name, action) {
796
- if (!(await connection.tableNames()).includes(name))
797
- return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
893
+ if (!(await connection.tableNames()).includes(name)) {
894
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
895
+ params: { name }
896
+ }));
897
+ }
798
898
  const table = await connection.openTable(name);
799
899
  try {
800
900
  const schema = await table.schema();
801
901
  const vectorType = schema.fields.find((field) => field.name === "vector")?.type;
802
902
  const metric = DistanceMetricSchema.safeParse(schema.metadata.get(COLLECTION_METRIC_KEY) ?? null);
803
903
  if (!vectorType || !("listSize" in vectorType) || typeof vectorType.listSize !== "number" || !Number.isInteger(vectorType.listSize) || vectorType.listSize <= 0 || !metric.success) {
804
- return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", { params: { name } }));
904
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
905
+ params: { name }
906
+ }));
805
907
  }
806
908
  const primaryKeys = schema.fields.filter((field) => field.metadata.has(PRIMARY_KEY_POSITION) || ["true", "1", "yes"].includes(field.metadata.get(PRIMARY_KEY_FLAG)?.toLowerCase() ?? ""));
807
909
  const idField = primaryKeys[0];
@@ -810,8 +912,15 @@ function createLancedbProvider() {
810
912
  if (!driver.success)
811
913
  return driver;
812
914
  const jsonMetadata = schema.fields.find((field) => field.name === "metadata")?.metadata.get("ARROW:extension:name") === "lance.json";
813
- const inputSchema = jsonMetadata ? schema.assign([driver.data.makeJsonField("metadata", false)]) : void 0;
814
- return await action(table, { dimension: vectorType.listSize, metric: metric.data, atomicWrites, inputSchema });
915
+ const inputSchema = jsonMetadata ? schema.assign([
916
+ driver.data.makeJsonField("metadata", false)
917
+ ]) : void 0;
918
+ return await action(table, {
919
+ dimension: vectorType.listSize,
920
+ metric: metric.data,
921
+ atomicWrites,
922
+ inputSchema
923
+ });
815
924
  } finally {
816
925
  table.close();
817
926
  }
@@ -820,7 +929,10 @@ function createLancedbProvider() {
820
929
  return err(
821
930
  HaiVecdbError.DIMENSION_MISMATCH,
822
931
  vecdbM("vecdb_dimensionMismatch", {
823
- params: { expected: String(expected), actual: String(actual) }
932
+ params: {
933
+ expected: String(expected),
934
+ actual: String(actual)
935
+ }
824
936
  })
825
937
  );
826
938
  }
@@ -847,29 +959,49 @@ function createLancedbProvider() {
847
959
  return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_duplicateDocumentIds"));
848
960
  return null;
849
961
  }
850
- const ctx = { isConnected: () => connection !== null, logger: logger2, operationLog: () => config?.operationLog };
962
+ const ctx = {
963
+ isConnected: () => connection !== null,
964
+ logger: logger2,
965
+ operationLog: () => config?.operationLog
966
+ };
851
967
  const collectionDriver = {
852
968
  async create(name, options) {
853
969
  logger2.debug("Creating collection", { name, dimension: options.dimension, metric: options.metric });
854
970
  const tableNames = await connection.tableNames();
855
971
  if (tableNames.includes(name)) {
856
- return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", { params: { name } }));
972
+ return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
973
+ params: { name }
974
+ }));
857
975
  }
858
976
  const dimension = options.dimension;
859
977
  const parsedMetric = DistanceMetricSchema.safeParse(options.metric ?? config?.metric);
860
- if (!parsedMetric.success)
861
- return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", { params: { name } }));
978
+ if (!parsedMetric.success) {
979
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
980
+ params: { name }
981
+ }));
982
+ }
862
983
  const metric = parsedMetric.data;
863
984
  const driver = loadLancedb();
864
985
  if (!driver.success)
865
986
  return driver;
866
- const inferred = driver.data.makeArrowTable([{
867
- id: "",
868
- vector: Array.from({ length: dimension }).fill(0),
869
- content: "",
870
- metadata: "{}"
871
- }], {}, /* @__PURE__ */ new Map([[COLLECTION_METRIC_KEY, metric]])).schema;
872
- const schema = inferred.assign(inferred.fields.map((field) => field.name === "id" ? field.clone({ nullable: false, metadata: new Map([...field.metadata, [PRIMARY_KEY_FLAG, "true"], [PRIMARY_KEY_POSITION, "1"]]) }) : field.name === "metadata" ? driver.data.makeJsonField("metadata", false) : field));
987
+ const inferred = driver.data.makeArrowTable([
988
+ {
989
+ id: "",
990
+ vector: Array.from({ length: dimension }).fill(0),
991
+ content: "",
992
+ metadata: "{}"
993
+ }
994
+ ], {}, /* @__PURE__ */ new Map([
995
+ [COLLECTION_METRIC_KEY, metric]
996
+ ])).schema;
997
+ const schema = inferred.assign(inferred.fields.map((field) => field.name === "id" ? field.clone({
998
+ nullable: false,
999
+ metadata: new Map([
1000
+ ...field.metadata,
1001
+ [PRIMARY_KEY_FLAG, "true"],
1002
+ [PRIMARY_KEY_POSITION, "1"]
1003
+ ])
1004
+ }) : field.name === "metadata" ? driver.data.makeJsonField("metadata", false) : field));
873
1005
  const table = await connection.createEmptyTable(name, schema);
874
1006
  table.close();
875
1007
  logger2.info("Collection created", { name, dimension, metric });
@@ -879,7 +1011,9 @@ function createLancedbProvider() {
879
1011
  logger2.debug("Dropping collection", { name });
880
1012
  const tableNames = await connection.tableNames();
881
1013
  if (!tableNames.includes(name)) {
882
- return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
1014
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
1015
+ params: { name }
1016
+ }));
883
1017
  }
884
1018
  await connection.dropTable(name);
885
1019
  logger2.info("Collection dropped", { name });
@@ -892,7 +1026,12 @@ function createLancedbProvider() {
892
1026
  },
893
1027
  async info(name) {
894
1028
  logger2.debug("Getting collection info", { name });
895
- return withCollection(name, async (table, meta) => ok({ name, dimension: meta.dimension, metric: meta.metric, count: await table.countRows() }));
1029
+ return withCollection(name, async (table, meta) => ok({
1030
+ name,
1031
+ dimension: meta.dimension,
1032
+ metric: meta.metric,
1033
+ count: await table.countRows()
1034
+ }));
896
1035
  },
897
1036
  async list() {
898
1037
  logger2.debug("Listing collections");
@@ -980,7 +1119,7 @@ function createLancedbProvider() {
980
1119
  const results = await query.toArray();
981
1120
  const searchResults = results.map((row) => {
982
1121
  const distance = row._distance ?? 0;
983
- const score = meta.metric === "cosine" ? Math.max(0, Math.min(1, 1 - distance / 2)) : meta.metric === "dot" ? 1 / (1 + Math.exp(distance - 1)) : 1 / (1 + Math.max(0, distance));
1122
+ const score = vectorScoreFromDistance(meta.metric === "dot" ? distance - 1 : distance, meta.metric);
984
1123
  return {
985
1124
  id: row.id,
986
1125
  score,
@@ -1003,7 +1142,9 @@ function createLancedbProvider() {
1003
1142
  name: "lancedb",
1004
1143
  async connect(cfg) {
1005
1144
  if (cfg.type !== "lancedb") {
1006
- return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", { params: { type: cfg.type } }));
1145
+ return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
1146
+ params: { type: cfg.type }
1147
+ }));
1007
1148
  }
1008
1149
  const lanceConfig = cfg;
1009
1150
  const loadResult = loadLancedb();
@@ -1020,7 +1161,11 @@ function createLancedbProvider() {
1020
1161
  connection = null;
1021
1162
  config = null;
1022
1163
  logger2.error("Failed to connect to LanceDB", { error });
1023
- return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", { params: { error: String(error) } }), error);
1164
+ return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
1165
+ params: {
1166
+ error: String(error)
1167
+ }
1168
+ }), error);
1024
1169
  }
1025
1170
  },
1026
1171
  async close() {
@@ -1054,8 +1199,7 @@ function createPgvectorProvider() {
1054
1199
  function quoteIdent(name) {
1055
1200
  return `"${name.replace(/"/g, '""')}"`;
1056
1201
  }
1057
- function distanceOp() {
1058
- const metric = config?.metric ?? "cosine";
1202
+ function distanceOp(metric) {
1059
1203
  switch (metric) {
1060
1204
  case "cosine":
1061
1205
  return "<=>";
@@ -1067,18 +1211,8 @@ function createPgvectorProvider() {
1067
1211
  return "<=>";
1068
1212
  }
1069
1213
  }
1070
- function distanceToScore2(distance) {
1071
- const metric = config?.metric ?? "cosine";
1072
- switch (metric) {
1073
- case "cosine":
1074
- return 1 - distance / 2;
1075
- case "euclidean":
1076
- return 1 / (1 + distance);
1077
- case "dot":
1078
- return -distance;
1079
- default:
1080
- return 1 / (1 + distance);
1081
- }
1214
+ function distanceToScore2(distance, metric) {
1215
+ return vectorScoreFromDistance(distance, metric);
1082
1216
  }
1083
1217
  function isMissingModuleError(error, packageName) {
1084
1218
  const message = error instanceof Error ? error.message : String(error);
@@ -1090,13 +1224,19 @@ function createPgvectorProvider() {
1090
1224
  if (!isPgModule(mod)) {
1091
1225
  const error = new Error("pg driver module does not export Pool");
1092
1226
  logger3.error("Failed to load pg driver", { error: error.message });
1093
- return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "pg" } }), error);
1227
+ return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
1228
+ params: { driver: "pg" }
1229
+ }), error);
1094
1230
  }
1095
1231
  return ok(mod);
1096
1232
  } catch (error) {
1097
1233
  if (isMissingModuleError(error, "pg")) {
1098
- logger3.error("Failed to load pg driver", { error: error instanceof Error ? error.message : String(error) });
1099
- return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "pg" } }), error);
1234
+ logger3.error("Failed to load pg driver", {
1235
+ error: error instanceof Error ? error.message : String(error)
1236
+ });
1237
+ return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", {
1238
+ params: { driver: "pg" }
1239
+ }), error);
1100
1240
  }
1101
1241
  throw error;
1102
1242
  }
@@ -1108,14 +1248,34 @@ function createPgvectorProvider() {
1108
1248
  [table]
1109
1249
  );
1110
1250
  if (!existsResult.rows[0].exists) {
1111
- return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
1251
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
1252
+ params: { name }
1253
+ }));
1112
1254
  }
1113
1255
  const dimResult = await pool.query(`
1114
1256
  SELECT atttypmod FROM pg_attribute
1115
1257
  WHERE attrelid = $1::regclass AND attname = 'vector'
1116
- `, [quoteIdent(table)]);
1258
+ `, [
1259
+ quoteIdent(table)
1260
+ ]);
1117
1261
  return ok(dimResult.rows.length > 0 ? Number(dimResult.rows[0].atttypmod) : 0);
1118
1262
  }
1263
+ async function getCollectionMetric(name) {
1264
+ const result = await pool.query(`SELECT col_description(a.attrelid, a.attnum) AS definition,
1265
+ (SELECT opc.opcname FROM pg_index i JOIN pg_opclass opc ON opc.oid = ANY(i.indclass)
1266
+ WHERE i.indrelid = a.attrelid AND opc.opcname IN ('vector_cosine_ops', 'vector_l2_ops', 'vector_ip_ops') LIMIT 1) AS operator
1267
+ FROM pg_attribute a WHERE a.attrelid = $1::regclass AND a.attname = 'vector'`, [
1268
+ quoteIdent(tableName(name))
1269
+ ]);
1270
+ const row = result.rows[0];
1271
+ const stored = typeof row?.definition === "string" && row.definition.startsWith("hai.vecdb.metric=") ? row.definition.slice("hai.vecdb.metric=".length) : void 0;
1272
+ const inherited = row?.operator === "vector_cosine_ops" ? "cosine" : row?.operator === "vector_l2_ops" ? "euclidean" : row?.operator === "vector_ip_ops" ? "dot" : void 0;
1273
+ const metric = stored ?? inherited;
1274
+ const parsed = DistanceMetricSchema.safeParse(metric);
1275
+ if (metric === void 0 || !parsed.success)
1276
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_collectionMetricMissing"));
1277
+ return ok(parsed.data);
1278
+ }
1119
1279
  function validateVectorDimension(expected, actual) {
1120
1280
  if (expected === actual) {
1121
1281
  return null;
@@ -1123,7 +1283,10 @@ function createPgvectorProvider() {
1123
1283
  return err(
1124
1284
  HaiVecdbError.DIMENSION_MISMATCH,
1125
1285
  vecdbM("vecdb_dimensionMismatch", {
1126
- params: { expected: String(expected), actual: String(actual) }
1286
+ params: {
1287
+ expected: String(expected),
1288
+ actual: String(actual)
1289
+ }
1127
1290
  })
1128
1291
  );
1129
1292
  }
@@ -1136,12 +1299,16 @@ function createPgvectorProvider() {
1136
1299
  }
1137
1300
  return null;
1138
1301
  }
1139
- const ctx = { isConnected: () => pool !== null, logger: logger3, operationLog: () => config?.operationLog };
1302
+ const ctx = {
1303
+ isConnected: () => pool !== null,
1304
+ logger: logger3,
1305
+ operationLog: () => config?.operationLog
1306
+ };
1140
1307
  const collectionDriver = {
1141
1308
  async create(name, options) {
1142
1309
  const table = tableName(name);
1143
1310
  const dimension = options.dimension;
1144
- const metric = options.metric ?? config?.metric ?? "cosine";
1311
+ const metric = DistanceMetricSchema.parse(options.metric ?? config?.metric ?? "cosine");
1145
1312
  const indexType = config?.indexType ?? "hnsw";
1146
1313
  logger3.debug("Creating collection", { name, dimension, metric, indexType });
1147
1314
  const checkResult = await pool.query(
@@ -1149,7 +1316,9 @@ function createPgvectorProvider() {
1149
1316
  [table]
1150
1317
  );
1151
1318
  if (checkResult.rows[0].exists) {
1152
- return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", { params: { name } }));
1319
+ return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
1320
+ params: { name }
1321
+ }));
1153
1322
  }
1154
1323
  const qi = quoteIdent(table);
1155
1324
  await pool.query(`
@@ -1160,7 +1329,7 @@ function createPgvectorProvider() {
1160
1329
  metadata JSONB DEFAULT '{}'::jsonb
1161
1330
  )
1162
1331
  `);
1163
- const distOp = distanceOp();
1332
+ const distOp = distanceOp(metric);
1164
1333
  const opsClass = distOp === "<=>" ? "vector_cosine_ops" : distOp === "<->" ? "vector_l2_ops" : "vector_ip_ops";
1165
1334
  if (indexType === "hnsw") {
1166
1335
  await pool.query(`
@@ -1174,6 +1343,7 @@ function createPgvectorProvider() {
1174
1343
  WITH (lists = 100)
1175
1344
  `);
1176
1345
  }
1346
+ await pool.query(`COMMENT ON COLUMN ${qi}.vector IS 'hai.vecdb.metric=${metric}'`);
1177
1347
  logger3.info("Collection created", { name, dimension, metric, indexType });
1178
1348
  return ok(void 0);
1179
1349
  },
@@ -1185,7 +1355,9 @@ function createPgvectorProvider() {
1185
1355
  [table]
1186
1356
  );
1187
1357
  if (!checkResult.rows[0].exists) {
1188
- return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
1358
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
1359
+ params: { name }
1360
+ }));
1189
1361
  }
1190
1362
  await pool.query(`DROP TABLE ${quoteIdent(table)}`);
1191
1363
  logger3.info("Collection dropped", { name });
@@ -1195,7 +1367,9 @@ function createPgvectorProvider() {
1195
1367
  logger3.debug("Checking collection exists", { name });
1196
1368
  const result = await pool.query(
1197
1369
  `SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $1)`,
1198
- [tableName(name)]
1370
+ [
1371
+ tableName(name)
1372
+ ]
1199
1373
  );
1200
1374
  return ok(result.rows[0].exists);
1201
1375
  },
@@ -1206,12 +1380,15 @@ function createPgvectorProvider() {
1206
1380
  if (!dimensionResult.success) {
1207
1381
  return dimensionResult;
1208
1382
  }
1383
+ const metric = await getCollectionMetric(name);
1384
+ if (!metric.success)
1385
+ return metric;
1209
1386
  const countResult = await pool.query(`SELECT COUNT(*) as count FROM ${quoteIdent(table)}`);
1210
1387
  const count = Number.parseInt(String(countResult.rows[0].count), 10);
1211
1388
  return ok({
1212
1389
  name,
1213
1390
  dimension: dimensionResult.data,
1214
- metric: config?.metric ?? "cosine",
1391
+ metric: metric.data,
1215
1392
  count
1216
1393
  });
1217
1394
  },
@@ -1299,7 +1476,6 @@ function createPgvectorProvider() {
1299
1476
  const table = tableName(collection);
1300
1477
  const topK = options?.topK ?? 10;
1301
1478
  const minScore = options?.minScore ?? 0;
1302
- const op = distanceOp();
1303
1479
  logger3.debug("Searching vectors", { collection, topK, hasFilter: !!options?.filter });
1304
1480
  const dimensionResult = await getCollectionDimension(collection);
1305
1481
  if (!dimensionResult.success) {
@@ -1309,6 +1485,10 @@ function createPgvectorProvider() {
1309
1485
  if (dimensionValidation) {
1310
1486
  return dimensionValidation;
1311
1487
  }
1488
+ const metric = await getCollectionMetric(collection);
1489
+ if (!metric.success)
1490
+ return metric;
1491
+ const op = distanceOp(metric.data);
1312
1492
  const vectorStr = `[${vector.join(",")}]`;
1313
1493
  let filterSQL = "";
1314
1494
  const filterParams = [vectorStr, topK];
@@ -1333,7 +1513,7 @@ function createPgvectorProvider() {
1333
1513
  filterParams
1334
1514
  );
1335
1515
  const searchResults = result.rows.map((row) => {
1336
- const score = distanceToScore2(row.distance);
1516
+ const score = distanceToScore2(row.distance, metric.data);
1337
1517
  return {
1338
1518
  id: row.id,
1339
1519
  score,
@@ -1354,7 +1534,9 @@ function createPgvectorProvider() {
1354
1534
  name: "pgvector",
1355
1535
  async connect(cfg) {
1356
1536
  if (cfg.type !== "pgvector") {
1357
- return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", { params: { type: cfg.type } }));
1537
+ return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
1538
+ params: { type: cfg.type }
1539
+ }));
1358
1540
  }
1359
1541
  const pgConfig = cfg;
1360
1542
  try {
@@ -1376,8 +1558,14 @@ function createPgvectorProvider() {
1376
1558
  logger3.info("pgvector connected", { host: pgConfig.host, database: pgConfig.database });
1377
1559
  return ok(void 0);
1378
1560
  } catch (error) {
1379
- logger3.error("Failed to connect to pgvector", { error: error instanceof Error ? error.message : String(error) });
1380
- return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
1561
+ logger3.error("Failed to connect to pgvector", {
1562
+ error: error instanceof Error ? error.message : String(error)
1563
+ });
1564
+ return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
1565
+ params: {
1566
+ error: error instanceof Error ? error.message : String(error)
1567
+ }
1568
+ }), error);
1381
1569
  }
1382
1570
  },
1383
1571
  async close() {
@@ -1390,8 +1578,14 @@ function createPgvectorProvider() {
1390
1578
  logger3.info("pgvector connection closed");
1391
1579
  return ok(void 0);
1392
1580
  } catch (error) {
1393
- logger3.error("Failed to close pgvector connection", { error: error instanceof Error ? error.message : String(error) });
1394
- return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_closeFailed", { params: { error: String(error) } }), error);
1581
+ logger3.error("Failed to close pgvector connection", {
1582
+ error: error instanceof Error ? error.message : String(error)
1583
+ });
1584
+ return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_closeFailed", {
1585
+ params: {
1586
+ error: String(error)
1587
+ }
1588
+ }), error);
1395
1589
  }
1396
1590
  },
1397
1591
  isConnected() {
@@ -1401,14 +1595,152 @@ function createPgvectorProvider() {
1401
1595
  vector: createBaseVectorOps(ctx, vectorDriver)
1402
1596
  };
1403
1597
  }
1404
- var logger4 = core.logger.child({ module: "vecdb", scope: "qdrant" });
1405
- function isQdrantModule(value) {
1406
- if (typeof value !== "object" || value === null) {
1407
- return false;
1598
+ var MAX_RESPONSE_BYTES = 16 * 1024 * 1024;
1599
+ var pointSchema = z.object({
1600
+ id: z.union([
1601
+ z.string(),
1602
+ z.number()
1603
+ ]),
1604
+ score: z.number().optional(),
1605
+ payload: z.record(z.string(), z.unknown()).nullable().optional(),
1606
+ vector: z.array(z.number()).optional()
1607
+ });
1608
+ var collectionSchema = z.object({
1609
+ config: z.object({
1610
+ params: z.object({
1611
+ vectors: z.unknown()
1612
+ })
1613
+ }),
1614
+ points_count: z.number().nullable().optional()
1615
+ });
1616
+ var QdrantHttpError = class extends Error {
1617
+ constructor(status) {
1618
+ super(`Qdrant HTTP request failed (${status})`);
1619
+ this.status = status;
1408
1620
  }
1409
- const maybeModule = value;
1410
- return typeof maybeModule.QdrantClient === "function";
1411
- }
1621
+ status;
1622
+ };
1623
+ var QdrantRestClient = class {
1624
+ constructor(url, apiKey) {
1625
+ this.apiKey = apiKey;
1626
+ this.base = new URL(url);
1627
+ if (!["http:", "https:"].includes(this.base.protocol) || this.base.username || this.base.password)
1628
+ throw new Error("Qdrant URL must use HTTP(S) without embedded credentials");
1629
+ }
1630
+ apiKey;
1631
+ base;
1632
+ async perform(path, method, schema, body) {
1633
+ const url = new URL(this.base);
1634
+ const [pathname, query] = path.split("?");
1635
+ url.pathname = `${url.pathname.replace(/\/$/, "")}${pathname}`;
1636
+ for (const [key, value] of new URLSearchParams(query)) url.searchParams.set(key, value);
1637
+ const response = await fetch(url, {
1638
+ method,
1639
+ redirect: "error",
1640
+ signal: AbortSignal.timeout(3e4),
1641
+ headers: {
1642
+ "content-type": "application/json",
1643
+ ...this.apiKey ? { "api-key": this.apiKey } : {}
1644
+ },
1645
+ body: body === void 0 ? void 0 : JSON.stringify(body)
1646
+ });
1647
+ if (!response.ok) {
1648
+ await response.body?.cancel();
1649
+ throw new QdrantHttpError(response.status);
1650
+ }
1651
+ const reader = response.body?.getReader();
1652
+ if (!reader)
1653
+ throw new Error("Qdrant response body is missing");
1654
+ let size = 0;
1655
+ const chunks = [];
1656
+ try {
1657
+ while (true) {
1658
+ const chunk = await reader.read();
1659
+ if (chunk.done)
1660
+ break;
1661
+ size += chunk.value.byteLength;
1662
+ if (size > MAX_RESPONSE_BYTES)
1663
+ throw new Error("Qdrant response exceeds 16 MiB");
1664
+ chunks.push(chunk.value);
1665
+ }
1666
+ } finally {
1667
+ await reader.cancel().catch(() => void 0);
1668
+ reader.releaseLock();
1669
+ }
1670
+ const bytes = new Uint8Array(size);
1671
+ let offset = 0;
1672
+ for (const chunk of chunks) {
1673
+ bytes.set(chunk, offset);
1674
+ offset += chunk.byteLength;
1675
+ }
1676
+ let bodyJson;
1677
+ try {
1678
+ bodyJson = JSON.parse(new TextDecoder().decode(bytes));
1679
+ } catch {
1680
+ throw new Error("Qdrant returned invalid JSON");
1681
+ }
1682
+ const envelope = z.object({
1683
+ status: z.literal("ok"),
1684
+ result: schema
1685
+ }).safeParse(bodyJson);
1686
+ if (!envelope.success)
1687
+ throw new Error("Qdrant returned an invalid response");
1688
+ return envelope.data.result;
1689
+ }
1690
+ async request(path, method, schema, body) {
1691
+ try {
1692
+ return await this.perform(path, method, schema, body);
1693
+ } catch (error) {
1694
+ if (error instanceof QdrantHttpError)
1695
+ throw error;
1696
+ core.logger.child({ module: "vecdb", scope: "qdrant-http" }).error("Qdrant HTTP transport failed", {
1697
+ error: core.sanitize.sanitizeSensitiveFields(error)
1698
+ });
1699
+ throw new Error("Qdrant request failed");
1700
+ }
1701
+ }
1702
+ getCollections() {
1703
+ return this.request("/collections", "GET", z.object({
1704
+ collections: z.array(z.object({
1705
+ name: z.string()
1706
+ }))
1707
+ }));
1708
+ }
1709
+ getCollection(name) {
1710
+ return this.request(`/collections/${encodeURIComponent(name)}`, "GET", collectionSchema);
1711
+ }
1712
+ createCollection(name, options) {
1713
+ return this.request(`/collections/${encodeURIComponent(name)}`, "PUT", z.boolean(), options);
1714
+ }
1715
+ deleteCollection(name) {
1716
+ return this.request(`/collections/${encodeURIComponent(name)}`, "DELETE", z.boolean());
1717
+ }
1718
+ upsert(name, options) {
1719
+ return this.request(`/collections/${encodeURIComponent(name)}/points?wait=${options.wait}`, "PUT", z.object({
1720
+ status: z.literal("completed")
1721
+ }), { points: options.points });
1722
+ }
1723
+ delete(name, options) {
1724
+ return this.request(`/collections/${encodeURIComponent(name)}/points/delete?wait=${options.wait}`, "POST", z.object({
1725
+ status: z.literal("completed")
1726
+ }), { points: options.points });
1727
+ }
1728
+ query(name, options) {
1729
+ return this.request(`/collections/${encodeURIComponent(name)}/points/query`, "POST", z.object({
1730
+ points: z.array(pointSchema.extend({
1731
+ score: z.number()
1732
+ }))
1733
+ }), options);
1734
+ }
1735
+ count(name, options) {
1736
+ return this.request(`/collections/${encodeURIComponent(name)}/points/count`, "POST", z.object({
1737
+ count: z.number()
1738
+ }), options);
1739
+ }
1740
+ };
1741
+
1742
+ // src/providers/vecdb-provider-qdrant.ts
1743
+ var logger4 = core.logger.child({ module: "vecdb", scope: "qdrant" });
1412
1744
  function hashToUuid(id) {
1413
1745
  const hash = createHash("sha256").update(id).digest("hex");
1414
1746
  return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
@@ -1426,27 +1758,6 @@ function toQdrantDistance(metric) {
1426
1758
  function createQdrantProvider() {
1427
1759
  let client = null;
1428
1760
  let config = null;
1429
- function isMissingModuleError(error, packageName) {
1430
- const message = error instanceof Error ? error.message : String(error);
1431
- return message.includes(`'${packageName}'`) || message.includes(`"${packageName}"`) || message.includes(packageName);
1432
- }
1433
- async function loadQdrantClient() {
1434
- try {
1435
- const mod = await import('@qdrant/js-client-rest');
1436
- if (!isQdrantModule(mod)) {
1437
- const error = new Error("@qdrant/js-client-rest driver module does not export QdrantClient");
1438
- logger4.error("Failed to load Qdrant driver", { error: error.message });
1439
- return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "@qdrant/js-client-rest" } }), error);
1440
- }
1441
- return ok(mod);
1442
- } catch (error) {
1443
- if (isMissingModuleError(error, "@qdrant/js-client-rest")) {
1444
- logger4.error("Failed to load Qdrant driver", { error: error instanceof Error ? error.message : String(error) });
1445
- return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "@qdrant/js-client-rest" } }), error);
1446
- }
1447
- throw error;
1448
- }
1449
- }
1450
1761
  function isQdrantNotFoundError(error) {
1451
1762
  return typeof error === "object" && error !== null && "status" in error && error.status === 404;
1452
1763
  }
@@ -1455,14 +1766,16 @@ function createQdrantProvider() {
1455
1766
  return ok(await client.getCollection(name));
1456
1767
  } catch (error) {
1457
1768
  if (isQdrantNotFoundError(error)) {
1458
- return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
1769
+ return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
1770
+ params: { name }
1771
+ }));
1459
1772
  }
1460
1773
  throw error;
1461
1774
  }
1462
1775
  }
1463
1776
  function readVectorDefinition(info) {
1464
1777
  const vectors = info.config.params.vectors;
1465
- if (!vectors || !("size" in vectors) || typeof vectors.size !== "number" || !Number.isSafeInteger(vectors.size) || vectors.size <= 0)
1778
+ if (!vectors || typeof vectors !== "object" || !("size" in vectors) || typeof vectors.size !== "number" || !Number.isSafeInteger(vectors.size) || vectors.size <= 0)
1466
1779
  return void 0;
1467
1780
  if (!("distance" in vectors))
1468
1781
  return void 0;
@@ -1475,7 +1788,9 @@ function createQdrantProvider() {
1475
1788
  return infoResult;
1476
1789
  }
1477
1790
  const definition = readVectorDefinition(infoResult.data);
1478
- return definition ? ok(definition.dimension) : err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", { params: { name } }));
1791
+ return definition ? ok(definition.dimension) : err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
1792
+ params: { name }
1793
+ }));
1479
1794
  }
1480
1795
  function validateVectorDimension(expected, actual) {
1481
1796
  if (expected === actual) {
@@ -1484,18 +1799,27 @@ function createQdrantProvider() {
1484
1799
  return err(
1485
1800
  HaiVecdbError.DIMENSION_MISMATCH,
1486
1801
  vecdbM("vecdb_dimensionMismatch", {
1487
- params: { expected: String(expected), actual: String(actual) }
1802
+ params: {
1803
+ expected: String(expected),
1804
+ actual: String(actual)
1805
+ }
1488
1806
  })
1489
1807
  );
1490
1808
  }
1491
- const ctx = { isConnected: () => client !== null, logger: logger4, operationLog: () => config?.operationLog };
1809
+ const ctx = {
1810
+ isConnected: () => client !== null,
1811
+ logger: logger4,
1812
+ operationLog: () => config?.operationLog
1813
+ };
1492
1814
  const collectionDriver = {
1493
1815
  async create(name, options) {
1494
1816
  logger4.debug("Creating collection", { name, dimension: options.dimension, metric: options.metric });
1495
1817
  const collections = await client.getCollections();
1496
1818
  const exists = collections.collections.some((c) => c.name === name);
1497
1819
  if (exists) {
1498
- return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", { params: { name } }));
1820
+ return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", {
1821
+ params: { name }
1822
+ }));
1499
1823
  }
1500
1824
  const metric = options.metric ?? config?.metric ?? "cosine";
1501
1825
  await client.createCollection(name, {
@@ -1537,8 +1861,11 @@ function createQdrantProvider() {
1537
1861
  }
1538
1862
  const collectionInfo = infoResult.data;
1539
1863
  const definition = readVectorDefinition(collectionInfo);
1540
- if (!definition)
1541
- return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", { params: { name } }));
1864
+ if (!definition) {
1865
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
1866
+ params: { name }
1867
+ }));
1868
+ }
1542
1869
  return ok({
1543
1870
  name,
1544
1871
  ...definition,
@@ -1614,11 +1941,16 @@ function createQdrantProvider() {
1614
1941
  const topK = options?.topK ?? 10;
1615
1942
  const minScore = options?.minScore ?? 0;
1616
1943
  logger4.debug("Searching vectors", { collection, topK, hasFilter: !!options?.filter });
1617
- const dimensionResult = await getCollectionDimension(collection);
1618
- if (!dimensionResult.success) {
1619
- return dimensionResult;
1944
+ const collectionInfo = await getCollectionInfo(collection);
1945
+ if (!collectionInfo.success)
1946
+ return collectionInfo;
1947
+ const definition = readVectorDefinition(collectionInfo.data);
1948
+ if (!definition) {
1949
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", {
1950
+ params: { name: collection }
1951
+ }));
1620
1952
  }
1621
- const dimensionValidation = validateVectorDimension(dimensionResult.data, vector.length);
1953
+ const dimensionValidation = validateVectorDimension(definition.dimension, vector.length);
1622
1954
  if (dimensionValidation) {
1623
1955
  return dimensionValidation;
1624
1956
  }
@@ -1633,7 +1965,6 @@ function createQdrantProvider() {
1633
1965
  const searchResult = await client.query(collection, {
1634
1966
  query: vector,
1635
1967
  limit: topK,
1636
- score_threshold: minScore > 0 ? minScore : void 0,
1637
1968
  filter,
1638
1969
  with_payload: true
1639
1970
  });
@@ -1642,11 +1973,12 @@ function createQdrantProvider() {
1642
1973
  const { _id, _content, ...metadata } = payload;
1643
1974
  return {
1644
1975
  id: typeof _id === "string" ? _id : "",
1645
- score: point.score,
1976
+ // Qdrant 余弦/内积返回相似值,欧氏返回距离;统一后再应用公开阈值。
1977
+ score: vectorScoreFromDistance(definition.metric === "cosine" ? 1 - point.score : definition.metric === "dot" ? -point.score : point.score, definition.metric),
1646
1978
  content: typeof _content === "string" && _content.length > 0 ? _content : void 0,
1647
1979
  metadata: Object.keys(metadata).length > 0 ? metadata : void 0
1648
1980
  };
1649
- });
1981
+ }).filter((point) => point.score >= minScore);
1650
1982
  return ok(results);
1651
1983
  },
1652
1984
  async count(collection) {
@@ -1659,27 +1991,27 @@ function createQdrantProvider() {
1659
1991
  name: "qdrant",
1660
1992
  async connect(cfg) {
1661
1993
  if (cfg.type !== "qdrant") {
1662
- return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", { params: { type: cfg.type } }));
1994
+ return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", {
1995
+ params: { type: cfg.type }
1996
+ }));
1663
1997
  }
1664
1998
  const qdrantConfig = cfg;
1665
1999
  try {
1666
- const loadResult = await loadQdrantClient();
1667
- if (!loadResult.success) {
1668
- return loadResult;
1669
- }
1670
- const { QdrantClient: QdrantClientClass } = loadResult.data;
1671
- const qdrantClient = new QdrantClientClass({
1672
- url: qdrantConfig.url,
1673
- apiKey: qdrantConfig.apiKey
1674
- });
2000
+ const qdrantClient = new QdrantRestClient(qdrantConfig.url, qdrantConfig.apiKey);
1675
2001
  await qdrantClient.getCollections();
1676
2002
  client = qdrantClient;
1677
2003
  config = qdrantConfig;
1678
- logger4.info("Qdrant connected", { url: core.sanitize.sanitizeUrl(qdrantConfig.url) });
2004
+ logger4.info("Qdrant connected", {
2005
+ url: core.sanitize.sanitizeUrl(qdrantConfig.url)
2006
+ });
1679
2007
  return ok(void 0);
1680
2008
  } catch (error) {
1681
- logger4.error("Failed to connect to Qdrant", { error: error instanceof Error ? error.message : String(error) });
1682
- return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", { params: { error: String(error) } }), error);
2009
+ logger4.error("Failed to connect to Qdrant", {
2010
+ error: core.sanitize.sanitizeSensitiveFields(error)
2011
+ });
2012
+ return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", {
2013
+ params: { error: "Qdrant connection failed" }
2014
+ }));
1683
2015
  }
1684
2016
  },
1685
2017
  async close() {
@@ -1714,24 +2046,44 @@ async function closeOwnedProvider() {
1714
2046
  return ok(void 0);
1715
2047
  try {
1716
2048
  const result = await pendingProvider.close();
1717
- if (!result.success)
1718
- return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true } });
2049
+ if (!result.success) {
2050
+ return err({
2051
+ ...result.error,
2052
+ ext: { ...result.error.ext, cleanupPending: true }
2053
+ });
2054
+ }
1719
2055
  pendingProvider = null;
1720
2056
  return result;
1721
2057
  } catch (cause) {
1722
- return err({ ...HaiVecdbError.CONNECTION_FAILED, message: vecdbM("vecdb_closeFailed", { params: { error: cause instanceof Error ? cause.message : String(cause) } }), cause, ext: { cleanupPending: true } });
2058
+ return err({
2059
+ ...HaiVecdbError.CONNECTION_FAILED,
2060
+ message: vecdbM("vecdb_closeFailed", {
2061
+ params: {
2062
+ error: cause instanceof Error ? cause.message : String(cause)
2063
+ }
2064
+ }),
2065
+ cause,
2066
+ ext: { cleanupPending: true }
2067
+ });
1723
2068
  }
1724
2069
  }
1725
2070
  async function failInitialization(result) {
1726
2071
  const cleanup = await closeOwnedProvider();
1727
2072
  if (result.success)
1728
2073
  return result;
1729
- if (!cleanup.success)
1730
- return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error } });
2074
+ if (!cleanup.success) {
2075
+ return err({
2076
+ ...result.error,
2077
+ ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error }
2078
+ });
2079
+ }
1731
2080
  const ext = { ...result.error.ext };
1732
2081
  delete ext.cleanupPending;
1733
2082
  delete ext.cleanupError;
1734
- return err({ ...result.error, ext: Object.keys(ext).length ? ext : void 0 });
2083
+ return err({
2084
+ ...result.error,
2085
+ ext: Object.keys(ext).length ? ext : void 0
2086
+ });
1735
2087
  }
1736
2088
  function createProvider(config) {
1737
2089
  switch (config.type) {
@@ -1790,7 +2142,9 @@ var vecdb = {
1790
2142
  const parseResult = VecdbConfigSchema.safeParse(config);
1791
2143
  if (!parseResult.success) {
1792
2144
  logger5.error("Vecdb config validation failed", { error: parseResult.error.message });
1793
- return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_configError", { params: { error: parseResult.error.message } }), parseResult.error);
2145
+ return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_configError", {
2146
+ params: { error: parseResult.error.message }
2147
+ }), parseResult.error);
1794
2148
  }
1795
2149
  const parsed = parseResult.data;
1796
2150
  if (core.module.requiresSharedState(process.env) && (parsed.type === "lancedb" || parsed.type === "chroma" && !!parsed.path && !parsed.url))
@@ -1813,7 +2167,11 @@ var vecdb = {
1813
2167
  return ok(void 0);
1814
2168
  } catch (error) {
1815
2169
  logger5.error("Vecdb module initialization failed", { error });
1816
- return await failInitialization(err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error));
2170
+ return await failInitialization(err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_initFailed", {
2171
+ params: {
2172
+ error: error instanceof Error ? error.message : String(error)
2173
+ }
2174
+ }), error));
1817
2175
  }
1818
2176
  } finally {
1819
2177
  initInProgress = false;