@semiont/vectors 0.5.32 → 0.5.34

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.
@@ -1,3 +1,5 @@
1
+ import { EMBED_TIMEOUT_MS, EmbeddingProviderError } from './chunk-N6SZJFFY.js';
2
+
1
3
  // src/embedding/ollama.ts
2
4
  var OllamaEmbeddingProvider = class {
3
5
  config;
@@ -10,6 +12,7 @@ var OllamaEmbeddingProvider = class {
10
12
  const response = await fetch(`${baseURL}/api/embed`, {
11
13
  method: "POST",
12
14
  headers: { "Content-Type": "application/json" },
15
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
13
16
  body: JSON.stringify({
14
17
  model: this.config.model,
15
18
  input: text
@@ -17,7 +20,7 @@ var OllamaEmbeddingProvider = class {
17
20
  });
18
21
  if (!response.ok) {
19
22
  const body = await response.text();
20
- throw new Error(`Ollama embed error ${response.status}: ${body}`);
23
+ throw new EmbeddingProviderError("ollama", response.status, this.config.model, body);
21
24
  }
22
25
  const json = await response.json();
23
26
  return json.embeddings[0];
@@ -27,6 +30,7 @@ var OllamaEmbeddingProvider = class {
27
30
  const response = await fetch(`${baseURL}/api/embed`, {
28
31
  method: "POST",
29
32
  headers: { "Content-Type": "application/json" },
33
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
30
34
  body: JSON.stringify({
31
35
  model: this.config.model,
32
36
  input: texts
@@ -34,7 +38,7 @@ var OllamaEmbeddingProvider = class {
34
38
  });
35
39
  if (!response.ok) {
36
40
  const body = await response.text();
37
- throw new Error(`Ollama embed error ${response.status}: ${body}`);
41
+ throw new EmbeddingProviderError("ollama", response.status, this.config.model, body);
38
42
  }
39
43
  const json = await response.json();
40
44
  return json.embeddings;
@@ -61,5 +65,5 @@ var OllamaEmbeddingProvider = class {
61
65
  };
62
66
 
63
67
  export { OllamaEmbeddingProvider };
64
- //# sourceMappingURL=chunk-GWXB645H.js.map
65
- //# sourceMappingURL=chunk-GWXB645H.js.map
68
+ //# sourceMappingURL=chunk-BZ3U6GAE.js.map
69
+ //# sourceMappingURL=chunk-BZ3U6GAE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/embedding/ollama.ts"],"names":[],"mappings":"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","file":"chunk-BZ3U6GAE.js","sourcesContent":["/**\n * Ollama Embedding Provider\n *\n * Local embedding via the Ollama API.\n * Uses models like nomic-embed-text, all-minilm, etc.\n */\n\nimport type { EmbeddingProvider } from './interface';\nimport { EmbeddingProviderError, EMBED_TIMEOUT_MS } from './provider-error';\n\nexport interface OllamaEmbeddingConfig {\n model: string;\n baseURL?: string;\n}\n\nexport class OllamaEmbeddingProvider implements EmbeddingProvider {\n private config: OllamaEmbeddingConfig;\n private dimensionsPromise?: Promise<number>;\n\n constructor(config: OllamaEmbeddingConfig) {\n this.config = config;\n }\n\n async embed(text: string): Promise<number[]> {\n const baseURL = this.config.baseURL ?? 'http://localhost:11434';\n\n const response = await fetch(`${baseURL}/api/embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),\n body: JSON.stringify({\n model: this.config.model,\n input: text,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new EmbeddingProviderError('ollama', response.status, this.config.model, body);\n }\n\n const json = await response.json() as { embeddings: number[][] };\n return json.embeddings[0];\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n // Ollama's /api/embed supports batch input\n const baseURL = this.config.baseURL ?? 'http://localhost:11434';\n\n const response = await fetch(`${baseURL}/api/embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),\n body: JSON.stringify({\n model: this.config.model,\n input: texts,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new EmbeddingProviderError('ollama', response.status, this.config.model, body);\n }\n\n const json = await response.json() as { embeddings: number[][] };\n return json.embeddings;\n }\n\n dimensions(): Promise<number> {\n if (!this.dimensionsPromise) {\n this.dimensionsPromise = this.measureDimensions().catch((err: unknown) => {\n // Never cache a failed discovery — a transient outage would otherwise\n // pin every future call to the same rejection.\n this.dimensionsPromise = undefined;\n throw err;\n });\n }\n return this.dimensionsPromise;\n }\n\n private async measureDimensions(): Promise<number> {\n // Dimensionality is intrinsic to the model, so the model is the\n // authority: embed a probe and measure it. A hand-maintained table goes\n // stale the day a new model ships and silently mis-sizes the index.\n const probe = await this.embed('dimension probe');\n if (!Array.isArray(probe) || probe.length === 0) {\n throw new Error(`Ollama returned no embedding for dimension probe of model '${this.config.model}'`);\n }\n return probe.length;\n }\n\n model(): string {\n return this.config.model;\n }\n}\n"]}
@@ -0,0 +1,27 @@
1
+ // src/embedding/provider-error.ts
2
+ var EMBED_TIMEOUT_MS = 15e3;
3
+ var EmbeddingProviderError = class extends Error {
4
+ provider;
5
+ status;
6
+ /** The model that was asked for — the throw site knows it, so it travels as a
7
+ * field. A consumer needing it must never have to read it out of `body`:
8
+ * that is the substring-matching this class exists to end, and Ollama's body
9
+ * happens to name the model while Voyage's does not. */
10
+ model;
11
+ body;
12
+ constructor(provider, status, model, body) {
13
+ super(`${provider} embed error ${status}: ${body}`);
14
+ this.name = "EmbeddingProviderError";
15
+ this.provider = provider;
16
+ this.status = status;
17
+ this.model = model;
18
+ this.body = body;
19
+ }
20
+ };
21
+ function isColdModelError(error) {
22
+ return error instanceof EmbeddingProviderError && error.status === 404;
23
+ }
24
+
25
+ export { EMBED_TIMEOUT_MS, EmbeddingProviderError, isColdModelError };
26
+ //# sourceMappingURL=chunk-N6SZJFFY.js.map
27
+ //# sourceMappingURL=chunk-N6SZJFFY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/embedding/provider-error.ts"],"names":[],"mappings":";AA2BO,IAAM,gBAAA,GAAmB;AAEzB,IAAM,sBAAA,GAAN,cAAqC,KAAA,CAAM;AAAA,EACvC,QAAA;AAAA,EACA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA;AAAA,EACA,IAAA;AAAA,EAET,WAAA,CAAY,QAAA,EAAiC,MAAA,EAAgB,KAAA,EAAe,IAAA,EAAc;AAIxF,IAAA,KAAA,CAAM,GAAG,QAAQ,CAAA,aAAA,EAAgB,MAAM,CAAA,EAAA,EAAK,IAAI,CAAA,CAAE,CAAA;AAClD,IAAA,IAAA,CAAK,IAAA,GAAO,wBAAA;AACZ,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AACF;AAyBO,SAAS,iBAAiB,KAAA,EAAiD;AAChF,EAAA,OAAO,KAAA,YAAiB,sBAAA,IAA0B,KAAA,CAAM,MAAA,KAAW,GAAA;AACrE","file":"chunk-N6SZJFFY.js","sourcesContent":["/**\n * An embedding provider's HTTP failure, carrying the status it came from.\n *\n * Both providers used to throw `new Error(\\`… error ${status}: ${body}\\`)`, which\n * put the status in the text and nowhere else — so no caller could branch on it\n * without matching a substring. That is the mirror this codebase refuses\n * elsewhere, and it is why a cold model cache was unclassifiable and therefore\n * fatal on every first boot of a fresh KB.\n *\n * One class for both providers so they agree by construction rather than by two\n * people remembering the same shape.\n */\n\nexport type EmbeddingProviderName = 'ollama' | 'voyage';\n\n/**\n * Deadline on ONE embedding request.\n *\n * Without it a retry budget is a count with no wall clock: delays are bounded by\n * the policy, but a `fetch` with no signal can sit in TCP retransmit for minutes\n * under loss, so N attempts × unbounded is unbounded. `/bus/emit` learned this\n * already — the caller's deadline governs one attempt, the policy governs the set.\n *\n * 15s rather than something tighter because Ollama can spend real time LOADING a\n * model into memory on the first embed after a pull lands. That is a success in\n * progress, and a 5s deadline would abort it just as it was about to work.\n */\nexport const EMBED_TIMEOUT_MS = 15_000;\n\nexport class EmbeddingProviderError extends Error {\n readonly provider: EmbeddingProviderName;\n readonly status: number;\n /** The model that was asked for — the throw site knows it, so it travels as a\n * field. A consumer needing it must never have to read it out of `body`:\n * that is the substring-matching this class exists to end, and Ollama's body\n * happens to name the model while Voyage's does not. */\n readonly model: string;\n readonly body: string;\n\n constructor(provider: EmbeddingProviderName, status: number, model: string, body: string) {\n // The message keeps its old shape — status included — so existing log lines\n // and anything matching on them read exactly as before. The fields are what\n // is new; the prose is not a step backwards.\n super(`${provider} embed error ${status}: ${body}`);\n this.name = 'EmbeddingProviderError';\n this.provider = provider;\n this.status = status;\n this.model = model;\n this.body = body;\n }\n}\n\n/**\n * True when the model has not been pulled YET.\n *\n * The case `isTransientFetchError` did not anticipate, and deliberately does not\n * cover: its exclusion of HTTP-level failures is right for a gateway 401 — \"the\n * server is UP and rejected us; retrying won't change its mind\" — and wrong here.\n * A cold-cache 404 means the server is up, the *resource* is not there yet, and\n * it will be. Do not widen `isTransientFetchError` to reach this; its narrowness\n * is load-bearing for the reasoning it already carries.\n *\n * Narrow on two axes, both on purpose:\n * - **only 404.** A 401 is a bad key, a 400 a bad request; neither becomes a 200\n * by waiting. 429/503 from a provider under load are arguably retryable too,\n * but nothing has observed them here and a predicate should not speculate.\n * - **only this error type.** A 404 from the gateway means not-found, and\n * retrying that is wrong. Structure, not a bare status, is what distinguishes\n * \"not yet\" from \"not there\".\n *\n * It cannot tell \"not pulled yet\" from \"the configured model name is wrong\" —\n * both are a 404 and the provider says nothing more. That is accepted: a typo now\n * fails after the retry budget instead of instantly, which is why the message on\n * exhaustion must name both possibilities.\n */\nexport function isColdModelError(error: unknown): error is EmbeddingProviderError {\n return error instanceof EmbeddingProviderError && error.status === 404;\n}\n"]}
@@ -1,3 +1,5 @@
1
+ import { EMBED_TIMEOUT_MS, EmbeddingProviderError } from './chunk-N6SZJFFY.js';
2
+
1
3
  // src/embedding/voyage.ts
2
4
  var VoyageEmbeddingProvider = class {
3
5
  config;
@@ -17,6 +19,7 @@ var VoyageEmbeddingProvider = class {
17
19
  "Content-Type": "application/json",
18
20
  "Authorization": `Bearer ${this.config.apiKey}`
19
21
  },
22
+ signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),
20
23
  body: JSON.stringify({
21
24
  model: this.config.model,
22
25
  input: texts
@@ -24,7 +27,7 @@ var VoyageEmbeddingProvider = class {
24
27
  });
25
28
  if (!response.ok) {
26
29
  const body = await response.text();
27
- throw new Error(`Voyage API error ${response.status}: ${body}`);
30
+ throw new EmbeddingProviderError("voyage", response.status, this.config.model, body);
28
31
  }
29
32
  const json = await response.json();
30
33
  return json.data.map((d) => d.embedding);
@@ -51,5 +54,5 @@ var VoyageEmbeddingProvider = class {
51
54
  };
52
55
 
53
56
  export { VoyageEmbeddingProvider };
54
- //# sourceMappingURL=chunk-PJ72DF6E.js.map
55
- //# sourceMappingURL=chunk-PJ72DF6E.js.map
57
+ //# sourceMappingURL=chunk-RWMSF6N3.js.map
58
+ //# sourceMappingURL=chunk-RWMSF6N3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/embedding/voyage.ts"],"names":[],"mappings":";;;AAgBO,IAAM,0BAAN,MAA2D;AAAA,EACxD,MAAA;AAAA,EACA,iBAAA;AAAA,EAER,YAAY,MAAA,EAAsB;AAChC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,MAAM,MAAM,IAAA,EAAiC;AAC3C,IAAA,MAAM,UAAU,MAAM,IAAA,CAAK,UAAA,CAAW,CAAC,IAAI,CAAC,CAAA;AAC5C,IAAA,OAAO,QAAQ,CAAC,CAAA;AAAA,EAClB;AAAA,EAEA,MAAM,WAAW,KAAA,EAAsC;AACrD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,QAAA,IAAY,wCAAA;AAEzC,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,QAAA,EAAU;AAAA,MACrC,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,eAAA,EAAiB,CAAA,OAAA,EAAU,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAAA,OAC/C;AAAA,MACA,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,gBAAgB,CAAA;AAAA,MAC5C,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,QACnB,KAAA,EAAO,KAAK,MAAA,CAAO,KAAA;AAAA,QACnB,KAAA,EAAO;AAAA,OACR;AAAA,KACF,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,MAAA,MAAM,IAAI,uBAAuB,QAAA,EAAU,QAAA,CAAS,QAAQ,IAAA,CAAK,MAAA,CAAO,OAAO,IAAI,CAAA;AAAA,IACrF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,SAAS,CAAA;AAAA,EACvC;AAAA,EAEA,UAAA,GAA8B;AAC5B,IAAA,IAAI,CAAC,KAAK,iBAAA,EAAmB;AAC3B,MAAA,IAAA,CAAK,oBAAoB,IAAA,CAAK,iBAAA,EAAkB,CAAE,KAAA,CAAM,CAAC,GAAA,KAAiB;AAGxE,QAAA,IAAA,CAAK,iBAAA,GAAoB,MAAA;AACzB,QAAA,MAAM,GAAA;AAAA,MACR,CAAC,CAAA;AAAA,IACH;AACA,IAAA,OAAO,IAAA,CAAK,iBAAA;AAAA,EACd;AAAA,EAEA,MAAc,iBAAA,GAAqC;AAIjD,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,KAAA,CAAM,iBAAiB,CAAA;AAChD,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,WAAW,CAAA,EAAG;AAC/C,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,2DAAA,EAA8D,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,IACpG;AACA,IAAA,OAAO,KAAA,CAAM,MAAA;AAAA,EACf;AAAA,EAEA,KAAA,GAAgB;AACd,IAAA,OAAO,KAAK,MAAA,CAAO,KAAA;AAAA,EACrB;AACF","file":"chunk-RWMSF6N3.js","sourcesContent":["/**\n * Voyage AI Embedding Provider\n *\n * Cloud embedding via the Voyage AI API.\n * Requires a Voyage AI API key (distinct from Anthropic inference keys).\n */\n\nimport type { EmbeddingProvider } from './interface';\nimport { EmbeddingProviderError, EMBED_TIMEOUT_MS } from './provider-error';\n\nexport interface VoyageConfig {\n apiKey: string;\n model: string;\n endpoint?: string;\n}\n\nexport class VoyageEmbeddingProvider implements EmbeddingProvider {\n private config: VoyageConfig;\n private dimensionsPromise?: Promise<number>;\n\n constructor(config: VoyageConfig) {\n this.config = config;\n }\n\n async embed(text: string): Promise<number[]> {\n const results = await this.embedBatch([text]);\n return results[0];\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n const endpoint = this.config.endpoint ?? 'https://api.voyageai.com/v1/embeddings';\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.config.apiKey}`,\n },\n signal: AbortSignal.timeout(EMBED_TIMEOUT_MS),\n body: JSON.stringify({\n model: this.config.model,\n input: texts,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new EmbeddingProviderError('voyage', response.status, this.config.model, body);\n }\n\n const json = await response.json() as { data: Array<{ embedding: number[] }> };\n return json.data.map(d => d.embedding);\n }\n\n dimensions(): Promise<number> {\n if (!this.dimensionsPromise) {\n this.dimensionsPromise = this.measureDimensions().catch((err: unknown) => {\n // Never cache a failed discovery — a transient outage would otherwise\n // pin every future call to the same rejection.\n this.dimensionsPromise = undefined;\n throw err;\n });\n }\n return this.dimensionsPromise;\n }\n\n private async measureDimensions(): Promise<number> {\n // Dimensionality is intrinsic to the model, so the model is the\n // authority: embed a probe and measure it. A hand-maintained table goes\n // stale the day a new model ships and silently mis-sizes the index.\n const probe = await this.embed('dimension probe');\n if (!Array.isArray(probe) || probe.length === 0) {\n throw new Error(`Voyage returned no embedding for dimension probe of model '${this.config.model}'`);\n }\n return probe.length;\n }\n\n model(): string {\n return this.config.model;\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
+ import { isColdModelError } from './chunk-N6SZJFFY.js';
1
2
  import { createHash } from 'crypto';
2
-
3
- // src/store/qdrant.ts
3
+ import { retryWithBackoff, isTransientFetchError } from '@semiont/core';
4
4
 
5
5
  // src/store/merge.ts
6
6
  function mergeByResource(hits) {
@@ -12,6 +12,23 @@ function mergeByResource(hits) {
12
12
  }
13
13
  return [...bestByResource.values()].sort((a, b) => b.score - a.score);
14
14
  }
15
+ var EMBEDDING_PROVIDER_RETRY = {
16
+ attempts: 12,
17
+ initialDelayMs: 1e3,
18
+ maxDelayMs: 15e3
19
+ };
20
+ var notReady = (error) => isColdModelError(error) || isTransientFetchError(error);
21
+ async function resolveDimensions(dimensions, signal) {
22
+ try {
23
+ return await retryWithBackoff(dimensions, notReady, EMBEDDING_PROVIDER_RETRY, void 0, signal);
24
+ } catch (error) {
25
+ if (!isColdModelError(error)) throw error;
26
+ throw new Error(
27
+ `Embedding model '${error.model}' is still unavailable on ${error.provider} after ${EMBEDDING_PROVIDER_RETRY.attempts} attempts. Either it has not been pulled, or the configured model name is wrong \u2014 the provider answers 404 for both.`,
28
+ { cause: error }
29
+ );
30
+ }
31
+ }
15
32
 
16
33
  // src/store/qdrant.ts
17
34
  function toQdrantId(input) {
@@ -92,8 +109,9 @@ var QdrantVectorStore = class _QdrantVectorStore {
92
109
  return;
93
110
  } catch {
94
111
  }
112
+ const size = await resolveDimensions(this.config.dimensions, this.config.signal);
95
113
  await this.qdrant.createCollection(name, {
96
- vectors: { size: await this.config.dimensions(), distance: "Cosine" }
114
+ vectors: { size, distance: "Cosine" }
97
115
  });
98
116
  }
99
117
  /**
@@ -300,6 +318,6 @@ var QdrantVectorStore = class _QdrantVectorStore {
300
318
  }
301
319
  };
302
320
 
303
- export { QdrantVectorStore, mergeByResource };
304
- //# sourceMappingURL=chunk-WCD6P6QQ.js.map
305
- //# sourceMappingURL=chunk-WCD6P6QQ.js.map
321
+ export { EMBEDDING_PROVIDER_RETRY, QdrantVectorStore, mergeByResource, resolveDimensions };
322
+ //# sourceMappingURL=chunk-U2EFPQG6.js.map
323
+ //# sourceMappingURL=chunk-U2EFPQG6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store/merge.ts","../src/embedding/resolve-dimensions.ts","../src/store/qdrant.ts"],"names":[],"mappings":"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","file":"chunk-U2EFPQG6.js","sourcesContent":["/**\n * Chunk→resource max-sim merge.\n *\n * Vector search returns *chunk*-level hits: one resource can match on several\n * of its chunks, and a caller that wants a list of resources has to fold them.\n * The fold is max-sim (late interaction): a resource scores as its single\n * best-matching chunk, and carries that chunk's text — so the caller can show\n * the passage that actually matched. Averaging instead would blur a\n * multi-topic resource into a vague centroid and lose which passage hit;\n * keeping the first hit seen would make the result order-dependent.\n *\n * Three callers share this: both `searchByResource` implementations (qdrant\n * batches one query per source chunk; memory scores every candidate against\n * every source chunk) and the SEMANTIC-FALLBACK fold over raw `searchResources`\n * output, whose hits map one-to-one with no dedup. All three normalise to\n * `VectorSearchResult[]` first, so this fold is the only copy of the logic.\n *\n * Deliberately does NOT slice to a limit: `searchByResource` and the fallback\n * want different slices, and a fold that silently truncates is the kind of\n * hidden cap that reads as \"covered everything\" when it didn't.\n */\n\nimport type { VectorSearchResult } from './interface';\n\n/**\n * Fold chunk-level hits to one entry per resource — the highest-scoring chunk\n * wins, carrying its own text and metadata — sorted score-descending.\n */\nexport function mergeByResource(hits: VectorSearchResult[]): VectorSearchResult[] {\n const bestByResource = new Map<string, VectorSearchResult>();\n\n for (const hit of hits) {\n const key = String(hit.resourceId);\n const prev = bestByResource.get(key);\n if (!prev || hit.score > prev.score) bestByResource.set(key, hit);\n }\n\n return [...bestByResource.values()].sort((a, b) => b.score - a.score);\n}\n","/**\n * Resolving a collection's vector width against a provider that may not be ready.\n *\n * The width is discovered by embedding a probe (the model is the authority — a\n * hand-maintained table goes stale the day a new model ships), so creating a\n * collection is the one boot step that needs the embedding provider to be live.\n * On the first boot of a fresh KB that races Ollama's model pull, and losing it\n * used to be `Fatal`: measured 3, 4 and 5 restarts for archivist, librarian and\n * smelter, with the archivist's flapping separately stranding `semiont-worker`\n * in `Created` forever.\n *\n * Reuses `STARTUP_FETCH_RETRY` **by name** — one place decides how long a boot\n * waits for a dependency, rather than a second startup-retry constant that drifts\n * from it.\n *\n * Called only where a collection must be CREATED. A warm KB short-circuits before\n * reaching here and contacts no provider at boot, so this adds no steady-state\n * latency.\n */\n\nimport { retryWithBackoff, isTransientFetchError, type RetryPolicy } from '@semiont/core';\nimport { isColdModelError } from './provider-error';\n\n/**\n * How long a boot waits for the embedding provider to become usable.\n *\n * **Not `STARTUP_FETCH_RETRY`**, which this originally reused by name on the\n * plan's instruction. That policy answers *\"how long does a boot wait for a peer\n * to start listening?\"* — seconds. This answers *\"how long does a boot wait for a\n * model to download?\"* — the incident's own measurement was \"under a minute\", and\n * `STARTUP_FETCH_RETRY`'s ~39s ceiling (≈29s expected, once jittered) would have\n * expired just before the thing it was waiting for arrived.\n *\n * Two facts that happen to be measured in seconds are still two facts. One\n * constant is right when it is one fact; sharing it here would mean every future\n * adjustment to the gateway's patience silently moved this one.\n *\n * Worst case ≈ `retryBudgetMs(EMBEDDING_PROVIDER_RETRY, EMBED_TIMEOUT_MS)` — see\n * `startup-timeout.test.ts`, which gates it against the deadline it could race.\n */\nexport const EMBEDDING_PROVIDER_RETRY: RetryPolicy = {\n attempts: 12,\n initialDelayMs: 1_000,\n maxDelayMs: 15_000,\n};\n\n/**\n * Not ready YET, by either route: the model is still downloading (404), or the\n * provider is not listening at all (connection refused).\n *\n * Composed rather than choosing one, because at boot they are the same wait with\n * the same collateral — a flapping archivist strands `semiont-worker` in\n * `Created` whichever error caused the flap. Retrying connection-refused to the\n * GATEWAY and not to Ollama, in the same three mains, was an asymmetry nobody\n * chose; `authenticate()` has used `isTransientFetchError` at boot all along.\n */\nconst notReady = (error: unknown): boolean =>\n isColdModelError(error) || isTransientFetchError(error);\n\nexport async function resolveDimensions(\n dimensions: () => Promise<number>,\n /** The caller's boot deadline, if it has one. Stops the retry when it fires,\n * so a caller racing a timeout against this cannot cut it short by surprise. */\n signal?: AbortSignal,\n): Promise<number> {\n try {\n return await retryWithBackoff(dimensions, notReady, EMBEDDING_PROVIDER_RETRY, undefined, signal);\n } catch (error) {\n // `isColdModelError`, NOT `instanceof EmbeddingProviderError`: a 401 is also\n // one of those, and swapping the check let a bad API key fall through to the\n // \"still unavailable\" message instead of rethrowing. It is a type guard, so\n // `error.model` narrows without a second test.\n // Connection-level failures propagate with their own message — \"fetch\n // failed\" against a host that never answered needs no help being read, and\n // dressing it up as a model problem would misdirect.\n if (!isColdModelError(error)) throw error;\n // Both possibilities are live and one 404 does not distinguish them. The old\n // message implied only the first, which sends a reader watching for a pull\n // that will never finish because the name is wrong.\n //\n // The model comes off the error as a FIELD. It is not threaded down through\n // `QdrantConfig` and `VectorStoreConfig` (three layers of plumbing for a\n // string) and it is not read back out of the provider's body — the throw site\n // knows it, so it travels with the failure. Reading it out of prose would be\n // the substring-matching this whole fix exists to end, and it would only work\n // for Ollama: Voyage's 404 body does not name the model.\n throw new Error(\n `Embedding model '${error.model}' is still unavailable on ${error.provider} after ` +\n `${EMBEDDING_PROVIDER_RETRY.attempts} attempts. Either it has not been pulled, or the ` +\n `configured model name is wrong — the provider answers 404 for both.`,\n { cause: error },\n );\n }\n}\n","/**\n * Qdrant VectorStore Implementation\n *\n * Uses the Qdrant REST API via @qdrant/js-client-rest.\n * Manages two collections: 'resources' and 'annotations'.\n */\n\nimport { createHash } from 'crypto';\nimport type { QdrantClient, Schemas } from '@qdrant/js-client-rest';\nimport type { ResourceId, AnnotationId } from '@semiont/core';\nimport type { VectorStore, EmbeddingChunk, AnnotationPayload, VectorSearchResult, SearchOptions, ResourceStamp } from './interface';\nimport { mergeByResource } from './merge';\nimport { resolveDimensions } from '../embedding/resolve-dimensions';\n\n/**\n * Generate a deterministic UUID v5-style ID from an arbitrary string.\n * Qdrant requires point IDs to be UUIDs or unsigned integers.\n */\nfunction toQdrantId(input: string): string {\n const hex = createHash('md5').update(input).digest('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n/**\n * Map a scored point to the store-agnostic result shape. Shared by every read\n * path so the payload→result contract (including `machineRead`) has one home.\n */\nfunction toSearchResult(point: Schemas['ScoredPoint']): VectorSearchResult {\n const payload = point.payload ?? {};\n return {\n id: String(point.id),\n score: point.score,\n resourceId: payload.resourceId as ResourceId,\n annotationId: payload.annotationId as AnnotationId | undefined,\n text: payload.text as string,\n entityTypes: payload.entityTypes as string[] | undefined,\n ...(payload.machineRead ? { machineRead: true } : {}),\n };\n}\n\nexport interface QdrantConfig {\n host: string;\n port: number;\n /** Resolves the embedding dimensionality; called only when creating a collection. */\n dimensions: () => Promise<number>;\n /** The caller's boot deadline. Resolving the dimensionality retries while the\n * provider warms up, and this is what stops it if the caller gives up first. */\n signal?: AbortSignal;\n}\n\n/** The payload fields a stamp is read from — one list for the scroll and the\n * targeted read, so the two cannot drift. */\nconst STAMP_FIELDS = ['resourceId', 'contentChecksum', 'entityTypes', 'machineRead'];\n\nfunction toStamp(payload: Record<string, unknown> | null | undefined): ResourceStamp {\n const checksum = payload?.contentChecksum;\n const entityTypes = payload?.entityTypes;\n return {\n contentChecksum: typeof checksum === 'string' ? checksum : undefined,\n entityTypes: Array.isArray(entityTypes)\n ? entityTypes.filter((t): t is string => typeof t === 'string')\n : [],\n ...(payload?.machineRead ? { machineRead: true } : {}),\n };\n}\n\nexport class QdrantVectorStore implements VectorStore {\n /**\n * Per-batch point over-fetch for `searchByResource`. The batch limit caps\n * points while the caller wants resources, so multi-chunk targets would\n * otherwise crowd the top-K. See the call site.\n */\n private static readonly SEARCH_BY_RESOURCE_OVER_FETCH = 4;\n\n private client: QdrantClient | null = null;\n private config: QdrantConfig;\n\n constructor(config: QdrantConfig) {\n this.config = config;\n }\n\n private get qdrant(): QdrantClient {\n if (!this.client) throw new Error('QdrantVectorStore is not connected');\n return this.client;\n }\n\n async connect(): Promise<void> {\n const { QdrantClient } = await import('@qdrant/js-client-rest');\n this.client = new QdrantClient({\n host: this.config.host,\n port: this.config.port,\n });\n\n // Ensure collections exist\n await this.ensureCollection('resources');\n await this.ensureCollection('annotations');\n // Payload indexes so filtered operations scale:\n // - entityTypes: the excludeEntityTypes recall filter.\n // - resourceId: searchByResource's by-resource scroll + self-exclusion, and\n // the per-resource delete paths (deleteResourceVectors /\n // deleteAnnotationVectorsForResource).\n await this.ensurePayloadIndex('resources', 'entityTypes');\n await this.ensurePayloadIndex('resources', 'resourceId');\n await this.ensurePayloadIndex('annotations', 'resourceId');\n }\n\n async disconnect(): Promise<void> {\n this.client = null;\n }\n\n async clearAll(): Promise<void> {\n try { await this.qdrant.deleteCollection('resources'); } catch { /* may not exist */ }\n try { await this.qdrant.deleteCollection('annotations'); } catch { /* may not exist */ }\n await this.ensureCollection('resources');\n await this.ensureCollection('annotations');\n }\n\n isConnected(): boolean {\n return this.client !== null;\n }\n\n private async ensureCollection(name: string): Promise<void> {\n try {\n await this.qdrant.getCollection(name);\n return;\n } catch { /* absent — create it below, which is the only path needing the size */ }\n\n // The dimensionality is discovered from the embedding provider, so it is\n // resolved HERE and nowhere else: an existing collection already encodes\n // its vector size, and consulting the provider to re-derive a number we\n // would not use is exactly the eager-probe coupling this avoids.\n //\n // And HERE is also where the retry belongs, not around `connect()`: wrapping\n // connect would re-run `getCollection` and both `ensurePayloadIndex` calls on\n // every attempt — idempotent, but it widens the retry over operations that\n // were not failing. Only this one call needs the provider to be live.\n const size = await resolveDimensions(this.config.dimensions, this.config.signal);\n await this.qdrant.createCollection(name, {\n vectors: { size, distance: 'Cosine' },\n });\n }\n\n /**\n * Idempotently create a keyword payload index. Qdrant accepts a repeat call\n * for an already-indexed field, so this runs safely on every connect and\n * back-fills the index on collections created before the field was indexed.\n */\n private async ensurePayloadIndex(collection: string, field: string): Promise<void> {\n try {\n await this.qdrant.createPayloadIndex(collection, { field_name: field, field_schema: 'keyword' });\n } catch { /* already indexed, or created concurrently */ }\n }\n\n async upsertResourceVectors(resourceId: ResourceId, chunks: EmbeddingChunk[], contentChecksum: string, entityTypes: string[], machineRead?: boolean): Promise<void> {\n // Replace semantics: purge existing chunks first, or a resource that\n // shrinks leaves orphan points at the higher chunk indices.\n await this.deleteResourceVectors(resourceId);\n if (chunks.length === 0) return;\n\n const points = chunks.map((chunk) => ({\n id: toQdrantId(`${resourceId}-${chunk.chunkIndex}`),\n vector: chunk.embedding,\n payload: {\n resourceId: String(resourceId),\n chunkIndex: chunk.chunkIndex,\n text: chunk.text,\n contentChecksum,\n entityTypes,\n // Only stamped when true: absence is the common case and carries no\n // claim, so a native extraction stores nothing extra.\n ...(machineRead ? { machineRead: true } : {}),\n },\n }));\n\n await this.qdrant.upsert('resources', { points });\n }\n\n async upsertAnnotationVector(\n annotationId: AnnotationId,\n embedding: number[],\n payload: AnnotationPayload\n ): Promise<void> {\n await this.qdrant.upsert('annotations', {\n points: [{\n id: toQdrantId(String(annotationId)),\n vector: embedding,\n payload: {\n annotationId: String(payload.annotationId),\n resourceId: String(payload.resourceId),\n motivation: payload.motivation,\n entityTypes: payload.entityTypes,\n text: payload.exactText,\n ...(payload.machineRead ? { machineRead: true } : {}),\n },\n }],\n });\n }\n\n async deleteResourceVectors(resourceId: ResourceId): Promise<void> {\n await this.qdrant.delete('resources', {\n filter: {\n must: [{ key: 'resourceId', match: { value: String(resourceId) } }],\n },\n });\n }\n\n async deleteAnnotationVector(annotationId: AnnotationId): Promise<void> {\n await this.qdrant.delete('annotations', {\n points: [toQdrantId(String(annotationId))],\n });\n }\n\n async deleteAnnotationVectorsForResource(resourceId: ResourceId): Promise<void> {\n await this.qdrant.delete('annotations', {\n filter: {\n must: [{ key: 'resourceId', match: { value: String(resourceId) } }],\n },\n });\n }\n\n async count(): Promise<number> {\n const [resources, annotations] = await Promise.all([\n this.qdrant.count('resources', { exact: true }),\n this.qdrant.count('annotations', { exact: true }),\n ]);\n return resources.count + annotations.count;\n }\n\n async updateResourceEntityTypes(resourceId: ResourceId, entityTypes: string[]): Promise<void> {\n // Payload-only rewrite across the resource's points — no vectors touched,\n // no embedding involved (S13). Qdrant set_payload with a filter is a\n // no-op when the resource has no points.\n await this.qdrant.setPayload('resources', {\n payload: { entityTypes },\n filter: {\n must: [{ key: 'resourceId', match: { value: String(resourceId) } }],\n },\n });\n }\n\n async getResourceStamp(resourceId: ResourceId): Promise<ResourceStamp | undefined> {\n const page = await this.qdrant.scroll('resources', {\n limit: 1,\n filter: { must: [{ key: 'resourceId', match: { value: String(resourceId) } }] },\n with_payload: STAMP_FIELDS,\n with_vector: false,\n });\n const point = page.points[0];\n return point ? toStamp(point.payload) : undefined;\n }\n\n async listResourceStamps(): Promise<Map<string, ResourceStamp>> {\n const stamps = new Map<string, ResourceStamp>();\n let offset: Schemas['ScrollRequest']['offset'] = undefined;\n do {\n const page = await this.qdrant.scroll('resources', {\n limit: 1000,\n offset,\n with_payload: STAMP_FIELDS,\n with_vector: false,\n });\n for (const point of page.points) {\n const rid = point.payload?.resourceId;\n if (typeof rid !== 'string' || stamps.has(rid)) continue;\n stamps.set(rid, toStamp(point.payload));\n }\n offset = page.next_page_offset ?? undefined;\n } while (offset !== undefined && offset !== null);\n return stamps;\n }\n\n async listAnnotationIds(): Promise<Set<string>> {\n return this.scrollPayloadField('annotations', 'annotationId');\n }\n\n /** Collect the distinct values of one payload field across a collection. */\n private async scrollPayloadField(collection: string, field: string): Promise<Set<string>> {\n const values = new Set<string>();\n let offset: Schemas['ScrollRequest']['offset'] = undefined;\n do {\n const page = await this.qdrant.scroll(collection, {\n limit: 1000,\n offset,\n with_payload: [field],\n with_vector: false,\n });\n for (const point of page.points) {\n const value = point.payload?.[field];\n if (typeof value === 'string') values.add(value);\n }\n offset = page.next_page_offset ?? undefined;\n } while (offset !== undefined && offset !== null);\n return values;\n }\n\n async searchResources(embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n return this.search('resources', embedding, opts);\n }\n\n async searchAnnotations(embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n return this.search('annotations', embedding, opts);\n }\n\n async searchByResource(resourceId: ResourceId, opts: SearchOptions): Promise<VectorSearchResult[]> {\n // Fetch the resource's stored chunk vectors (page through all of them so a\n // long resource isn't silently truncated).\n const queryVectors: number[][] = [];\n let offset: Schemas['ScrollRequest']['offset'] = undefined;\n do {\n const page = await this.qdrant.scroll('resources', {\n filter: { must: [{ key: 'resourceId', match: { value: String(resourceId) } }] },\n with_vector: true,\n with_payload: false,\n limit: 256,\n offset,\n });\n for (const point of page.points) {\n if (Array.isArray(point.vector)) queryVectors.push(point.vector as number[]);\n }\n offset = page.next_page_offset ?? undefined;\n } while (offset !== undefined && offset !== null);\n\n if (queryVectors.length === 0) return [];\n\n // Self-exclude the source; carry the caller's filter (e.g. excludeEntityTypes).\n const filter = this.buildFilter({ ...opts.filter, excludeResourceId: resourceId });\n\n // One batched query per source chunk (single round-trip). Each batch's\n // `limit` caps POINTS, but the merge below yields RESOURCES — so a target\n // with many chunks can fill several point-slots and crowd out resources\n // that belong in the top-`limit` after folding. Over-fetch per batch to\n // leave the merge material to work with; the memory store scores every\n // candidate exhaustively, and without this the two gateways disagree on\n // recall. Headroom, not a guarantee: a resource whose chunks are all\n // outranked by OVER_FETCH×limit points in every batch is still missed.\n // Qdrant's group-by would make it exact, but only via the `search`-family\n // endpoints 1.19.0 removed (see qdrant-query-api.test.ts).\n const searches = queryVectors.map((vector) => ({\n query: vector,\n limit: opts.limit * QdrantVectorStore.SEARCH_BY_RESOURCE_OVER_FETCH,\n score_threshold: opts.scoreThreshold,\n filter: filter ?? undefined,\n with_payload: true,\n }));\n const batches = await this.qdrant.queryBatch('resources', { searches });\n\n // Normalise to VectorSearchResult, then fold chunk hits to one entry per\n // resource (max-sim) via the shared merge; slice to the caller's limit.\n const hits = batches.flatMap((batch) => batch.points.map((r) => toSearchResult(r)));\n return mergeByResource(hits).slice(0, opts.limit);\n }\n\n private async search(collection: string, embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n const filter = this.buildFilter(opts.filter);\n\n // `query`, not `search`: the REST client dropped `search`/`searchBatch` in\n // 1.19.0 in favour of the universal query endpoint. Same request but the\n // vector moves to `query`, and the response is wrapped in `{ points }`.\n const { points } = await this.qdrant.query(collection, {\n query: embedding,\n limit: opts.limit,\n score_threshold: opts.scoreThreshold,\n filter: filter ?? undefined,\n with_payload: true,\n });\n\n return points.map((r) => toSearchResult(r));\n }\n\n private buildFilter(filter?: SearchOptions['filter']): Schemas['Filter'] | null {\n if (!filter) return null;\n\n const must: Schemas['FieldCondition'][] = [];\n\n if (filter.entityTypes && filter.entityTypes.length > 0) {\n // any-of: match payloads whose `entityTypes` array contains at least one\n // of the requested types. Matches the memory store's `some(t => ...)`\n // semantics; pushing one `must` clause per type would mean all-of.\n must.push({ key: 'entityTypes', match: { any: filter.entityTypes } });\n }\n\n if (filter.resourceId) {\n must.push({ key: 'resourceId', match: { value: String(filter.resourceId) } });\n }\n\n if (filter.motivation) {\n must.push({ key: 'motivation', match: { value: filter.motivation } });\n }\n\n const must_not: Schemas['FieldCondition'][] = [];\n\n if (filter.excludeResourceId) {\n must_not.push({ key: 'resourceId', match: { value: String(filter.excludeResourceId) } });\n }\n\n if (filter.excludeEntityTypes && filter.excludeEntityTypes.length > 0) {\n // any-of exclusion: drop points whose entityTypes contain any of these.\n must_not.push({ key: 'entityTypes', match: { any: filter.excludeEntityTypes } });\n }\n\n if (must.length === 0 && must_not.length === 0) return null;\n\n return {\n ...(must.length > 0 ? { must } : {}),\n ...(must_not.length > 0 ? { must_not } : {}),\n };\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnnotationId, ResourceId } from '@semiont/core';
1
+ import { AnnotationId, ResourceId, RetryPolicy } from '@semiont/core';
2
2
 
3
3
  /**
4
4
  * VectorStore Interface
@@ -155,6 +155,9 @@ interface QdrantConfig {
155
155
  port: number;
156
156
  /** Resolves the embedding dimensionality; called only when creating a collection. */
157
157
  dimensions: () => Promise<number>;
158
+ /** The caller's boot deadline. Resolving the dimensionality retries while the
159
+ * provider warms up, and this is what stops it if the caller gives up first. */
160
+ signal?: AbortSignal;
158
161
  }
159
162
  declare class QdrantVectorStore implements VectorStore {
160
163
  /**
@@ -252,6 +255,8 @@ interface VectorStoreConfig {
252
255
  * without touching the embedding provider at all.
253
256
  */
254
257
  dimensions: () => Promise<number>;
258
+ /** The caller's boot deadline, passed to whatever retries while warming up. */
259
+ signal?: AbortSignal;
255
260
  }
256
261
  declare function createVectorStore(config: VectorStoreConfig): Promise<VectorStore>;
257
262
 
@@ -364,5 +369,110 @@ interface EmbeddingConfig {
364
369
  }
365
370
  declare function createEmbeddingProvider(config: EmbeddingConfig): Promise<EmbeddingProvider>;
366
371
 
367
- export { MemoryVectorStore, OllamaEmbeddingProvider, QdrantVectorStore, VoyageEmbeddingProvider, createEmbeddingProvider, createVectorStore, mergeByResource };
372
+ /**
373
+ * An embedding provider's HTTP failure, carrying the status it came from.
374
+ *
375
+ * Both providers used to throw `new Error(\`… error ${status}: ${body}\`)`, which
376
+ * put the status in the text and nowhere else — so no caller could branch on it
377
+ * without matching a substring. That is the mirror this codebase refuses
378
+ * elsewhere, and it is why a cold model cache was unclassifiable and therefore
379
+ * fatal on every first boot of a fresh KB.
380
+ *
381
+ * One class for both providers so they agree by construction rather than by two
382
+ * people remembering the same shape.
383
+ */
384
+ type EmbeddingProviderName = 'ollama' | 'voyage';
385
+ /**
386
+ * Deadline on ONE embedding request.
387
+ *
388
+ * Without it a retry budget is a count with no wall clock: delays are bounded by
389
+ * the policy, but a `fetch` with no signal can sit in TCP retransmit for minutes
390
+ * under loss, so N attempts × unbounded is unbounded. `/bus/emit` learned this
391
+ * already — the caller's deadline governs one attempt, the policy governs the set.
392
+ *
393
+ * 15s rather than something tighter because Ollama can spend real time LOADING a
394
+ * model into memory on the first embed after a pull lands. That is a success in
395
+ * progress, and a 5s deadline would abort it just as it was about to work.
396
+ */
397
+ declare const EMBED_TIMEOUT_MS = 15000;
398
+ declare class EmbeddingProviderError extends Error {
399
+ readonly provider: EmbeddingProviderName;
400
+ readonly status: number;
401
+ /** The model that was asked for — the throw site knows it, so it travels as a
402
+ * field. A consumer needing it must never have to read it out of `body`:
403
+ * that is the substring-matching this class exists to end, and Ollama's body
404
+ * happens to name the model while Voyage's does not. */
405
+ readonly model: string;
406
+ readonly body: string;
407
+ constructor(provider: EmbeddingProviderName, status: number, model: string, body: string);
408
+ }
409
+ /**
410
+ * True when the model has not been pulled YET.
411
+ *
412
+ * The case `isTransientFetchError` did not anticipate, and deliberately does not
413
+ * cover: its exclusion of HTTP-level failures is right for a gateway 401 — "the
414
+ * server is UP and rejected us; retrying won't change its mind" — and wrong here.
415
+ * A cold-cache 404 means the server is up, the *resource* is not there yet, and
416
+ * it will be. Do not widen `isTransientFetchError` to reach this; its narrowness
417
+ * is load-bearing for the reasoning it already carries.
418
+ *
419
+ * Narrow on two axes, both on purpose:
420
+ * - **only 404.** A 401 is a bad key, a 400 a bad request; neither becomes a 200
421
+ * by waiting. 429/503 from a provider under load are arguably retryable too,
422
+ * but nothing has observed them here and a predicate should not speculate.
423
+ * - **only this error type.** A 404 from the gateway means not-found, and
424
+ * retrying that is wrong. Structure, not a bare status, is what distinguishes
425
+ * "not yet" from "not there".
426
+ *
427
+ * It cannot tell "not pulled yet" from "the configured model name is wrong" —
428
+ * both are a 404 and the provider says nothing more. That is accepted: a typo now
429
+ * fails after the retry budget instead of instantly, which is why the message on
430
+ * exhaustion must name both possibilities.
431
+ */
432
+ declare function isColdModelError(error: unknown): error is EmbeddingProviderError;
433
+
434
+ /**
435
+ * Resolving a collection's vector width against a provider that may not be ready.
436
+ *
437
+ * The width is discovered by embedding a probe (the model is the authority — a
438
+ * hand-maintained table goes stale the day a new model ships), so creating a
439
+ * collection is the one boot step that needs the embedding provider to be live.
440
+ * On the first boot of a fresh KB that races Ollama's model pull, and losing it
441
+ * used to be `Fatal`: measured 3, 4 and 5 restarts for archivist, librarian and
442
+ * smelter, with the archivist's flapping separately stranding `semiont-worker`
443
+ * in `Created` forever.
444
+ *
445
+ * Reuses `STARTUP_FETCH_RETRY` **by name** — one place decides how long a boot
446
+ * waits for a dependency, rather than a second startup-retry constant that drifts
447
+ * from it.
448
+ *
449
+ * Called only where a collection must be CREATED. A warm KB short-circuits before
450
+ * reaching here and contacts no provider at boot, so this adds no steady-state
451
+ * latency.
452
+ */
453
+
454
+ /**
455
+ * How long a boot waits for the embedding provider to become usable.
456
+ *
457
+ * **Not `STARTUP_FETCH_RETRY`**, which this originally reused by name on the
458
+ * plan's instruction. That policy answers *"how long does a boot wait for a peer
459
+ * to start listening?"* — seconds. This answers *"how long does a boot wait for a
460
+ * model to download?"* — the incident's own measurement was "under a minute", and
461
+ * `STARTUP_FETCH_RETRY`'s ~39s ceiling (≈29s expected, once jittered) would have
462
+ * expired just before the thing it was waiting for arrived.
463
+ *
464
+ * Two facts that happen to be measured in seconds are still two facts. One
465
+ * constant is right when it is one fact; sharing it here would mean every future
466
+ * adjustment to the gateway's patience silently moved this one.
467
+ *
468
+ * Worst case ≈ `retryBudgetMs(EMBEDDING_PROVIDER_RETRY, EMBED_TIMEOUT_MS)` — see
469
+ * `startup-timeout.test.ts`, which gates it against the deadline it could race.
470
+ */
471
+ declare const EMBEDDING_PROVIDER_RETRY: RetryPolicy;
472
+ declare function resolveDimensions(dimensions: () => Promise<number>,
473
+ /** The caller's boot deadline, if it has one. Stops the retry when it fires,
474
+ * so a caller racing a timeout against this cannot cut it short by surprise. */
475
+ signal?: AbortSignal): Promise<number>;
476
+
477
+ export { EMBEDDING_PROVIDER_RETRY, EMBED_TIMEOUT_MS, EmbeddingProviderError, MemoryVectorStore, OllamaEmbeddingProvider, QdrantVectorStore, VoyageEmbeddingProvider, createEmbeddingProvider, createVectorStore, isColdModelError, mergeByResource, resolveDimensions };
368
478
  export type { AnnotationPayload, EmbeddingChunk, EmbeddingConfig, EmbeddingProvider, OllamaEmbeddingConfig, QdrantConfig, SearchOptions, VectorSearchResult, VectorStore, VectorStoreConfig, VoyageConfig };
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
- import { mergeByResource } from './chunk-WCD6P6QQ.js';
2
- export { QdrantVectorStore, mergeByResource } from './chunk-WCD6P6QQ.js';
3
- export { VoyageEmbeddingProvider } from './chunk-PJ72DF6E.js';
4
- export { OllamaEmbeddingProvider } from './chunk-GWXB645H.js';
1
+ import { mergeByResource } from './chunk-U2EFPQG6.js';
2
+ export { EMBEDDING_PROVIDER_RETRY, QdrantVectorStore, mergeByResource, resolveDimensions } from './chunk-U2EFPQG6.js';
3
+ export { VoyageEmbeddingProvider } from './chunk-RWMSF6N3.js';
4
+ export { OllamaEmbeddingProvider } from './chunk-BZ3U6GAE.js';
5
+ export { EMBED_TIMEOUT_MS, EmbeddingProviderError, isColdModelError } from './chunk-N6SZJFFY.js';
5
6
 
6
7
  // src/store/memory.ts
7
8
  function toStamp(point) {
@@ -183,11 +184,12 @@ var MemoryVectorStore = class {
183
184
  async function createVectorStore(config) {
184
185
  let store;
185
186
  if (config.type === "qdrant") {
186
- const { QdrantVectorStore: QdrantVectorStore2 } = await import('./qdrant-EUQ53YHP.js');
187
+ const { QdrantVectorStore: QdrantVectorStore2 } = await import('./qdrant-5OKYC35C.js');
187
188
  store = new QdrantVectorStore2({
188
189
  host: config.host ?? "localhost",
189
190
  port: config.port ?? 6333,
190
- dimensions: config.dimensions
191
+ dimensions: config.dimensions,
192
+ ...config.signal ? { signal: config.signal } : {}
191
193
  });
192
194
  } else {
193
195
  store = new MemoryVectorStore();
@@ -199,7 +201,7 @@ async function createVectorStore(config) {
199
201
  // src/embedding/factory.ts
200
202
  async function createEmbeddingProvider(config) {
201
203
  if (config.type === "voyage") {
202
- const { VoyageEmbeddingProvider: VoyageEmbeddingProvider2 } = await import('./voyage-MQMTWUMO.js');
204
+ const { VoyageEmbeddingProvider: VoyageEmbeddingProvider2 } = await import('./voyage-NLKOWD2K.js');
203
205
  if (!config.apiKey) throw new Error("apiKey is required for Voyage embedding provider");
204
206
  return new VoyageEmbeddingProvider2({
205
207
  apiKey: config.apiKey,
@@ -208,7 +210,7 @@ async function createEmbeddingProvider(config) {
208
210
  });
209
211
  }
210
212
  if (config.type === "ollama") {
211
- const { OllamaEmbeddingProvider: OllamaEmbeddingProvider2 } = await import('./ollama-GGIIWEBW.js');
213
+ const { OllamaEmbeddingProvider: OllamaEmbeddingProvider2 } = await import('./ollama-7H7HZL7J.js');
212
214
  return new OllamaEmbeddingProvider2({
213
215
  model: config.model,
214
216
  baseURL: config.baseURL
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/store/memory.ts","../src/store/factory.ts","../src/embedding/factory.ts"],"names":["QdrantVectorStore","VoyageEmbeddingProvider","OllamaEmbeddingProvider"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * In-Memory VectorStore Implementation\n *\n * For testing and development without a running Qdrant instance.\n * Uses brute-force cosine similarity search.\n */\n\nimport type { ResourceId, AnnotationId } from '@semiont/core';\nimport type { VectorStore, EmbeddingChunk, AnnotationPayload, VectorSearchResult, SearchOptions, ResourceStamp } from './interface';\nimport { mergeByResource } from './merge';\n\nfunction toStamp(point: { payload: { contentChecksum?: string; entityTypes?: string[]; machineRead?: boolean } }): ResourceStamp {\n return {\n contentChecksum: point.payload.contentChecksum,\n entityTypes: point.payload.entityTypes ?? [],\n ...(point.payload.machineRead ? { machineRead: true } : {}),\n };\n}\n\ninterface StoredPoint {\n id: string;\n vector: number[];\n payload: {\n resourceId: string;\n annotationId?: string;\n chunkIndex?: number;\n text: string;\n contentChecksum?: string;\n motivation?: string;\n entityTypes?: string[];\n machineRead?: boolean;\n };\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dotProduct = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dotProduct += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dotProduct / denom;\n}\n\nexport class MemoryVectorStore implements VectorStore {\n private resources: StoredPoint[] = [];\n private annotations: StoredPoint[] = [];\n private connected = false;\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async clearAll(): Promise<void> {\n this.resources = [];\n this.annotations = [];\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n async upsertResourceVectors(resourceId: ResourceId, chunks: EmbeddingChunk[], contentChecksum: string, entityTypes: string[], machineRead?: boolean): Promise<void> {\n // Remove existing vectors for this resource\n this.resources = this.resources.filter(p => p.payload.resourceId !== String(resourceId));\n\n for (const chunk of chunks) {\n this.resources.push({\n id: `${resourceId}-${chunk.chunkIndex}`,\n vector: chunk.embedding,\n payload: {\n resourceId: String(resourceId),\n chunkIndex: chunk.chunkIndex,\n text: chunk.text,\n contentChecksum,\n entityTypes,\n // Only stamped when true: absence is the common case and carries no\n // claim, so a native extraction stores nothing extra.\n ...(machineRead ? { machineRead: true } : {}),\n },\n });\n }\n }\n\n async upsertAnnotationVector(\n annotationId: AnnotationId,\n embedding: number[],\n payload: AnnotationPayload\n ): Promise<void> {\n this.annotations = this.annotations.filter(p => p.id !== String(annotationId));\n this.annotations.push({\n id: String(annotationId),\n vector: embedding,\n payload: {\n annotationId: String(payload.annotationId),\n resourceId: String(payload.resourceId),\n motivation: payload.motivation,\n entityTypes: payload.entityTypes,\n text: payload.exactText,\n ...(payload.machineRead ? { machineRead: true } : {}),\n },\n });\n }\n\n async deleteResourceVectors(resourceId: ResourceId): Promise<void> {\n this.resources = this.resources.filter(p => p.payload.resourceId !== String(resourceId));\n }\n\n async deleteAnnotationVector(annotationId: AnnotationId): Promise<void> {\n this.annotations = this.annotations.filter(p => p.id !== String(annotationId));\n }\n\n async deleteAnnotationVectorsForResource(resourceId: ResourceId): Promise<void> {\n this.annotations = this.annotations.filter(p => p.payload.resourceId !== String(resourceId));\n }\n\n async count(): Promise<number> {\n return this.resources.length + this.annotations.length;\n }\n\n async updateResourceEntityTypes(resourceId: ResourceId, entityTypes: string[]): Promise<void> {\n for (const p of this.resources) {\n if (p.payload.resourceId === String(resourceId)) {\n p.payload.entityTypes = entityTypes;\n }\n }\n }\n\n async listResourceStamps(): Promise<Map<string, ResourceStamp>> {\n const stamps = new Map<string, ResourceStamp>();\n for (const p of this.resources) {\n if (!stamps.has(p.payload.resourceId)) {\n stamps.set(p.payload.resourceId, toStamp(p));\n }\n }\n return stamps;\n }\n\n async getResourceStamp(resourceId: ResourceId): Promise<ResourceStamp | undefined> {\n const point = this.resources.find(p => p.payload.resourceId === String(resourceId));\n return point ? toStamp(point) : undefined;\n }\n\n async listAnnotationIds(): Promise<Set<string>> {\n const ids = new Set<string>();\n for (const p of this.annotations) {\n if (p.payload.annotationId) ids.add(p.payload.annotationId);\n }\n return ids;\n }\n\n async searchResources(embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n return this.search(this.resources, embedding, opts);\n }\n\n async searchAnnotations(embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n return this.search(this.annotations, embedding, opts);\n }\n\n async searchByResource(resourceId: ResourceId, opts: SearchOptions): Promise<VectorSearchResult[]> {\n const rid = String(resourceId);\n const queryPoints = this.resources.filter(p => p.payload.resourceId === rid);\n if (queryPoints.length === 0) return [];\n\n // Self-exclude the source; carry the caller's filter (e.g. excludeEntityTypes).\n const filter: SearchOptions['filter'] = { ...opts.filter, excludeResourceId: resourceId };\n\n // Score every candidate point against the source's chunks (exhaustive — no\n // per-chunk top-K to crowd anything out), then let the shared max-sim fold\n // collapse chunk hits to one entry per resource.\n const hits: VectorSearchResult[] = [];\n for (const cand of this.resources) {\n if (!this.passesFilter(cand, filter)) continue;\n let best = -Infinity;\n for (const q of queryPoints) {\n const score = cosineSimilarity(q.vector, cand.vector);\n if (score > best) best = score;\n }\n if (opts.scoreThreshold !== undefined && best < opts.scoreThreshold) continue;\n hits.push(this.toResult({ ...cand, score: best }));\n }\n\n return mergeByResource(hits).slice(0, opts.limit);\n }\n\n private passesFilter(p: StoredPoint, filter: SearchOptions['filter']): boolean {\n if (!filter) return true;\n const f = filter;\n if (f.resourceId && p.payload.resourceId !== String(f.resourceId)) return false;\n if (f.excludeResourceId && p.payload.resourceId === String(f.excludeResourceId)) return false;\n if (f.motivation && p.payload.motivation !== f.motivation) return false;\n if (f.entityTypes && f.entityTypes.length > 0) {\n const pTypes = p.payload.entityTypes ?? [];\n if (!f.entityTypes.some(t => pTypes.includes(t))) return false;\n }\n if (f.excludeEntityTypes && f.excludeEntityTypes.length > 0) {\n const pTypes = p.payload.entityTypes ?? [];\n if (f.excludeEntityTypes.some(t => pTypes.includes(t))) return false;\n }\n return true;\n }\n\n private search(points: StoredPoint[], embedding: number[], opts: SearchOptions): VectorSearchResult[] {\n const filtered = points.filter(p => this.passesFilter(p, opts.filter));\n\n const scored = filtered.map(p => ({\n ...p,\n score: cosineSimilarity(embedding, p.vector),\n }));\n\n scored.sort((a, b) => b.score - a.score);\n\n if (opts.scoreThreshold !== undefined) {\n const threshold = opts.scoreThreshold;\n return scored\n .filter(s => s.score >= threshold)\n .slice(0, opts.limit)\n .map(s => this.toResult(s));\n }\n\n return scored.slice(0, opts.limit).map(s => this.toResult(s));\n }\n\n private toResult(s: StoredPoint & { score: number }): VectorSearchResult {\n return {\n id: s.id,\n score: s.score,\n resourceId: s.payload.resourceId as ResourceId,\n annotationId: s.payload.annotationId as AnnotationId | undefined,\n text: s.payload.text,\n entityTypes: s.payload.entityTypes,\n ...(s.payload.machineRead ? { machineRead: true } : {}),\n };\n }\n}\n","/**\n * VectorStore Factory\n *\n * Creates a connected VectorStore instance based on configuration.\n */\n\nimport type { VectorStore } from './interface';\nimport { MemoryVectorStore } from './memory';\n\nexport interface VectorStoreConfig {\n type: 'qdrant' | 'memory';\n host?: string;\n port?: number;\n /**\n * Resolves the embedding dimensionality — a *thunk*, not a value, so the\n * provider is consulted only by a store that actually needs the number and\n * only when it needs it. Mirrors how inference treats provider-derived\n * facts: the client is constructed with zero I/O and its `limits()` are\n * discovered at the point of use, never eagerly at startup.\n *\n * `MemoryVectorStore` never calls it. Qdrant calls it only when it has to\n * *create* a collection, so an already-provisioned instance connects\n * without touching the embedding provider at all.\n */\n dimensions: () => Promise<number>;\n}\n\nexport async function createVectorStore(config: VectorStoreConfig): Promise<VectorStore> {\n let store: VectorStore;\n\n if (config.type === 'qdrant') {\n const { QdrantVectorStore } = await import('./qdrant');\n store = new QdrantVectorStore({\n host: config.host ?? 'localhost',\n port: config.port ?? 6333,\n dimensions: config.dimensions,\n });\n } else {\n // MemoryVectorStore holds whatever vectors it is handed — it never reads a\n // configured dimensionality, so the thunk is deliberately never called and\n // a memory-backed deployment boots without contacting the provider.\n store = new MemoryVectorStore();\n }\n\n await store.connect();\n return store;\n}\n","/**\n * EmbeddingProvider Factory\n */\n\nimport type { EmbeddingProvider } from './interface';\n\nexport interface EmbeddingConfig {\n type: 'voyage' | 'ollama';\n model: string;\n apiKey?: string;\n baseURL?: string;\n endpoint?: string;\n}\n\nexport async function createEmbeddingProvider(config: EmbeddingConfig): Promise<EmbeddingProvider> {\n if (config.type === 'voyage') {\n const { VoyageEmbeddingProvider } = await import('./voyage');\n if (!config.apiKey) throw new Error('apiKey is required for Voyage embedding provider');\n return new VoyageEmbeddingProvider({\n apiKey: config.apiKey,\n model: config.model,\n endpoint: config.endpoint,\n });\n }\n\n if (config.type === 'ollama') {\n const { OllamaEmbeddingProvider } = await import('./ollama');\n return new OllamaEmbeddingProvider({\n model: config.model,\n baseURL: config.baseURL,\n });\n }\n\n throw new Error(`Unknown embedding provider type: ${config.type}`);\n}\n"]}
1
+ {"version":3,"sources":["../src/store/memory.ts","../src/store/factory.ts","../src/embedding/factory.ts"],"names":["QdrantVectorStore","VoyageEmbeddingProvider","OllamaEmbeddingProvider"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * In-Memory VectorStore Implementation\n *\n * For testing and development without a running Qdrant instance.\n * Uses brute-force cosine similarity search.\n */\n\nimport type { ResourceId, AnnotationId } from '@semiont/core';\nimport type { VectorStore, EmbeddingChunk, AnnotationPayload, VectorSearchResult, SearchOptions, ResourceStamp } from './interface';\nimport { mergeByResource } from './merge';\n\nfunction toStamp(point: { payload: { contentChecksum?: string; entityTypes?: string[]; machineRead?: boolean } }): ResourceStamp {\n return {\n contentChecksum: point.payload.contentChecksum,\n entityTypes: point.payload.entityTypes ?? [],\n ...(point.payload.machineRead ? { machineRead: true } : {}),\n };\n}\n\ninterface StoredPoint {\n id: string;\n vector: number[];\n payload: {\n resourceId: string;\n annotationId?: string;\n chunkIndex?: number;\n text: string;\n contentChecksum?: string;\n motivation?: string;\n entityTypes?: string[];\n machineRead?: boolean;\n };\n}\n\nfunction cosineSimilarity(a: number[], b: number[]): number {\n let dotProduct = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dotProduct += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n return denom === 0 ? 0 : dotProduct / denom;\n}\n\nexport class MemoryVectorStore implements VectorStore {\n private resources: StoredPoint[] = [];\n private annotations: StoredPoint[] = [];\n private connected = false;\n\n async connect(): Promise<void> {\n this.connected = true;\n }\n\n async disconnect(): Promise<void> {\n this.connected = false;\n }\n\n async clearAll(): Promise<void> {\n this.resources = [];\n this.annotations = [];\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n async upsertResourceVectors(resourceId: ResourceId, chunks: EmbeddingChunk[], contentChecksum: string, entityTypes: string[], machineRead?: boolean): Promise<void> {\n // Remove existing vectors for this resource\n this.resources = this.resources.filter(p => p.payload.resourceId !== String(resourceId));\n\n for (const chunk of chunks) {\n this.resources.push({\n id: `${resourceId}-${chunk.chunkIndex}`,\n vector: chunk.embedding,\n payload: {\n resourceId: String(resourceId),\n chunkIndex: chunk.chunkIndex,\n text: chunk.text,\n contentChecksum,\n entityTypes,\n // Only stamped when true: absence is the common case and carries no\n // claim, so a native extraction stores nothing extra.\n ...(machineRead ? { machineRead: true } : {}),\n },\n });\n }\n }\n\n async upsertAnnotationVector(\n annotationId: AnnotationId,\n embedding: number[],\n payload: AnnotationPayload\n ): Promise<void> {\n this.annotations = this.annotations.filter(p => p.id !== String(annotationId));\n this.annotations.push({\n id: String(annotationId),\n vector: embedding,\n payload: {\n annotationId: String(payload.annotationId),\n resourceId: String(payload.resourceId),\n motivation: payload.motivation,\n entityTypes: payload.entityTypes,\n text: payload.exactText,\n ...(payload.machineRead ? { machineRead: true } : {}),\n },\n });\n }\n\n async deleteResourceVectors(resourceId: ResourceId): Promise<void> {\n this.resources = this.resources.filter(p => p.payload.resourceId !== String(resourceId));\n }\n\n async deleteAnnotationVector(annotationId: AnnotationId): Promise<void> {\n this.annotations = this.annotations.filter(p => p.id !== String(annotationId));\n }\n\n async deleteAnnotationVectorsForResource(resourceId: ResourceId): Promise<void> {\n this.annotations = this.annotations.filter(p => p.payload.resourceId !== String(resourceId));\n }\n\n async count(): Promise<number> {\n return this.resources.length + this.annotations.length;\n }\n\n async updateResourceEntityTypes(resourceId: ResourceId, entityTypes: string[]): Promise<void> {\n for (const p of this.resources) {\n if (p.payload.resourceId === String(resourceId)) {\n p.payload.entityTypes = entityTypes;\n }\n }\n }\n\n async listResourceStamps(): Promise<Map<string, ResourceStamp>> {\n const stamps = new Map<string, ResourceStamp>();\n for (const p of this.resources) {\n if (!stamps.has(p.payload.resourceId)) {\n stamps.set(p.payload.resourceId, toStamp(p));\n }\n }\n return stamps;\n }\n\n async getResourceStamp(resourceId: ResourceId): Promise<ResourceStamp | undefined> {\n const point = this.resources.find(p => p.payload.resourceId === String(resourceId));\n return point ? toStamp(point) : undefined;\n }\n\n async listAnnotationIds(): Promise<Set<string>> {\n const ids = new Set<string>();\n for (const p of this.annotations) {\n if (p.payload.annotationId) ids.add(p.payload.annotationId);\n }\n return ids;\n }\n\n async searchResources(embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n return this.search(this.resources, embedding, opts);\n }\n\n async searchAnnotations(embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n return this.search(this.annotations, embedding, opts);\n }\n\n async searchByResource(resourceId: ResourceId, opts: SearchOptions): Promise<VectorSearchResult[]> {\n const rid = String(resourceId);\n const queryPoints = this.resources.filter(p => p.payload.resourceId === rid);\n if (queryPoints.length === 0) return [];\n\n // Self-exclude the source; carry the caller's filter (e.g. excludeEntityTypes).\n const filter: SearchOptions['filter'] = { ...opts.filter, excludeResourceId: resourceId };\n\n // Score every candidate point against the source's chunks (exhaustive — no\n // per-chunk top-K to crowd anything out), then let the shared max-sim fold\n // collapse chunk hits to one entry per resource.\n const hits: VectorSearchResult[] = [];\n for (const cand of this.resources) {\n if (!this.passesFilter(cand, filter)) continue;\n let best = -Infinity;\n for (const q of queryPoints) {\n const score = cosineSimilarity(q.vector, cand.vector);\n if (score > best) best = score;\n }\n if (opts.scoreThreshold !== undefined && best < opts.scoreThreshold) continue;\n hits.push(this.toResult({ ...cand, score: best }));\n }\n\n return mergeByResource(hits).slice(0, opts.limit);\n }\n\n private passesFilter(p: StoredPoint, filter: SearchOptions['filter']): boolean {\n if (!filter) return true;\n const f = filter;\n if (f.resourceId && p.payload.resourceId !== String(f.resourceId)) return false;\n if (f.excludeResourceId && p.payload.resourceId === String(f.excludeResourceId)) return false;\n if (f.motivation && p.payload.motivation !== f.motivation) return false;\n if (f.entityTypes && f.entityTypes.length > 0) {\n const pTypes = p.payload.entityTypes ?? [];\n if (!f.entityTypes.some(t => pTypes.includes(t))) return false;\n }\n if (f.excludeEntityTypes && f.excludeEntityTypes.length > 0) {\n const pTypes = p.payload.entityTypes ?? [];\n if (f.excludeEntityTypes.some(t => pTypes.includes(t))) return false;\n }\n return true;\n }\n\n private search(points: StoredPoint[], embedding: number[], opts: SearchOptions): VectorSearchResult[] {\n const filtered = points.filter(p => this.passesFilter(p, opts.filter));\n\n const scored = filtered.map(p => ({\n ...p,\n score: cosineSimilarity(embedding, p.vector),\n }));\n\n scored.sort((a, b) => b.score - a.score);\n\n if (opts.scoreThreshold !== undefined) {\n const threshold = opts.scoreThreshold;\n return scored\n .filter(s => s.score >= threshold)\n .slice(0, opts.limit)\n .map(s => this.toResult(s));\n }\n\n return scored.slice(0, opts.limit).map(s => this.toResult(s));\n }\n\n private toResult(s: StoredPoint & { score: number }): VectorSearchResult {\n return {\n id: s.id,\n score: s.score,\n resourceId: s.payload.resourceId as ResourceId,\n annotationId: s.payload.annotationId as AnnotationId | undefined,\n text: s.payload.text,\n entityTypes: s.payload.entityTypes,\n ...(s.payload.machineRead ? { machineRead: true } : {}),\n };\n }\n}\n","/**\n * VectorStore Factory\n *\n * Creates a connected VectorStore instance based on configuration.\n */\n\nimport type { VectorStore } from './interface';\nimport { MemoryVectorStore } from './memory';\n\nexport interface VectorStoreConfig {\n type: 'qdrant' | 'memory';\n host?: string;\n port?: number;\n /**\n * Resolves the embedding dimensionality — a *thunk*, not a value, so the\n * provider is consulted only by a store that actually needs the number and\n * only when it needs it. Mirrors how inference treats provider-derived\n * facts: the client is constructed with zero I/O and its `limits()` are\n * discovered at the point of use, never eagerly at startup.\n *\n * `MemoryVectorStore` never calls it. Qdrant calls it only when it has to\n * *create* a collection, so an already-provisioned instance connects\n * without touching the embedding provider at all.\n */\n dimensions: () => Promise<number>;\n /** The caller's boot deadline, passed to whatever retries while warming up. */\n signal?: AbortSignal;\n}\n\nexport async function createVectorStore(config: VectorStoreConfig): Promise<VectorStore> {\n let store: VectorStore;\n\n if (config.type === 'qdrant') {\n const { QdrantVectorStore } = await import('./qdrant');\n store = new QdrantVectorStore({\n host: config.host ?? 'localhost',\n port: config.port ?? 6333,\n dimensions: config.dimensions,\n ...(config.signal ? { signal: config.signal } : {}),\n });\n } else {\n // MemoryVectorStore holds whatever vectors it is handed — it never reads a\n // configured dimensionality, so the thunk is deliberately never called and\n // a memory-backed deployment boots without contacting the provider.\n store = new MemoryVectorStore();\n }\n\n await store.connect();\n return store;\n}\n","/**\n * EmbeddingProvider Factory\n */\n\nimport type { EmbeddingProvider } from './interface';\n\nexport interface EmbeddingConfig {\n type: 'voyage' | 'ollama';\n model: string;\n apiKey?: string;\n baseURL?: string;\n endpoint?: string;\n}\n\nexport async function createEmbeddingProvider(config: EmbeddingConfig): Promise<EmbeddingProvider> {\n if (config.type === 'voyage') {\n const { VoyageEmbeddingProvider } = await import('./voyage');\n if (!config.apiKey) throw new Error('apiKey is required for Voyage embedding provider');\n return new VoyageEmbeddingProvider({\n apiKey: config.apiKey,\n model: config.model,\n endpoint: config.endpoint,\n });\n }\n\n if (config.type === 'ollama') {\n const { OllamaEmbeddingProvider } = await import('./ollama');\n return new OllamaEmbeddingProvider({\n model: config.model,\n baseURL: config.baseURL,\n });\n }\n\n throw new Error(`Unknown embedding provider type: ${config.type}`);\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export { OllamaEmbeddingProvider } from './chunk-BZ3U6GAE.js';
2
+ import './chunk-N6SZJFFY.js';
3
+ //# sourceMappingURL=ollama-7H7HZL7J.js.map
4
+ //# sourceMappingURL=ollama-7H7HZL7J.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"ollama-GGIIWEBW.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"ollama-7H7HZL7J.js"}
@@ -0,0 +1,4 @@
1
+ export { QdrantVectorStore } from './chunk-U2EFPQG6.js';
2
+ import './chunk-N6SZJFFY.js';
3
+ //# sourceMappingURL=qdrant-5OKYC35C.js.map
4
+ //# sourceMappingURL=qdrant-5OKYC35C.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"qdrant-EUQ53YHP.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"qdrant-5OKYC35C.js"}
@@ -0,0 +1,4 @@
1
+ export { VoyageEmbeddingProvider } from './chunk-RWMSF6N3.js';
2
+ import './chunk-N6SZJFFY.js';
3
+ //# sourceMappingURL=voyage-NLKOWD2K.js.map
4
+ //# sourceMappingURL=voyage-NLKOWD2K.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"voyage-MQMTWUMO.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"voyage-NLKOWD2K.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@semiont/vectors",
3
- "version": "0.5.32",
3
+ "version": "0.5.34",
4
4
  "engines": {
5
5
  "node": ">=24.0.0"
6
6
  },
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@qdrant/js-client-rest": "^1.18.0",
33
- "@semiont/core": "0.5.32"
33
+ "@semiont/core": "0.5.34"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@vitest/coverage-v8": "^4.1.11",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/embedding/ollama.ts"],"names":[],"mappings":"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","file":"chunk-GWXB645H.js","sourcesContent":["/**\n * Ollama Embedding Provider\n *\n * Local embedding via the Ollama API.\n * Uses models like nomic-embed-text, all-minilm, etc.\n */\n\nimport type { EmbeddingProvider } from './interface';\n\nexport interface OllamaEmbeddingConfig {\n model: string;\n baseURL?: string;\n}\n\nexport class OllamaEmbeddingProvider implements EmbeddingProvider {\n private config: OllamaEmbeddingConfig;\n private dimensionsPromise?: Promise<number>;\n\n constructor(config: OllamaEmbeddingConfig) {\n this.config = config;\n }\n\n async embed(text: string): Promise<number[]> {\n const baseURL = this.config.baseURL ?? 'http://localhost:11434';\n\n const response = await fetch(`${baseURL}/api/embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n input: text,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Ollama embed error ${response.status}: ${body}`);\n }\n\n const json = await response.json() as { embeddings: number[][] };\n return json.embeddings[0];\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n // Ollama's /api/embed supports batch input\n const baseURL = this.config.baseURL ?? 'http://localhost:11434';\n\n const response = await fetch(`${baseURL}/api/embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n input: texts,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Ollama embed error ${response.status}: ${body}`);\n }\n\n const json = await response.json() as { embeddings: number[][] };\n return json.embeddings;\n }\n\n dimensions(): Promise<number> {\n if (!this.dimensionsPromise) {\n this.dimensionsPromise = this.measureDimensions().catch((err: unknown) => {\n // Never cache a failed discovery — a transient outage would otherwise\n // pin every future call to the same rejection.\n this.dimensionsPromise = undefined;\n throw err;\n });\n }\n return this.dimensionsPromise;\n }\n\n private async measureDimensions(): Promise<number> {\n // Dimensionality is intrinsic to the model, so the model is the\n // authority: embed a probe and measure it. A hand-maintained table goes\n // stale the day a new model ships and silently mis-sizes the index.\n const probe = await this.embed('dimension probe');\n if (!Array.isArray(probe) || probe.length === 0) {\n throw new Error(`Ollama returned no embedding for dimension probe of model '${this.config.model}'`);\n }\n return probe.length;\n }\n\n model(): string {\n return this.config.model;\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/embedding/voyage.ts"],"names":[],"mappings":";AAeO,IAAM,0BAAN,MAA2D;AAAA,EACxD,MAAA;AAAA,EACA,iBAAA;AAAA,EAER,YAAY,MAAA,EAAsB;AAChC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,MAAM,MAAM,IAAA,EAAiC;AAC3C,IAAA,MAAM,UAAU,MAAM,IAAA,CAAK,UAAA,CAAW,CAAC,IAAI,CAAC,CAAA;AAC5C,IAAA,OAAO,QAAQ,CAAC,CAAA;AAAA,EAClB;AAAA,EAEA,MAAM,WAAW,KAAA,EAAsC;AACrD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,QAAA,IAAY,wCAAA;AAEzC,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,QAAA,EAAU;AAAA,MACrC,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,eAAA,EAAiB,CAAA,OAAA,EAAU,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAAA,OAC/C;AAAA,MACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,QACnB,KAAA,EAAO,KAAK,MAAA,CAAO,KAAA;AAAA,QACnB,KAAA,EAAO;AAAA,OACR;AAAA,KACF,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,SAAS,MAAM,CAAA,EAAA,EAAK,IAAI,CAAA,CAAE,CAAA;AAAA,IAChE;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,SAAS,CAAA;AAAA,EACvC;AAAA,EAEA,UAAA,GAA8B;AAC5B,IAAA,IAAI,CAAC,KAAK,iBAAA,EAAmB;AAC3B,MAAA,IAAA,CAAK,oBAAoB,IAAA,CAAK,iBAAA,EAAkB,CAAE,KAAA,CAAM,CAAC,GAAA,KAAiB;AAGxE,QAAA,IAAA,CAAK,iBAAA,GAAoB,MAAA;AACzB,QAAA,MAAM,GAAA;AAAA,MACR,CAAC,CAAA;AAAA,IACH;AACA,IAAA,OAAO,IAAA,CAAK,iBAAA;AAAA,EACd;AAAA,EAEA,MAAc,iBAAA,GAAqC;AAIjD,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,KAAA,CAAM,iBAAiB,CAAA;AAChD,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,WAAW,CAAA,EAAG;AAC/C,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,2DAAA,EAA8D,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,IACpG;AACA,IAAA,OAAO,KAAA,CAAM,MAAA;AAAA,EACf;AAAA,EAEA,KAAA,GAAgB;AACd,IAAA,OAAO,KAAK,MAAA,CAAO,KAAA;AAAA,EACrB;AACF","file":"chunk-PJ72DF6E.js","sourcesContent":["/**\n * Voyage AI Embedding Provider\n *\n * Cloud embedding via the Voyage AI API.\n * Requires a Voyage AI API key (distinct from Anthropic inference keys).\n */\n\nimport type { EmbeddingProvider } from './interface';\n\nexport interface VoyageConfig {\n apiKey: string;\n model: string;\n endpoint?: string;\n}\n\nexport class VoyageEmbeddingProvider implements EmbeddingProvider {\n private config: VoyageConfig;\n private dimensionsPromise?: Promise<number>;\n\n constructor(config: VoyageConfig) {\n this.config = config;\n }\n\n async embed(text: string): Promise<number[]> {\n const results = await this.embedBatch([text]);\n return results[0];\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n const endpoint = this.config.endpoint ?? 'https://api.voyageai.com/v1/embeddings';\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n model: this.config.model,\n input: texts,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Voyage API error ${response.status}: ${body}`);\n }\n\n const json = await response.json() as { data: Array<{ embedding: number[] }> };\n return json.data.map(d => d.embedding);\n }\n\n dimensions(): Promise<number> {\n if (!this.dimensionsPromise) {\n this.dimensionsPromise = this.measureDimensions().catch((err: unknown) => {\n // Never cache a failed discovery — a transient outage would otherwise\n // pin every future call to the same rejection.\n this.dimensionsPromise = undefined;\n throw err;\n });\n }\n return this.dimensionsPromise;\n }\n\n private async measureDimensions(): Promise<number> {\n // Dimensionality is intrinsic to the model, so the model is the\n // authority: embed a probe and measure it. A hand-maintained table goes\n // stale the day a new model ships and silently mis-sizes the index.\n const probe = await this.embed('dimension probe');\n if (!Array.isArray(probe) || probe.length === 0) {\n throw new Error(`Voyage returned no embedding for dimension probe of model '${this.config.model}'`);\n }\n return probe.length;\n }\n\n model(): string {\n return this.config.model;\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/store/merge.ts","../src/store/qdrant.ts"],"names":[],"mappings":"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","file":"chunk-WCD6P6QQ.js","sourcesContent":["/**\n * Chunk→resource max-sim merge.\n *\n * Vector search returns *chunk*-level hits: one resource can match on several\n * of its chunks, and a caller that wants a list of resources has to fold them.\n * The fold is max-sim (late interaction): a resource scores as its single\n * best-matching chunk, and carries that chunk's text — so the caller can show\n * the passage that actually matched. Averaging instead would blur a\n * multi-topic resource into a vague centroid and lose which passage hit;\n * keeping the first hit seen would make the result order-dependent.\n *\n * Three callers share this: both `searchByResource` implementations (qdrant\n * batches one query per source chunk; memory scores every candidate against\n * every source chunk) and the SEMANTIC-FALLBACK fold over raw `searchResources`\n * output, whose hits map one-to-one with no dedup. All three normalise to\n * `VectorSearchResult[]` first, so this fold is the only copy of the logic.\n *\n * Deliberately does NOT slice to a limit: `searchByResource` and the fallback\n * want different slices, and a fold that silently truncates is the kind of\n * hidden cap that reads as \"covered everything\" when it didn't.\n */\n\nimport type { VectorSearchResult } from './interface';\n\n/**\n * Fold chunk-level hits to one entry per resource — the highest-scoring chunk\n * wins, carrying its own text and metadata — sorted score-descending.\n */\nexport function mergeByResource(hits: VectorSearchResult[]): VectorSearchResult[] {\n const bestByResource = new Map<string, VectorSearchResult>();\n\n for (const hit of hits) {\n const key = String(hit.resourceId);\n const prev = bestByResource.get(key);\n if (!prev || hit.score > prev.score) bestByResource.set(key, hit);\n }\n\n return [...bestByResource.values()].sort((a, b) => b.score - a.score);\n}\n","/**\n * Qdrant VectorStore Implementation\n *\n * Uses the Qdrant REST API via @qdrant/js-client-rest.\n * Manages two collections: 'resources' and 'annotations'.\n */\n\nimport { createHash } from 'crypto';\nimport type { QdrantClient, Schemas } from '@qdrant/js-client-rest';\nimport type { ResourceId, AnnotationId } from '@semiont/core';\nimport type { VectorStore, EmbeddingChunk, AnnotationPayload, VectorSearchResult, SearchOptions, ResourceStamp } from './interface';\nimport { mergeByResource } from './merge';\n\n/**\n * Generate a deterministic UUID v5-style ID from an arbitrary string.\n * Qdrant requires point IDs to be UUIDs or unsigned integers.\n */\nfunction toQdrantId(input: string): string {\n const hex = createHash('md5').update(input).digest('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n/**\n * Map a scored point to the store-agnostic result shape. Shared by every read\n * path so the payload→result contract (including `machineRead`) has one home.\n */\nfunction toSearchResult(point: Schemas['ScoredPoint']): VectorSearchResult {\n const payload = point.payload ?? {};\n return {\n id: String(point.id),\n score: point.score,\n resourceId: payload.resourceId as ResourceId,\n annotationId: payload.annotationId as AnnotationId | undefined,\n text: payload.text as string,\n entityTypes: payload.entityTypes as string[] | undefined,\n ...(payload.machineRead ? { machineRead: true } : {}),\n };\n}\n\nexport interface QdrantConfig {\n host: string;\n port: number;\n /** Resolves the embedding dimensionality; called only when creating a collection. */\n dimensions: () => Promise<number>;\n}\n\n/** The payload fields a stamp is read from — one list for the scroll and the\n * targeted read, so the two cannot drift. */\nconst STAMP_FIELDS = ['resourceId', 'contentChecksum', 'entityTypes', 'machineRead'];\n\nfunction toStamp(payload: Record<string, unknown> | null | undefined): ResourceStamp {\n const checksum = payload?.contentChecksum;\n const entityTypes = payload?.entityTypes;\n return {\n contentChecksum: typeof checksum === 'string' ? checksum : undefined,\n entityTypes: Array.isArray(entityTypes)\n ? entityTypes.filter((t): t is string => typeof t === 'string')\n : [],\n ...(payload?.machineRead ? { machineRead: true } : {}),\n };\n}\n\nexport class QdrantVectorStore implements VectorStore {\n /**\n * Per-batch point over-fetch for `searchByResource`. The batch limit caps\n * points while the caller wants resources, so multi-chunk targets would\n * otherwise crowd the top-K. See the call site.\n */\n private static readonly SEARCH_BY_RESOURCE_OVER_FETCH = 4;\n\n private client: QdrantClient | null = null;\n private config: QdrantConfig;\n\n constructor(config: QdrantConfig) {\n this.config = config;\n }\n\n private get qdrant(): QdrantClient {\n if (!this.client) throw new Error('QdrantVectorStore is not connected');\n return this.client;\n }\n\n async connect(): Promise<void> {\n const { QdrantClient } = await import('@qdrant/js-client-rest');\n this.client = new QdrantClient({\n host: this.config.host,\n port: this.config.port,\n });\n\n // Ensure collections exist\n await this.ensureCollection('resources');\n await this.ensureCollection('annotations');\n // Payload indexes so filtered operations scale:\n // - entityTypes: the excludeEntityTypes recall filter.\n // - resourceId: searchByResource's by-resource scroll + self-exclusion, and\n // the per-resource delete paths (deleteResourceVectors /\n // deleteAnnotationVectorsForResource).\n await this.ensurePayloadIndex('resources', 'entityTypes');\n await this.ensurePayloadIndex('resources', 'resourceId');\n await this.ensurePayloadIndex('annotations', 'resourceId');\n }\n\n async disconnect(): Promise<void> {\n this.client = null;\n }\n\n async clearAll(): Promise<void> {\n try { await this.qdrant.deleteCollection('resources'); } catch { /* may not exist */ }\n try { await this.qdrant.deleteCollection('annotations'); } catch { /* may not exist */ }\n await this.ensureCollection('resources');\n await this.ensureCollection('annotations');\n }\n\n isConnected(): boolean {\n return this.client !== null;\n }\n\n private async ensureCollection(name: string): Promise<void> {\n try {\n await this.qdrant.getCollection(name);\n return;\n } catch { /* absent — create it below, which is the only path needing the size */ }\n\n // The dimensionality is discovered from the embedding provider, so it is\n // resolved HERE and nowhere else: an existing collection already encodes\n // its vector size, and consulting the provider to re-derive a number we\n // would not use is exactly the eager-probe coupling this avoids.\n await this.qdrant.createCollection(name, {\n vectors: { size: await this.config.dimensions(), distance: 'Cosine' },\n });\n }\n\n /**\n * Idempotently create a keyword payload index. Qdrant accepts a repeat call\n * for an already-indexed field, so this runs safely on every connect and\n * back-fills the index on collections created before the field was indexed.\n */\n private async ensurePayloadIndex(collection: string, field: string): Promise<void> {\n try {\n await this.qdrant.createPayloadIndex(collection, { field_name: field, field_schema: 'keyword' });\n } catch { /* already indexed, or created concurrently */ }\n }\n\n async upsertResourceVectors(resourceId: ResourceId, chunks: EmbeddingChunk[], contentChecksum: string, entityTypes: string[], machineRead?: boolean): Promise<void> {\n // Replace semantics: purge existing chunks first, or a resource that\n // shrinks leaves orphan points at the higher chunk indices.\n await this.deleteResourceVectors(resourceId);\n if (chunks.length === 0) return;\n\n const points = chunks.map((chunk) => ({\n id: toQdrantId(`${resourceId}-${chunk.chunkIndex}`),\n vector: chunk.embedding,\n payload: {\n resourceId: String(resourceId),\n chunkIndex: chunk.chunkIndex,\n text: chunk.text,\n contentChecksum,\n entityTypes,\n // Only stamped when true: absence is the common case and carries no\n // claim, so a native extraction stores nothing extra.\n ...(machineRead ? { machineRead: true } : {}),\n },\n }));\n\n await this.qdrant.upsert('resources', { points });\n }\n\n async upsertAnnotationVector(\n annotationId: AnnotationId,\n embedding: number[],\n payload: AnnotationPayload\n ): Promise<void> {\n await this.qdrant.upsert('annotations', {\n points: [{\n id: toQdrantId(String(annotationId)),\n vector: embedding,\n payload: {\n annotationId: String(payload.annotationId),\n resourceId: String(payload.resourceId),\n motivation: payload.motivation,\n entityTypes: payload.entityTypes,\n text: payload.exactText,\n ...(payload.machineRead ? { machineRead: true } : {}),\n },\n }],\n });\n }\n\n async deleteResourceVectors(resourceId: ResourceId): Promise<void> {\n await this.qdrant.delete('resources', {\n filter: {\n must: [{ key: 'resourceId', match: { value: String(resourceId) } }],\n },\n });\n }\n\n async deleteAnnotationVector(annotationId: AnnotationId): Promise<void> {\n await this.qdrant.delete('annotations', {\n points: [toQdrantId(String(annotationId))],\n });\n }\n\n async deleteAnnotationVectorsForResource(resourceId: ResourceId): Promise<void> {\n await this.qdrant.delete('annotations', {\n filter: {\n must: [{ key: 'resourceId', match: { value: String(resourceId) } }],\n },\n });\n }\n\n async count(): Promise<number> {\n const [resources, annotations] = await Promise.all([\n this.qdrant.count('resources', { exact: true }),\n this.qdrant.count('annotations', { exact: true }),\n ]);\n return resources.count + annotations.count;\n }\n\n async updateResourceEntityTypes(resourceId: ResourceId, entityTypes: string[]): Promise<void> {\n // Payload-only rewrite across the resource's points — no vectors touched,\n // no embedding involved (S13). Qdrant set_payload with a filter is a\n // no-op when the resource has no points.\n await this.qdrant.setPayload('resources', {\n payload: { entityTypes },\n filter: {\n must: [{ key: 'resourceId', match: { value: String(resourceId) } }],\n },\n });\n }\n\n async getResourceStamp(resourceId: ResourceId): Promise<ResourceStamp | undefined> {\n const page = await this.qdrant.scroll('resources', {\n limit: 1,\n filter: { must: [{ key: 'resourceId', match: { value: String(resourceId) } }] },\n with_payload: STAMP_FIELDS,\n with_vector: false,\n });\n const point = page.points[0];\n return point ? toStamp(point.payload) : undefined;\n }\n\n async listResourceStamps(): Promise<Map<string, ResourceStamp>> {\n const stamps = new Map<string, ResourceStamp>();\n let offset: Schemas['ScrollRequest']['offset'] = undefined;\n do {\n const page = await this.qdrant.scroll('resources', {\n limit: 1000,\n offset,\n with_payload: STAMP_FIELDS,\n with_vector: false,\n });\n for (const point of page.points) {\n const rid = point.payload?.resourceId;\n if (typeof rid !== 'string' || stamps.has(rid)) continue;\n stamps.set(rid, toStamp(point.payload));\n }\n offset = page.next_page_offset ?? undefined;\n } while (offset !== undefined && offset !== null);\n return stamps;\n }\n\n async listAnnotationIds(): Promise<Set<string>> {\n return this.scrollPayloadField('annotations', 'annotationId');\n }\n\n /** Collect the distinct values of one payload field across a collection. */\n private async scrollPayloadField(collection: string, field: string): Promise<Set<string>> {\n const values = new Set<string>();\n let offset: Schemas['ScrollRequest']['offset'] = undefined;\n do {\n const page = await this.qdrant.scroll(collection, {\n limit: 1000,\n offset,\n with_payload: [field],\n with_vector: false,\n });\n for (const point of page.points) {\n const value = point.payload?.[field];\n if (typeof value === 'string') values.add(value);\n }\n offset = page.next_page_offset ?? undefined;\n } while (offset !== undefined && offset !== null);\n return values;\n }\n\n async searchResources(embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n return this.search('resources', embedding, opts);\n }\n\n async searchAnnotations(embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n return this.search('annotations', embedding, opts);\n }\n\n async searchByResource(resourceId: ResourceId, opts: SearchOptions): Promise<VectorSearchResult[]> {\n // Fetch the resource's stored chunk vectors (page through all of them so a\n // long resource isn't silently truncated).\n const queryVectors: number[][] = [];\n let offset: Schemas['ScrollRequest']['offset'] = undefined;\n do {\n const page = await this.qdrant.scroll('resources', {\n filter: { must: [{ key: 'resourceId', match: { value: String(resourceId) } }] },\n with_vector: true,\n with_payload: false,\n limit: 256,\n offset,\n });\n for (const point of page.points) {\n if (Array.isArray(point.vector)) queryVectors.push(point.vector as number[]);\n }\n offset = page.next_page_offset ?? undefined;\n } while (offset !== undefined && offset !== null);\n\n if (queryVectors.length === 0) return [];\n\n // Self-exclude the source; carry the caller's filter (e.g. excludeEntityTypes).\n const filter = this.buildFilter({ ...opts.filter, excludeResourceId: resourceId });\n\n // One batched query per source chunk (single round-trip). Each batch's\n // `limit` caps POINTS, but the merge below yields RESOURCES — so a target\n // with many chunks can fill several point-slots and crowd out resources\n // that belong in the top-`limit` after folding. Over-fetch per batch to\n // leave the merge material to work with; the memory store scores every\n // candidate exhaustively, and without this the two gateways disagree on\n // recall. Headroom, not a guarantee: a resource whose chunks are all\n // outranked by OVER_FETCH×limit points in every batch is still missed.\n // Qdrant's group-by would make it exact, but only via the `search`-family\n // endpoints 1.19.0 removed (see qdrant-query-api.test.ts).\n const searches = queryVectors.map((vector) => ({\n query: vector,\n limit: opts.limit * QdrantVectorStore.SEARCH_BY_RESOURCE_OVER_FETCH,\n score_threshold: opts.scoreThreshold,\n filter: filter ?? undefined,\n with_payload: true,\n }));\n const batches = await this.qdrant.queryBatch('resources', { searches });\n\n // Normalise to VectorSearchResult, then fold chunk hits to one entry per\n // resource (max-sim) via the shared merge; slice to the caller's limit.\n const hits = batches.flatMap((batch) => batch.points.map((r) => toSearchResult(r)));\n return mergeByResource(hits).slice(0, opts.limit);\n }\n\n private async search(collection: string, embedding: number[], opts: SearchOptions): Promise<VectorSearchResult[]> {\n const filter = this.buildFilter(opts.filter);\n\n // `query`, not `search`: the REST client dropped `search`/`searchBatch` in\n // 1.19.0 in favour of the universal query endpoint. Same request but the\n // vector moves to `query`, and the response is wrapped in `{ points }`.\n const { points } = await this.qdrant.query(collection, {\n query: embedding,\n limit: opts.limit,\n score_threshold: opts.scoreThreshold,\n filter: filter ?? undefined,\n with_payload: true,\n });\n\n return points.map((r) => toSearchResult(r));\n }\n\n private buildFilter(filter?: SearchOptions['filter']): Schemas['Filter'] | null {\n if (!filter) return null;\n\n const must: Schemas['FieldCondition'][] = [];\n\n if (filter.entityTypes && filter.entityTypes.length > 0) {\n // any-of: match payloads whose `entityTypes` array contains at least one\n // of the requested types. Matches the memory store's `some(t => ...)`\n // semantics; pushing one `must` clause per type would mean all-of.\n must.push({ key: 'entityTypes', match: { any: filter.entityTypes } });\n }\n\n if (filter.resourceId) {\n must.push({ key: 'resourceId', match: { value: String(filter.resourceId) } });\n }\n\n if (filter.motivation) {\n must.push({ key: 'motivation', match: { value: filter.motivation } });\n }\n\n const must_not: Schemas['FieldCondition'][] = [];\n\n if (filter.excludeResourceId) {\n must_not.push({ key: 'resourceId', match: { value: String(filter.excludeResourceId) } });\n }\n\n if (filter.excludeEntityTypes && filter.excludeEntityTypes.length > 0) {\n // any-of exclusion: drop points whose entityTypes contain any of these.\n must_not.push({ key: 'entityTypes', match: { any: filter.excludeEntityTypes } });\n }\n\n if (must.length === 0 && must_not.length === 0) return null;\n\n return {\n ...(must.length > 0 ? { must } : {}),\n ...(must_not.length > 0 ? { must_not } : {}),\n };\n }\n}\n"]}
@@ -1,3 +0,0 @@
1
- export { OllamaEmbeddingProvider } from './chunk-GWXB645H.js';
2
- //# sourceMappingURL=ollama-GGIIWEBW.js.map
3
- //# sourceMappingURL=ollama-GGIIWEBW.js.map
@@ -1,3 +0,0 @@
1
- export { QdrantVectorStore } from './chunk-WCD6P6QQ.js';
2
- //# sourceMappingURL=qdrant-EUQ53YHP.js.map
3
- //# sourceMappingURL=qdrant-EUQ53YHP.js.map
@@ -1,3 +0,0 @@
1
- export { VoyageEmbeddingProvider } from './chunk-PJ72DF6E.js';
2
- //# sourceMappingURL=voyage-MQMTWUMO.js.map
3
- //# sourceMappingURL=voyage-MQMTWUMO.js.map