sift-light 1.1.0-1 → 1.1.0
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 +7 -0
- package/README.md +29 -1
- package/README.zh-CN.md +29 -1
- package/package.json +1 -1
- package/plugins/sift-light/.claude-plugin/plugin.json +1 -1
- package/plugins/sift-light/.codex-plugin/plugin.json +1 -1
- package/plugins/sift-light/.mcp.json +1 -1
- package/plugins/sift-light/kimi.plugin.json +2 -2
- package/plugins/sift-light/omp-extension.mjs +587 -153
- package/plugins/sift-light/package.json +1 -1
- package/src/config-reader.ts +147 -44
- package/src/config.ts +1 -0
- package/src/mcp-semantic-judge.ts +3 -0
- package/src/mcp-server.mjs +659 -222
- package/src/mcp-server.ts +8 -1
- package/src/mcp.ts +4 -1
- package/src/media-config.ts +21 -0
- package/src/media-search.ts +55 -19
- package/src/native-text-config.ts +1 -1
- package/src/remote-media-client.ts +363 -0
- package/src/semantic-judge.ts +217 -106
- package/src/service.ts +5 -2
|
@@ -176,6 +176,42 @@ function isRawSiftLightConfig(value) {
|
|
|
176
176
|
function isRawSemanticJudgeConfig(value) {
|
|
177
177
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178
178
|
}
|
|
179
|
+
function isRawMediaConfig(value) {
|
|
180
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
181
|
+
}
|
|
182
|
+
function parseEndpoint(value, path, field) {
|
|
183
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 2048) {
|
|
184
|
+
throw new Error(`Invalid sift-light config at ${path}: ${field} must be a nonempty URL`);
|
|
185
|
+
}
|
|
186
|
+
let parsedEndpoint;
|
|
187
|
+
try {
|
|
188
|
+
parsedEndpoint = new URL(value);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
throw new Error(`Invalid sift-light config at ${path}: ${field} must be a URL`, {
|
|
191
|
+
cause: error
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
195
|
+
const secureEndpoint = parsedEndpoint.protocol === "https:" || parsedEndpoint.protocol === "http:" && loopbackHosts.has(parsedEndpoint.hostname);
|
|
196
|
+
if (!secureEndpoint || parsedEndpoint.username || parsedEndpoint.password) {
|
|
197
|
+
throw new Error(`Invalid sift-light config at ${path}: ${field} must use HTTPS, except that HTTP is allowed for localhost loopback development; URL credentials are not allowed`);
|
|
198
|
+
}
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
function parseApiKeyEnv(value, path, field) {
|
|
202
|
+
if (value === undefined)
|
|
203
|
+
return;
|
|
204
|
+
if (typeof value !== "string" || !/^[A-Z][A-Z0-9_]{0,127}$/u.test(value)) {
|
|
205
|
+
throw new Error(`Invalid sift-light config at ${path}: ${field} must be an uppercase environment variable name`);
|
|
206
|
+
}
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
function parseBoundedModel(value, path, field) {
|
|
210
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 128 || /[\r\n\0]/u.test(value)) {
|
|
211
|
+
throw new Error(`Invalid sift-light config at ${path}: ${field} must be bounded single-line text`);
|
|
212
|
+
}
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
179
215
|
function boundedInteger(value, fallback, minimum, maximum, field) {
|
|
180
216
|
const candidate = value ?? fallback;
|
|
181
217
|
if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < minimum || candidate > maximum) {
|
|
@@ -207,45 +243,61 @@ function parseSemanticJudge(value, path) {
|
|
|
207
243
|
throw new Error(`Invalid sift-light config at ${path}: semanticJudge.enabled must be a boolean`);
|
|
208
244
|
}
|
|
209
245
|
const provider = value.provider ?? DEFAULT_SEMANTIC_JUDGE_CONFIG.provider;
|
|
210
|
-
if (provider !== "jev") {
|
|
211
|
-
throw new Error(`Invalid sift-light config at ${path}: semanticJudge.provider must be "jev"`);
|
|
212
|
-
}
|
|
213
|
-
const endpoint = value.endpoint ?? DEFAULT_SEMANTIC_JUDGE_CONFIG.endpoint;
|
|
214
|
-
if (typeof endpoint !== "string" || endpoint.length === 0 || endpoint.length > 2048) {
|
|
215
|
-
throw new Error(`Invalid sift-light config at ${path}: semanticJudge.endpoint must be a nonempty URL`);
|
|
216
|
-
}
|
|
217
|
-
let parsedEndpoint;
|
|
218
|
-
try {
|
|
219
|
-
parsedEndpoint = new URL(endpoint);
|
|
220
|
-
} catch (error) {
|
|
221
|
-
throw new Error(`Invalid sift-light config at ${path}: semanticJudge.endpoint must be a URL`, {
|
|
222
|
-
cause: error
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
226
|
-
const secureEndpoint = parsedEndpoint.protocol === "https:" || parsedEndpoint.protocol === "http:" && loopbackHosts.has(parsedEndpoint.hostname);
|
|
227
|
-
if (!secureEndpoint || parsedEndpoint.username || parsedEndpoint.password) {
|
|
228
|
-
throw new Error(`Invalid sift-light config at ${path}: semanticJudge.endpoint must use HTTPS, except that HTTP is allowed for localhost loopback development; URL credentials are not allowed`);
|
|
229
|
-
}
|
|
230
|
-
const apiKeyEnv = value.apiKeyEnv ?? DEFAULT_SEMANTIC_JUDGE_CONFIG.apiKeyEnv;
|
|
231
|
-
if (typeof apiKeyEnv !== "string" || !/^[A-Z][A-Z0-9_]{0,127}$/u.test(apiKeyEnv)) {
|
|
232
|
-
throw new Error(`Invalid sift-light config at ${path}: semanticJudge.apiKeyEnv must be an uppercase environment variable name`);
|
|
233
|
-
}
|
|
234
|
-
const model = value.model ?? DEFAULT_SEMANTIC_JUDGE_CONFIG.model;
|
|
235
|
-
if (typeof model !== "string" || model.length === 0 || model.length > 128 || /[\r\n\0]/u.test(model)) {
|
|
236
|
-
throw new Error(`Invalid sift-light config at ${path}: semanticJudge.model must be bounded single-line text`);
|
|
246
|
+
if (provider !== "jev" && provider !== "http-json") {
|
|
247
|
+
throw new Error(`Invalid sift-light config at ${path}: semanticJudge.provider must be "jev" or "http-json"`);
|
|
237
248
|
}
|
|
249
|
+
const endpoint = parseEndpoint(value.endpoint ?? (provider === "jev" ? DEFAULT_SEMANTIC_JUDGE_CONFIG.endpoint : undefined), path, "semanticJudge.endpoint");
|
|
250
|
+
const apiKeyEnv = parseApiKeyEnv(value.apiKeyEnv, path, "semanticJudge.apiKeyEnv") ?? (provider === "jev" ? DEFAULT_SEMANTIC_JUDGE_CONFIG.apiKeyEnv : undefined);
|
|
251
|
+
const model = parseBoundedModel(value.model ?? (provider === "jev" ? DEFAULT_SEMANTIC_JUDGE_CONFIG.model : undefined), path, "semanticJudge.model");
|
|
238
252
|
return {
|
|
239
253
|
enabled,
|
|
240
254
|
provider,
|
|
241
255
|
endpoint,
|
|
242
|
-
apiKeyEnv,
|
|
256
|
+
...apiKeyEnv === undefined ? {} : { apiKeyEnv },
|
|
243
257
|
model,
|
|
244
258
|
timeoutMs: boundedInteger(value.timeoutMs, DEFAULT_SEMANTIC_JUDGE_CONFIG.timeoutMs, 1000, 1200000, `config at ${path}: semanticJudge.timeoutMs`),
|
|
245
259
|
maxCandidates: boundedInteger(value.maxCandidates, DEFAULT_SEMANTIC_JUDGE_CONFIG.maxCandidates, 1, 20, `config at ${path}: semanticJudge.maxCandidates`),
|
|
246
260
|
maxRetries: boundedInteger(value.maxRetries, DEFAULT_SEMANTIC_JUDGE_CONFIG.maxRetries, 0, 5, `config at ${path}: semanticJudge.maxRetries`)
|
|
247
261
|
};
|
|
248
262
|
}
|
|
263
|
+
function parseMedia(value, path) {
|
|
264
|
+
if (value === undefined)
|
|
265
|
+
return;
|
|
266
|
+
if (!isRawMediaConfig(value)) {
|
|
267
|
+
throw new Error(`Invalid sift-light config at ${path}: media must be an object`);
|
|
268
|
+
}
|
|
269
|
+
const unknown = Object.keys(value).filter((key) => ![
|
|
270
|
+
"provider",
|
|
271
|
+
"endpoint",
|
|
272
|
+
"apiKeyEnv",
|
|
273
|
+
"model",
|
|
274
|
+
"timeoutMs",
|
|
275
|
+
"maxRetries",
|
|
276
|
+
"maxRequestBytes",
|
|
277
|
+
"renderer"
|
|
278
|
+
].includes(key));
|
|
279
|
+
if (unknown.length > 0) {
|
|
280
|
+
throw new Error(`Invalid sift-light config at ${path}: unsupported media fields; accepted fields are provider, endpoint, apiKeyEnv, model, timeoutMs, maxRetries, maxRequestBytes and renderer`);
|
|
281
|
+
}
|
|
282
|
+
const provider = value.provider ?? "http-json";
|
|
283
|
+
if (provider !== "http-json") {
|
|
284
|
+
throw new Error(`Invalid sift-light config at ${path}: media.provider must be "http-json"`);
|
|
285
|
+
}
|
|
286
|
+
const endpoint = parseEndpoint(value.endpoint, path, "media.endpoint");
|
|
287
|
+
const apiKeyEnv = parseApiKeyEnv(value.apiKeyEnv, path, "media.apiKeyEnv");
|
|
288
|
+
const model = parseBoundedModel(value.model, path, "media.model");
|
|
289
|
+
const renderer = parseNativeTextConfig(value.renderer, "media.renderer");
|
|
290
|
+
return {
|
|
291
|
+
provider,
|
|
292
|
+
endpoint,
|
|
293
|
+
...apiKeyEnv === undefined ? {} : { apiKeyEnv },
|
|
294
|
+
model,
|
|
295
|
+
timeoutMs: boundedInteger(value.timeoutMs, 120000, 1000, 1200000, `config at ${path}: media.timeoutMs`),
|
|
296
|
+
maxRetries: boundedInteger(value.maxRetries, 2, 0, 5, `config at ${path}: media.maxRetries`),
|
|
297
|
+
maxRequestBytes: boundedInteger(value.maxRequestBytes, 32 * 1024 * 1024, 1 * 1024 * 1024, 64 * 1024 * 1024, `config at ${path}: media.maxRequestBytes`),
|
|
298
|
+
...renderer === undefined ? {} : { renderer }
|
|
299
|
+
};
|
|
300
|
+
}
|
|
249
301
|
function parseConfig(value, path) {
|
|
250
302
|
if (!isRawSiftLightConfig(value)) {
|
|
251
303
|
throw new Error(`Invalid sift-light config at ${path}: expected a JSON object`);
|
|
@@ -257,10 +309,11 @@ function parseConfig(value, path) {
|
|
|
257
309
|
"semanticJudge",
|
|
258
310
|
"dataSources",
|
|
259
311
|
"nativeText",
|
|
260
|
-
"nativeMedia"
|
|
312
|
+
"nativeMedia",
|
|
313
|
+
"media"
|
|
261
314
|
].includes(key));
|
|
262
315
|
if (unknown.length > 0) {
|
|
263
|
-
throw new Error(`Invalid sift-light config at ${path}: unsupported configuration fields; only locale, enforceSearch, vectorSearchEnabled, semanticJudge, dataSources, nativeText and
|
|
316
|
+
throw new Error(`Invalid sift-light config at ${path}: unsupported configuration fields; only locale, enforceSearch, vectorSearchEnabled, semanticJudge, dataSources, nativeText, nativeMedia and media are accepted`);
|
|
264
317
|
}
|
|
265
318
|
const { locale, enforceSearch, vectorSearchEnabled } = value;
|
|
266
319
|
if (locale !== undefined && locale !== "en" && locale !== "zh-CN") {
|
|
@@ -272,11 +325,15 @@ function parseConfig(value, path) {
|
|
|
272
325
|
const enforcement = normalizeSearchEnforcement(enforceSearch, `config at ${path}`);
|
|
273
326
|
const nativeText = parseNativeTextConfig(value.nativeText);
|
|
274
327
|
const nativeMedia = parseNativeTextConfig(value.nativeMedia, "nativeMedia");
|
|
328
|
+
const media = parseMedia(value.media, `${path}: media`);
|
|
329
|
+
if (nativeMedia && media)
|
|
330
|
+
throw new Error(`Invalid sift-light config at ${path}: choose nativeMedia or media, not both`);
|
|
275
331
|
if (nativeText && vectorSearchEnabled !== true)
|
|
276
332
|
throw new Error("nativeText requires vectorSearchEnabled:true");
|
|
277
333
|
return {
|
|
278
334
|
...nativeText ? { nativeText } : {},
|
|
279
335
|
...nativeMedia ? { nativeMedia } : {},
|
|
336
|
+
...media ? { media } : {},
|
|
280
337
|
locale: locale ?? DEFAULT_SIFT_LIGHT_CONFIG.locale,
|
|
281
338
|
enforceSearch: enforcement,
|
|
282
339
|
vectorSearchEnabled: vectorSearchEnabled ?? DEFAULT_SIFT_LIGHT_CONFIG.vectorSearchEnabled ?? false,
|
|
@@ -2060,7 +2117,7 @@ function createCtagsStructureProvider(options = {}) {
|
|
|
2060
2117
|
// package.json
|
|
2061
2118
|
var package_default = {
|
|
2062
2119
|
name: "sift-light",
|
|
2063
|
-
version: "1.1.0
|
|
2120
|
+
version: "1.1.0",
|
|
2064
2121
|
description: "Context-efficient local search for files, documents, notes and logs across Pi, OMP and MCP clients",
|
|
2065
2122
|
keywords: [
|
|
2066
2123
|
"ai-agent",
|
|
@@ -2676,10 +2733,10 @@ class OperationLifecycle {
|
|
|
2676
2733
|
}
|
|
2677
2734
|
|
|
2678
2735
|
// src/media-search.ts
|
|
2679
|
-
import { createHash, randomUUID as
|
|
2680
|
-
import { mkdtemp, mkdir, readFile as
|
|
2736
|
+
import { createHash, randomUUID as randomUUID4 } from "crypto";
|
|
2737
|
+
import { mkdtemp, mkdir, readFile as readFile4, realpath as realpath3, rm, stat as stat5, writeFile } from "fs/promises";
|
|
2681
2738
|
import { tmpdir } from "os";
|
|
2682
|
-
import { basename as basename2, dirname as dirname2, extname, join as join3, resolve as resolve8 } from "path";
|
|
2739
|
+
import { basename as basename2, dirname as dirname2, extname as extname2, join as join3, resolve as resolve8 } from "path";
|
|
2683
2740
|
|
|
2684
2741
|
// node_modules/zod/v4/core/util.js
|
|
2685
2742
|
function getEnumValues(entries) {
|
|
@@ -8295,6 +8352,278 @@ class ResidentWorkerClient {
|
|
|
8295
8352
|
}
|
|
8296
8353
|
}
|
|
8297
8354
|
|
|
8355
|
+
// src/remote-media-client.ts
|
|
8356
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
8357
|
+
import { readFile as readFile3, stat as stat4 } from "fs/promises";
|
|
8358
|
+
import { extname } from "path";
|
|
8359
|
+
var MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
8360
|
+
var MAX_FILE_ID_LENGTH = 128;
|
|
8361
|
+
|
|
8362
|
+
class RemoteMediaRequestError extends SiftLightError {
|
|
8363
|
+
retryable;
|
|
8364
|
+
constructor(message, retryable, options) {
|
|
8365
|
+
super(message, options);
|
|
8366
|
+
this.name = "RemoteMediaRequestError";
|
|
8367
|
+
this.retryable = retryable;
|
|
8368
|
+
}
|
|
8369
|
+
}
|
|
8370
|
+
var remoteResponseSchema = object({
|
|
8371
|
+
version: literal(1),
|
|
8372
|
+
indexedFiles: number2().int().nonnegative(),
|
|
8373
|
+
hits: array(object({
|
|
8374
|
+
fileId: string2().min(1).max(MAX_FILE_ID_LENGTH),
|
|
8375
|
+
score: number2().finite(),
|
|
8376
|
+
kind: _enum(["image", "scan"]),
|
|
8377
|
+
page: number2().int().positive().nullable().optional(),
|
|
8378
|
+
chunkCount: number2().int().positive(),
|
|
8379
|
+
width: number2().int().nonnegative(),
|
|
8380
|
+
height: number2().int().nonnegative()
|
|
8381
|
+
}).strict()).max(10)
|
|
8382
|
+
}).strict();
|
|
8383
|
+
function mimeType(path) {
|
|
8384
|
+
switch (extname(path).toLowerCase()) {
|
|
8385
|
+
case ".png":
|
|
8386
|
+
return "image/png";
|
|
8387
|
+
case ".jpg":
|
|
8388
|
+
case ".jpeg":
|
|
8389
|
+
return "image/jpeg";
|
|
8390
|
+
case ".gif":
|
|
8391
|
+
return "image/gif";
|
|
8392
|
+
case ".bmp":
|
|
8393
|
+
return "image/bmp";
|
|
8394
|
+
case ".tif":
|
|
8395
|
+
case ".tiff":
|
|
8396
|
+
return "image/tiff";
|
|
8397
|
+
case ".heic":
|
|
8398
|
+
return "image/heic";
|
|
8399
|
+
case ".heif":
|
|
8400
|
+
return "image/heif";
|
|
8401
|
+
case ".webp":
|
|
8402
|
+
return "image/webp";
|
|
8403
|
+
case ".pdf":
|
|
8404
|
+
return "application/pdf";
|
|
8405
|
+
default:
|
|
8406
|
+
return "application/octet-stream";
|
|
8407
|
+
}
|
|
8408
|
+
}
|
|
8409
|
+
async function boundedResponseText(response) {
|
|
8410
|
+
const contentLength = response.headers.get("content-length");
|
|
8411
|
+
if (contentLength !== null && Number(contentLength) > MAX_RESPONSE_BYTES)
|
|
8412
|
+
throw new SiftLightError("Remote media response exceeded the 4 MiB limit");
|
|
8413
|
+
if (!response.body) {
|
|
8414
|
+
const text = await response.text();
|
|
8415
|
+
if (new TextEncoder().encode(text).byteLength > MAX_RESPONSE_BYTES)
|
|
8416
|
+
throw new SiftLightError("Remote media response exceeded the 4 MiB limit");
|
|
8417
|
+
return text;
|
|
8418
|
+
}
|
|
8419
|
+
const reader = response.body.getReader();
|
|
8420
|
+
const chunks = [];
|
|
8421
|
+
let bytes = 0;
|
|
8422
|
+
try {
|
|
8423
|
+
while (true) {
|
|
8424
|
+
const result = await reader.read();
|
|
8425
|
+
if (result.done)
|
|
8426
|
+
break;
|
|
8427
|
+
bytes += result.value.byteLength;
|
|
8428
|
+
if (bytes > MAX_RESPONSE_BYTES) {
|
|
8429
|
+
await reader.cancel();
|
|
8430
|
+
throw new SiftLightError("Remote media response exceeded the 4 MiB limit");
|
|
8431
|
+
}
|
|
8432
|
+
chunks.push(result.value);
|
|
8433
|
+
}
|
|
8434
|
+
} finally {
|
|
8435
|
+
reader.releaseLock();
|
|
8436
|
+
}
|
|
8437
|
+
const all = new Uint8Array(bytes);
|
|
8438
|
+
let offset = 0;
|
|
8439
|
+
for (const chunk of chunks) {
|
|
8440
|
+
all.set(chunk, offset);
|
|
8441
|
+
offset += chunk.byteLength;
|
|
8442
|
+
}
|
|
8443
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(all);
|
|
8444
|
+
}
|
|
8445
|
+
function retryableStatus(status) {
|
|
8446
|
+
return status === 408 || status === 429 || status === 529 || status >= 500;
|
|
8447
|
+
}
|
|
8448
|
+
function retryDelay(attempt) {
|
|
8449
|
+
return Math.min(20000, 400 * 2 ** attempt);
|
|
8450
|
+
}
|
|
8451
|
+
async function waitForRetry(delayMs, signal) {
|
|
8452
|
+
if (signal?.aborted)
|
|
8453
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
8454
|
+
await new Promise((resolve, reject) => {
|
|
8455
|
+
const timer = setTimeout(() => {
|
|
8456
|
+
signal?.removeEventListener("abort", onAbort);
|
|
8457
|
+
resolve();
|
|
8458
|
+
}, delayMs);
|
|
8459
|
+
const onAbort = () => {
|
|
8460
|
+
clearTimeout(timer);
|
|
8461
|
+
signal?.removeEventListener("abort", onAbort);
|
|
8462
|
+
reject(signal?.reason instanceof Error ? signal.reason : new Error("Operation aborted"));
|
|
8463
|
+
};
|
|
8464
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8465
|
+
});
|
|
8466
|
+
}
|
|
8467
|
+
function base642(bytes) {
|
|
8468
|
+
return bytes.toString("base64");
|
|
8469
|
+
}
|
|
8470
|
+
function base64Length(bytes) {
|
|
8471
|
+
return Math.ceil(bytes / 3) * 4;
|
|
8472
|
+
}
|
|
8473
|
+
|
|
8474
|
+
class RemoteMediaClient {
|
|
8475
|
+
#config;
|
|
8476
|
+
#key;
|
|
8477
|
+
#fetcher;
|
|
8478
|
+
constructor(config, environment = process.env, fetcher = fetch) {
|
|
8479
|
+
this.#config = config;
|
|
8480
|
+
this.#key = config.apiKeyEnv ? environment[config.apiKeyEnv]?.trim() : undefined;
|
|
8481
|
+
if (config.apiKeyEnv && !this.#key) {
|
|
8482
|
+
throw new SiftLightError(`Media provider is configured with ${config.apiKeyEnv}, but that environment variable is missing or empty`);
|
|
8483
|
+
}
|
|
8484
|
+
this.#fetcher = fetcher;
|
|
8485
|
+
}
|
|
8486
|
+
async search(request) {
|
|
8487
|
+
const id = randomUUID3();
|
|
8488
|
+
const paths = new Map;
|
|
8489
|
+
const files = [];
|
|
8490
|
+
const fileSizes = [];
|
|
8491
|
+
for (const file of request.files) {
|
|
8492
|
+
if (file.id.length === 0 || file.id.length > MAX_FILE_ID_LENGTH || /[\r\n\0]/u.test(file.id) || paths.has(file.id))
|
|
8493
|
+
throw new SiftLightError("Remote media file ids must be unique bounded text");
|
|
8494
|
+
paths.set(file.id, file.path);
|
|
8495
|
+
const metadata2 = await stat4(file.path);
|
|
8496
|
+
if (!metadata2.isFile())
|
|
8497
|
+
throw new SiftLightError("Remote media files must be regular files");
|
|
8498
|
+
fileSizes.push(metadata2.size);
|
|
8499
|
+
}
|
|
8500
|
+
let imageSize;
|
|
8501
|
+
if (request.imagePath !== undefined) {
|
|
8502
|
+
const metadata2 = await stat4(request.imagePath);
|
|
8503
|
+
if (!metadata2.isFile())
|
|
8504
|
+
throw new SiftLightError("Remote media query image must be a regular file");
|
|
8505
|
+
imageSize = metadata2.size;
|
|
8506
|
+
}
|
|
8507
|
+
const requestSkeleton = JSON.stringify({
|
|
8508
|
+
version: 1,
|
|
8509
|
+
model: this.#config.model,
|
|
8510
|
+
...request.query === undefined ? {} : { query: request.query },
|
|
8511
|
+
...request.imagePath === undefined ? {} : { image: { mimeType: mimeType(request.imagePath), data: "" } },
|
|
8512
|
+
kind: request.kind,
|
|
8513
|
+
expectedFiles: request.expectedFiles,
|
|
8514
|
+
files: []
|
|
8515
|
+
});
|
|
8516
|
+
let estimatedBytes = new TextEncoder().encode(requestSkeleton).byteLength;
|
|
8517
|
+
if (imageSize !== undefined)
|
|
8518
|
+
estimatedBytes += base64Length(imageSize);
|
|
8519
|
+
for (let index = 0;index < request.files.length; index += 1) {
|
|
8520
|
+
const file = request.files[index];
|
|
8521
|
+
const fileSkeleton = JSON.stringify({
|
|
8522
|
+
id: file?.id,
|
|
8523
|
+
mimeType: file ? mimeType(file.path) : "application/octet-stream",
|
|
8524
|
+
data: ""
|
|
8525
|
+
});
|
|
8526
|
+
estimatedBytes += new TextEncoder().encode(fileSkeleton).byteLength;
|
|
8527
|
+
estimatedBytes += base64Length(fileSizes[index] ?? 0);
|
|
8528
|
+
}
|
|
8529
|
+
if (request.files.length > 1)
|
|
8530
|
+
estimatedBytes += request.files.length - 1;
|
|
8531
|
+
if (estimatedBytes > this.#config.maxRequestBytes)
|
|
8532
|
+
throw new SiftLightError(`Remote media request exceeded the configured ${String(this.#config.maxRequestBytes)} byte limit`);
|
|
8533
|
+
for (const file of request.files) {
|
|
8534
|
+
files.push({
|
|
8535
|
+
id: file.id,
|
|
8536
|
+
mimeType: mimeType(file.path),
|
|
8537
|
+
data: base642(await readFile3(file.path, { signal: request.signal }))
|
|
8538
|
+
});
|
|
8539
|
+
}
|
|
8540
|
+
const image = request.imagePath === undefined ? undefined : {
|
|
8541
|
+
mimeType: mimeType(request.imagePath),
|
|
8542
|
+
data: base642(await readFile3(request.imagePath, { signal: request.signal }))
|
|
8543
|
+
};
|
|
8544
|
+
const body2 = JSON.stringify({
|
|
8545
|
+
version: 1,
|
|
8546
|
+
model: this.#config.model,
|
|
8547
|
+
...request.query === undefined ? {} : { query: request.query },
|
|
8548
|
+
...image === undefined ? {} : { image },
|
|
8549
|
+
kind: request.kind,
|
|
8550
|
+
expectedFiles: request.expectedFiles,
|
|
8551
|
+
files
|
|
8552
|
+
});
|
|
8553
|
+
if (new TextEncoder().encode(body2).byteLength > this.#config.maxRequestBytes)
|
|
8554
|
+
throw new SiftLightError(`Remote media request exceeded the configured ${String(this.#config.maxRequestBytes)} byte limit`);
|
|
8555
|
+
for (let attempt = 0;attempt <= this.#config.maxRetries; attempt += 1) {
|
|
8556
|
+
const controller = new AbortController;
|
|
8557
|
+
const onParentAbort = () => controller.abort(request.signal?.reason);
|
|
8558
|
+
request.signal?.addEventListener("abort", onParentAbort, { once: true });
|
|
8559
|
+
const timeout = setTimeout(() => controller.abort(new Error("Remote media request timed out")), this.#config.timeoutMs);
|
|
8560
|
+
try {
|
|
8561
|
+
const headers = { "content-type": "application/json" };
|
|
8562
|
+
if (this.#key)
|
|
8563
|
+
headers.authorization = `Bearer ${this.#key}`;
|
|
8564
|
+
const response = await this.#fetcher(this.#config.endpoint, {
|
|
8565
|
+
method: "POST",
|
|
8566
|
+
headers,
|
|
8567
|
+
body: body2,
|
|
8568
|
+
signal: controller.signal
|
|
8569
|
+
});
|
|
8570
|
+
if (!response.ok) {
|
|
8571
|
+
throw new RemoteMediaRequestError(`Remote media provider returned HTTP ${String(response.status)}`, retryableStatus(response.status));
|
|
8572
|
+
}
|
|
8573
|
+
let parsed;
|
|
8574
|
+
try {
|
|
8575
|
+
const raw = JSON.parse(await boundedResponseText(response));
|
|
8576
|
+
parsed = remoteResponseSchema.parse(raw);
|
|
8577
|
+
} catch (error) {
|
|
8578
|
+
throw new SiftLightError("Remote media provider returned an invalid response", {
|
|
8579
|
+
cause: error
|
|
8580
|
+
});
|
|
8581
|
+
}
|
|
8582
|
+
if (parsed.indexedFiles !== request.expectedFiles)
|
|
8583
|
+
throw new SiftLightError("Remote media provider indexed an unexpected file count");
|
|
8584
|
+
return {
|
|
8585
|
+
version: 1,
|
|
8586
|
+
id,
|
|
8587
|
+
indexedFiles: parsed.indexedFiles,
|
|
8588
|
+
modelLoads: 0,
|
|
8589
|
+
modelReused: true,
|
|
8590
|
+
processId: process.pid,
|
|
8591
|
+
hits: parsed.hits.map((hit) => {
|
|
8592
|
+
const path = paths.get(hit.fileId);
|
|
8593
|
+
if (!path)
|
|
8594
|
+
throw new SiftLightError("Remote media provider returned an unknown file id");
|
|
8595
|
+
return {
|
|
8596
|
+
path,
|
|
8597
|
+
score: hit.score,
|
|
8598
|
+
kind: hit.kind,
|
|
8599
|
+
page: hit.page ?? null,
|
|
8600
|
+
chunkCount: hit.chunkCount,
|
|
8601
|
+
width: hit.width,
|
|
8602
|
+
height: hit.height
|
|
8603
|
+
};
|
|
8604
|
+
})
|
|
8605
|
+
};
|
|
8606
|
+
} catch (error) {
|
|
8607
|
+
if (request.signal?.aborted)
|
|
8608
|
+
throw error;
|
|
8609
|
+
const retryable = error instanceof RemoteMediaRequestError ? error.retryable : !(error instanceof SiftLightError);
|
|
8610
|
+
if (!retryable || attempt >= this.#config.maxRetries)
|
|
8611
|
+
throw error;
|
|
8612
|
+
await waitForRetry(retryDelay(attempt), request.signal);
|
|
8613
|
+
} finally {
|
|
8614
|
+
clearTimeout(timeout);
|
|
8615
|
+
request.signal?.removeEventListener("abort", onParentAbort);
|
|
8616
|
+
}
|
|
8617
|
+
}
|
|
8618
|
+
throw new SiftLightError("Remote media request failed");
|
|
8619
|
+
}
|
|
8620
|
+
async close() {}
|
|
8621
|
+
}
|
|
8622
|
+
function mediaMimeType(path) {
|
|
8623
|
+
const type = mimeType(path);
|
|
8624
|
+
return type === "application/octet-stream" ? undefined : type;
|
|
8625
|
+
}
|
|
8626
|
+
|
|
8298
8627
|
// src/workspace-files.ts
|
|
8299
8628
|
import { relative as relative3, resolve as resolve7, sep as sep2 } from "path";
|
|
8300
8629
|
class EnumerationLimit extends Error {
|
|
@@ -8417,7 +8746,7 @@ var responseSchema = object({
|
|
|
8417
8746
|
version: literal(1),
|
|
8418
8747
|
id: string2(),
|
|
8419
8748
|
indexedFiles: number2().int().nonnegative(),
|
|
8420
|
-
modelLoads:
|
|
8749
|
+
modelLoads: number2().int().nonnegative(),
|
|
8421
8750
|
modelReused: boolean2(),
|
|
8422
8751
|
processId: number2().int().positive(),
|
|
8423
8752
|
hits: array(object({
|
|
@@ -8453,11 +8782,11 @@ async function sourceBytes(cwd, path, signal) {
|
|
|
8453
8782
|
const canonical = await policy.resolveExistingPath(path);
|
|
8454
8783
|
if (!canonical)
|
|
8455
8784
|
throw new SiftLightError(`Media source unavailable: ${path}`);
|
|
8456
|
-
const before = await
|
|
8785
|
+
const before = await stat5(canonical);
|
|
8457
8786
|
if (!before.isFile() || before.size > maxFileBytes)
|
|
8458
8787
|
throw new SiftLightError(`Media source must be a regular file at most ${String(maxFileBytes)} bytes: ${path}`);
|
|
8459
|
-
const bytes = await
|
|
8460
|
-
const after = await
|
|
8788
|
+
const bytes = await readFile4(canonical, { signal });
|
|
8789
|
+
const after = await stat5(canonical);
|
|
8461
8790
|
if (bytes.length > maxFileBytes || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs || await policy.resolveExistingPath(path) !== canonical)
|
|
8462
8791
|
throw new SiftLightError(`Media source changed during read: ${path}`);
|
|
8463
8792
|
return { canonical, bytes };
|
|
@@ -8465,6 +8794,7 @@ async function sourceBytes(cwd, path, signal) {
|
|
|
8465
8794
|
|
|
8466
8795
|
class MediaSearch {
|
|
8467
8796
|
#config;
|
|
8797
|
+
#remoteConfig;
|
|
8468
8798
|
#operations = new OperationLifecycle({ deadlineMs: 300000 });
|
|
8469
8799
|
#queue = new OwnedTaskQueue;
|
|
8470
8800
|
#lifecycle = new AbortController;
|
|
@@ -8472,9 +8802,13 @@ class MediaSearch {
|
|
|
8472
8802
|
#known = new Map;
|
|
8473
8803
|
#directory;
|
|
8474
8804
|
#worker;
|
|
8805
|
+
#remote;
|
|
8475
8806
|
#closed = false;
|
|
8476
8807
|
constructor(config) {
|
|
8477
|
-
|
|
8808
|
+
if ("provider" in config)
|
|
8809
|
+
this.#remoteConfig = config;
|
|
8810
|
+
else
|
|
8811
|
+
this.#config = config;
|
|
8478
8812
|
}
|
|
8479
8813
|
async search(value, cwd, signal) {
|
|
8480
8814
|
const request = mediaRequestSchema.parse(value);
|
|
@@ -8530,7 +8864,7 @@ class MediaSearch {
|
|
|
8530
8864
|
const reasons = [...inventory.reasons];
|
|
8531
8865
|
let total = 0;
|
|
8532
8866
|
for (const path of inventory.paths) {
|
|
8533
|
-
if (!extensions.has(
|
|
8867
|
+
if (!extensions.has(extname2(path).toLowerCase()))
|
|
8534
8868
|
continue;
|
|
8535
8869
|
if (signal.aborted)
|
|
8536
8870
|
throw abortError();
|
|
@@ -8568,20 +8902,37 @@ class MediaSearch {
|
|
|
8568
8902
|
let imageSource;
|
|
8569
8903
|
let queryImage;
|
|
8570
8904
|
if (r.image) {
|
|
8571
|
-
if (!extensions.has(
|
|
8905
|
+
if (!extensions.has(extname2(r.image).toLowerCase()) || extname2(r.image).toLowerCase() === ".pdf")
|
|
8572
8906
|
throw new SiftLightError("image must be a supported local image file");
|
|
8573
8907
|
imageSource = await sourceBytes(cwd, r.image, signal);
|
|
8574
|
-
queryImage = join3(root, "query" +
|
|
8908
|
+
queryImage = join3(root, "query" + extname2(r.image).toLowerCase());
|
|
8575
8909
|
await writeFile(queryImage, imageSource.bytes, { mode: 384 });
|
|
8576
8910
|
}
|
|
8577
8911
|
let response;
|
|
8578
8912
|
if (admitted.size) {
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8913
|
+
if (this.#config) {
|
|
8914
|
+
this.#worker ??= new ResidentWorkerClient(this.#config, root, corpus, join3(root, "index.sqlite"), { parse: (value) => responseSchema.parse(value), maxResponseBytes: 128 * 1024 });
|
|
8915
|
+
response = await this.#worker.search({
|
|
8916
|
+
...queryImage ? { image: queryImage } : { query: r.query },
|
|
8917
|
+
kind: r.kind,
|
|
8918
|
+
expectedFiles: admitted.size
|
|
8919
|
+
}, signal);
|
|
8920
|
+
} else if (this.#remoteConfig) {
|
|
8921
|
+
this.#remote ??= new RemoteMediaClient(this.#remoteConfig);
|
|
8922
|
+
const remoteFiles = [...admitted.values()].map((entry) => ({
|
|
8923
|
+
id: createHash("sha256").update(entry.staged).digest("hex").slice(0, 32),
|
|
8924
|
+
path: entry.staged
|
|
8925
|
+
}));
|
|
8926
|
+
response = await this.#remote.search({
|
|
8927
|
+
...queryImage ? { imagePath: queryImage } : { query: r.query },
|
|
8928
|
+
kind: r.kind,
|
|
8929
|
+
expectedFiles: admitted.size,
|
|
8930
|
+
files: remoteFiles,
|
|
8931
|
+
signal
|
|
8932
|
+
});
|
|
8933
|
+
}
|
|
8934
|
+
if (!response)
|
|
8935
|
+
throw new SiftLightError("Media provider is not configured");
|
|
8585
8936
|
if (response.indexedFiles !== admitted.size)
|
|
8586
8937
|
throw new SiftLightError("Media index inventory mismatch");
|
|
8587
8938
|
}
|
|
@@ -8589,7 +8940,7 @@ class MediaSearch {
|
|
|
8589
8940
|
const seen = new Set;
|
|
8590
8941
|
for (const hit of response?.hits ?? []) {
|
|
8591
8942
|
const source = admitted.get(resolve8(hit.path));
|
|
8592
|
-
if (!source || hit.kind === "scan" !== (
|
|
8943
|
+
if (!source || hit.kind === "scan" !== (extname2(source.path).toLowerCase() === ".pdf") || seen.has(hit.path) || hit.kind === "scan" !== (hit.page !== null) || hit.page !== null && hit.page > hit.chunkCount)
|
|
8593
8944
|
throw new SiftLightError("Media result source or page is inconsistent");
|
|
8594
8945
|
seen.add(hit.path);
|
|
8595
8946
|
saved.push({ hit, source });
|
|
@@ -8609,7 +8960,7 @@ class MediaSearch {
|
|
|
8609
8960
|
}
|
|
8610
8961
|
if (signal.aborted)
|
|
8611
8962
|
throw abortError();
|
|
8612
|
-
const cursor =
|
|
8963
|
+
const cursor = randomUUID4();
|
|
8613
8964
|
for (const [id, snapshot] of this.#snapshots)
|
|
8614
8965
|
if (Date.now() - snapshot.createdAt > 600000)
|
|
8615
8966
|
this.#snapshots.delete(id);
|
|
@@ -8672,11 +9023,23 @@ ${JSON.stringify(details)}`,
|
|
|
8672
9023
|
throw new SiftLightError("Media source changed; search again before inspection");
|
|
8673
9024
|
const folder = await mkdtemp(join3(await this.#root(), "inspect-"));
|
|
8674
9025
|
try {
|
|
8675
|
-
const input = join3(folder, "source" +
|
|
9026
|
+
const input = join3(folder, "source" + extname2(item.source.path));
|
|
8676
9027
|
const output = join3(folder, "preview.png");
|
|
8677
9028
|
await writeFile(input, current.bytes, { mode: 384 });
|
|
9029
|
+
const renderer = this.#config ?? this.#remoteConfig?.renderer;
|
|
9030
|
+
if (!renderer) {
|
|
9031
|
+
const mimeType = mediaMimeType(item.source.path);
|
|
9032
|
+
if (mimeType?.startsWith("image/") && mimeType === "image/png") {
|
|
9033
|
+
return {
|
|
9034
|
+
text: JSON.stringify(details),
|
|
9035
|
+
details,
|
|
9036
|
+
image: { data: current.bytes.toString("base64"), mimeType }
|
|
9037
|
+
};
|
|
9038
|
+
}
|
|
9039
|
+
throw new SiftLightError("Media preview requires nativeMedia or media.renderer; validate still works without a local renderer");
|
|
9040
|
+
}
|
|
8678
9041
|
const result = await runOwnedProcess({
|
|
8679
|
-
executable:
|
|
9042
|
+
executable: renderer.executable,
|
|
8680
9043
|
args: ["--render", input, String(item.hit.page ?? 0), output],
|
|
8681
9044
|
cwd: folder,
|
|
8682
9045
|
signal
|
|
@@ -8687,7 +9050,7 @@ ${JSON.stringify(details)}`,
|
|
|
8687
9050
|
});
|
|
8688
9051
|
if (result.code !== 0)
|
|
8689
9052
|
throw new SiftLightError(`Media preview failed: ${result.stderr}`);
|
|
8690
|
-
const preview = await
|
|
9053
|
+
const preview = await readFile4(output);
|
|
8691
9054
|
if (preview.length > 4 * 1024 * 1024)
|
|
8692
9055
|
throw new SiftLightError("Media preview exceeds 4 MiB");
|
|
8693
9056
|
return {
|
|
@@ -8712,6 +9075,7 @@ ${JSON.stringify(details)}`,
|
|
|
8712
9075
|
await this.#queue.run(async () => {
|
|
8713
9076
|
try {
|
|
8714
9077
|
await this.#worker?.close();
|
|
9078
|
+
await this.#remote?.close();
|
|
8715
9079
|
} finally {
|
|
8716
9080
|
this.#snapshots.clear();
|
|
8717
9081
|
if (this.#directory)
|
|
@@ -8730,7 +9094,7 @@ class DataSourceError extends Error {
|
|
|
8730
9094
|
this.code = code;
|
|
8731
9095
|
}
|
|
8732
9096
|
}
|
|
8733
|
-
var
|
|
9097
|
+
var MAX_RESPONSE_BYTES2 = 1024 * 1024;
|
|
8734
9098
|
async function credential(config) {
|
|
8735
9099
|
let value;
|
|
8736
9100
|
if (config.tokenEnv)
|
|
@@ -8785,7 +9149,7 @@ async function sourcePost(config, path, payload, signal) {
|
|
|
8785
9149
|
if (part.done)
|
|
8786
9150
|
break;
|
|
8787
9151
|
size += part.value.byteLength;
|
|
8788
|
-
if (size >
|
|
9152
|
+
if (size > MAX_RESPONSE_BYTES2) {
|
|
8789
9153
|
await reader.cancel();
|
|
8790
9154
|
throw new DataSourceError("protocol");
|
|
8791
9155
|
}
|
|
@@ -9035,14 +9399,14 @@ class DataSources {
|
|
|
9035
9399
|
import { createHash as createHash7 } from "crypto";
|
|
9036
9400
|
|
|
9037
9401
|
// src/concept-worker-client.ts
|
|
9038
|
-
import { randomUUID as
|
|
9402
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
9039
9403
|
import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
|
|
9040
9404
|
import { dirname as dirname3, join as join5 } from "path";
|
|
9041
9405
|
import { fileURLToPath } from "url";
|
|
9042
9406
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
9043
9407
|
|
|
9044
9408
|
// src/concept-model.ts
|
|
9045
|
-
import { stat as
|
|
9409
|
+
import { stat as stat6 } from "fs/promises";
|
|
9046
9410
|
import { homedir as homedir2 } from "os";
|
|
9047
9411
|
import { join as join4, resolve as resolve9 } from "path";
|
|
9048
9412
|
var CONCEPT_MODEL = "Xenova/multilingual-e5-small";
|
|
@@ -9085,7 +9449,7 @@ function conceptModelDirectory() {
|
|
|
9085
9449
|
async function inspectConceptModel(directory = conceptModelDirectory()) {
|
|
9086
9450
|
for (const asset of CONCEPT_ASSETS) {
|
|
9087
9451
|
try {
|
|
9088
|
-
const information = await
|
|
9452
|
+
const information = await stat6(join4(directory, asset.path));
|
|
9089
9453
|
if (!information.isFile() || information.size !== asset.bytes)
|
|
9090
9454
|
return {
|
|
9091
9455
|
installed: false,
|
|
@@ -9181,7 +9545,7 @@ class ConceptWorkerClient {
|
|
|
9181
9545
|
throw new SiftLightError("Concept worker idle timeout must be 1\u2013300000 ms");
|
|
9182
9546
|
}
|
|
9183
9547
|
async#start() {
|
|
9184
|
-
const stagingRoot = join5(conceptCacheDirectory(), ".staging",
|
|
9548
|
+
const stagingRoot = join5(conceptCacheDirectory(), ".staging", randomUUID5());
|
|
9185
9549
|
await mkdir2(stagingRoot, { recursive: true });
|
|
9186
9550
|
const ready = Promise.withResolvers();
|
|
9187
9551
|
ready.promise.catch(() => {
|
|
@@ -9315,7 +9679,7 @@ class ConceptWorkerClient {
|
|
|
9315
9679
|
const input = await session.ready;
|
|
9316
9680
|
if (combined.aborted)
|
|
9317
9681
|
throw abortError();
|
|
9318
|
-
const id =
|
|
9682
|
+
const id = randomUUID5();
|
|
9319
9683
|
const body2 = JSON.stringify({
|
|
9320
9684
|
id,
|
|
9321
9685
|
query,
|
|
@@ -10109,7 +10473,7 @@ function deriveSyntaxFacts(tree, language, text) {
|
|
|
10109
10473
|
}
|
|
10110
10474
|
|
|
10111
10475
|
// src/language-capability-definitions.ts
|
|
10112
|
-
import { extname as
|
|
10476
|
+
import { extname as extname3 } from "path";
|
|
10113
10477
|
var TYPESCRIPT_LANGUAGE_CAPABILITIES = [
|
|
10114
10478
|
{
|
|
10115
10479
|
id: "ast-grep.outline",
|
|
@@ -10258,14 +10622,14 @@ var DEFAULT_NEUTRAL_CAPABILITIES = [
|
|
|
10258
10622
|
}
|
|
10259
10623
|
];
|
|
10260
10624
|
function descriptorForPath(descriptors, path) {
|
|
10261
|
-
const extension =
|
|
10625
|
+
const extension = extname3(path).toLowerCase();
|
|
10262
10626
|
return descriptors.find((item) => item.extensions.includes(extension));
|
|
10263
10627
|
}
|
|
10264
10628
|
function languageForPath(descriptors, path) {
|
|
10265
10629
|
return descriptorForPath(descriptors, path)?.language;
|
|
10266
10630
|
}
|
|
10267
10631
|
function outlineExtension(path) {
|
|
10268
|
-
const extension =
|
|
10632
|
+
const extension = extname3(path).toLowerCase();
|
|
10269
10633
|
return extension || undefined;
|
|
10270
10634
|
}
|
|
10271
10635
|
|
|
@@ -10441,7 +10805,7 @@ async function parseSyntax(path, text, signal, pattern) {
|
|
|
10441
10805
|
import { createHash as createHash4 } from "crypto";
|
|
10442
10806
|
|
|
10443
10807
|
// src/source-access.ts
|
|
10444
|
-
import { extname as
|
|
10808
|
+
import { extname as extname4, resolve as resolve13 } from "path";
|
|
10445
10809
|
|
|
10446
10810
|
// src/historical-paths.ts
|
|
10447
10811
|
import { lstat, mkdir as mkdir3, mkdtemp as mkdtemp2, open as open3, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -11574,7 +11938,7 @@ class SyntaxQueue {
|
|
|
11574
11938
|
throw new SourceDocumentError("encoding", "Syntax requires lossless UTF-8 source");
|
|
11575
11939
|
const origin = document2.reference.origin;
|
|
11576
11940
|
const revision = origin.kind === "worktree" ? origin.contentHash : origin.blob;
|
|
11577
|
-
const key = `${
|
|
11941
|
+
const key = `${extname4(document2.path).toLowerCase()}\x00${revision}\x00${pattern ?? ""}`;
|
|
11578
11942
|
const cached = this.#cache.get(key);
|
|
11579
11943
|
if (cached) {
|
|
11580
11944
|
this.#cache.delete(key);
|
|
@@ -12353,7 +12717,7 @@ async function structuralSearch(input, access) {
|
|
|
12353
12717
|
|
|
12354
12718
|
// src/native-text-search.ts
|
|
12355
12719
|
import { createHash as createHash6 } from "crypto";
|
|
12356
|
-
import { mkdtemp as mkdtemp3, mkdir as mkdir4, readFile as
|
|
12720
|
+
import { mkdtemp as mkdtemp3, mkdir as mkdir4, readFile as readFile5, realpath as realpath5, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
12357
12721
|
import { tmpdir as tmpdir3 } from "os";
|
|
12358
12722
|
import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute8, join as join7, resolve as resolve15 } from "path";
|
|
12359
12723
|
|
|
@@ -12477,7 +12841,7 @@ function createNativeTextSearch(config, search) {
|
|
|
12477
12841
|
if (!entry || seen.has(entry.staged))
|
|
12478
12842
|
throw new SiftLightError("Native text returned an unknown or duplicate source");
|
|
12479
12843
|
seen.add(entry.staged);
|
|
12480
|
-
const content = await
|
|
12844
|
+
const content = await readFile5(entry.staged);
|
|
12481
12845
|
if (createHash6("sha256").update(content).digest("hex") !== entry.hash)
|
|
12482
12846
|
throw new SiftLightError("Native staged source changed");
|
|
12483
12847
|
const document2 = new SourceDocument(entry.source, content);
|
|
@@ -12558,10 +12922,10 @@ function rankRetrievalFiles(current, native) {
|
|
|
12558
12922
|
}
|
|
12559
12923
|
|
|
12560
12924
|
// src/evidence-service.ts
|
|
12561
|
-
import { dirname as dirname7, extname as
|
|
12925
|
+
import { dirname as dirname7, extname as extname5, resolve as resolve24 } from "path";
|
|
12562
12926
|
|
|
12563
12927
|
// src/analysis-store.ts
|
|
12564
|
-
import { randomUUID as
|
|
12928
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
12565
12929
|
|
|
12566
12930
|
// src/analysis-types.ts
|
|
12567
12931
|
var SEMANTIC_JUDGE_CLASSIFICATIONS = [
|
|
@@ -12946,7 +13310,7 @@ class AnalysisStore {
|
|
|
12946
13310
|
}
|
|
12947
13311
|
create(result, summarize) {
|
|
12948
13312
|
this.#expire();
|
|
12949
|
-
const id =
|
|
13313
|
+
const id = randomUUID6();
|
|
12950
13314
|
const bounded = {
|
|
12951
13315
|
...result,
|
|
12952
13316
|
...result.retrievalLanes ? {
|
|
@@ -15191,7 +15555,7 @@ async function discoverFiles(input, cwd, signal) {
|
|
|
15191
15555
|
}
|
|
15192
15556
|
|
|
15193
15557
|
// src/source-continuations.ts
|
|
15194
|
-
import { randomUUID as
|
|
15558
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
15195
15559
|
|
|
15196
15560
|
// src/source-pages.ts
|
|
15197
15561
|
function mergeByteRanges(ranges) {
|
|
@@ -15295,7 +15659,7 @@ class SourceContinuations {
|
|
|
15295
15659
|
create(source, target, gaps, boundary) {
|
|
15296
15660
|
this.#sweep();
|
|
15297
15661
|
const item = {
|
|
15298
|
-
id:
|
|
15662
|
+
id: randomUUID7(),
|
|
15299
15663
|
source: structuredClone(source),
|
|
15300
15664
|
target: mergeByteRanges(target),
|
|
15301
15665
|
gaps: mergeByteRanges(gaps),
|
|
@@ -15891,7 +16255,7 @@ async function continueSource(cursor, access, continuations, expectedPath) {
|
|
|
15891
16255
|
}
|
|
15892
16256
|
|
|
15893
16257
|
// src/evidence-validation.ts
|
|
15894
|
-
import { realpath as realpath7, stat as
|
|
16258
|
+
import { realpath as realpath7, stat as stat7 } from "fs/promises";
|
|
15895
16259
|
import { resolve as resolve22 } from "path";
|
|
15896
16260
|
|
|
15897
16261
|
// src/evidence-validity.ts
|
|
@@ -15929,9 +16293,9 @@ async function confirmWorktreeState(path, cwd, signal) {
|
|
|
15929
16293
|
const policy = new SearchPathPolicy(cwd);
|
|
15930
16294
|
try {
|
|
15931
16295
|
policy.assertPath(absolute);
|
|
15932
|
-
const before = await
|
|
16296
|
+
const before = await stat7(absolute);
|
|
15933
16297
|
const beforeCanonical = await realpath7(absolute);
|
|
15934
|
-
const after = await
|
|
16298
|
+
const after = await stat7(absolute);
|
|
15935
16299
|
const afterCanonical = await realpath7(absolute);
|
|
15936
16300
|
if (!before.isFile() || !after.isFile()) {
|
|
15937
16301
|
return { status: "stale", reason: "Source is no longer a regular file" };
|
|
@@ -16073,9 +16437,9 @@ async function validateSnapshotTarget(target, cwd, policy, signal) {
|
|
|
16073
16437
|
const result = await confirmWorktreeState(target.path, cwd, signal);
|
|
16074
16438
|
return { path: target.path, role: "source", ...result };
|
|
16075
16439
|
}
|
|
16076
|
-
const before = await
|
|
16440
|
+
const before = await stat7(absolute);
|
|
16077
16441
|
const beforeCanonical = canonical;
|
|
16078
|
-
const after = await
|
|
16442
|
+
const after = await stat7(absolute);
|
|
16079
16443
|
const afterCanonical = await realpath7(absolute);
|
|
16080
16444
|
if (!before.isFile() || !after.isFile()) {
|
|
16081
16445
|
return {
|
|
@@ -17029,7 +17393,7 @@ async function judgeSemanticCandidateBatches(runner, query, batches, signal) {
|
|
|
17029
17393
|
|
|
17030
17394
|
// src/semantic-judge.ts
|
|
17031
17395
|
var MAX_CANDIDATE_CHARS = 4000;
|
|
17032
|
-
var
|
|
17396
|
+
var MAX_RESPONSE_BYTES3 = 4 * 1024 * 1024;
|
|
17033
17397
|
var MAX_ERROR_CHARS = 512;
|
|
17034
17398
|
var CLASSIFICATION_PRIORITY = {
|
|
17035
17399
|
"implementation-candidate": 0,
|
|
@@ -17119,14 +17483,22 @@ function requestBody(query, candidates, model) {
|
|
|
17119
17483
|
questions
|
|
17120
17484
|
};
|
|
17121
17485
|
}
|
|
17486
|
+
function httpJsonRequestBody(query, candidates, model) {
|
|
17487
|
+
return {
|
|
17488
|
+
version: 1,
|
|
17489
|
+
model,
|
|
17490
|
+
query,
|
|
17491
|
+
candidates: candidates.map(({ id, excerpt }) => ({ id, excerpt }))
|
|
17492
|
+
};
|
|
17493
|
+
}
|
|
17122
17494
|
async function responseText(response) {
|
|
17123
17495
|
if (!response.body) {
|
|
17124
17496
|
const contentLength = response.headers.get("content-length");
|
|
17125
|
-
if (contentLength !== null && Number(contentLength) >
|
|
17497
|
+
if (contentLength !== null && Number(contentLength) > MAX_RESPONSE_BYTES3) {
|
|
17126
17498
|
throw new SemanticJudgeRequestError("Semantic judge response exceeded the 4 MiB limit", false);
|
|
17127
17499
|
}
|
|
17128
17500
|
const text = await response.text();
|
|
17129
|
-
if (new TextEncoder().encode(text).byteLength >
|
|
17501
|
+
if (new TextEncoder().encode(text).byteLength > MAX_RESPONSE_BYTES3) {
|
|
17130
17502
|
throw new SemanticJudgeRequestError("Semantic judge response exceeded the 4 MiB limit", false);
|
|
17131
17503
|
}
|
|
17132
17504
|
return text;
|
|
@@ -17141,7 +17513,7 @@ async function responseText(response) {
|
|
|
17141
17513
|
break;
|
|
17142
17514
|
const chunk = result.value;
|
|
17143
17515
|
bytes += chunk.byteLength;
|
|
17144
|
-
if (bytes >
|
|
17516
|
+
if (bytes > MAX_RESPONSE_BYTES3) {
|
|
17145
17517
|
await reader.cancel();
|
|
17146
17518
|
throw new SemanticJudgeRequestError("Semantic judge response exceeded the 4 MiB limit", false);
|
|
17147
17519
|
}
|
|
@@ -17158,10 +17530,10 @@ async function responseText(response) {
|
|
|
17158
17530
|
}
|
|
17159
17531
|
return new TextDecoder("utf-8", { fatal: true }).decode(all);
|
|
17160
17532
|
}
|
|
17161
|
-
function
|
|
17533
|
+
function retryDelay2(attempt) {
|
|
17162
17534
|
return Math.min(20000, 400 * 2 ** attempt);
|
|
17163
17535
|
}
|
|
17164
|
-
async function
|
|
17536
|
+
async function waitForRetry2(delayMs, signal) {
|
|
17165
17537
|
if (signal?.aborted)
|
|
17166
17538
|
throw signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
17167
17539
|
await new Promise((resolve, reject) => {
|
|
@@ -17179,68 +17551,127 @@ async function waitForRetry(delayMs, signal) {
|
|
|
17179
17551
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
17180
17552
|
});
|
|
17181
17553
|
}
|
|
17554
|
+
async function fetchSemanticResponse(config, key, fetcher, body2, parentSignal) {
|
|
17555
|
+
if (new TextEncoder().encode(body2).byteLength > MAX_SEMANTIC_JUDGE_REQUEST_BYTES) {
|
|
17556
|
+
throw new SemanticJudgePayloadTooLargeError("Semantic judge request exceeded the 64 KiB request limit");
|
|
17557
|
+
}
|
|
17558
|
+
for (let attempt = 0;attempt <= config.maxRetries; attempt += 1) {
|
|
17559
|
+
const controller = new AbortController;
|
|
17560
|
+
const onParentAbort = () => controller.abort(parentSignal?.reason);
|
|
17561
|
+
parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
17562
|
+
const timeout = setTimeout(() => controller.abort(new Error("Semantic judge request timed out")), config.timeoutMs);
|
|
17563
|
+
try {
|
|
17564
|
+
const headers = { "content-type": "application/json" };
|
|
17565
|
+
if (key)
|
|
17566
|
+
headers.authorization = `Bearer ${key}`;
|
|
17567
|
+
const response = await fetcher(config.endpoint, {
|
|
17568
|
+
method: "POST",
|
|
17569
|
+
headers,
|
|
17570
|
+
body: body2,
|
|
17571
|
+
signal: controller.signal
|
|
17572
|
+
});
|
|
17573
|
+
if (!response.ok) {
|
|
17574
|
+
if (response.status === 413) {
|
|
17575
|
+
throw new SemanticJudgePayloadTooLargeError("Semantic judge rejected an oversized request with HTTP 413");
|
|
17576
|
+
}
|
|
17577
|
+
const retryable = response.status === 408 || response.status === 429 || response.status === 529 || response.status >= 500;
|
|
17578
|
+
throw new SemanticJudgeRequestError(`Semantic judge returned HTTP ${String(response.status)}`, retryable);
|
|
17579
|
+
}
|
|
17580
|
+
const raw = JSON.parse(await responseText(response));
|
|
17581
|
+
if (!isRecord3(raw)) {
|
|
17582
|
+
throw new SemanticJudgeRequestError("Semantic judge response must be an object", false);
|
|
17583
|
+
}
|
|
17584
|
+
return raw;
|
|
17585
|
+
} catch (error) {
|
|
17586
|
+
if (parentSignal?.aborted)
|
|
17587
|
+
throw error;
|
|
17588
|
+
const retryable = error instanceof SemanticJudgePayloadTooLargeError ? false : error instanceof SemanticJudgeRequestError ? error.retryable : true;
|
|
17589
|
+
if (!retryable || attempt >= config.maxRetries)
|
|
17590
|
+
throw error;
|
|
17591
|
+
await waitForRetry2(retryDelay2(attempt), parentSignal);
|
|
17592
|
+
} finally {
|
|
17593
|
+
clearTimeout(timeout);
|
|
17594
|
+
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
17595
|
+
}
|
|
17596
|
+
}
|
|
17597
|
+
throw new SemanticJudgeRequestError("Semantic judge request failed", false);
|
|
17598
|
+
}
|
|
17599
|
+
function responseModel(raw, config) {
|
|
17600
|
+
const model = raw.model ?? config.model;
|
|
17601
|
+
if (typeof model !== "string" || model.length === 0 || model.length > 128 || /[\r\n\0]/u.test(model)) {
|
|
17602
|
+
throw new SemanticJudgeRequestError("Semantic judge response model must be bounded single-line text", false);
|
|
17603
|
+
}
|
|
17604
|
+
return model;
|
|
17605
|
+
}
|
|
17606
|
+
function responseUsage(raw) {
|
|
17607
|
+
const usage = isRecord3(raw.usage) ? raw.usage : undefined;
|
|
17608
|
+
const inputTokens = typeof usage?.input_tokens === "number" && Number.isSafeInteger(usage.input_tokens) && usage.input_tokens >= 0 ? usage.input_tokens : undefined;
|
|
17609
|
+
const outputTokens = typeof usage?.output_tokens === "number" && Number.isSafeInteger(usage.output_tokens) && usage.output_tokens >= 0 ? usage.output_tokens : undefined;
|
|
17610
|
+
return {
|
|
17611
|
+
...inputTokens === undefined ? {} : { inputTokens },
|
|
17612
|
+
...outputTokens === undefined ? {} : { outputTokens }
|
|
17613
|
+
};
|
|
17614
|
+
}
|
|
17615
|
+
function parseHttpJsonResult(raw, candidates, config, elapsedMs) {
|
|
17616
|
+
if (raw.version !== 1 || !Array.isArray(raw.judgments)) {
|
|
17617
|
+
throw new SemanticJudgeRequestError("HTTP JSON semantic judge response must contain version 1 judgments", false);
|
|
17618
|
+
}
|
|
17619
|
+
const candidateIndexes = new Map(candidates.map((candidate, index) => [candidate.id, index]));
|
|
17620
|
+
const judgments = new Map;
|
|
17621
|
+
for (const value of raw.judgments) {
|
|
17622
|
+
if (!isRecord3(value) || typeof value.id !== "string") {
|
|
17623
|
+
throw new SemanticJudgeRequestError("HTTP JSON semantic judge judgment has an invalid id", false);
|
|
17624
|
+
}
|
|
17625
|
+
const candidateIndex = candidateIndexes.get(value.id);
|
|
17626
|
+
if (candidateIndex === undefined || judgments.has(candidateIndex)) {
|
|
17627
|
+
throw new SemanticJudgeRequestError("HTTP JSON semantic judge response contains an unknown or duplicate candidate", false);
|
|
17628
|
+
}
|
|
17629
|
+
if (!isClassification(value.choice)) {
|
|
17630
|
+
throw new SemanticJudgeRequestError(`HTTP JSON semantic judge response has an invalid choice for ${value.id}`, false);
|
|
17631
|
+
}
|
|
17632
|
+
const confidence = value.confidence === undefined ? undefined : probability(value.confidence, "confidence");
|
|
17633
|
+
judgments.set(candidateIndex, {
|
|
17634
|
+
candidateIndex,
|
|
17635
|
+
classification: value.choice,
|
|
17636
|
+
probability: probability(value.probability, "probability"),
|
|
17637
|
+
...confidence === undefined ? {} : { confidence }
|
|
17638
|
+
});
|
|
17639
|
+
}
|
|
17640
|
+
if (judgments.size !== candidates.length) {
|
|
17641
|
+
throw new SemanticJudgeRequestError(`HTTP JSON semantic judge response judged ${String(judgments.size)} of ${String(candidates.length)} candidates`, false);
|
|
17642
|
+
}
|
|
17643
|
+
const usage = responseUsage(raw);
|
|
17644
|
+
return {
|
|
17645
|
+
model: responseModel(raw, config),
|
|
17646
|
+
judgments: candidates.map((_candidate, index) => judgments.get(index)),
|
|
17647
|
+
...usage,
|
|
17648
|
+
elapsedMs
|
|
17649
|
+
};
|
|
17650
|
+
}
|
|
17182
17651
|
function createJevRunner(config, key, fetcher) {
|
|
17183
17652
|
return async (query, candidates, parentSignal) => {
|
|
17184
17653
|
const startedAt = performance.now();
|
|
17185
17654
|
const body2 = JSON.stringify(requestBody(query, candidates, config.model));
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
for (let attempt = 0;attempt <= config.maxRetries; attempt += 1) {
|
|
17190
|
-
const controller = new AbortController;
|
|
17191
|
-
const onParentAbort = () => controller.abort(parentSignal?.reason);
|
|
17192
|
-
parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
17193
|
-
const timeout = setTimeout(() => controller.abort(new Error("Semantic judge request timed out")), config.timeoutMs);
|
|
17194
|
-
try {
|
|
17195
|
-
const response = await fetcher(config.endpoint, {
|
|
17196
|
-
method: "POST",
|
|
17197
|
-
headers: {
|
|
17198
|
-
"content-type": "application/json",
|
|
17199
|
-
authorization: `Bearer ${key}`
|
|
17200
|
-
},
|
|
17201
|
-
body: body2,
|
|
17202
|
-
signal: controller.signal
|
|
17203
|
-
});
|
|
17204
|
-
if (!response.ok) {
|
|
17205
|
-
if (response.status === 413) {
|
|
17206
|
-
throw new SemanticJudgePayloadTooLargeError("Semantic judge rejected an oversized request with HTTP 413");
|
|
17207
|
-
}
|
|
17208
|
-
const retryable = response.status === 408 || response.status === 429 || response.status === 529 || response.status >= 500;
|
|
17209
|
-
throw new SemanticJudgeRequestError(`Semantic judge returned HTTP ${String(response.status)}`, retryable);
|
|
17210
|
-
}
|
|
17211
|
-
const raw = JSON.parse(await responseText(response));
|
|
17212
|
-
if (!isRecord3(raw) || !isRecord3(raw.answers)) {
|
|
17213
|
-
throw new SemanticJudgeRequestError("Semantic judge response did not contain answers", false);
|
|
17214
|
-
}
|
|
17215
|
-
const answers = raw.answers;
|
|
17216
|
-
const judgments = candidates.map((candidate, index) => parseJudgment(answers[candidate.id], index));
|
|
17217
|
-
const model = raw.model ?? config.model;
|
|
17218
|
-
if (typeof model !== "string" || model.length === 0 || model.length > 128 || /[\r\n\0]/u.test(model)) {
|
|
17219
|
-
throw new SemanticJudgeRequestError("Semantic judge response model must be bounded single-line text", false);
|
|
17220
|
-
}
|
|
17221
|
-
const usage = isRecord3(raw.usage) ? raw.usage : undefined;
|
|
17222
|
-
const inputTokens = typeof usage?.input_tokens === "number" && Number.isSafeInteger(usage.input_tokens) && usage.input_tokens >= 0 ? usage.input_tokens : undefined;
|
|
17223
|
-
const outputTokens = typeof usage?.output_tokens === "number" && Number.isSafeInteger(usage.output_tokens) && usage.output_tokens >= 0 ? usage.output_tokens : undefined;
|
|
17224
|
-
return {
|
|
17225
|
-
model,
|
|
17226
|
-
judgments,
|
|
17227
|
-
...inputTokens === undefined ? {} : { inputTokens },
|
|
17228
|
-
...outputTokens === undefined ? {} : { outputTokens },
|
|
17229
|
-
elapsedMs: Math.round(performance.now() - startedAt)
|
|
17230
|
-
};
|
|
17231
|
-
} catch (error) {
|
|
17232
|
-
if (parentSignal?.aborted)
|
|
17233
|
-
throw error;
|
|
17234
|
-
const retryable = error instanceof SemanticJudgePayloadTooLargeError ? false : error instanceof SemanticJudgeRequestError ? error.retryable : true;
|
|
17235
|
-
if (!retryable || attempt >= config.maxRetries)
|
|
17236
|
-
throw error;
|
|
17237
|
-
await waitForRetry(retryDelay(attempt), parentSignal);
|
|
17238
|
-
} finally {
|
|
17239
|
-
clearTimeout(timeout);
|
|
17240
|
-
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
17241
|
-
}
|
|
17655
|
+
const raw = await fetchSemanticResponse(config, key, fetcher, body2, parentSignal);
|
|
17656
|
+
if (!isRecord3(raw.answers)) {
|
|
17657
|
+
throw new SemanticJudgeRequestError("Semantic judge response did not contain answers", false);
|
|
17242
17658
|
}
|
|
17243
|
-
|
|
17659
|
+
const answers = raw.answers;
|
|
17660
|
+
const judgments = candidates.map((candidate, index) => parseJudgment(answers[candidate.id], index));
|
|
17661
|
+
const usage = responseUsage(raw);
|
|
17662
|
+
return {
|
|
17663
|
+
model: responseModel(raw, config),
|
|
17664
|
+
judgments,
|
|
17665
|
+
...usage,
|
|
17666
|
+
elapsedMs: Math.round(performance.now() - startedAt)
|
|
17667
|
+
};
|
|
17668
|
+
};
|
|
17669
|
+
}
|
|
17670
|
+
function createHttpJsonRunner(config, key, fetcher) {
|
|
17671
|
+
return async (query, candidates, parentSignal) => {
|
|
17672
|
+
const startedAt = performance.now();
|
|
17673
|
+
const raw = await fetchSemanticResponse(config, key, fetcher, JSON.stringify(httpJsonRequestBody(query, candidates, config.model)), parentSignal);
|
|
17674
|
+
return parseHttpJsonResult(raw, candidates, config, Math.round(performance.now() - startedAt));
|
|
17244
17675
|
};
|
|
17245
17676
|
}
|
|
17246
17677
|
function createDisabledSemanticJudgeIntegration(config) {
|
|
@@ -17249,14 +17680,16 @@ function createDisabledSemanticJudgeIntegration(config) {
|
|
|
17249
17680
|
function createSemanticJudgeIntegration(config, environment = process.env, fetcher = fetch) {
|
|
17250
17681
|
if (!config.enabled)
|
|
17251
17682
|
return createDisabledSemanticJudgeIntegration(config);
|
|
17252
|
-
const key = environment[config.apiKeyEnv]?.trim();
|
|
17253
|
-
if (!key) {
|
|
17254
|
-
throw new SemanticJudgeConfigurationError(`Semantic judge is enabled, but environment variable ${config.apiKeyEnv} is missing or empty`);
|
|
17683
|
+
const key = config.apiKeyEnv ? environment[config.apiKeyEnv]?.trim() : undefined;
|
|
17684
|
+
if (config.provider === "jev" && !key) {
|
|
17685
|
+
throw new SemanticJudgeConfigurationError(`Semantic judge is enabled, but environment variable ${config.apiKeyEnv ?? "TYPESAFE_API_KEY"} is missing or empty`);
|
|
17255
17686
|
}
|
|
17256
|
-
if (config.provider
|
|
17257
|
-
throw new SemanticJudgeConfigurationError(`
|
|
17687
|
+
if (config.provider === "http-json" && config.apiKeyEnv && !key) {
|
|
17688
|
+
throw new SemanticJudgeConfigurationError(`Semantic judge is enabled, but environment variable ${config.apiKeyEnv} is missing or empty`);
|
|
17258
17689
|
}
|
|
17259
|
-
|
|
17690
|
+
if (config.provider === "jev")
|
|
17691
|
+
return { config, runner: createJevRunner(config, key, fetcher) };
|
|
17692
|
+
return { config, runner: createHttpJsonRunner(config, key, fetcher) };
|
|
17260
17693
|
}
|
|
17261
17694
|
function createConfiguredSemanticJudgeIntegration(config, environment = process.env) {
|
|
17262
17695
|
const judge = config.semanticJudge ?? DEFAULT_SEMANTIC_JUDGE_CONFIG;
|
|
@@ -19078,7 +19511,7 @@ class EvidenceService {
|
|
|
19078
19511
|
throw new SiftLightError(`No outline provider is registered for ${document2.path}; use mode=capabilities to inspect available language modes`);
|
|
19079
19512
|
}
|
|
19080
19513
|
if (!language && !isPython || language === "go") {
|
|
19081
|
-
const extension =
|
|
19514
|
+
const extension = extname5(document2.path) || "extensionless source";
|
|
19082
19515
|
throw new SiftLightError(`${input.mode} is unavailable for ${extension}; choose a language capability from mode=capabilities or use ordinary content search`);
|
|
19083
19516
|
}
|
|
19084
19517
|
if (input.mode === "outline") {
|
|
@@ -19271,7 +19704,7 @@ function normalizeRequestAliases(request) {
|
|
|
19271
19704
|
}
|
|
19272
19705
|
|
|
19273
19706
|
// src/format.ts
|
|
19274
|
-
import { readFile as
|
|
19707
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
19275
19708
|
var RESULT_METADATA_RESERVE_BYTES = 1024;
|
|
19276
19709
|
var RESULT_METADATA_RESERVE_CHARACTERS = 512;
|
|
19277
19710
|
|
|
@@ -19405,7 +19838,7 @@ async function loadContextLines(match, expectedRevision, cache, signal) {
|
|
|
19405
19838
|
cache.set(match.absolutePath, changed);
|
|
19406
19839
|
return changed;
|
|
19407
19840
|
}
|
|
19408
|
-
const content = await
|
|
19841
|
+
const content = await readFile6(match.absolutePath, { encoding: "utf8", signal });
|
|
19409
19842
|
const afterRevision = await getSourceRevision(match.absolutePath);
|
|
19410
19843
|
if (!afterRevision || !sameSourceRevision(expectedRevision, afterRevision)) {
|
|
19411
19844
|
const changed = { status: "changed" };
|
|
@@ -19730,7 +20163,7 @@ ${lineRows.join(`
|
|
|
19730
20163
|
}
|
|
19731
20164
|
|
|
19732
20165
|
// src/snapshot-store.ts
|
|
19733
|
-
import { randomUUID as
|
|
20166
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
19734
20167
|
class SnapshotStore {
|
|
19735
20168
|
#snapshots = new Map;
|
|
19736
20169
|
#expired = new Set;
|
|
@@ -19755,7 +20188,7 @@ class SnapshotStore {
|
|
|
19755
20188
|
const now = this.#now();
|
|
19756
20189
|
const snapshot = {
|
|
19757
20190
|
...scan,
|
|
19758
|
-
id:
|
|
20191
|
+
id: randomUUID8(),
|
|
19759
20192
|
createdAt: now,
|
|
19760
20193
|
lastAccessedAt: now
|
|
19761
20194
|
};
|
|
@@ -20525,7 +20958,7 @@ class SiftLightService {
|
|
|
20525
20958
|
}
|
|
20526
20959
|
mediaSearch(value, cwd, signal) {
|
|
20527
20960
|
if (!this.#media)
|
|
20528
|
-
throw new SiftLightError("Visual search is disabled; configure nativeMedia and restart the host");
|
|
20961
|
+
throw new SiftLightError("Visual search is disabled; configure nativeMedia or media and restart the host");
|
|
20529
20962
|
return this.#media.search(value, cwd, signal);
|
|
20530
20963
|
}
|
|
20531
20964
|
#dataSources;
|
|
@@ -20541,7 +20974,8 @@ class SiftLightService {
|
|
|
20541
20974
|
#operations;
|
|
20542
20975
|
#reusableSummarySnapshots = new WeakSet;
|
|
20543
20976
|
constructor(options) {
|
|
20544
|
-
|
|
20977
|
+
const mediaConfig = options.nativeMedia ?? options.media;
|
|
20978
|
+
this.#media = mediaConfig ? new MediaSearch(mediaConfig) : undefined;
|
|
20545
20979
|
this.#dataSources = new DataSources(options.dataSources);
|
|
20546
20980
|
this.#runRipgrep = options.runRipgrep;
|
|
20547
20981
|
this.#snapshots = options.snapshots ?? new SnapshotStore;
|