@prom.codes/context-mcp 0.2.0 → 0.2.2
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/README.md +2 -2
- package/dist/bin.js +480 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @prom.codes/context-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
prom.codes Context — local-first codebase indexing & retrieval as an MCP server (stdio).
|
|
4
4
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ Prometheus Context Engine — local-first codebase indexing & retrieval as an MC
|
|
|
22
22
|
|
|
23
23
|
With a `PROMETHEUS_API_KEY` the server runs in managed mode: it indexes
|
|
24
24
|
your workspace into a local SQLite database (`~/.prometheus/<hash>.db`)
|
|
25
|
-
and embeds via the
|
|
25
|
+
and embeds via the prom.codes API. Your code never leaves your machine —
|
|
26
26
|
only embedding text transits to the API.
|
|
27
27
|
|
|
28
28
|
## Native modules
|
package/dist/bin.js
CHANGED
|
@@ -7160,6 +7160,377 @@ var HashEmbeddingProvider = class {
|
|
|
7160
7160
|
}
|
|
7161
7161
|
};
|
|
7162
7162
|
|
|
7163
|
+
// ../rerank-voyage/dist/index.js
|
|
7164
|
+
var DEFAULT_MODEL2 = "rerank-2.5";
|
|
7165
|
+
var DEFAULT_BASE_URL = "https://api.voyageai.com/v1";
|
|
7166
|
+
var DEFAULT_BATCH5 = 100;
|
|
7167
|
+
var DEFAULT_RETRIES5 = 6;
|
|
7168
|
+
var DEFAULT_BACKOFF5 = 2e3;
|
|
7169
|
+
var DEFAULT_RETRY_MAX2 = 6e4;
|
|
7170
|
+
function parseRetryAfterMs2(value, now = Date.now()) {
|
|
7171
|
+
if (value === null)
|
|
7172
|
+
return null;
|
|
7173
|
+
const trimmed = value.trim();
|
|
7174
|
+
if (trimmed === "")
|
|
7175
|
+
return null;
|
|
7176
|
+
if (/^[0-9]+(\.[0-9]+)?$/.test(trimmed)) {
|
|
7177
|
+
const secs = Number(trimmed);
|
|
7178
|
+
if (!Number.isFinite(secs) || secs < 0)
|
|
7179
|
+
return null;
|
|
7180
|
+
return Math.round(secs * 1e3);
|
|
7181
|
+
}
|
|
7182
|
+
if (!/[A-Za-z]/.test(trimmed))
|
|
7183
|
+
return null;
|
|
7184
|
+
const ts = Date.parse(trimmed);
|
|
7185
|
+
if (!Number.isFinite(ts))
|
|
7186
|
+
return null;
|
|
7187
|
+
const delta = ts - now;
|
|
7188
|
+
return delta > 0 ? delta : 0;
|
|
7189
|
+
}
|
|
7190
|
+
function sleep5(ms, signal) {
|
|
7191
|
+
return new Promise((resolve6, reject) => {
|
|
7192
|
+
if (signal?.aborted === true) {
|
|
7193
|
+
reject(new Error("aborted"));
|
|
7194
|
+
return;
|
|
7195
|
+
}
|
|
7196
|
+
const timer = setTimeout(() => {
|
|
7197
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7198
|
+
resolve6();
|
|
7199
|
+
}, ms);
|
|
7200
|
+
const onAbort = () => {
|
|
7201
|
+
clearTimeout(timer);
|
|
7202
|
+
reject(new Error("aborted"));
|
|
7203
|
+
};
|
|
7204
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7205
|
+
});
|
|
7206
|
+
}
|
|
7207
|
+
function nonRetryable5(message) {
|
|
7208
|
+
const err = new Error(message);
|
|
7209
|
+
err.nonRetryable = true;
|
|
7210
|
+
return err;
|
|
7211
|
+
}
|
|
7212
|
+
var VoyageRerankProvider = class {
|
|
7213
|
+
name;
|
|
7214
|
+
model;
|
|
7215
|
+
region;
|
|
7216
|
+
#baseUrl;
|
|
7217
|
+
#apiKey;
|
|
7218
|
+
#batchSize;
|
|
7219
|
+
#maxRetries;
|
|
7220
|
+
#retryBaseMs;
|
|
7221
|
+
#retryMaxMs;
|
|
7222
|
+
#fetch;
|
|
7223
|
+
constructor(opts) {
|
|
7224
|
+
if (typeof opts.apiKey !== "string" || opts.apiKey === "") {
|
|
7225
|
+
throw new Error("VoyageRerankProvider: apiKey is required");
|
|
7226
|
+
}
|
|
7227
|
+
if (opts.batchSize !== void 0 && (!Number.isInteger(opts.batchSize) || opts.batchSize <= 0 || opts.batchSize > 1e3)) {
|
|
7228
|
+
throw new Error(`VoyageRerankProvider: batchSize must be an integer in 1..1000, got ${opts.batchSize}`);
|
|
7229
|
+
}
|
|
7230
|
+
this.model = opts.model ?? DEFAULT_MODEL2;
|
|
7231
|
+
this.name = opts.name ?? `voyage:${this.model}`;
|
|
7232
|
+
this.region = opts.region ?? "us";
|
|
7233
|
+
this.#baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
7234
|
+
this.#apiKey = opts.apiKey;
|
|
7235
|
+
this.#batchSize = opts.batchSize ?? DEFAULT_BATCH5;
|
|
7236
|
+
this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES5;
|
|
7237
|
+
this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF5;
|
|
7238
|
+
this.#retryMaxMs = opts.retryMaxMs ?? DEFAULT_RETRY_MAX2;
|
|
7239
|
+
this.#fetch = opts.fetch ?? fetch;
|
|
7240
|
+
}
|
|
7241
|
+
async rerank(query, candidates, opts) {
|
|
7242
|
+
if (candidates.length === 0)
|
|
7243
|
+
return [];
|
|
7244
|
+
const all = new Array(candidates.length);
|
|
7245
|
+
let cursor = 0;
|
|
7246
|
+
for (let start = 0; start < candidates.length; start += this.#batchSize) {
|
|
7247
|
+
const slice = candidates.slice(start, start + this.#batchSize);
|
|
7248
|
+
const scored = await this.#rerankBatch(query, slice, opts?.signal);
|
|
7249
|
+
for (const hit of scored) {
|
|
7250
|
+
const globalIndex = start + hit.localIndex;
|
|
7251
|
+
const cand = candidates[globalIndex];
|
|
7252
|
+
all[cursor++] = { id: cand.id, index: globalIndex, score: hit.score };
|
|
7253
|
+
}
|
|
7254
|
+
}
|
|
7255
|
+
all.sort((a, b) => b.score - a.score);
|
|
7256
|
+
if (opts?.topK !== void 0 && opts.topK >= 0 && opts.topK < all.length) {
|
|
7257
|
+
return all.slice(0, opts.topK);
|
|
7258
|
+
}
|
|
7259
|
+
return all;
|
|
7260
|
+
}
|
|
7261
|
+
async #rerankBatch(query, batch, signal) {
|
|
7262
|
+
const body = {
|
|
7263
|
+
query,
|
|
7264
|
+
documents: batch.map((c) => c.text),
|
|
7265
|
+
model: this.model,
|
|
7266
|
+
return_documents: false,
|
|
7267
|
+
truncation: true
|
|
7268
|
+
};
|
|
7269
|
+
const init = {
|
|
7270
|
+
method: "POST",
|
|
7271
|
+
headers: {
|
|
7272
|
+
"content-type": "application/json",
|
|
7273
|
+
authorization: `Bearer ${this.#apiKey}`
|
|
7274
|
+
},
|
|
7275
|
+
body: JSON.stringify(body)
|
|
7276
|
+
};
|
|
7277
|
+
if (signal !== void 0)
|
|
7278
|
+
init.signal = signal;
|
|
7279
|
+
let attempt = 0;
|
|
7280
|
+
let lastError = null;
|
|
7281
|
+
while (attempt <= this.#maxRetries) {
|
|
7282
|
+
try {
|
|
7283
|
+
const res = await this.#fetch(`${this.#baseUrl}/rerank`, init);
|
|
7284
|
+
if (res.status === 429 || res.status >= 500 && res.status < 600) {
|
|
7285
|
+
lastError = new Error(`${this.name}: HTTP ${res.status}`);
|
|
7286
|
+
attempt += 1;
|
|
7287
|
+
if (attempt > this.#maxRetries)
|
|
7288
|
+
break;
|
|
7289
|
+
const backoff = this.#computeBackoff(attempt, res.headers.get("retry-after"));
|
|
7290
|
+
await sleep5(backoff, signal);
|
|
7291
|
+
continue;
|
|
7292
|
+
}
|
|
7293
|
+
if (!res.ok) {
|
|
7294
|
+
const text = await res.text().catch(() => "");
|
|
7295
|
+
throw nonRetryable5(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
|
|
7296
|
+
}
|
|
7297
|
+
const payload = await res.json();
|
|
7298
|
+
return this.#decode(payload, batch.length);
|
|
7299
|
+
} catch (err) {
|
|
7300
|
+
if (err?.name === "AbortError")
|
|
7301
|
+
throw err;
|
|
7302
|
+
if (err?.nonRetryable === true)
|
|
7303
|
+
throw err;
|
|
7304
|
+
if (attempt >= this.#maxRetries)
|
|
7305
|
+
throw err;
|
|
7306
|
+
lastError = err;
|
|
7307
|
+
attempt += 1;
|
|
7308
|
+
await sleep5(this.#computeBackoff(attempt, null), signal);
|
|
7309
|
+
}
|
|
7310
|
+
}
|
|
7311
|
+
throw lastError instanceof Error ? lastError : new Error(`${this.name}: exhausted ${this.#maxRetries} retries`);
|
|
7312
|
+
}
|
|
7313
|
+
#computeBackoff(attempt, retryAfterHeader) {
|
|
7314
|
+
const exp = this.#retryBaseMs * 2 ** Math.max(0, attempt - 1);
|
|
7315
|
+
const advised = parseRetryAfterMs2(retryAfterHeader);
|
|
7316
|
+
const lower = advised === null ? exp : Math.max(exp, advised);
|
|
7317
|
+
return Math.min(lower, this.#retryMaxMs);
|
|
7318
|
+
}
|
|
7319
|
+
#decode(payload, expected) {
|
|
7320
|
+
if (!Array.isArray(payload.data) || payload.data.length !== expected) {
|
|
7321
|
+
throw nonRetryable5(`${this.name}: expected ${expected} rerank rows, got ${payload.data?.length ?? 0}`);
|
|
7322
|
+
}
|
|
7323
|
+
return payload.data.map((row) => {
|
|
7324
|
+
if (!Number.isInteger(row.index) || row.index < 0 || row.index >= expected) {
|
|
7325
|
+
throw nonRetryable5(`${this.name}: invalid index ${row.index} in rerank response`);
|
|
7326
|
+
}
|
|
7327
|
+
if (typeof row.relevance_score !== "number" || !Number.isFinite(row.relevance_score)) {
|
|
7328
|
+
throw nonRetryable5(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
|
|
7329
|
+
}
|
|
7330
|
+
return { localIndex: row.index, score: row.relevance_score };
|
|
7331
|
+
});
|
|
7332
|
+
}
|
|
7333
|
+
};
|
|
7334
|
+
|
|
7335
|
+
// ../rerank-openai-compat/dist/index.js
|
|
7336
|
+
var DEFAULT_MODEL3 = "bge-reranker-base";
|
|
7337
|
+
var DEFAULT_BATCH6 = 100;
|
|
7338
|
+
var DEFAULT_RETRIES6 = 6;
|
|
7339
|
+
var DEFAULT_BACKOFF6 = 2e3;
|
|
7340
|
+
var DEFAULT_RETRY_MAX3 = 6e4;
|
|
7341
|
+
var DEFAULT_TIMEOUT = 18e4;
|
|
7342
|
+
function parseRetryAfterMs3(value, now = Date.now()) {
|
|
7343
|
+
if (value === null)
|
|
7344
|
+
return null;
|
|
7345
|
+
const trimmed = value.trim();
|
|
7346
|
+
if (trimmed === "")
|
|
7347
|
+
return null;
|
|
7348
|
+
if (/^[0-9]+(\.[0-9]+)?$/.test(trimmed)) {
|
|
7349
|
+
const secs = Number(trimmed);
|
|
7350
|
+
if (!Number.isFinite(secs) || secs < 0)
|
|
7351
|
+
return null;
|
|
7352
|
+
return Math.round(secs * 1e3);
|
|
7353
|
+
}
|
|
7354
|
+
if (!/[A-Za-z]/.test(trimmed))
|
|
7355
|
+
return null;
|
|
7356
|
+
const ts = Date.parse(trimmed);
|
|
7357
|
+
if (!Number.isFinite(ts))
|
|
7358
|
+
return null;
|
|
7359
|
+
const delta = ts - now;
|
|
7360
|
+
return delta > 0 ? delta : 0;
|
|
7361
|
+
}
|
|
7362
|
+
function sleep6(ms, signal) {
|
|
7363
|
+
return new Promise((resolve6, reject) => {
|
|
7364
|
+
if (signal?.aborted === true) {
|
|
7365
|
+
reject(new Error("aborted"));
|
|
7366
|
+
return;
|
|
7367
|
+
}
|
|
7368
|
+
const timer = setTimeout(() => {
|
|
7369
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7370
|
+
resolve6();
|
|
7371
|
+
}, ms);
|
|
7372
|
+
const onAbort = () => {
|
|
7373
|
+
clearTimeout(timer);
|
|
7374
|
+
reject(new Error("aborted"));
|
|
7375
|
+
};
|
|
7376
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7377
|
+
});
|
|
7378
|
+
}
|
|
7379
|
+
function nonRetryable6(message) {
|
|
7380
|
+
const err = new Error(message);
|
|
7381
|
+
err.nonRetryable = true;
|
|
7382
|
+
return err;
|
|
7383
|
+
}
|
|
7384
|
+
var OpenAICompatRerankProvider = class {
|
|
7385
|
+
name;
|
|
7386
|
+
model;
|
|
7387
|
+
region;
|
|
7388
|
+
#baseUrl;
|
|
7389
|
+
#apiKey;
|
|
7390
|
+
#batchSize;
|
|
7391
|
+
#maxRetries;
|
|
7392
|
+
#retryBaseMs;
|
|
7393
|
+
#retryMaxMs;
|
|
7394
|
+
#timeoutMs;
|
|
7395
|
+
#fetch;
|
|
7396
|
+
constructor(opts) {
|
|
7397
|
+
if (typeof opts.baseUrl !== "string" || opts.baseUrl === "") {
|
|
7398
|
+
throw new Error("OpenAICompatRerankProvider: baseUrl is required");
|
|
7399
|
+
}
|
|
7400
|
+
if (opts.batchSize !== void 0 && (!Number.isInteger(opts.batchSize) || opts.batchSize <= 0 || opts.batchSize > 1e3)) {
|
|
7401
|
+
throw new Error(`OpenAICompatRerankProvider: batchSize must be an integer in 1..1000, got ${opts.batchSize}`);
|
|
7402
|
+
}
|
|
7403
|
+
if (opts.timeoutMs !== void 0 && (!Number.isInteger(opts.timeoutMs) || opts.timeoutMs < 0)) {
|
|
7404
|
+
throw new Error(`OpenAICompatRerankProvider: timeoutMs must be a non-negative integer (0 disables), got ${opts.timeoutMs}`);
|
|
7405
|
+
}
|
|
7406
|
+
this.model = opts.model ?? DEFAULT_MODEL3;
|
|
7407
|
+
this.name = opts.name ?? `openai-compat:${this.model}`;
|
|
7408
|
+
this.region = opts.region ?? "self-hosted";
|
|
7409
|
+
this.#baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
7410
|
+
this.#apiKey = opts.apiKey === void 0 || opts.apiKey === "" ? void 0 : opts.apiKey;
|
|
7411
|
+
this.#batchSize = opts.batchSize ?? DEFAULT_BATCH6;
|
|
7412
|
+
this.#maxRetries = opts.maxRetries ?? DEFAULT_RETRIES6;
|
|
7413
|
+
this.#retryBaseMs = opts.retryBaseMs ?? DEFAULT_BACKOFF6;
|
|
7414
|
+
this.#retryMaxMs = opts.retryMaxMs ?? DEFAULT_RETRY_MAX3;
|
|
7415
|
+
this.#timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT;
|
|
7416
|
+
this.#fetch = opts.fetch ?? fetch;
|
|
7417
|
+
}
|
|
7418
|
+
async rerank(query, candidates, opts) {
|
|
7419
|
+
if (candidates.length === 0)
|
|
7420
|
+
return [];
|
|
7421
|
+
const all = new Array(candidates.length);
|
|
7422
|
+
let cursor = 0;
|
|
7423
|
+
for (let start = 0; start < candidates.length; start += this.#batchSize) {
|
|
7424
|
+
const slice = candidates.slice(start, start + this.#batchSize);
|
|
7425
|
+
const scored = await this.#rerankBatch(query, slice, opts?.signal);
|
|
7426
|
+
for (const hit of scored) {
|
|
7427
|
+
const globalIndex = start + hit.localIndex;
|
|
7428
|
+
const cand = candidates[globalIndex];
|
|
7429
|
+
all[cursor++] = { id: cand.id, index: globalIndex, score: hit.score };
|
|
7430
|
+
}
|
|
7431
|
+
}
|
|
7432
|
+
all.sort((a, b) => b.score - a.score);
|
|
7433
|
+
if (opts?.topK !== void 0 && opts.topK >= 0 && opts.topK < all.length) {
|
|
7434
|
+
return all.slice(0, opts.topK);
|
|
7435
|
+
}
|
|
7436
|
+
return all;
|
|
7437
|
+
}
|
|
7438
|
+
async #rerankBatch(query, batch, signal) {
|
|
7439
|
+
const body = {
|
|
7440
|
+
query,
|
|
7441
|
+
documents: batch.map((c) => c.text),
|
|
7442
|
+
model: this.model,
|
|
7443
|
+
return_documents: false
|
|
7444
|
+
};
|
|
7445
|
+
const headers = { "content-type": "application/json" };
|
|
7446
|
+
if (this.#apiKey !== void 0)
|
|
7447
|
+
headers.authorization = `Bearer ${this.#apiKey}`;
|
|
7448
|
+
const payloadJson = JSON.stringify(body);
|
|
7449
|
+
let attempt = 0;
|
|
7450
|
+
let lastError = null;
|
|
7451
|
+
while (attempt <= this.#maxRetries) {
|
|
7452
|
+
const controller = new AbortController();
|
|
7453
|
+
let timedOut = false;
|
|
7454
|
+
let timer;
|
|
7455
|
+
if (this.#timeoutMs > 0) {
|
|
7456
|
+
timer = setTimeout(() => {
|
|
7457
|
+
timedOut = true;
|
|
7458
|
+
controller.abort();
|
|
7459
|
+
}, this.#timeoutMs);
|
|
7460
|
+
}
|
|
7461
|
+
const onParentAbort = () => controller.abort();
|
|
7462
|
+
if (signal !== void 0) {
|
|
7463
|
+
if (signal.aborted)
|
|
7464
|
+
controller.abort();
|
|
7465
|
+
else
|
|
7466
|
+
signal.addEventListener("abort", onParentAbort, { once: true });
|
|
7467
|
+
}
|
|
7468
|
+
const init = {
|
|
7469
|
+
method: "POST",
|
|
7470
|
+
headers,
|
|
7471
|
+
body: payloadJson,
|
|
7472
|
+
signal: controller.signal
|
|
7473
|
+
};
|
|
7474
|
+
try {
|
|
7475
|
+
const res = await this.#fetch(`${this.#baseUrl}/rerank`, init);
|
|
7476
|
+
if (res.status === 429 || res.status >= 500 && res.status < 600) {
|
|
7477
|
+
lastError = new Error(`${this.name}: HTTP ${res.status}`);
|
|
7478
|
+
attempt += 1;
|
|
7479
|
+
if (attempt > this.#maxRetries)
|
|
7480
|
+
break;
|
|
7481
|
+
const backoff = this.#computeBackoff(attempt, res.headers.get("retry-after"));
|
|
7482
|
+
await sleep6(backoff, signal);
|
|
7483
|
+
continue;
|
|
7484
|
+
}
|
|
7485
|
+
if (!res.ok) {
|
|
7486
|
+
const text = await res.text().catch(() => "");
|
|
7487
|
+
throw nonRetryable6(`${this.name}: HTTP ${res.status} ${res.statusText}${text === "" ? "" : ` \u2014 ${text}`}`);
|
|
7488
|
+
}
|
|
7489
|
+
const payload = await res.json();
|
|
7490
|
+
return this.#decode(payload, batch.length);
|
|
7491
|
+
} catch (err) {
|
|
7492
|
+
const isAbort = err?.name === "AbortError";
|
|
7493
|
+
if (isAbort && !timedOut)
|
|
7494
|
+
throw err;
|
|
7495
|
+
if (!isAbort && err?.nonRetryable === true)
|
|
7496
|
+
throw err;
|
|
7497
|
+
const normalized = timedOut ? new Error(`${this.name}: request timed out after ${this.#timeoutMs}ms`) : err;
|
|
7498
|
+
if (attempt >= this.#maxRetries)
|
|
7499
|
+
throw normalized;
|
|
7500
|
+
lastError = normalized;
|
|
7501
|
+
attempt += 1;
|
|
7502
|
+
await sleep6(this.#computeBackoff(attempt, null), signal);
|
|
7503
|
+
} finally {
|
|
7504
|
+
if (timer !== void 0)
|
|
7505
|
+
clearTimeout(timer);
|
|
7506
|
+
if (signal !== void 0)
|
|
7507
|
+
signal.removeEventListener("abort", onParentAbort);
|
|
7508
|
+
}
|
|
7509
|
+
}
|
|
7510
|
+
throw lastError instanceof Error ? lastError : new Error(`${this.name}: exhausted ${this.#maxRetries} retries`);
|
|
7511
|
+
}
|
|
7512
|
+
#computeBackoff(attempt, retryAfterHeader) {
|
|
7513
|
+
const exp = this.#retryBaseMs * 2 ** Math.max(0, attempt - 1);
|
|
7514
|
+
const advised = parseRetryAfterMs3(retryAfterHeader);
|
|
7515
|
+
const lower = advised === null ? exp : Math.max(exp, advised);
|
|
7516
|
+
return Math.min(lower, this.#retryMaxMs);
|
|
7517
|
+
}
|
|
7518
|
+
#decode(payload, expected) {
|
|
7519
|
+
if (!Array.isArray(payload.results) || payload.results.length !== expected) {
|
|
7520
|
+
throw nonRetryable6(`${this.name}: expected ${expected} rerank rows, got ${payload.results?.length ?? 0}`);
|
|
7521
|
+
}
|
|
7522
|
+
return payload.results.map((row) => {
|
|
7523
|
+
if (!Number.isInteger(row.index) || row.index < 0 || row.index >= expected) {
|
|
7524
|
+
throw nonRetryable6(`${this.name}: invalid index ${row.index} in rerank response`);
|
|
7525
|
+
}
|
|
7526
|
+
if (typeof row.relevance_score !== "number" || !Number.isFinite(row.relevance_score)) {
|
|
7527
|
+
throw nonRetryable6(`${this.name}: invalid relevance_score ${row.relevance_score} at index ${row.index}`);
|
|
7528
|
+
}
|
|
7529
|
+
return { localIndex: row.index, score: row.relevance_score };
|
|
7530
|
+
});
|
|
7531
|
+
}
|
|
7532
|
+
};
|
|
7533
|
+
|
|
7163
7534
|
// dist/composition.js
|
|
7164
7535
|
var RegionModeViolation = class extends Error {
|
|
7165
7536
|
mode;
|
|
@@ -7395,6 +7766,56 @@ function discoverEmbeddingProvider(env, fetchImpl) {
|
|
|
7395
7766
|
validateRegionMode(regionMode, picked.id, picked.provider.region);
|
|
7396
7767
|
return { ...picked, regionMode };
|
|
7397
7768
|
}
|
|
7769
|
+
function discoverRerankProvider(env, fetchImpl) {
|
|
7770
|
+
const regionMode = parseRegionMode(env.PROMETHEUS_REGION_MODE);
|
|
7771
|
+
const forced = env.PROMETHEUS_RERANK_PROVIDER?.toLowerCase() ?? "none";
|
|
7772
|
+
if (forced === "" || forced === "none")
|
|
7773
|
+
return { id: "none", provider: null };
|
|
7774
|
+
if (forced === "voyage") {
|
|
7775
|
+
const apiKey = env.VOYAGE_API_KEY;
|
|
7776
|
+
if (apiKey === void 0 || apiKey === "") {
|
|
7777
|
+
throw new NoProviderError(`rerank provider "voyage" requested but VOYAGE_API_KEY is missing`);
|
|
7778
|
+
}
|
|
7779
|
+
const model = env.VOYAGE_RERANK_MODEL ?? "rerank-2.5";
|
|
7780
|
+
const region = "us";
|
|
7781
|
+
if (regionMode !== "default") {
|
|
7782
|
+
throw new RegionModeViolation(regionMode, "voyage", region, regionMode === "eu-strict" ? ["nomic", "bge-m3", "mistral", "hash"] : ["nomic", "bge-m3", "hash"]);
|
|
7783
|
+
}
|
|
7784
|
+
const provider = new VoyageRerankProvider({
|
|
7785
|
+
name: "voyage-rerank",
|
|
7786
|
+
apiKey,
|
|
7787
|
+
model,
|
|
7788
|
+
region,
|
|
7789
|
+
baseUrl: env.VOYAGE_BASE_URL ?? "https://api.voyageai.com/v1",
|
|
7790
|
+
maxRetries: intEnv(env, "VOYAGE_RERANK_MAX_RETRIES", 6),
|
|
7791
|
+
retryBaseMs: intEnv(env, "VOYAGE_RERANK_RETRY_BASE_MS", 2e3),
|
|
7792
|
+
batchSize: intEnv(env, "VOYAGE_RERANK_BATCH", 100),
|
|
7793
|
+
...fetchOpt(fetchImpl)
|
|
7794
|
+
});
|
|
7795
|
+
return { id: "voyage", provider };
|
|
7796
|
+
}
|
|
7797
|
+
if (forced === "bge" || forced === "generic") {
|
|
7798
|
+
const baseUrl = env.PROMETHEUS_RERANK_ENDPOINT;
|
|
7799
|
+
if (baseUrl === void 0 || baseUrl === "") {
|
|
7800
|
+
throw new NoProviderError(`rerank provider "${forced}" requested but PROMETHEUS_RERANK_ENDPOINT is missing`);
|
|
7801
|
+
}
|
|
7802
|
+
const model = env.PROMETHEUS_RERANK_MODEL ?? "bge-reranker-base";
|
|
7803
|
+
const provider = new OpenAICompatRerankProvider({
|
|
7804
|
+
name: env.PROMETHEUS_RERANK_NAME ?? `bge-rerank:${model}`,
|
|
7805
|
+
model,
|
|
7806
|
+
region: "self-hosted",
|
|
7807
|
+
baseUrl,
|
|
7808
|
+
maxRetries: intEnv(env, "PROMETHEUS_RERANK_MAX_RETRIES", 6),
|
|
7809
|
+
retryBaseMs: intEnv(env, "PROMETHEUS_RERANK_RETRY_BASE_MS", 2e3),
|
|
7810
|
+
batchSize: intEnv(env, "PROMETHEUS_RERANK_BATCH", 100),
|
|
7811
|
+
timeoutMs: intEnv(env, "PROMETHEUS_RERANK_TIMEOUT_MS", 18e4),
|
|
7812
|
+
...apiKeyOpt(env.PROMETHEUS_RERANK_API_KEY),
|
|
7813
|
+
...fetchOpt(fetchImpl)
|
|
7814
|
+
});
|
|
7815
|
+
return { id: "bge", provider };
|
|
7816
|
+
}
|
|
7817
|
+
throw new NoProviderError(`unknown PROMETHEUS_RERANK_PROVIDER="${forced}" (expected "none", "voyage", or "bge")`);
|
|
7818
|
+
}
|
|
7398
7819
|
function getStableDbPath(workspaceRoot) {
|
|
7399
7820
|
const abs = resolve4(workspaceRoot);
|
|
7400
7821
|
const hash = createHash3("sha256").update(abs).digest("hex").slice(0, 16);
|
|
@@ -7478,6 +7899,8 @@ async function composeFromEnv(opts) {
|
|
|
7478
7899
|
const { id: storageBackend, adapter: storage, dbPath } = discoverStorageBackend(env, regionMode, storageOptions);
|
|
7479
7900
|
await storage.init();
|
|
7480
7901
|
const retriever = new HybridRetriever({ storage, embedder });
|
|
7902
|
+
const { id: rerankId, provider: reranker } = discoverRerankProvider(env, opts.fetch);
|
|
7903
|
+
const rerankTopN = intEnv(env, "PROMETHEUS_RERANK_TOP_N", 100);
|
|
7481
7904
|
const managed = apiKeyPresent && storageBackend === "sqlite";
|
|
7482
7905
|
let closed = false;
|
|
7483
7906
|
return {
|
|
@@ -7492,6 +7915,9 @@ async function composeFromEnv(opts) {
|
|
|
7492
7915
|
storageBackend,
|
|
7493
7916
|
managed,
|
|
7494
7917
|
dbPath,
|
|
7918
|
+
reranker,
|
|
7919
|
+
rerankId,
|
|
7920
|
+
rerankTopN,
|
|
7495
7921
|
async close() {
|
|
7496
7922
|
if (closed)
|
|
7497
7923
|
return;
|
|
@@ -7645,6 +8071,7 @@ var MAX_K = 50;
|
|
|
7645
8071
|
var DEFAULT_K2 = 10;
|
|
7646
8072
|
var MAX_FILE_BYTES = 256 * 1024;
|
|
7647
8073
|
var MAX_SNIPPET_BYTES = 1500;
|
|
8074
|
+
var RERANK_DOC_BYTES = 4096;
|
|
7648
8075
|
function symbolToJson(s) {
|
|
7649
8076
|
return {
|
|
7650
8077
|
name: s.name,
|
|
@@ -7671,7 +8098,7 @@ function textResult(payload) {
|
|
|
7671
8098
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
7672
8099
|
};
|
|
7673
8100
|
}
|
|
7674
|
-
async function snippetForSymbol(workspaceRoot, symbol, cache) {
|
|
8101
|
+
async function snippetForSymbol(workspaceRoot, symbol, cache, capBytes = MAX_SNIPPET_BYTES) {
|
|
7675
8102
|
try {
|
|
7676
8103
|
const relPath = symbol.filePath;
|
|
7677
8104
|
if (isSensitivePath(relPath))
|
|
@@ -7689,13 +8116,46 @@ async function snippetForSymbol(workspaceRoot, symbol, cache) {
|
|
|
7689
8116
|
if (!(endByte > startByte))
|
|
7690
8117
|
return null;
|
|
7691
8118
|
const full = buf.subarray(startByte, endByte);
|
|
7692
|
-
const truncated = full.byteLength >
|
|
7693
|
-
const view = truncated ? full.subarray(0,
|
|
8119
|
+
const truncated = full.byteLength > capBytes;
|
|
8120
|
+
const view = truncated ? full.subarray(0, capBytes) : full;
|
|
7694
8121
|
return { text: view.toString("utf8"), truncated };
|
|
7695
8122
|
} catch {
|
|
7696
8123
|
return null;
|
|
7697
8124
|
}
|
|
7698
8125
|
}
|
|
8126
|
+
async function rerankHead(reranker, query, head, workspaceRoot, cache) {
|
|
8127
|
+
const candidates = [];
|
|
8128
|
+
for (let i = 0; i < head.length; i++) {
|
|
8129
|
+
const snip = await snippetForSymbol(workspaceRoot, head[i].symbol, cache, RERANK_DOC_BYTES);
|
|
8130
|
+
if (snip === null)
|
|
8131
|
+
continue;
|
|
8132
|
+
candidates.push({ id: String(i), text: snip.text });
|
|
8133
|
+
}
|
|
8134
|
+
if (candidates.length === 0)
|
|
8135
|
+
return null;
|
|
8136
|
+
let hits;
|
|
8137
|
+
try {
|
|
8138
|
+
hits = await reranker.rerank(query, candidates, { topK: candidates.length });
|
|
8139
|
+
} catch {
|
|
8140
|
+
return null;
|
|
8141
|
+
}
|
|
8142
|
+
const out = [];
|
|
8143
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8144
|
+
for (const hit of hits) {
|
|
8145
|
+
const i = Number(hit.id);
|
|
8146
|
+
if (!Number.isFinite(i) || seen.has(i))
|
|
8147
|
+
continue;
|
|
8148
|
+
seen.add(i);
|
|
8149
|
+
const r = head[i];
|
|
8150
|
+
if (r !== void 0)
|
|
8151
|
+
out.push(r);
|
|
8152
|
+
}
|
|
8153
|
+
for (let i = 0; i < head.length; i++) {
|
|
8154
|
+
if (!seen.has(i))
|
|
8155
|
+
out.push(head[i]);
|
|
8156
|
+
}
|
|
8157
|
+
return out;
|
|
8158
|
+
}
|
|
7699
8159
|
function resolveInWorkspace(workspaceRoot, input) {
|
|
7700
8160
|
if (input === "")
|
|
7701
8161
|
throw new Error("path must not be empty.");
|
|
@@ -7768,7 +8228,7 @@ var changedSinceInput = {
|
|
|
7768
8228
|
};
|
|
7769
8229
|
var emptyInput = {};
|
|
7770
8230
|
function registerTools(server, deps) {
|
|
7771
|
-
const { storage, retriever, workspaceRoot, workspaceId, workspaceName, regionMode, providerId, storageBackend } = deps;
|
|
8231
|
+
const { storage, retriever, workspaceRoot, workspaceId, workspaceName, regionMode, providerId, storageBackend, reranker, rerankTopN } = deps;
|
|
7772
8232
|
server.registerTool("search_code", {
|
|
7773
8233
|
title: "Hybrid code search",
|
|
7774
8234
|
description: "PRIMARY code search for this workspace \u2014 call this FIRST to find where something is defined, used or implemented, before reading files or guessing paths. Hybrid retrieval (lexical FTS + vector + symbol graph, RRF-fused) over natural-language or symbol queries. Returns the top-k symbols with provenance AND an inline source snippet per hit, so the result is usually actionable without a follow-up get_file. Set `includeSnippet: false` to omit the inline code (symbols only).",
|
|
@@ -7776,8 +8236,21 @@ function registerTools(server, deps) {
|
|
|
7776
8236
|
}, async (args) => {
|
|
7777
8237
|
const k = clampK(args.k);
|
|
7778
8238
|
const includeSnippet = args.includeSnippet ?? true;
|
|
7779
|
-
const results = await retriever.search(args.query, { k });
|
|
7780
8239
|
const cache = /* @__PURE__ */ new Map();
|
|
8240
|
+
const poolK = reranker ? Math.max(k, rerankTopN ?? 100) : k;
|
|
8241
|
+
const pool = await retriever.search(args.query, { k: poolK });
|
|
8242
|
+
let ordered = pool;
|
|
8243
|
+
let reranked = false;
|
|
8244
|
+
if (reranker && pool.length > 0) {
|
|
8245
|
+
const head = pool.slice(0, rerankTopN ?? 100);
|
|
8246
|
+
const tail = pool.slice(rerankTopN);
|
|
8247
|
+
const reorderedHead = await rerankHead(reranker, args.query, head, workspaceRoot, cache);
|
|
8248
|
+
if (reorderedHead !== null) {
|
|
8249
|
+
ordered = reorderedHead.concat(tail);
|
|
8250
|
+
reranked = true;
|
|
8251
|
+
}
|
|
8252
|
+
}
|
|
8253
|
+
const results = ordered.slice(0, k);
|
|
7781
8254
|
const mapped = await Promise.all(results.map(async (r) => {
|
|
7782
8255
|
const base = {
|
|
7783
8256
|
score: r.score,
|
|
@@ -7789,7 +8262,7 @@ function registerTools(server, deps) {
|
|
|
7789
8262
|
const snip = await snippetForSymbol(workspaceRoot, r.symbol, cache);
|
|
7790
8263
|
return snip === null ? base : { ...base, snippet: snip.text, snippetTruncated: snip.truncated };
|
|
7791
8264
|
}));
|
|
7792
|
-
return textResult({ query: args.query, k, results: mapped });
|
|
8265
|
+
return textResult({ query: args.query, k, reranked, results: mapped });
|
|
7793
8266
|
});
|
|
7794
8267
|
server.registerTool("get_symbol", {
|
|
7795
8268
|
title: "Exact symbol lookup",
|
|
@@ -7951,7 +8424,7 @@ function registerTools(server, deps) {
|
|
|
7951
8424
|
var SERVER_IDENTITY = {
|
|
7952
8425
|
name: "prometheus-context-mcp",
|
|
7953
8426
|
version: PROMETHEUS_VERSION,
|
|
7954
|
-
title: "
|
|
8427
|
+
title: "prom.codes Context"
|
|
7955
8428
|
};
|
|
7956
8429
|
function createServer(deps, options = {}) {
|
|
7957
8430
|
const identity = { ...SERVER_IDENTITY, ...options.identity ?? {} };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prom.codes/context-mcp",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "prom.codes Context — local-first codebase indexing & retrieval as an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"prometheus-context-mcp": "dist/bin.js"
|