codesearch-mcp 0.2.3-alpha → 0.3.0-alpha
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/CHANGELOG.md +31 -1
- package/README.md +46 -26
- package/dist/app.js +5 -19
- package/dist/src/chunking/AstChunker.d.ts +12 -0
- package/dist/src/chunking/AstChunker.js +30 -0
- package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
- package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
- package/dist/src/chunking/FragmentMerger.d.ts +11 -0
- package/dist/src/chunking/FragmentMerger.js +61 -0
- package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
- package/dist/src/chunking/ImportOnlyFilter.js +14 -0
- package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
- package/dist/src/chunking/LangchainTextSplitter.js +133 -0
- package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
- package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
- package/dist/src/chunking/RunPacker.d.ts +12 -0
- package/dist/src/chunking/RunPacker.js +84 -0
- package/dist/src/chunking/create-chunker.d.ts +3 -0
- package/dist/src/chunking/create-chunker.js +15 -0
- package/dist/src/chunking/interfaces.d.ts +27 -0
- package/dist/src/chunking/interfaces.js +2 -0
- package/dist/src/commands/ConfigCommand.js +1 -9
- package/dist/src/commands/EvalCommand.d.ts +4 -3
- package/dist/src/commands/EvalCommand.js +85 -52
- package/dist/src/commands/IndexCommand.js +0 -22
- package/dist/src/commands/McpCommand.js +2 -8
- package/dist/src/commands/WebCommand.js +2 -5
- package/dist/src/config-error.d.ts +0 -4
- package/dist/src/config-error.js +0 -4
- package/dist/src/config-store.d.ts +1 -54
- package/dist/src/config-store.js +8 -69
- package/dist/src/config.d.ts +8 -17
- package/dist/src/config.js +35 -34
- package/dist/src/constants.d.ts +2 -28
- package/dist/src/constants.js +6 -28
- package/dist/src/container.d.ts +6 -0
- package/dist/src/container.js +12 -3
- package/dist/src/db/schema.d.ts +17 -0
- package/dist/src/db/schema.js +1 -6
- package/dist/src/declarations/DeclarationTree.d.ts +19 -0
- package/dist/src/declarations/DeclarationTree.js +66 -0
- package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
- package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
- package/dist/src/declarations/SourceLines.d.ts +37 -0
- package/dist/src/declarations/SourceLines.js +415 -0
- package/dist/src/dependency-traversal.d.ts +0 -6
- package/dist/src/dependency-traversal.js +0 -6
- package/dist/src/enriched-prompt.d.ts +0 -6
- package/dist/src/enriched-prompt.js +0 -6
- package/dist/src/error-record.d.ts +12 -0
- package/dist/src/error-record.js +248 -0
- package/dist/src/eval-corpus.d.ts +16 -0
- package/dist/src/eval-corpus.js +169 -0
- package/dist/src/eval-results.d.ts +6 -3
- package/dist/src/eval-results.js +309 -26
- package/dist/src/file-utils.d.ts +0 -5
- package/dist/src/file-utils.js +0 -5
- package/dist/src/graph.d.ts +0 -25
- package/dist/src/graph.js +0 -28
- package/dist/src/hnsw-settings.d.ts +3 -0
- package/dist/src/hnsw-settings.js +24 -0
- package/dist/src/interfaces/IAstParser.d.ts +2 -39
- package/dist/src/interfaces/IChunker.d.ts +3 -12
- package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
- package/dist/src/interfaces/ICorpusInspector.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +2 -22
- package/dist/src/interfaces/IEmbedder.d.ts +0 -9
- package/dist/src/interfaces/ILogger.d.ts +1 -11
- package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
- package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
- package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
- package/dist/src/interfaces/IRetrievalEvaluator.d.ts +134 -12
- package/dist/src/interfaces/ISearcher.d.ts +16 -5
- package/dist/src/interrupt.js +0 -2
- package/dist/src/owner-path.d.ts +9 -10
- package/dist/src/owner-path.js +11 -23
- package/dist/src/project-scope.d.ts +0 -12
- package/dist/src/project-scope.js +0 -12
- package/dist/src/project-selector.d.ts +0 -5
- package/dist/src/project-selector.js +0 -11
- package/dist/src/retrieval-eval.d.ts +14 -6
- package/dist/src/retrieval-eval.js +345 -59
- package/dist/src/search/result-shaping.d.ts +4 -0
- package/dist/src/search/result-shaping.js +19 -0
- package/dist/src/services/AstParserService.d.ts +2 -62
- package/dist/src/services/AstParserService.js +25 -137
- package/dist/src/services/DatabaseService.d.ts +2 -7
- package/dist/src/services/DatabaseService.js +42 -29
- package/dist/src/services/EmbedderFactory.d.ts +0 -4
- package/dist/src/services/EmbedderFactory.js +0 -5
- package/dist/src/services/IndexerService.d.ts +0 -16
- package/dist/src/services/IndexerService.js +16 -45
- package/dist/src/services/InfinityEmbedder.d.ts +0 -21
- package/dist/src/services/InfinityEmbedder.js +0 -15
- package/dist/src/services/LoggerService.d.ts +7 -5
- package/dist/src/services/LoggerService.js +37 -41
- package/dist/src/services/McpServer.d.ts +22 -14
- package/dist/src/services/McpServer.js +70 -37
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
- package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
- package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
- package/dist/src/services/NxMonorepoResolver.js +5 -56
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
- package/dist/src/services/OpenAICompatibleEmbedder.js +0 -14
- package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
- package/dist/src/services/ProjectDiscoveryService.js +0 -4
- package/dist/src/services/SearcherService.d.ts +9 -116
- package/dist/src/services/SearcherService.js +95 -176
- package/dist/src/services/VoyageEmbedder.d.ts +0 -6
- package/dist/src/services/VoyageEmbedder.js +0 -6
- package/dist/src/services/WebServerService.d.ts +1 -53
- package/dist/src/services/WebServerService.js +11 -57
- package/dist/src/services/http-retry.d.ts +0 -7
- package/dist/src/services/http-retry.js +1 -10
- package/dist/src/services/infinity-provisioning.d.ts +0 -74
- package/dist/src/services/infinity-provisioning.js +5 -88
- package/dist/src/services/legacy-ollama-index.d.ts +0 -16
- package/dist/src/services/legacy-ollama-index.js +0 -23
- package/dist/src/services/reranker.d.ts +0 -9
- package/dist/src/services/reranker.js +46 -12
- package/dist/src/setup.d.ts +11 -23
- package/dist/src/setup.js +98 -71
- package/dist/src/symbol-name.d.ts +0 -12
- package/dist/src/symbol-name.js +0 -13
- package/dist/src/symbol-rows.d.ts +0 -14
- package/dist/src/symbol-rows.js +0 -14
- package/dist/src/tool-call-log.d.ts +2 -0
- package/dist/src/tool-call-log.js +24 -0
- package/dist/src/tools/GetDependenciesTool.js +1 -1
- package/dist/src/tools/GetDependentsTool.js +1 -1
- package/dist/src/tools/SearchCodebaseTool.js +6 -3
- package/dist/src/tools/format-helpers.d.ts +0 -6
- package/dist/src/tools/format-helpers.js +87 -14
- package/dist/src/web/dashboard.d.ts +0 -8
- package/dist/src/web/dashboard.js +7 -10
- package/package.json +2 -2
- package/dist/src/services/AstChunker.d.ts +0 -42
- package/dist/src/services/AstChunker.js +0 -158
- package/docs/web-dashboard.md +0 -206
|
@@ -51,19 +51,9 @@ exports.requiredModelSet = requiredModelSet;
|
|
|
51
51
|
const fs = __importStar(require("fs"));
|
|
52
52
|
const path = __importStar(require("path"));
|
|
53
53
|
const child_process_1 = require("child_process");
|
|
54
|
-
/**
|
|
55
|
-
* Strip trailing slashes and a trailing `/embeddings` or `/v1` to get the server
|
|
56
|
-
* root — Infinity serves `/health`, `/models`, `/rerank`, `/embeddings` from the
|
|
57
|
-
* root, so a baseURL written with an OpenAI-style suffix must be normalized here
|
|
58
|
-
* to keep the probe/models URLs aligned with the embeddings URL.
|
|
59
|
-
*/
|
|
60
54
|
function serverRoot(baseURL) {
|
|
61
55
|
return baseURL.replace(/\/+$/, '').replace(/\/(embeddings|v1)$/, '');
|
|
62
56
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Probe an Infinity-style `/health` endpoint. Resolves `true` when the server
|
|
65
|
-
* answers with a 2xx, `false` on any error, non-2xx, or timeout.
|
|
66
|
-
*/
|
|
67
57
|
async function probeInfinityHealth(baseURL, fetchImpl = fetch, timeoutMs = 2000) {
|
|
68
58
|
try {
|
|
69
59
|
const res = await fetchImpl(`${serverRoot(baseURL)}/health`, {
|
|
@@ -76,11 +66,6 @@ async function probeInfinityHealth(baseURL, fetchImpl = fetch, timeoutMs = 2000)
|
|
|
76
66
|
return false;
|
|
77
67
|
}
|
|
78
68
|
}
|
|
79
|
-
/**
|
|
80
|
-
* List the model ids an Infinity server currently serves via its OpenAI-style
|
|
81
|
-
* `/models` listing (`{ data: [{ id }] }`). Returns `[]` on any error, non-2xx,
|
|
82
|
-
* or timeout — callers treat an empty/unverifiable listing as "served".
|
|
83
|
-
*/
|
|
84
69
|
async function listInfinityModels(baseURL, fetchImpl = fetch, timeoutMs = 2000) {
|
|
85
70
|
try {
|
|
86
71
|
const res = await fetchImpl(`${serverRoot(baseURL)}/models`, {
|
|
@@ -96,13 +81,6 @@ async function listInfinityModels(baseURL, fetchImpl = fetch, timeoutMs = 2000)
|
|
|
96
81
|
return [];
|
|
97
82
|
}
|
|
98
83
|
}
|
|
99
|
-
// ---------------------------------------------------------------------------
|
|
100
|
-
// Compose-file parsing — deliberately line-based, not a YAML library: we only
|
|
101
|
-
// ever make targeted, well-anchored edits to our own shipped template, and
|
|
102
|
-
// need "every other byte unchanged" — a round-trip through a YAML AST would
|
|
103
|
-
// not guarantee that.
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
/** [start, end) line range of a top-level `services:` entry (2-space indent), or null if absent. */
|
|
106
84
|
function findServiceBlock(lines, serviceName) {
|
|
107
85
|
const headerRe = new RegExp(`^ ${serviceName}:\\s*$`);
|
|
108
86
|
const start = lines.findIndex(l => headerRe.test(l));
|
|
@@ -120,15 +98,12 @@ function findServiceBlock(lines, serviceName) {
|
|
|
120
98
|
}
|
|
121
99
|
return [start, end];
|
|
122
100
|
}
|
|
123
|
-
/** Extract a published host port from one `ports:` list item's raw line(s), or null if unrecognised. */
|
|
124
101
|
function parsePortItem(item) {
|
|
125
102
|
if (item.length === 1) {
|
|
126
103
|
const raw = item[0].replace(/^["']|["']$/g, '').trim();
|
|
127
|
-
// Short form: "HOST:CONTAINER" or "IP:HOST:CONTAINER" (optional /tcp|/udp).
|
|
128
104
|
const m = raw.match(/^(?:(?:\[[0-9a-fA-F:]+\]|[0-9.]+):)?(\d+):(\d+)(?:\/(?:tcp|udp))?$/);
|
|
129
105
|
return m ? m[1] : null;
|
|
130
106
|
}
|
|
131
|
-
// Long form block ("- target: 7997\n published: 7997\n protocol: tcp").
|
|
132
107
|
for (const line of item) {
|
|
133
108
|
const m = line.match(/published:\s*["']?(\d+)["']?/);
|
|
134
109
|
if (m)
|
|
@@ -136,7 +111,6 @@ function parsePortItem(item) {
|
|
|
136
111
|
}
|
|
137
112
|
return null;
|
|
138
113
|
}
|
|
139
|
-
/** Published host port of a service's `ports:` entries within `serviceLines`, or null if none/unrecognised. */
|
|
140
114
|
function extractPublishedPort(serviceLines) {
|
|
141
115
|
const portsIdx = serviceLines.findIndex(l => /^\s*ports:\s*$/.test(l));
|
|
142
116
|
if (portsIdx === -1)
|
|
@@ -177,11 +151,6 @@ function normalizedHost(hostname) {
|
|
|
177
151
|
function schemeDefaultPort(protocol) {
|
|
178
152
|
return protocol === 'https:' ? '443' : '80';
|
|
179
153
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Two URLs target the same server when scheme, (loopback-normalised) host, and
|
|
182
|
-
* port (explicit or scheme default) all match; path is ignored. An unparsable
|
|
183
|
-
* URL is never the same server as another.
|
|
184
|
-
*/
|
|
185
154
|
function sameServer(a, b) {
|
|
186
155
|
try {
|
|
187
156
|
const ua = new URL(a);
|
|
@@ -198,13 +167,6 @@ function sameServer(a, b) {
|
|
|
198
167
|
return false;
|
|
199
168
|
}
|
|
200
169
|
}
|
|
201
|
-
/**
|
|
202
|
-
* "Managed" per the managed-vs-external requirement: the compose file exists,
|
|
203
|
-
* declares an `infinity` service, the base URL host is loopback, and its port
|
|
204
|
-
* (explicit or scheme default) equals the service's published host port.
|
|
205
|
-
* Never throws — an unreadable/unparsable file or an unrecognised port form
|
|
206
|
-
* is simply "external".
|
|
207
|
-
*/
|
|
208
170
|
function isManagedInfinity(baseURL, composePath) {
|
|
209
171
|
try {
|
|
210
172
|
let url;
|
|
@@ -231,7 +193,6 @@ function isManagedInfinity(baseURL, composePath) {
|
|
|
231
193
|
return false;
|
|
232
194
|
}
|
|
233
195
|
}
|
|
234
|
-
/** The literal (non-`${…}`) `INFINITY_MODEL_ID` value in the compose file's `infinity` service, or null. */
|
|
235
196
|
function extractModelIdValue(composeContent) {
|
|
236
197
|
const lines = composeContent.split(/\r?\n/);
|
|
237
198
|
const block = findServiceBlock(lines, 'infinity');
|
|
@@ -246,7 +207,6 @@ function extractModelIdValue(composeContent) {
|
|
|
246
207
|
}
|
|
247
208
|
return null;
|
|
248
209
|
}
|
|
249
|
-
/** Write `content` to `targetPath` via a same-directory temp file + rename; removes the temp file on failure. */
|
|
250
210
|
function atomicWriteFile(targetPath, content, mode) {
|
|
251
211
|
const dir = path.dirname(targetPath);
|
|
252
212
|
const tmpPath = path.join(dir, `.${path.basename(targetPath)}.tmp-${process.pid}-${Date.now()}`);
|
|
@@ -258,19 +218,10 @@ function atomicWriteFile(targetPath, content, mode) {
|
|
|
258
218
|
try {
|
|
259
219
|
fs.unlinkSync(tmpPath);
|
|
260
220
|
}
|
|
261
|
-
catch {
|
|
221
|
+
catch { }
|
|
262
222
|
throw err;
|
|
263
223
|
}
|
|
264
224
|
}
|
|
265
|
-
/**
|
|
266
|
-
* Write `modelSet` (embedding model first) as the literal `INFINITY_MODEL_ID`
|
|
267
|
-
* of the compose file's `infinity` service, and remove a `profiles:` entry of
|
|
268
|
-
* that service if present (a file written by an earlier version). At most
|
|
269
|
-
* those two targeted edits — every other byte unchanged. Atomic (temp file +
|
|
270
|
-
* rename), keeps the original file's mode. Throws (no write, original file
|
|
271
|
-
* unchanged) when the file cannot be read, has no `infinity` service, has no
|
|
272
|
-
* `INFINITY_MODEL_ID:` line, or the write itself fails.
|
|
273
|
-
*/
|
|
274
225
|
function writeComposeModelSet(composePath, modelSet) {
|
|
275
226
|
let content;
|
|
276
227
|
let mode;
|
|
@@ -279,7 +230,7 @@ function writeComposeModelSet(composePath, modelSet) {
|
|
|
279
230
|
mode = fs.statSync(composePath).mode;
|
|
280
231
|
}
|
|
281
232
|
catch (err) {
|
|
282
|
-
throw new Error(`Cannot read managed compose file at ${composePath}: ${err.message}
|
|
233
|
+
throw new Error(`Cannot read managed compose file at ${composePath}: ${err.message}`, { cause: err });
|
|
283
234
|
}
|
|
284
235
|
const newline = content.includes('\r\n') ? '\r\n' : '\n';
|
|
285
236
|
const lines = content.split(/\r?\n/);
|
|
@@ -327,10 +278,9 @@ function writeComposeModelSet(composePath, modelSet) {
|
|
|
327
278
|
atomicWriteFile(composePath, outLines.join(newline), mode);
|
|
328
279
|
}
|
|
329
280
|
catch (err) {
|
|
330
|
-
throw new Error(`Failed to write managed compose file at ${composePath}: ${err.message}
|
|
281
|
+
throw new Error(`Failed to write managed compose file at ${composePath}: ${err.message}`, { cause: err });
|
|
331
282
|
}
|
|
332
283
|
}
|
|
333
|
-
/** True when an NVIDIA GPU + `nvidia-smi` is available on this host. */
|
|
334
284
|
function hasNvidiaGpu() {
|
|
335
285
|
try {
|
|
336
286
|
return (0, child_process_1.spawnSync)('nvidia-smi', ['-L'], { stdio: 'ignore' }).status === 0;
|
|
@@ -342,7 +292,6 @@ function hasNvidiaGpu() {
|
|
|
342
292
|
function gpuOverridePath(composePath) {
|
|
343
293
|
return path.join(path.dirname(composePath), 'docker-compose.infinity-gpu.yml');
|
|
344
294
|
}
|
|
345
|
-
/** The `-f` file args a docker compose invocation against `composePath` should use (base, plus GPU override when present + detected). */
|
|
346
295
|
function composeFileArgs(composePath) {
|
|
347
296
|
const gpu = gpuOverridePath(composePath);
|
|
348
297
|
return (fs.existsSync(gpu) && hasNvidiaGpu()) ? ['-f', composePath, '-f', gpu] : ['-f', composePath];
|
|
@@ -354,7 +303,6 @@ function defaultRunCommand(cmd, args) {
|
|
|
354
303
|
function sleep(ms) {
|
|
355
304
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
356
305
|
}
|
|
357
|
-
/** Parse `docker compose ps --all --format json` output (JSON-lines, or an array) for the `infinity` service's state. */
|
|
358
306
|
function parseInfinityState(stdout) {
|
|
359
307
|
const readState = (obj) => {
|
|
360
308
|
const s = String(obj?.State ?? '').toLowerCase();
|
|
@@ -366,7 +314,7 @@ function parseInfinityState(stdout) {
|
|
|
366
314
|
if (obj?.Service === 'infinity')
|
|
367
315
|
return readState(obj);
|
|
368
316
|
}
|
|
369
|
-
catch {
|
|
317
|
+
catch { }
|
|
370
318
|
}
|
|
371
319
|
try {
|
|
372
320
|
const arr = JSON.parse(stdout);
|
|
@@ -376,14 +324,9 @@ function parseInfinityState(stdout) {
|
|
|
376
324
|
return readState(svc);
|
|
377
325
|
}
|
|
378
326
|
}
|
|
379
|
-
catch {
|
|
327
|
+
catch { }
|
|
380
328
|
return 'missing';
|
|
381
329
|
}
|
|
382
|
-
/**
|
|
383
|
-
* Wait for `/health` after a start/recreate, failing fast on a crashed or
|
|
384
|
-
* restart-looping container instead of waiting out the full timeout, then
|
|
385
|
-
* re-check the models listing once more. Poll interval capped at 5s.
|
|
386
|
-
*/
|
|
387
330
|
async function waitForReady(base, files, modelSetToWrite, requiredModels, deps) {
|
|
388
331
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
389
332
|
const runCommand = deps.runCommand ?? defaultRunCommand;
|
|
@@ -412,7 +355,6 @@ async function waitForReady(base, files, modelSetToWrite, requiredModels, deps)
|
|
|
412
355
|
consecutiveRestarting = 0;
|
|
413
356
|
}
|
|
414
357
|
}
|
|
415
|
-
// A failing `ps` is ignored — neither counts toward nor resets the restarting streak.
|
|
416
358
|
if (Date.now() >= deadline) {
|
|
417
359
|
throw new Error(`Infinity did not become ready within ${Math.round(timeoutMs / 1000)}s at ${base}.`);
|
|
418
360
|
}
|
|
@@ -438,13 +380,6 @@ async function recreateAndWait(base, composePath, modelSetToWrite, requiredModel
|
|
|
438
380
|
}
|
|
439
381
|
await waitForReady(base, files, modelSetToWrite, requiredModels, deps);
|
|
440
382
|
}
|
|
441
|
-
/**
|
|
442
|
-
* Classify the backend against `requiredModels` without touching Docker:
|
|
443
|
-
* reachable+served (or unverifiable listing) → `served`; reachable+missing →
|
|
444
|
-
* `missing-managed`/`missing-external`; unreachable → `unreachable-managed`/
|
|
445
|
-
* `unreachable-external`. Shared by `ensureBackendReady`, the web-dashboard
|
|
446
|
-
* decision table, and `rag-setup`'s declined-Docker branch.
|
|
447
|
-
*/
|
|
448
383
|
async function evaluateBackend(baseURL, composePath, requiredModels, deps = {}) {
|
|
449
384
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
450
385
|
const base = serverRoot(baseURL);
|
|
@@ -458,7 +393,6 @@ async function evaluateBackend(baseURL, composePath, requiredModels, deps = {})
|
|
|
458
393
|
}
|
|
459
394
|
return { action: managed ? 'unreachable-managed' : 'unreachable-external', managed, missingModels: [], servedModels: [] };
|
|
460
395
|
}
|
|
461
|
-
/** Human-readable manual instructions for a `missing-external`/`unreachable-external` evaluation. */
|
|
462
396
|
function manualInstructionMessage(evaluation, base, modelSetToWrite) {
|
|
463
397
|
if (evaluation.action === 'missing-external') {
|
|
464
398
|
return `Infinity at ${base} serves [${evaluation.servedModels.join(', ')}] but is missing [${evaluation.missingModels.join(', ')}]. ` +
|
|
@@ -470,15 +404,6 @@ function manualInstructionMessage(evaluation, base, modelSetToWrite) {
|
|
|
470
404
|
` v2 --model-id ${modelSetToWrite.join(';')} --port 7997\n` +
|
|
471
405
|
`(drop --gpus all to run on CPU), or re-run rag-setup to provision it.`;
|
|
472
406
|
}
|
|
473
|
-
/**
|
|
474
|
-
* Bring the Infinity backend to a state serving `requiredModels`, per the
|
|
475
|
-
* managed-vs-external decision tree:
|
|
476
|
-
* served → return (no Docker)
|
|
477
|
-
* missing-managed → recreate (writes modelSetToWrite) + wait
|
|
478
|
-
* missing-external → throw (manual --model-id instructions)
|
|
479
|
-
* unreachable-managed → (re)create + wait
|
|
480
|
-
* unreachable-external → throw (manual launch instructions)
|
|
481
|
-
*/
|
|
482
407
|
async function ensureBackendReady(opts) {
|
|
483
408
|
const { baseURL, requiredModels, modelSetToWrite, composePath } = opts;
|
|
484
409
|
const deps = opts.deps ?? {};
|
|
@@ -496,17 +421,9 @@ async function ensureBackendReady(opts) {
|
|
|
496
421
|
return;
|
|
497
422
|
}
|
|
498
423
|
}
|
|
499
|
-
/** `ensureBackendReady` with the full set as both the requirement and the write (rag-setup, web dashboard). */
|
|
500
424
|
async function ensureModelSetServed(baseURL, composePath, modelSet, deps) {
|
|
501
425
|
return ensureBackendReady({ baseURL, requiredModels: modelSet, modelSetToWrite: modelSet, composePath, deps });
|
|
502
426
|
}
|
|
503
|
-
/**
|
|
504
|
-
* The model set a managed Infinity container must serve, computed from a
|
|
505
|
-
* `Config`: the embedding model, followed by the reranker model only when
|
|
506
|
-
* reranking is enabled with a model on the same server as embeddings (the
|
|
507
|
-
* effective `reranker.url`, which `Config` has already resolved from an
|
|
508
|
-
* explicit value or derived from `reranker.baseURL`).
|
|
509
|
-
*/
|
|
510
427
|
function requiredModelSet(cfg) {
|
|
511
428
|
const set = [cfg.embedding.model];
|
|
512
429
|
if (cfg.reranker.enabled && cfg.reranker.model && sameServer(cfg.reranker.url, cfg.embedding.baseURL)) {
|
|
@@ -3,22 +3,6 @@ export interface IndexedIdentity {
|
|
|
3
3
|
provider: string;
|
|
4
4
|
model: string;
|
|
5
5
|
}
|
|
6
|
-
/** A pre-`index_meta` install (populated `code_chunk`, no `index_meta` row) is always this. */
|
|
7
6
|
export declare const LEGACY_OLLAMA_IDENTITY: IndexedIdentity;
|
|
8
|
-
/**
|
|
9
|
-
* Whether `code_chunk` has at least one row, tolerant only of a missing
|
|
10
|
-
* table (→ false). Any other database error propagates. Exported so
|
|
11
|
-
* `SearcherService.assertIndexMatches` can reuse the same strict count
|
|
12
|
-
* (it reads `index_meta` via the existing lenient `IDatabase.readIndexMeta()`,
|
|
13
|
-
* but the chunk count must fail loudly on a real DB error).
|
|
14
|
-
*/
|
|
15
7
|
export declare function hasIndexedChunks(db: IDatabase): Promise<boolean>;
|
|
16
|
-
/**
|
|
17
|
-
* Resolve the "indexed identity" under the shared legacy interpretation used
|
|
18
|
-
* both by the `rag-index` legacy-Ollama guard and the search-refusal check:
|
|
19
|
-
* an `index_meta` row wins; absent a row, a populated `code_chunk` is
|
|
20
|
-
* `ollama`/`bge-m3`; an empty (or altogether absent) DB is `null` — not
|
|
21
|
-
* Ollama-built, no refusal. Missing tables count as absent/empty; any other
|
|
22
|
-
* database error propagates rather than being read as "not Ollama-built".
|
|
23
|
-
*/
|
|
24
8
|
export declare function resolveIndexedIdentity(db: IDatabase): Promise<IndexedIdentity | null>;
|
|
@@ -3,18 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LEGACY_OLLAMA_IDENTITY = void 0;
|
|
4
4
|
exports.hasIndexedChunks = hasIndexedChunks;
|
|
5
5
|
exports.resolveIndexedIdentity = resolveIndexedIdentity;
|
|
6
|
-
/** A pre-`index_meta` install (populated `code_chunk`, no `index_meta` row) is always this. */
|
|
7
6
|
exports.LEGACY_OLLAMA_IDENTITY = { provider: 'ollama', model: 'bge-m3' };
|
|
8
7
|
function isMissingTableError(err) {
|
|
9
8
|
return /does not exist/i.test(err?.message ?? '');
|
|
10
9
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Read `index_meta`'s (provider, model), tolerant only of a missing table
|
|
13
|
-
* (→ null, un-migrated DB). Any other database error propagates — unlike
|
|
14
|
-
* `IDatabase.readIndexMeta()`, which swallows every error into `null`, this
|
|
15
|
-
* is used where a genuine DB error must fail the caller loudly rather than
|
|
16
|
-
* be misread as "no row".
|
|
17
|
-
*/
|
|
18
10
|
async function readIndexMetaStrict(db) {
|
|
19
11
|
try {
|
|
20
12
|
const rows = await db.query(`SELECT provider, model FROM index_meta LIMIT 1`);
|
|
@@ -27,13 +19,6 @@ async function readIndexMetaStrict(db) {
|
|
|
27
19
|
throw err;
|
|
28
20
|
}
|
|
29
21
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Whether `code_chunk` has at least one row, tolerant only of a missing
|
|
32
|
-
* table (→ false). Any other database error propagates. Exported so
|
|
33
|
-
* `SearcherService.assertIndexMatches` can reuse the same strict count
|
|
34
|
-
* (it reads `index_meta` via the existing lenient `IDatabase.readIndexMeta()`,
|
|
35
|
-
* but the chunk count must fail loudly on a real DB error).
|
|
36
|
-
*/
|
|
37
22
|
async function hasIndexedChunks(db) {
|
|
38
23
|
try {
|
|
39
24
|
const rows = await db.query(`SELECT COUNT(*)::int AS n FROM code_chunk`);
|
|
@@ -45,14 +30,6 @@ async function hasIndexedChunks(db) {
|
|
|
45
30
|
throw err;
|
|
46
31
|
}
|
|
47
32
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Resolve the "indexed identity" under the shared legacy interpretation used
|
|
50
|
-
* both by the `rag-index` legacy-Ollama guard and the search-refusal check:
|
|
51
|
-
* an `index_meta` row wins; absent a row, a populated `code_chunk` is
|
|
52
|
-
* `ollama`/`bge-m3`; an empty (or altogether absent) DB is `null` — not
|
|
53
|
-
* Ollama-built, no refusal. Missing tables count as absent/empty; any other
|
|
54
|
-
* database error propagates rather than being read as "not Ollama-built".
|
|
55
|
-
*/
|
|
56
33
|
async function resolveIndexedIdentity(db) {
|
|
57
34
|
const meta = await readIndexMetaStrict(db);
|
|
58
35
|
if (meta)
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import type { Config } from '../config';
|
|
2
2
|
import type { ILogger } from '../interfaces/ILogger';
|
|
3
|
-
/**
|
|
4
|
-
* Build a rerank function from `config.reranker`, or return `undefined` when
|
|
5
|
-
* reranking is disabled / unconfigured. Targets an OpenAI-compatible `/rerank`
|
|
6
|
-
* endpoint returning `{ results: [{ index }] }` (Infinity, TEI, vLLM,
|
|
7
|
-
* Jina/Cohere-shape). The embedding provider is independent of the rerank
|
|
8
|
-
* server (`reranker.baseURL`), so e.g. Voyage embeddings + an Infinity
|
|
9
|
-
* reranker is fine.
|
|
10
|
-
* Returns original indices into `documents`, best-first.
|
|
11
|
-
*/
|
|
12
3
|
export declare function makeReranker(config: Config, logger: ILogger, deps?: {
|
|
13
4
|
fetchImpl?: typeof fetch;
|
|
14
5
|
baseDelayMs?: number;
|
|
@@ -3,17 +3,42 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.makeReranker = makeReranker;
|
|
4
4
|
const config_1 = require("../config");
|
|
5
5
|
const http_retry_1 = require("./http-retry");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
function indexList(items) {
|
|
7
|
+
if (!Array.isArray(items))
|
|
8
|
+
return null;
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const item of items) {
|
|
11
|
+
const index = item?.index;
|
|
12
|
+
if (typeof index !== 'number')
|
|
13
|
+
return null;
|
|
14
|
+
out.push(index);
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
const WIRES = {
|
|
19
|
+
'openai-compatible': {
|
|
20
|
+
body: (model, query, documents, topN) => ({ model, query, documents, top_n: topN, truncate: true }),
|
|
21
|
+
indices: json => indexList(json?.results),
|
|
22
|
+
},
|
|
23
|
+
voyage: {
|
|
24
|
+
body: (model, query, documents, topN) => ({ model, query, documents, top_k: topN, truncation: true }),
|
|
25
|
+
indices: (json) => {
|
|
26
|
+
const data = json?.data;
|
|
27
|
+
if (!Array.isArray(data))
|
|
28
|
+
return null;
|
|
29
|
+
if (data.some(d => typeof d?.relevance_score !== 'number'))
|
|
30
|
+
return null;
|
|
31
|
+
const sorted = [...data].sort((a, b) => b.relevance_score - a.relevance_score);
|
|
32
|
+
return indexList(sorted);
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
15
36
|
function makeReranker(config, logger, deps = {}) {
|
|
16
37
|
const r = config.reranker;
|
|
38
|
+
if (r.missingApiKey) {
|
|
39
|
+
logger.warn('reranker: reranker.provider is voyage but reranker.apiKey is not set — reranking disabled. Set reranker.apiKey or reranker.enabled: false');
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
17
42
|
if (!r.enabled)
|
|
18
43
|
return undefined;
|
|
19
44
|
if (!r.model) {
|
|
@@ -21,6 +46,7 @@ function makeReranker(config, logger, deps = {}) {
|
|
|
21
46
|
return undefined;
|
|
22
47
|
}
|
|
23
48
|
const doFetch = deps.fetchImpl ?? fetch;
|
|
49
|
+
const wire = WIRES[r.provider] ?? WIRES['openai-compatible'];
|
|
24
50
|
return async (query, documents, topN) => {
|
|
25
51
|
return (0, http_retry_1.withRetry)(async () => {
|
|
26
52
|
const headers = { 'Content-Type': 'application/json' };
|
|
@@ -31,14 +57,22 @@ function makeReranker(config, logger, deps = {}) {
|
|
|
31
57
|
method: 'POST',
|
|
32
58
|
headers,
|
|
33
59
|
signal: AbortSignal.timeout(30_000),
|
|
34
|
-
body: JSON.stringify(
|
|
60
|
+
body: JSON.stringify(wire.body(r.model, query, documents, topN)),
|
|
35
61
|
});
|
|
36
62
|
if (!res.ok) {
|
|
37
63
|
const body = (await res.text().catch(() => res.statusText)).slice(0, 200);
|
|
38
64
|
throw new http_retry_1.HttpError(res.status, `rerank failed: ${res.status} ${body}`);
|
|
39
65
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
66
|
+
const json = await res.json().catch((err) => {
|
|
67
|
+
if (err instanceof SyntaxError)
|
|
68
|
+
return null;
|
|
69
|
+
throw err;
|
|
70
|
+
});
|
|
71
|
+
const indices = wire.indices(json);
|
|
72
|
+
if (!indices || indices.some(i => !Number.isInteger(i) || i < 0 || i >= documents.length)) {
|
|
73
|
+
throw new Error(`rerank failed: unexpected response from ${r.url}`);
|
|
74
|
+
}
|
|
75
|
+
return indices;
|
|
42
76
|
}, { logger, baseDelayMs: deps.baseDelayMs, label: 'rerank' });
|
|
43
77
|
};
|
|
44
78
|
}
|
package/dist/src/setup.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type EmbeddingProvider } from './constants';
|
|
2
|
+
import { type JsonConfig } from './config-store';
|
|
2
3
|
import { Config } from './config';
|
|
3
4
|
import { type ProvisioningDeps } from './services/infinity-provisioning';
|
|
4
5
|
import type { LoggerService } from './services/LoggerService';
|
|
@@ -6,40 +7,27 @@ type SetupStep = {
|
|
|
6
7
|
q: string;
|
|
7
8
|
a: string;
|
|
8
9
|
};
|
|
9
|
-
/** Providers the wizard offers, in prompt order; the first is the preselected default. Exported for unit testing. */
|
|
10
10
|
export declare const EMBEDDING_PROVIDER_CHOICES: readonly EmbeddingProvider[];
|
|
11
|
-
/**
|
|
12
|
-
* Default answer for the "start Docker containers?" prompt: no only when both
|
|
13
|
-
* the database and the embedding backend are already reachable. Pure — exported
|
|
14
|
-
* for unit testing.
|
|
15
|
-
*/
|
|
16
11
|
export declare function dockerPromptDefault(dbReachable: boolean, embReachable: boolean): 'y' | 'n';
|
|
12
|
+
export type RerankerChoice = 'infinity' | 'voyage' | 'openai-compatible' | 'none';
|
|
13
|
+
type RerankerBlock = NonNullable<JsonConfig['reranker']>;
|
|
14
|
+
export interface RerankerPromptIO {
|
|
15
|
+
prompt(label: string, def: string): Promise<string>;
|
|
16
|
+
ask(question: string): Promise<string>;
|
|
17
|
+
writeEnvKey(name: string, value: string): void;
|
|
18
|
+
log(message: string): void;
|
|
19
|
+
}
|
|
20
|
+
export declare function rerankerChoices(embProvider: EmbeddingProvider): RerankerChoice[];
|
|
21
|
+
export declare function promptReranker(embProvider: EmbeddingProvider, embBaseURL: string, io: RerankerPromptIO): Promise<RerankerBlock>;
|
|
17
22
|
interface DbDockerOpts {
|
|
18
23
|
dbPort: string;
|
|
19
24
|
dbUser: string;
|
|
20
25
|
dbPass: string;
|
|
21
|
-
/** Bring up the local db container (skip when a remote db is already reachable). */
|
|
22
26
|
startDb: boolean;
|
|
23
27
|
logger?: LoggerService;
|
|
24
28
|
steps?: SetupStep[];
|
|
25
29
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Regenerate `CONFIG_DIR/docker-compose.yml` from the shipped template
|
|
28
|
-
* (carrying over an existing literal `INFINITY_MODEL_ID`, written atomically),
|
|
29
|
-
* copy the GPU override, and start the DB container when requested. Runs
|
|
30
|
-
* BEFORE the config file is written — its failure aborts setup unsaved,
|
|
31
|
-
* matching the existing "abort without saving" behavior of the DB step. Does
|
|
32
|
-
* NOT touch Infinity provisioning (moved after `writeConfigJson` — see D6a).
|
|
33
|
-
*/
|
|
34
30
|
export declare function prepareComposeAndStartDb(opts: DbDockerOpts): Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
* Infinity provisioning (D6a) — runs AFTER the config file is written, so no
|
|
37
|
-
* outcome here can leave the config unsaved. `savedConfig` is a fresh read of
|
|
38
|
-
* the just-written file (never the process's own startup config). Exit-status
|
|
39
|
-
* rules: 0 for served / timeout (warning) / external instructions / declined
|
|
40
|
-
* Docker; 1 for any other provisioning failure — the cause is printed and the
|
|
41
|
-
* remaining setup steps (DB schema init) still run.
|
|
42
|
-
*/
|
|
43
31
|
export declare function provisionInfinity(savedConfig: Config, startDocker: boolean, logger?: LoggerService, deps?: ProvisioningDeps, composePathOverride?: string): Promise<void>;
|
|
44
32
|
export declare function runSetup(logger?: LoggerService): Promise<void>;
|
|
45
33
|
export {};
|