@reposkein/mcp 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/binary-digests.json +4 -4
- package/dist/embed/batch.d.ts +47 -0
- package/dist/embed/batch.js +92 -0
- package/dist/embed/batch.js.map +1 -0
- package/dist/embed/cache.d.ts +22 -48
- package/dist/embed/cache.js +44 -164
- package/dist/embed/cache.js.map +1 -1
- package/dist/embed/corpusVectors.d.ts +20 -0
- package/dist/embed/corpusVectors.js +60 -0
- package/dist/embed/corpusVectors.js.map +1 -0
- package/dist/embed/provider.d.ts +21 -2
- package/dist/embed/provider.js.map +1 -1
- package/dist/embed/providers/http.d.ts +3 -2
- package/dist/embed/providers/http.js +14 -1
- package/dist/embed/providers/http.js.map +1 -1
- package/dist/embed/providers/voyage.d.ts +3 -2
- package/dist/embed/providers/voyage.js +50 -54
- package/dist/embed/providers/voyage.js.map +1 -1
- package/dist/embed/vectorStore.d.ts +89 -0
- package/dist/embed/vectorStore.js +267 -0
- package/dist/embed/vectorStore.js.map +1 -0
- package/dist/guard/caps.d.ts +3 -0
- package/dist/guard/caps.js +4 -1
- package/dist/guard/caps.js.map +1 -1
- package/dist/search/bm25f.d.ts +17 -1
- package/dist/search/bm25f.js +89 -53
- package/dist/search/bm25f.js.map +1 -1
- package/dist/store/GraphStore.d.ts +6 -1
- package/dist/store/JsonlGraphStore.d.ts +47 -10
- package/dist/store/JsonlGraphStore.js +129 -50
- package/dist/store/JsonlGraphStore.js.map +1 -1
- package/dist/store/Neo4jGraphStore.d.ts +11 -0
- package/dist/store/Neo4jGraphStore.js +29 -2
- package/dist/store/Neo4jGraphStore.js.map +1 -1
- package/dist/store/jsonlGraph.d.ts +22 -0
- package/dist/store/jsonlGraph.js +75 -18
- package/dist/store/jsonlGraph.js.map +1 -1
- package/dist/store/jsonlLines.d.ts +21 -0
- package/dist/store/jsonlLines.js +64 -0
- package/dist/store/jsonlLines.js.map +1 -0
- package/dist/tools/readCypher.js +9 -2
- package/dist/tools/readCypher.js.map +1 -1
- package/dist/tools/semanticFind.js +9 -6
- package/dist/tools/semanticFind.js.map +1 -1
- package/package.json +1 -1
package/binary-digests.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"reposkein-indexer-darwin-arm64": "
|
|
3
|
-
"reposkein-indexer-linux-arm64": "
|
|
4
|
-
"reposkein-indexer-linux-x64": "
|
|
5
|
-
"reposkein-indexer-win32-x64.exe": "
|
|
2
|
+
"reposkein-indexer-darwin-arm64": "518e385eb352ea50dc3280a8c5f6070ef98b485d4279fe05ec238d135eab4831",
|
|
3
|
+
"reposkein-indexer-linux-arm64": "5b313b87049c658aae371306f6539a51913079c7fcf0e8457785e39ebf7d5290",
|
|
4
|
+
"reposkein-indexer-linux-x64": "3a1b66255f0b6ac9f7e874b360cd1f197a9a21ba9c8aaa559342a8b927d6e488",
|
|
5
|
+
"reposkein-indexer-win32-x64.exe": "ca9e36bd622218b83b5592ede264b101f4e1059416a0902ad0ac6cbdd6ea2c82"
|
|
6
6
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The batching seam for embeddings.
|
|
3
|
+
*
|
|
4
|
+
* An EmbeddingProvider can only express ONE request (embedBatch). Everything
|
|
5
|
+
* that decides how much work a request may carry lives here, so no adapter can
|
|
6
|
+
* opt out of the bound — the local HTTP adapter used to post an entire cold
|
|
7
|
+
* corpus in a single request (~5 GB at 50k symbols), because chunking was
|
|
8
|
+
* adapter-local and only the cloud adapter implemented it.
|
|
9
|
+
*
|
|
10
|
+
* Batches are handed to the caller as they complete, never accumulated, so peak
|
|
11
|
+
* memory on the embed path is proportional to one batch rather than the corpus.
|
|
12
|
+
*
|
|
13
|
+
* Config env vars (both optional; may only LOWER a provider's declared limits):
|
|
14
|
+
* REPOSKEIN_EMBED_MAX_BATCH_ITEMS max texts per request
|
|
15
|
+
* REPOSKEIN_EMBED_MAX_BATCH_TOKENS max estimated tokens per request
|
|
16
|
+
*/
|
|
17
|
+
import type { BatchLimits, EmbeddingProvider, EmbedKind } from "./provider.js";
|
|
18
|
+
/**
|
|
19
|
+
* Deterministic token estimate: ~4 characters per token, plus a small
|
|
20
|
+
* per-text overhead for the delimiters and special tokens a server adds.
|
|
21
|
+
*
|
|
22
|
+
* This is a memory budget, not a billing figure — it only has to be
|
|
23
|
+
* monotonic in length and never wildly under-count.
|
|
24
|
+
*/
|
|
25
|
+
export declare function estimateTokens(text: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* The limits actually applied to a provider: its own, optionally lowered by env.
|
|
28
|
+
*
|
|
29
|
+
* The env can only tighten. A provider's declared limit is a hard fact about
|
|
30
|
+
* its backend (Voyage rejects >1000 inputs); letting an env var exceed it would
|
|
31
|
+
* turn a memory knob into a source of 4xx errors.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveBatchLimits(provider: EmbeddingProvider, env?: NodeJS.ProcessEnv): BatchLimits;
|
|
34
|
+
/**
|
|
35
|
+
* Embed `texts` in bounded requests, handing each batch's vectors to `onBatch`
|
|
36
|
+
* as it arrives together with the offset into `texts` it starts at.
|
|
37
|
+
*
|
|
38
|
+
* Nothing is accumulated here: the caller decides what to keep. That is what
|
|
39
|
+
* lets embedCorpus persist progress per batch, so an interrupted cold run
|
|
40
|
+
* resumes instead of restarting.
|
|
41
|
+
*
|
|
42
|
+
* A text that exceeds maxTokens on its own is sent alone rather than dropped or
|
|
43
|
+
* split — the server truncates it if it must, and the loop always advances.
|
|
44
|
+
*
|
|
45
|
+
* Throws on the first provider failure; callers fall back to lexical.
|
|
46
|
+
*/
|
|
47
|
+
export declare function embedInBatches(provider: EmbeddingProvider, texts: string[], kind: EmbedKind, onBatch: (offset: number, vectors: number[][]) => void | Promise<void>, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The batching seam for embeddings.
|
|
3
|
+
*
|
|
4
|
+
* An EmbeddingProvider can only express ONE request (embedBatch). Everything
|
|
5
|
+
* that decides how much work a request may carry lives here, so no adapter can
|
|
6
|
+
* opt out of the bound — the local HTTP adapter used to post an entire cold
|
|
7
|
+
* corpus in a single request (~5 GB at 50k symbols), because chunking was
|
|
8
|
+
* adapter-local and only the cloud adapter implemented it.
|
|
9
|
+
*
|
|
10
|
+
* Batches are handed to the caller as they complete, never accumulated, so peak
|
|
11
|
+
* memory on the embed path is proportional to one batch rather than the corpus.
|
|
12
|
+
*
|
|
13
|
+
* Config env vars (both optional; may only LOWER a provider's declared limits):
|
|
14
|
+
* REPOSKEIN_EMBED_MAX_BATCH_ITEMS max texts per request
|
|
15
|
+
* REPOSKEIN_EMBED_MAX_BATCH_TOKENS max estimated tokens per request
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Deterministic token estimate: ~4 characters per token, plus a small
|
|
19
|
+
* per-text overhead for the delimiters and special tokens a server adds.
|
|
20
|
+
*
|
|
21
|
+
* This is a memory budget, not a billing figure — it only has to be
|
|
22
|
+
* monotonic in length and never wildly under-count.
|
|
23
|
+
*/
|
|
24
|
+
export function estimateTokens(text) {
|
|
25
|
+
return Math.ceil(text.length / 4) + 2;
|
|
26
|
+
}
|
|
27
|
+
/** Read a positive-integer env override, or undefined when absent/malformed. */
|
|
28
|
+
function positiveInt(raw) {
|
|
29
|
+
if (raw === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const n = Number(raw);
|
|
32
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The limits actually applied to a provider: its own, optionally lowered by env.
|
|
38
|
+
*
|
|
39
|
+
* The env can only tighten. A provider's declared limit is a hard fact about
|
|
40
|
+
* its backend (Voyage rejects >1000 inputs); letting an env var exceed it would
|
|
41
|
+
* turn a memory knob into a source of 4xx errors.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveBatchLimits(provider, env = process.env) {
|
|
44
|
+
const declared = provider.limits();
|
|
45
|
+
const items = positiveInt(env["REPOSKEIN_EMBED_MAX_BATCH_ITEMS"]);
|
|
46
|
+
const tokens = positiveInt(env["REPOSKEIN_EMBED_MAX_BATCH_TOKENS"]);
|
|
47
|
+
return {
|
|
48
|
+
maxItems: items === undefined ? declared.maxItems : Math.min(items, declared.maxItems),
|
|
49
|
+
maxTokens: tokens === undefined ? declared.maxTokens : Math.min(tokens, declared.maxTokens),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Embed `texts` in bounded requests, handing each batch's vectors to `onBatch`
|
|
54
|
+
* as it arrives together with the offset into `texts` it starts at.
|
|
55
|
+
*
|
|
56
|
+
* Nothing is accumulated here: the caller decides what to keep. That is what
|
|
57
|
+
* lets embedCorpus persist progress per batch, so an interrupted cold run
|
|
58
|
+
* resumes instead of restarting.
|
|
59
|
+
*
|
|
60
|
+
* A text that exceeds maxTokens on its own is sent alone rather than dropped or
|
|
61
|
+
* split — the server truncates it if it must, and the loop always advances.
|
|
62
|
+
*
|
|
63
|
+
* Throws on the first provider failure; callers fall back to lexical.
|
|
64
|
+
*/
|
|
65
|
+
export async function embedInBatches(provider, texts, kind, onBatch, env = process.env) {
|
|
66
|
+
if (texts.length === 0)
|
|
67
|
+
return;
|
|
68
|
+
const { maxItems, maxTokens } = resolveBatchLimits(provider, env);
|
|
69
|
+
let start = 0;
|
|
70
|
+
while (start < texts.length) {
|
|
71
|
+
let end = start;
|
|
72
|
+
let tokens = 0;
|
|
73
|
+
while (end < texts.length) {
|
|
74
|
+
const next = estimateTokens(texts[end]);
|
|
75
|
+
// Always take at least one text, however oversized, so the loop advances.
|
|
76
|
+
if (end > start && tokens + next > maxTokens)
|
|
77
|
+
break;
|
|
78
|
+
tokens += next;
|
|
79
|
+
end++;
|
|
80
|
+
if (end - start >= maxItems)
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
const batch = texts.slice(start, end);
|
|
84
|
+
const vectors = await provider.embedBatch(batch, kind);
|
|
85
|
+
if (vectors.length !== batch.length) {
|
|
86
|
+
throw new Error(`Embedding provider returned ${vectors.length} vectors for a batch of ${batch.length} texts — count mismatch`);
|
|
87
|
+
}
|
|
88
|
+
await onBatch(start, vectors);
|
|
89
|
+
start = end;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=batch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/embed/batch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AACxC,CAAC;AAED,gFAAgF;AAChF,SAAS,WAAW,CAAC,GAAuB;IAC1C,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACrD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAA2B,EAC3B,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC,CAAC;IACpE,OAAO;QACL,QAAQ,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC;QACtF,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC;KAC5F,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAA2B,EAC3B,KAAe,EACf,IAAe,EACf,OAAsE,EACtE,MAAyB,OAAO,CAAC,GAAG;IAEpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC/B,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,kBAAkB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAElE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5B,IAAI,GAAG,GAAG,KAAK,CAAC;QAChB,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,GAAG,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAE,CAAC,CAAC;YACzC,0EAA0E;YAC1E,IAAI,GAAG,GAAG,KAAK,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS;gBAAE,MAAM;YACpD,MAAM,IAAI,IAAI,CAAC;YACf,GAAG,EAAE,CAAC;YACN,IAAI,GAAG,GAAG,KAAK,IAAI,QAAQ;gBAAE,MAAM;QACrC,CAAC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACvD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CACb,+BAA+B,OAAO,CAAC,MAAM,2BAA2B,KAAK,CAAC,MAAM,yBAAyB,CAC9G,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC9B,KAAK,GAAG,GAAG,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/embed/cache.d.ts
CHANGED
|
@@ -1,35 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* How a corpus node becomes a document string, and how that string is keyed.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* —
|
|
4
|
+
* Storage itself lives in vectorStore.ts — fixed-stride float32 beside a JSONL
|
|
5
|
+
* index — and the orchestration in corpusVectors.ts. This file is only the
|
|
6
|
+
* text: what we embed, and the hash that decides when to re-embed it.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* sole per-row invalidation key. It covers all code/summary changes since the doc
|
|
13
|
-
* string is built from the same fields (qualified_name + signature + summary + file_path).
|
|
14
|
-
*
|
|
15
|
-
* Mirrors the atomic-write + best-effort pattern from mcp/src/store/sidecar.ts.
|
|
16
|
-
* Any I/O or provider failure in embedCorpus must NOT propagate — callers catch and
|
|
17
|
-
* fall back to the lexical result.
|
|
8
|
+
* Invalidation: `doc_hash` must match the hash of a freshly-built document
|
|
9
|
+
* string, so a change to qualified_name, signature, semantic_summary or
|
|
10
|
+
* file_path re-embeds the node. CorpusNode does not expose the committed
|
|
11
|
+
* content_hash, so this is the sole per-row key — which is sufficient, since
|
|
12
|
+
* the document is built from exactly those fields.
|
|
18
13
|
*/
|
|
19
|
-
import type { EmbeddingProvider } from "./provider.js";
|
|
20
14
|
import type { CorpusNode } from "../store/GraphStore.js";
|
|
21
|
-
/** One cached record per embedded node. */
|
|
22
|
-
export interface EmbedRecord {
|
|
23
|
-
id: string;
|
|
24
|
-
/** doc_hash: SHA-256 of the embedded document string (invalidation key). */
|
|
25
|
-
doc_hash: string;
|
|
26
|
-
v: number[];
|
|
27
|
-
}
|
|
28
15
|
/** Build the document string for a corpus node (deterministic, same for all calls).
|
|
29
16
|
* voyage-code-3 is code-specialized, so including code-ish context plays to its strength.
|
|
30
17
|
* Document = qualified_name + optional signature + optional summary + file_path.
|
|
31
18
|
*/
|
|
32
|
-
export declare function buildDocString(node: CorpusNode): string;
|
|
19
|
+
export declare function buildDocString(node: CorpusNode, budget?: number): string;
|
|
20
|
+
/**
|
|
21
|
+
* Longest document string we will hand a provider.
|
|
22
|
+
*
|
|
23
|
+
* This is a correctness bound, not a preference. The bundled embedding server
|
|
24
|
+
* rejects an over-long input with 413 (EMBED_MAX_INPUT_CHARS, default 8000),
|
|
25
|
+
* and one rejected document fails the whole embed call — so `semantic_find`
|
|
26
|
+
* would fall back to lexical, and keep falling back, because the offending
|
|
27
|
+
* document is never embedded and every later attempt reissues it. Keep this at
|
|
28
|
+
* or below the server's cap.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_DOC_CHAR_BUDGET = 8000;
|
|
31
|
+
export declare function docCharBudget(env?: NodeJS.ProcessEnv): number;
|
|
33
32
|
/** SHA-256 of a string, hex-encoded. */
|
|
34
33
|
export declare function sha256(s: string): string;
|
|
35
34
|
/**
|
|
@@ -39,28 +38,3 @@ export declare function sha256(s: string): string;
|
|
|
39
38
|
* the FS. Replace any run of unsafe characters with "_".
|
|
40
39
|
*/
|
|
41
40
|
export declare function sanitizeModelId(modelId: string): string;
|
|
42
|
-
/** Derive the cache file path for a given repo root + provider. */
|
|
43
|
-
export declare function cachePath(repoPath: string, provider: EmbeddingProvider): string;
|
|
44
|
-
/**
|
|
45
|
-
* Load cache into a Map keyed by node id. Missing file → empty map. Best-effort.
|
|
46
|
-
*
|
|
47
|
-
* Row validation: any row whose `v` is not an array of finite numbers of the
|
|
48
|
-
* correct length (matching provider.dims) is dropped — treated as a miss so
|
|
49
|
-
* it will be re-embedded. Pass `expectedDims` to enable dim-length validation;
|
|
50
|
-
* omit (undefined) to skip the length check (e.g. when the provider is not known
|
|
51
|
-
* at load time — but the embedCorpus function always passes it).
|
|
52
|
-
*/
|
|
53
|
-
export declare function loadCache(path: string, expectedDims?: number): Map<string, EmbedRecord>;
|
|
54
|
-
/** Rewrite the cache file (sorted by id). Atomic: write to .tmp then rename. Best-effort. */
|
|
55
|
-
export declare function saveCache(path: string, records: Map<string, EmbedRecord>): void;
|
|
56
|
-
/**
|
|
57
|
-
* Embed the corpus nodes using the provider, leveraging the cache for nodes
|
|
58
|
-
* whose doc_hash hasn't changed.
|
|
59
|
-
*
|
|
60
|
-
* Returns a Map<id, vector> for all nodes in corpus.
|
|
61
|
-
* Only embeds nodes whose cache entry is missing or stale.
|
|
62
|
-
* Persists updated cache to disk.
|
|
63
|
-
*
|
|
64
|
-
* Throws on embedding failure — callers must catch and fall back to lexical.
|
|
65
|
-
*/
|
|
66
|
-
export declare function embedCorpus(provider: EmbeddingProvider, repoPath: string, corpus: CorpusNode[]): Promise<Map<string, number[]>>;
|
package/dist/embed/cache.js
CHANGED
|
@@ -1,36 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* How a corpus node becomes a document string, and how that string is keyed.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* —
|
|
4
|
+
* Storage itself lives in vectorStore.ts — fixed-stride float32 beside a JSONL
|
|
5
|
+
* index — and the orchestration in corpusVectors.ts. This file is only the
|
|
6
|
+
* text: what we embed, and the hash that decides when to re-embed it.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* sole per-row invalidation key. It covers all code/summary changes since the doc
|
|
13
|
-
* string is built from the same fields (qualified_name + signature + summary + file_path).
|
|
14
|
-
*
|
|
15
|
-
* Mirrors the atomic-write + best-effort pattern from mcp/src/store/sidecar.ts.
|
|
16
|
-
* Any I/O or provider failure in embedCorpus must NOT propagate — callers catch and
|
|
17
|
-
* fall back to the lexical result.
|
|
8
|
+
* Invalidation: `doc_hash` must match the hash of a freshly-built document
|
|
9
|
+
* string, so a change to qualified_name, signature, semantic_summary or
|
|
10
|
+
* file_path re-embeds the node. CorpusNode does not expose the committed
|
|
11
|
+
* content_hash, so this is the sole per-row key — which is sufficient, since
|
|
12
|
+
* the document is built from exactly those fields.
|
|
18
13
|
*/
|
|
19
|
-
import { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from "node:fs";
|
|
20
|
-
import { join, dirname } from "node:path";
|
|
21
14
|
import { createHash } from "node:crypto";
|
|
22
15
|
/** Build the document string for a corpus node (deterministic, same for all calls).
|
|
23
16
|
* voyage-code-3 is code-specialized, so including code-ish context plays to its strength.
|
|
24
17
|
* Document = qualified_name + optional signature + optional summary + file_path.
|
|
25
18
|
*/
|
|
26
|
-
export function buildDocString(node) {
|
|
27
|
-
const
|
|
19
|
+
export function buildDocString(node, budget = docCharBudget()) {
|
|
20
|
+
const head = [node.qualified_name];
|
|
28
21
|
if (node.signature)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
head.push(node.signature);
|
|
23
|
+
const tail = [node.file_path];
|
|
24
|
+
// Trim the SUMMARY to fit, not the tail. It is the one unbounded field —
|
|
25
|
+
// agents author it — and the identifiers are what a search matches on.
|
|
26
|
+
const overhead = [...head, ...tail].join("\n").length + (node.summary ? 1 : 0);
|
|
27
|
+
let summary = node.summary ?? "";
|
|
28
|
+
if (overhead + summary.length > budget) {
|
|
29
|
+
summary = summary.slice(0, Math.max(0, budget - overhead));
|
|
30
|
+
}
|
|
31
|
+
const parts = [...head];
|
|
32
|
+
if (summary)
|
|
33
|
+
parts.push(summary);
|
|
34
|
+
parts.push(...tail);
|
|
35
|
+
// Last resort, for a node whose own name and path already exceed the budget.
|
|
36
|
+
return parts.join("\n").slice(0, budget);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Longest document string we will hand a provider.
|
|
40
|
+
*
|
|
41
|
+
* This is a correctness bound, not a preference. The bundled embedding server
|
|
42
|
+
* rejects an over-long input with 413 (EMBED_MAX_INPUT_CHARS, default 8000),
|
|
43
|
+
* and one rejected document fails the whole embed call — so `semantic_find`
|
|
44
|
+
* would fall back to lexical, and keep falling back, because the offending
|
|
45
|
+
* document is never embedded and every later attempt reissues it. Keep this at
|
|
46
|
+
* or below the server's cap.
|
|
47
|
+
*/
|
|
48
|
+
export const DEFAULT_DOC_CHAR_BUDGET = 8000;
|
|
49
|
+
export function docCharBudget(env = process.env) {
|
|
50
|
+
const raw = env["REPOSKEIN_EMBED_MAX_INPUT_CHARS"];
|
|
51
|
+
if (raw === undefined)
|
|
52
|
+
return DEFAULT_DOC_CHAR_BUDGET;
|
|
53
|
+
const n = Number(raw);
|
|
54
|
+
return Number.isInteger(n) && n > 0 ? n : DEFAULT_DOC_CHAR_BUDGET;
|
|
34
55
|
}
|
|
35
56
|
/** SHA-256 of a string, hex-encoded. */
|
|
36
57
|
export function sha256(s) {
|
|
@@ -47,145 +68,4 @@ export function sanitizeModelId(modelId) {
|
|
|
47
68
|
// Also collapse multiple consecutive underscores to avoid "___"-confusing names.
|
|
48
69
|
return modelId.replace(/[/\\:*?"<>|\x00-\x1f]+/g, "_");
|
|
49
70
|
}
|
|
50
|
-
/** Derive the cache file path for a given repo root + provider. */
|
|
51
|
-
export function cachePath(repoPath, provider) {
|
|
52
|
-
const safeModel = sanitizeModelId(provider.modelId());
|
|
53
|
-
const name = `${provider.id()}__${safeModel}__d${provider.dims()}`;
|
|
54
|
-
return join(repoPath, ".reposkein", "local", "embeddings", `${name}.jsonl`);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Load cache into a Map keyed by node id. Missing file → empty map. Best-effort.
|
|
58
|
-
*
|
|
59
|
-
* Row validation: any row whose `v` is not an array of finite numbers of the
|
|
60
|
-
* correct length (matching provider.dims) is dropped — treated as a miss so
|
|
61
|
-
* it will be re-embedded. Pass `expectedDims` to enable dim-length validation;
|
|
62
|
-
* omit (undefined) to skip the length check (e.g. when the provider is not known
|
|
63
|
-
* at load time — but the embedCorpus function always passes it).
|
|
64
|
-
*/
|
|
65
|
-
export function loadCache(path, expectedDims) {
|
|
66
|
-
const map = new Map();
|
|
67
|
-
if (!existsSync(path))
|
|
68
|
-
return map;
|
|
69
|
-
let text;
|
|
70
|
-
try {
|
|
71
|
-
text = readFileSync(path, "utf8");
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
return map;
|
|
75
|
-
}
|
|
76
|
-
for (const line of text.split("\n")) {
|
|
77
|
-
if (line.trim() === "")
|
|
78
|
-
continue;
|
|
79
|
-
try {
|
|
80
|
-
const o = JSON.parse(line);
|
|
81
|
-
if (typeof o["id"] === "string" &&
|
|
82
|
-
typeof o["doc_hash"] === "string" &&
|
|
83
|
-
Array.isArray(o["v"])) {
|
|
84
|
-
const v = o["v"];
|
|
85
|
-
// Validate: every element must be a finite number
|
|
86
|
-
const allFinite = v.every((x) => typeof x === "number" && isFinite(x));
|
|
87
|
-
if (!allFinite)
|
|
88
|
-
continue; // corrupt row — drop it (will be re-embedded)
|
|
89
|
-
// Validate: length must match the expected dims (when known)
|
|
90
|
-
if (expectedDims !== undefined && v.length !== expectedDims)
|
|
91
|
-
continue;
|
|
92
|
-
map.set(o["id"], {
|
|
93
|
-
id: o["id"],
|
|
94
|
-
doc_hash: o["doc_hash"],
|
|
95
|
-
v: v,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
// skip malformed line
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return map;
|
|
104
|
-
}
|
|
105
|
-
/** Rewrite the cache file (sorted by id). Atomic: write to .tmp then rename. Best-effort. */
|
|
106
|
-
export function saveCache(path, records) {
|
|
107
|
-
const lines = [...records.keys()].sort().map((id) => {
|
|
108
|
-
const r = records.get(id);
|
|
109
|
-
return JSON.stringify({ id: r.id, doc_hash: r.doc_hash, v: r.v });
|
|
110
|
-
});
|
|
111
|
-
const tmp = `${path}.tmp`;
|
|
112
|
-
try {
|
|
113
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
114
|
-
writeFileSync(tmp, lines.length ? lines.join("\n") + "\n" : "");
|
|
115
|
-
renameSync(tmp, path);
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// best-effort; write failure must not break the tool call
|
|
119
|
-
try {
|
|
120
|
-
if (existsSync(tmp))
|
|
121
|
-
unlinkSync(tmp);
|
|
122
|
-
}
|
|
123
|
-
catch { /* ignore */ }
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Embed the corpus nodes using the provider, leveraging the cache for nodes
|
|
128
|
-
* whose doc_hash hasn't changed.
|
|
129
|
-
*
|
|
130
|
-
* Returns a Map<id, vector> for all nodes in corpus.
|
|
131
|
-
* Only embeds nodes whose cache entry is missing or stale.
|
|
132
|
-
* Persists updated cache to disk.
|
|
133
|
-
*
|
|
134
|
-
* Throws on embedding failure — callers must catch and fall back to lexical.
|
|
135
|
-
*/
|
|
136
|
-
export async function embedCorpus(provider, repoPath, corpus) {
|
|
137
|
-
const path = cachePath(repoPath, provider);
|
|
138
|
-
const cache = loadCache(path, provider.dims());
|
|
139
|
-
// Compute document strings + hashes for all corpus nodes
|
|
140
|
-
const docStrings = new Map();
|
|
141
|
-
const docHashes = new Map();
|
|
142
|
-
for (const node of corpus) {
|
|
143
|
-
const doc = buildDocString(node);
|
|
144
|
-
docStrings.set(node.id, doc);
|
|
145
|
-
docHashes.set(node.id, sha256(doc));
|
|
146
|
-
}
|
|
147
|
-
// Identify which nodes need embedding (cache miss or stale doc_hash)
|
|
148
|
-
const toEmbed = [];
|
|
149
|
-
for (const node of corpus) {
|
|
150
|
-
const cached = cache.get(node.id);
|
|
151
|
-
const isHit = cached !== undefined && cached.doc_hash === docHashes.get(node.id);
|
|
152
|
-
if (!isHit) {
|
|
153
|
-
toEmbed.push(node);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
// Embed only misses
|
|
157
|
-
if (toEmbed.length > 0) {
|
|
158
|
-
const texts = toEmbed.map((n) => docStrings.get(n.id));
|
|
159
|
-
const vectors = await provider.embed(texts, "document");
|
|
160
|
-
// Guard: provider must return exactly as many vectors as we sent.
|
|
161
|
-
// A count mismatch means the provider is broken or mis-aligned — throw so
|
|
162
|
-
// the caller's try/catch falls back to lexical. Never write corrupt data.
|
|
163
|
-
if (vectors.length !== toEmbed.length) {
|
|
164
|
-
throw new Error(`Embedding provider returned ${vectors.length} vectors for ${toEmbed.length} texts — count mismatch; refusing to cache`);
|
|
165
|
-
}
|
|
166
|
-
const expectedDims = provider.dims();
|
|
167
|
-
for (let i = 0; i < toEmbed.length; i++) {
|
|
168
|
-
const vec = vectors[i];
|
|
169
|
-
if (!Array.isArray(vec) || vec.length !== expectedDims) {
|
|
170
|
-
throw new Error(`Embedding provider returned a vector with ${Array.isArray(vec) ? vec.length : "undefined"} dims at index ${i}; expected ${expectedDims} — refusing to cache`);
|
|
171
|
-
}
|
|
172
|
-
const node = toEmbed[i];
|
|
173
|
-
cache.set(node.id, {
|
|
174
|
-
id: node.id,
|
|
175
|
-
doc_hash: docHashes.get(node.id),
|
|
176
|
-
v: vec,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
saveCache(path, cache);
|
|
180
|
-
}
|
|
181
|
-
// Build result map for all corpus nodes
|
|
182
|
-
const result = new Map();
|
|
183
|
-
for (const node of corpus) {
|
|
184
|
-
const rec = cache.get(node.id);
|
|
185
|
-
if (rec) {
|
|
186
|
-
result.set(node.id, rec.v);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return result;
|
|
190
|
-
}
|
|
191
71
|
//# sourceMappingURL=cache.js.map
|
package/dist/embed/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/embed/cache.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/embed/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAgB,EAAE,MAAM,GAAG,aAAa,EAAE;IACvE,MAAM,IAAI,GAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7C,IAAI,IAAI,CAAC,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAExC,yEAAyE;IACzE,uEAAuE;IACvE,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;IACjC,IAAI,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;QACvC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACxB,IAAI,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;IACpB,6EAA6E;IAC7E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,MAAM,UAAU,aAAa,CAAC,MAAyB,OAAO,CAAC,GAAG;IAChE,MAAM,GAAG,GAAG,GAAG,CAAC,iCAAiC,CAAC,CAAC;IACnD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,uBAAuB,CAAC;IACtD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC;AACpE,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,oEAAoE;IACpE,iFAAiF;IACjF,OAAO,OAAO,CAAC,OAAO,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus vectors for `semantic_find`, behind the vector store.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the JSON cache's "hand me every vector as number[]" shape. Callers
|
|
5
|
+
* get a store they can ask for a ranked top-k; they never hold the corpus.
|
|
6
|
+
*/
|
|
7
|
+
import type { EmbeddingProvider } from "./provider.js";
|
|
8
|
+
import { type VectorStore } from "./vectorStore.js";
|
|
9
|
+
import type { CorpusNode } from "../store/GraphStore.js";
|
|
10
|
+
/** Base path (no extension) for a provider's vectors in this repo. */
|
|
11
|
+
export declare function vectorBasePath(repoPath: string, provider: EmbeddingProvider): string;
|
|
12
|
+
/**
|
|
13
|
+
* Ensure every corpus node has a current vector, and return the store.
|
|
14
|
+
*
|
|
15
|
+
* Only misses are embedded, in bounded batches, each flushed as it lands so an
|
|
16
|
+
* interrupted cold run resumes rather than restarting.
|
|
17
|
+
*
|
|
18
|
+
* Throws on embedding failure — callers catch and fall back to lexical.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensureCorpusVectors(provider: EmbeddingProvider, repoPath: string, corpus: CorpusNode[]): Promise<VectorStore>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus vectors for `semantic_find`, behind the vector store.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the JSON cache's "hand me every vector as number[]" shape. Callers
|
|
5
|
+
* get a store they can ask for a ranked top-k; they never hold the corpus.
|
|
6
|
+
*/
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { embedInBatches } from "./batch.js";
|
|
9
|
+
import { buildDocString, sanitizeModelId, sha256 } from "./cache.js";
|
|
10
|
+
import { openVectorStore, removeLegacyJsonCache } from "./vectorStore.js";
|
|
11
|
+
/** Base path (no extension) for a provider's vectors in this repo. */
|
|
12
|
+
export function vectorBasePath(repoPath, provider) {
|
|
13
|
+
const name = `${provider.id()}__${sanitizeModelId(provider.modelId())}__d${provider.dims()}`;
|
|
14
|
+
return join(repoPath, ".reposkein", "local", "embeddings", name);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ensure every corpus node has a current vector, and return the store.
|
|
18
|
+
*
|
|
19
|
+
* Only misses are embedded, in bounded batches, each flushed as it lands so an
|
|
20
|
+
* interrupted cold run resumes rather than restarting.
|
|
21
|
+
*
|
|
22
|
+
* Throws on embedding failure — callers catch and fall back to lexical.
|
|
23
|
+
*/
|
|
24
|
+
export async function ensureCorpusVectors(provider, repoPath, corpus) {
|
|
25
|
+
const base = vectorBasePath(repoPath, provider);
|
|
26
|
+
// The pre-binary cache is superseded, and at 200k nodes it was over 4 GB of
|
|
27
|
+
// dead JSON. Drop it the first time we write the store beside it.
|
|
28
|
+
removeLegacyJsonCache(base);
|
|
29
|
+
const store = openVectorStore(base, provider.dims());
|
|
30
|
+
const docs = new Map();
|
|
31
|
+
const hashes = new Map();
|
|
32
|
+
const misses = [];
|
|
33
|
+
for (const node of corpus) {
|
|
34
|
+
const doc = buildDocString(node);
|
|
35
|
+
docs.set(node.id, doc);
|
|
36
|
+
const hash = sha256(doc);
|
|
37
|
+
hashes.set(node.id, hash);
|
|
38
|
+
if (store.docHash(node.id) !== hash)
|
|
39
|
+
misses.push(node);
|
|
40
|
+
}
|
|
41
|
+
if (misses.length === 0)
|
|
42
|
+
return store;
|
|
43
|
+
const texts = misses.map((n) => docs.get(n.id));
|
|
44
|
+
const expectedDims = provider.dims();
|
|
45
|
+
await embedInBatches(provider, texts, "document", (offset, vectors) => {
|
|
46
|
+
const batch = [];
|
|
47
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
48
|
+
const vec = vectors[i];
|
|
49
|
+
if (!Array.isArray(vec) || vec.length !== expectedDims) {
|
|
50
|
+
throw new Error(`Embedding provider returned a vector with ${Array.isArray(vec) ? vec.length : "undefined"} dims at index ${offset + i}; expected ${expectedDims} — refusing to store`);
|
|
51
|
+
}
|
|
52
|
+
const node = misses[offset + i];
|
|
53
|
+
batch.push({ id: node.id, docHash: hashes.get(node.id), vector: vec });
|
|
54
|
+
}
|
|
55
|
+
store.upsertMany(batch);
|
|
56
|
+
store.flush();
|
|
57
|
+
});
|
|
58
|
+
return store;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=corpusVectors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpusVectors.js","sourceRoot":"","sources":["../../src/embed/corpusVectors.ts"],"names":[],"mappings":"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"}
|
package/dist/embed/provider.d.ts
CHANGED
|
@@ -12,6 +12,19 @@
|
|
|
12
12
|
* REPOSKEIN_EMBED_URL base URL for http provider
|
|
13
13
|
*/
|
|
14
14
|
export type EmbedKind = "document" | "query";
|
|
15
|
+
/**
|
|
16
|
+
* What a single request to this provider may carry.
|
|
17
|
+
*
|
|
18
|
+
* The shared batcher (embed/batch.ts) enforces these; an adapter never chunks
|
|
19
|
+
* for itself. `maxTokens` is measured with estimateTokens, a deterministic
|
|
20
|
+
* length heuristic — it is a memory budget, not a billing figure.
|
|
21
|
+
*/
|
|
22
|
+
export interface BatchLimits {
|
|
23
|
+
/** Max texts in one request. */
|
|
24
|
+
maxItems: number;
|
|
25
|
+
/** Max estimated tokens across one request's texts. */
|
|
26
|
+
maxTokens: number;
|
|
27
|
+
}
|
|
15
28
|
export interface EmbeddingProvider {
|
|
16
29
|
/** Stable provider id, e.g. "voyage". Forms part of the cache key. */
|
|
17
30
|
id(): string;
|
|
@@ -19,13 +32,19 @@ export interface EmbeddingProvider {
|
|
|
19
32
|
modelId(): string;
|
|
20
33
|
/** Output vector dimensionality, e.g. 1024. Part of the cache key. */
|
|
21
34
|
dims(): number;
|
|
35
|
+
/** Per-request limits. Callers go through embedInBatches, which enforces them. */
|
|
36
|
+
limits(): BatchLimits;
|
|
22
37
|
/**
|
|
23
|
-
* Embed
|
|
38
|
+
* Embed ONE request's worth of texts. Implementations must NOT batch
|
|
39
|
+
* internally: the caller has already sized this call against limits().
|
|
40
|
+
* Splitting here would put the memory bound back inside each adapter, which
|
|
41
|
+
* is exactly how the local HTTP adapter came to send whole corpora at once.
|
|
42
|
+
*
|
|
24
43
|
* kind maps to Voyage's input_type ("document" vs "query") — Voyage uses
|
|
25
44
|
* asymmetric embeddings so this materially changes the vectors.
|
|
26
45
|
* Returns one number[] per input, in order.
|
|
27
46
|
*/
|
|
28
|
-
|
|
47
|
+
embedBatch(texts: string[], kind: EmbedKind): Promise<number[][]>;
|
|
29
48
|
}
|
|
30
49
|
/**
|
|
31
50
|
* Build a provider from env vars, or null if embeddings are disabled (default).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/embed/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/embed/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAwCH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IACxE,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,0BAA0B,CAAC,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/E,IAAI,YAAY,KAAK,MAAM,IAAI,YAAY,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAEhE,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,EAAE,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;QAC1E,OAAO,IAAI,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,IAAI,YAAY,KAAK,MAAM,EAAE,CAAC;QAC5B,MAAM,EAAE,qBAAqB,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;QACtE,OAAO,IAAI,qBAAqB,CAAC,GAAG,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,IAAI,KAAK,CACb,sCAAsC,YAAY,kCAAkC,CACrF,CAAC;AACJ,CAAC"}
|