akm-cli 0.9.15-beta.1 → 0.9.15-beta.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/CHANGELOG.md +175 -13
- package/dist/akm +54 -1
- package/dist/akm-migrate +34 -1
- package/dist/cli.js +37 -1
- package/dist/commands/improve/locks.js +3 -2
- package/dist/commands/sources/installed-stashes.js +58 -16
- package/dist/commands/sources/stash-cli.js +17 -0
- package/dist/core/config/schema/embedding.js +41 -0
- package/dist/core/file-lock.js +49 -15
- package/dist/core/parent-watchdog.js +64 -0
- package/dist/core/run-lock.js +13 -2
- package/dist/indexer/index-rebuild-lock.js +4 -4
- package/dist/indexer/index-written-assets.js +9 -1
- package/dist/indexer/indexer.js +79 -16
- package/dist/indexer/materialize-embeddings.js +299 -33
- package/dist/indexer/search/search-source.js +23 -1
- package/dist/llm/embedders/remote.js +340 -42
- package/dist/scripts/akm-migrate-node.js +398 -77
- package/dist/scripts/akm-migrate.js +398 -77
- package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
- package/dist/storage/repositories/index-schema.js +16 -0
- package/dist/tasks/run/run-native-task.js +23 -1
- package/docs/migration/release-notes/0.9.15.md +85 -4
- package/docs/migration/release-notes/README.md +3 -2
- package/docs/reference/cli.md +28 -3
- package/docs/reference/configuration.md +67 -15
- package/package.json +1 -1
- package/schemas/akm-config.json +39 -0
|
@@ -35703,7 +35703,7 @@ var require_libvips = __commonJS((exports, module) => {
|
|
|
35703
35703
|
SPDX-License-Identifier: Apache-2.0
|
|
35704
35704
|
*/
|
|
35705
35705
|
var { spawnSync: spawnSync6 } = __require("child_process");
|
|
35706
|
-
var { createHash:
|
|
35706
|
+
var { createHash: createHash11 } = __require("crypto");
|
|
35707
35707
|
var semverCoerce = require_coerce2();
|
|
35708
35708
|
var semverGreaterThanOrEqualTo = require_gte2();
|
|
35709
35709
|
var semverSatisfies = require_satisfies2();
|
|
@@ -35791,7 +35791,7 @@ var require_libvips = __commonJS((exports, module) => {
|
|
|
35791
35791
|
}
|
|
35792
35792
|
return false;
|
|
35793
35793
|
};
|
|
35794
|
-
var sha512 = (s) =>
|
|
35794
|
+
var sha512 = (s) => createHash11("sha512").update(s).digest("hex");
|
|
35795
35795
|
var yarnLocator = () => {
|
|
35796
35796
|
try {
|
|
35797
35797
|
const identHash = sha512(`imgsharp-libvips-${buildPlatformArch()}`);
|
|
@@ -75245,11 +75245,14 @@ var EmbeddingConnectionConfigSchema = exports_external.object({
|
|
|
75245
75245
|
apiKey: symbolicOrWarnApiKey("embedding.apiKey").optional(),
|
|
75246
75246
|
dimension: positiveInt.max(4096).optional(),
|
|
75247
75247
|
localModel: exports_external.string().min(1).optional(),
|
|
75248
|
+
maxInputTokens: positiveInt.optional(),
|
|
75248
75249
|
maxTokens: positiveInt.optional(),
|
|
75249
75250
|
batchSize: positiveInt.optional(),
|
|
75250
75251
|
chunkSize: positiveInt.optional(),
|
|
75251
75252
|
contextLength: positiveInt.optional(),
|
|
75252
|
-
ollamaOptions: EmbeddingOllamaOptionsSchema.optional()
|
|
75253
|
+
ollamaOptions: EmbeddingOllamaOptionsSchema.optional(),
|
|
75254
|
+
timeoutMs: positiveInt.optional(),
|
|
75255
|
+
concurrency: positiveInt.max(16).optional()
|
|
75253
75256
|
}).passthrough();
|
|
75254
75257
|
|
|
75255
75258
|
// src/integrations/harnesses/ids.ts
|
|
@@ -76322,8 +76325,21 @@ function releaseLockRaw(lockPath) {
|
|
|
76322
76325
|
function tryAcquireLockSync(lockPath, payload) {
|
|
76323
76326
|
return withLockOperationMutex(lockPath, () => tryAcquireLockRaw(lockPath, payload));
|
|
76324
76327
|
}
|
|
76328
|
+
function launcherPidFromEnv() {
|
|
76329
|
+
const raw = process.env.AKM_LAUNCHER_PID;
|
|
76330
|
+
if (!raw)
|
|
76331
|
+
return;
|
|
76332
|
+
const pid = Number.parseInt(raw, 10);
|
|
76333
|
+
return Number.isInteger(pid) && pid > 0 ? pid : undefined;
|
|
76334
|
+
}
|
|
76325
76335
|
function createLockPayload(metadata = {}) {
|
|
76326
|
-
|
|
76336
|
+
const launcherPid = launcherPidFromEnv();
|
|
76337
|
+
return JSON.stringify({
|
|
76338
|
+
...metadata,
|
|
76339
|
+
pid: process.pid,
|
|
76340
|
+
...launcherPid !== undefined ? { launcherPid } : {},
|
|
76341
|
+
lockId: randomUUID()
|
|
76342
|
+
});
|
|
76327
76343
|
}
|
|
76328
76344
|
function probeLock(lockPath, opts) {
|
|
76329
76345
|
let snapshot;
|
|
@@ -76339,17 +76355,17 @@ function probeLock(lockPath, opts) {
|
|
|
76339
76355
|
return { state: "absent" };
|
|
76340
76356
|
const { rawContent, identity: identity3 } = snapshot;
|
|
76341
76357
|
const ageMs = Date.now() - identity3.mtimeMs;
|
|
76342
|
-
const holderPid =
|
|
76358
|
+
const { holderPid, launcherPid } = extractLockIdentity(rawContent);
|
|
76343
76359
|
if (holderPid === undefined) {
|
|
76344
76360
|
return { state: "stale", reason: "invalid_pid", ageMs, rawContent, identity: identity3 };
|
|
76345
76361
|
}
|
|
76346
76362
|
if (!isProcessAlive(holderPid)) {
|
|
76347
|
-
return { state: "stale", reason: "pid_dead", holderPid, ageMs, rawContent, identity: identity3 };
|
|
76363
|
+
return { state: "stale", reason: "pid_dead", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
|
|
76348
76364
|
}
|
|
76349
76365
|
if (opts?.staleAfterMs !== undefined && ageMs > opts.staleAfterMs) {
|
|
76350
|
-
return { state: "stale", reason: "age_exceeded", holderPid, ageMs, rawContent, identity: identity3 };
|
|
76366
|
+
return { state: "stale", reason: "age_exceeded", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
|
|
76351
76367
|
}
|
|
76352
|
-
return { state: "held", holderPid, ageMs, rawContent, identity: identity3 };
|
|
76368
|
+
return { state: "held", holderPid, launcherPid, ageMs, rawContent, identity: identity3 };
|
|
76353
76369
|
}
|
|
76354
76370
|
function reclaimStaleLock(lockPath, probe, options) {
|
|
76355
76371
|
if (probe.rawContent === undefined || probe.identity === undefined)
|
|
@@ -76417,21 +76433,25 @@ function releaseLock(ownership) {
|
|
|
76417
76433
|
}
|
|
76418
76434
|
});
|
|
76419
76435
|
}
|
|
76420
|
-
function
|
|
76436
|
+
function extractLockIdentity(content) {
|
|
76421
76437
|
const trimmed = content.trim();
|
|
76422
76438
|
if (!trimmed)
|
|
76423
|
-
return;
|
|
76439
|
+
return {};
|
|
76424
76440
|
if (trimmed.startsWith("{")) {
|
|
76425
76441
|
try {
|
|
76426
76442
|
const parsed = JSON.parse(trimmed);
|
|
76427
76443
|
const pid2 = typeof parsed.pid === "number" ? parsed.pid : Number.NaN;
|
|
76428
|
-
|
|
76444
|
+
const rawLauncherPid = typeof parsed.launcherPid === "number" ? parsed.launcherPid : Number.NaN;
|
|
76445
|
+
return {
|
|
76446
|
+
holderPid: Number.isInteger(pid2) && pid2 > 0 ? pid2 : undefined,
|
|
76447
|
+
launcherPid: Number.isInteger(rawLauncherPid) && rawLauncherPid > 0 ? rawLauncherPid : undefined
|
|
76448
|
+
};
|
|
76429
76449
|
} catch {
|
|
76430
|
-
return;
|
|
76450
|
+
return {};
|
|
76431
76451
|
}
|
|
76432
76452
|
}
|
|
76433
76453
|
const pid = Number.parseInt(trimmed, 10);
|
|
76434
|
-
return Number.isInteger(pid) && pid > 0 ? pid : undefined;
|
|
76454
|
+
return { holderPid: Number.isInteger(pid) && pid > 0 ? pid : undefined };
|
|
76435
76455
|
}
|
|
76436
76456
|
|
|
76437
76457
|
// src/core/config/config-io.ts
|
|
@@ -95693,7 +95713,7 @@ function listTxnJournalsTolerant(predicate) {
|
|
|
95693
95713
|
}
|
|
95694
95714
|
|
|
95695
95715
|
// src/commands/proposal/repository.ts
|
|
95696
|
-
import { createHash as
|
|
95716
|
+
import { createHash as createHash11, randomUUID as randomUUID6 } from "crypto";
|
|
95697
95717
|
import fs55 from "fs";
|
|
95698
95718
|
init_dist();
|
|
95699
95719
|
import path66 from "path";
|
|
@@ -95759,6 +95779,16 @@ import fs53 from "fs";
|
|
|
95759
95779
|
import path64 from "path";
|
|
95760
95780
|
init_errors();
|
|
95761
95781
|
init_paths();
|
|
95782
|
+
|
|
95783
|
+
// src/core/run-lock.ts
|
|
95784
|
+
init_errors();
|
|
95785
|
+
function formatLockHolderPid(holder) {
|
|
95786
|
+
if (holder.pid === null)
|
|
95787
|
+
return "unknown";
|
|
95788
|
+
return holder.launcherPid !== null ? `${holder.pid} (launcher ${holder.launcherPid})` : String(holder.pid);
|
|
95789
|
+
}
|
|
95790
|
+
|
|
95791
|
+
// src/indexer/index-written-assets.ts
|
|
95762
95792
|
init_warn();
|
|
95763
95793
|
|
|
95764
95794
|
// src/storage/repositories/index-connection.ts
|
|
@@ -96013,6 +96043,15 @@ function isCanonicalIndexGeneration(db) {
|
|
|
96013
96043
|
init_errors();
|
|
96014
96044
|
init_warn();
|
|
96015
96045
|
|
|
96046
|
+
// src/storage/repositories/embedding-salvage-repository.ts
|
|
96047
|
+
import { createHash as createHash9 } from "crypto";
|
|
96048
|
+
|
|
96049
|
+
// src/storage/repositories/embeddings-repository.ts
|
|
96050
|
+
function blobToEmbedding(blob) {
|
|
96051
|
+
const f32 = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
|
|
96052
|
+
return Array.from(f32);
|
|
96053
|
+
}
|
|
96054
|
+
|
|
96016
96055
|
// src/storage/repositories/index-meta-repository.ts
|
|
96017
96056
|
function getMeta(db, key) {
|
|
96018
96057
|
const row = db.prepare("SELECT value FROM index_meta WHERE key = ?").get(key);
|
|
@@ -96025,6 +96064,64 @@ function deleteMeta(db, key) {
|
|
|
96025
96064
|
db.prepare("DELETE FROM index_meta WHERE key = ?").run(key);
|
|
96026
96065
|
}
|
|
96027
96066
|
|
|
96067
|
+
// src/storage/repositories/index-sql.ts
|
|
96068
|
+
var SQLITE_CHUNK_SIZE = 500;
|
|
96069
|
+
|
|
96070
|
+
// src/storage/repositories/embedding-salvage-repository.ts
|
|
96071
|
+
function hashEmbeddableText(searchText) {
|
|
96072
|
+
return createHash9("sha256").update(searchText, "utf8").digest("hex");
|
|
96073
|
+
}
|
|
96074
|
+
function purgeEmbeddingSalvage(db) {
|
|
96075
|
+
db.exec("DELETE FROM embedding_salvage");
|
|
96076
|
+
}
|
|
96077
|
+
function relabelEmbeddingSalvageFingerprint(db, fromFingerprint, toFingerprint) {
|
|
96078
|
+
db.prepare("UPDATE embedding_salvage SET fingerprint = ? WHERE fingerprint = ?").run(toFingerprint, fromFingerprint);
|
|
96079
|
+
}
|
|
96080
|
+
function reuseSalvagedEmbeddings(db, entries, fingerprint, writeReused) {
|
|
96081
|
+
if (entries.length === 0)
|
|
96082
|
+
return { reusedCount: 0, remaining: [] };
|
|
96083
|
+
const anySalvageForFingerprint = db.prepare("SELECT 1 FROM embedding_salvage WHERE fingerprint = ? LIMIT 1").get(fingerprint);
|
|
96084
|
+
if (!anySalvageForFingerprint)
|
|
96085
|
+
return { reusedCount: 0, remaining: [...entries] };
|
|
96086
|
+
const hashes = entries.map((entry) => hashEmbeddableText(entry.searchText));
|
|
96087
|
+
const salvageByHash = new Map;
|
|
96088
|
+
const uniqueHashes = [...new Set(hashes)];
|
|
96089
|
+
for (let offset = 0;offset < uniqueHashes.length; offset += SQLITE_CHUNK_SIZE) {
|
|
96090
|
+
const chunk = uniqueHashes.slice(offset, offset + SQLITE_CHUNK_SIZE);
|
|
96091
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
96092
|
+
const rows = db.prepare(`SELECT content_hash AS contentHash, embedding FROM embedding_salvage WHERE fingerprint = ? AND content_hash IN (${placeholders})`).all(fingerprint, ...chunk);
|
|
96093
|
+
for (const row of rows)
|
|
96094
|
+
salvageByHash.set(row.contentHash, row.embedding);
|
|
96095
|
+
}
|
|
96096
|
+
if (salvageByHash.size === 0)
|
|
96097
|
+
return { reusedCount: 0, remaining: [...entries] };
|
|
96098
|
+
let reusedCount = 0;
|
|
96099
|
+
const remaining = [];
|
|
96100
|
+
for (let offset = 0;offset < entries.length; offset += SQLITE_CHUNK_SIZE) {
|
|
96101
|
+
const end = Math.min(offset + SQLITE_CHUNK_SIZE, entries.length);
|
|
96102
|
+
const chunkMatches = [];
|
|
96103
|
+
for (let i = offset;i < end; i++) {
|
|
96104
|
+
const entry = entries[i];
|
|
96105
|
+
const blob = salvageByHash.get(hashes[i]);
|
|
96106
|
+
if (blob)
|
|
96107
|
+
chunkMatches.push({ entry, blob });
|
|
96108
|
+
else
|
|
96109
|
+
remaining.push(entry);
|
|
96110
|
+
}
|
|
96111
|
+
if (chunkMatches.length === 0)
|
|
96112
|
+
continue;
|
|
96113
|
+
db.transaction(() => {
|
|
96114
|
+
for (const { entry, blob } of chunkMatches) {
|
|
96115
|
+
if (writeReused(entry, blobToEmbedding(blob)))
|
|
96116
|
+
reusedCount++;
|
|
96117
|
+
else
|
|
96118
|
+
remaining.push(entry);
|
|
96119
|
+
}
|
|
96120
|
+
})();
|
|
96121
|
+
}
|
|
96122
|
+
return { reusedCount, remaining };
|
|
96123
|
+
}
|
|
96124
|
+
|
|
96028
96125
|
// src/storage/repositories/index-vec-repository.ts
|
|
96029
96126
|
import { createRequire as createRequire3 } from "module";
|
|
96030
96127
|
|
|
@@ -96066,9 +96163,6 @@ function cosineSimilarity(a, b) {
|
|
|
96066
96163
|
return denom === 0 ? 0 : dot / denom;
|
|
96067
96164
|
}
|
|
96068
96165
|
|
|
96069
|
-
// src/storage/repositories/index-sql.ts
|
|
96070
|
-
var SQLITE_CHUNK_SIZE = 500;
|
|
96071
|
-
|
|
96072
96166
|
// src/storage/repositories/index-vec-repository.ts
|
|
96073
96167
|
var vecStatus = new WeakMap;
|
|
96074
96168
|
function loadVecExtension(db) {
|
|
@@ -96328,11 +96422,11 @@ init_warn();
|
|
|
96328
96422
|
|
|
96329
96423
|
// src/core/asset/markdown-fragments.ts
|
|
96330
96424
|
init_markdown();
|
|
96331
|
-
import { createHash as
|
|
96425
|
+
import { createHash as createHash10 } from "crypto";
|
|
96332
96426
|
var MARKDOWN_FRAGMENT_MAX_CHARS = 1600;
|
|
96333
96427
|
var MARKDOWN_FRAGMENT_PREFIX = "akm-fragment-";
|
|
96334
96428
|
function hash3(text) {
|
|
96335
|
-
return
|
|
96429
|
+
return createHash10("sha256").update(text).digest("hex");
|
|
96336
96430
|
}
|
|
96337
96431
|
function uniqueSlugs(body) {
|
|
96338
96432
|
const out = new Map;
|
|
@@ -96613,6 +96707,7 @@ function getEmbeddableEntryCount(db) {
|
|
|
96613
96707
|
}
|
|
96614
96708
|
|
|
96615
96709
|
// src/indexer/materialize-embeddings.ts
|
|
96710
|
+
init_paths();
|
|
96616
96711
|
init_warn();
|
|
96617
96712
|
|
|
96618
96713
|
// src/llm/embedders/cache.ts
|
|
@@ -96852,6 +96947,39 @@ var DEFAULT_TOKEN_BUDGET = 8000;
|
|
|
96852
96947
|
function estimateTokenCount(text) {
|
|
96853
96948
|
return Math.round(text.length / 4);
|
|
96854
96949
|
}
|
|
96950
|
+
var DEFAULT_MAX_INPUT_TOKENS = 512;
|
|
96951
|
+
function capEmbeddingText(text, maxTokens) {
|
|
96952
|
+
if (estimateTokenCount(text) <= maxTokens)
|
|
96953
|
+
return { text, truncated: false };
|
|
96954
|
+
const charBudget = Math.max(0, maxTokens * 4);
|
|
96955
|
+
let cut = Math.min(charBudget, text.length);
|
|
96956
|
+
if (cut > 0 && cut < text.length) {
|
|
96957
|
+
const code = text.charCodeAt(cut);
|
|
96958
|
+
if (code >= 56320 && code <= 57343)
|
|
96959
|
+
cut -= 1;
|
|
96960
|
+
}
|
|
96961
|
+
return { text: text.slice(0, cut), truncated: true };
|
|
96962
|
+
}
|
|
96963
|
+
var DEFAULT_EMBEDDING_TIMEOUT_MS = 120000;
|
|
96964
|
+
function resolveEmbeddingTimeoutMs(config) {
|
|
96965
|
+
return config.timeoutMs ?? DEFAULT_EMBEDDING_TIMEOUT_MS;
|
|
96966
|
+
}
|
|
96967
|
+
function scaleEmbeddingTimeoutMs(timeoutMs, requestTokens, tokenBudget) {
|
|
96968
|
+
const scaled = tokenBudget > 0 ? timeoutMs * (requestTokens / tokenBudget) : timeoutMs;
|
|
96969
|
+
return Math.min(Math.max(scaled, 30000), timeoutMs);
|
|
96970
|
+
}
|
|
96971
|
+
function isEmbeddingTimeoutError(err) {
|
|
96972
|
+
if (!(err instanceof Error))
|
|
96973
|
+
return false;
|
|
96974
|
+
if (err.name === "BodyReadTimeoutError")
|
|
96975
|
+
return true;
|
|
96976
|
+
return err.message.startsWith("Request timed out after ");
|
|
96977
|
+
}
|
|
96978
|
+
var embeddingTimeoutBackoffOverrideForTests;
|
|
96979
|
+
function embeddingTimeoutRetryBackoffMs(timeoutAttempt = 0) {
|
|
96980
|
+
const { baseMs, maxMs } = embeddingTimeoutBackoffOverrideForTests ?? { baseMs: 5000, maxMs: 60000 };
|
|
96981
|
+
return backoffDelay(timeoutAttempt, baseMs, maxMs);
|
|
96982
|
+
}
|
|
96855
96983
|
|
|
96856
96984
|
class ContextExceededError extends Error {
|
|
96857
96985
|
constructor(message) {
|
|
@@ -96859,13 +96987,15 @@ class ContextExceededError extends Error {
|
|
|
96859
96987
|
this.name = "ContextExceededError";
|
|
96860
96988
|
}
|
|
96861
96989
|
}
|
|
96862
|
-
var CONTEXT_EXCEEDED_PATTERN = /exceed_context_size_error|context size|context length|too many tokens/i;
|
|
96990
|
+
var CONTEXT_EXCEEDED_PATTERN = /exceed_context_size_error|context size|context length|too many tokens|input is too large to process|physical batch size|ubatch/i;
|
|
96863
96991
|
function isContextExceededResponse(status, body) {
|
|
96864
96992
|
if (status === 413)
|
|
96865
96993
|
return true;
|
|
96866
96994
|
return CONTEXT_EXCEEDED_PATTERN.test(body);
|
|
96867
96995
|
}
|
|
96868
96996
|
function resolveEmbeddingConcurrency(config) {
|
|
96997
|
+
if (typeof config.concurrency === "number")
|
|
96998
|
+
return config.concurrency;
|
|
96869
96999
|
return defaultConcurrencyForEndpoint(config.endpoint);
|
|
96870
97000
|
}
|
|
96871
97001
|
function buildTokenBoundedBatches(texts, tokenBudget, maxCount) {
|
|
@@ -96921,20 +97051,21 @@ class RemoteEmbedder {
|
|
|
96921
97051
|
if (ollamaOpts) {
|
|
96922
97052
|
body.options = ollamaOpts;
|
|
96923
97053
|
}
|
|
97054
|
+
const timeoutMs = resolveEmbeddingTimeoutMs(this.config);
|
|
96924
97055
|
const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(this.endpoint), {
|
|
96925
97056
|
method: "POST",
|
|
96926
97057
|
headers,
|
|
96927
97058
|
body: JSON.stringify(body)
|
|
96928
|
-
},
|
|
97059
|
+
}, timeoutMs, signal);
|
|
96929
97060
|
if (!response.ok) {
|
|
96930
|
-
const errBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs:
|
|
97061
|
+
const errBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }).catch((err) => {
|
|
96931
97062
|
if (signal?.aborted)
|
|
96932
97063
|
throw err;
|
|
96933
97064
|
return "";
|
|
96934
97065
|
});
|
|
96935
97066
|
throw new Error(`Embedding request failed (${response.status}): ${this.safeErrorBody(errBody)}`);
|
|
96936
97067
|
}
|
|
96937
|
-
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs:
|
|
97068
|
+
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }));
|
|
96938
97069
|
if (!json.data?.[0]?.embedding) {
|
|
96939
97070
|
throw new Error(`Unexpected embedding response format: missing data[0].embedding.${embeddingEndpointPathHint(this.endpoint)}`);
|
|
96940
97071
|
}
|
|
@@ -96946,9 +97077,10 @@ class RemoteEmbedder {
|
|
|
96946
97077
|
const results = new Array(texts.length).fill(undefined);
|
|
96947
97078
|
const headers = this.buildHeaders();
|
|
96948
97079
|
const ollamaOpts = resolveOllamaOptions(this.config);
|
|
96949
|
-
const tokenBudget = this.config.maxTokens ??
|
|
97080
|
+
const tokenBudget = this.config.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
96950
97081
|
const maxCount = this.config.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
96951
97082
|
const textBatches = buildTokenBoundedBatches(texts, tokenBudget, maxCount);
|
|
97083
|
+
const configuredTimeoutMs = resolveEmbeddingTimeoutMs(this.config);
|
|
96952
97084
|
const dispatchAbort = new AbortController;
|
|
96953
97085
|
const stopDispatch = (reason) => {
|
|
96954
97086
|
if (!dispatchAbort.signal.aborted)
|
|
@@ -96964,66 +97096,132 @@ class RemoteEmbedder {
|
|
|
96964
97096
|
}
|
|
96965
97097
|
}
|
|
96966
97098
|
let firstOnBatchError;
|
|
96967
|
-
const commitBatch = (indices, embeddings, model) => {
|
|
97099
|
+
const commitBatch = (indices, embeddings, model, outcome) => {
|
|
96968
97100
|
if (!onBatch)
|
|
96969
97101
|
return;
|
|
96970
97102
|
if (firstOnBatchError !== undefined)
|
|
96971
97103
|
return;
|
|
96972
97104
|
try {
|
|
96973
|
-
onBatch(indices, embeddings, model);
|
|
97105
|
+
onBatch(indices, embeddings, model, outcome);
|
|
96974
97106
|
} catch (err) {
|
|
96975
97107
|
firstOnBatchError = err;
|
|
96976
97108
|
stopDispatch(err);
|
|
96977
97109
|
}
|
|
96978
97110
|
};
|
|
96979
|
-
const requestAndCommit = async (indices) => {
|
|
97111
|
+
const requestAndCommit = async (indices, batchIndex, isTimeoutRetry = false, timeoutAttempt = 0) => {
|
|
97112
|
+
if (dispatchAbort.signal.aborted)
|
|
97113
|
+
return;
|
|
96980
97114
|
const batch = indices.map((i) => texts[i]);
|
|
97115
|
+
const requestTokens = batch.reduce((sum, text) => sum + estimateTokenCount(text), 0);
|
|
97116
|
+
const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens, tokenBudget);
|
|
97117
|
+
const requestStart = Date.now();
|
|
96981
97118
|
let batchEmbeddings;
|
|
96982
97119
|
let responseModel;
|
|
97120
|
+
let outcome;
|
|
97121
|
+
let failureReason;
|
|
96983
97122
|
try {
|
|
96984
|
-
const { vectors, model } = await this.requestBatch(batch, headers, ollamaOpts, signal);
|
|
97123
|
+
const { vectors, model } = await this.requestBatch(batch, headers, ollamaOpts, requestTimeoutMs, signal);
|
|
96985
97124
|
for (let k2 = 0;k2 < indices.length; k2++) {
|
|
96986
97125
|
results[indices[k2]] = vectors[k2];
|
|
96987
97126
|
}
|
|
96988
97127
|
batchEmbeddings = indices.map((i) => results[i]);
|
|
96989
97128
|
responseModel = model;
|
|
97129
|
+
outcome = "stored";
|
|
96990
97130
|
} catch (err) {
|
|
96991
97131
|
if (signal?.aborted)
|
|
96992
97132
|
throw err;
|
|
96993
97133
|
if (err instanceof ContextExceededError && indices.length > 1) {
|
|
96994
97134
|
const mid = Math.ceil(indices.length / 2);
|
|
96995
|
-
await requestAndCommit(indices.slice(0, mid));
|
|
96996
|
-
await requestAndCommit(indices.slice(mid));
|
|
97135
|
+
await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt);
|
|
97136
|
+
await requestAndCommit(indices.slice(mid), batchIndex, false, timeoutAttempt);
|
|
97137
|
+
return;
|
|
97138
|
+
}
|
|
97139
|
+
const timedOut = isEmbeddingTimeoutError(err);
|
|
97140
|
+
if (timedOut && !isTimeoutRetry) {
|
|
97141
|
+
const backoffMs = embeddingTimeoutRetryBackoffMs(timeoutAttempt);
|
|
97142
|
+
warnVerbose(`[embed] batch of ${batch.length} document(s) timed out after ${requestTimeoutMs}ms; retrying once after a ${Math.round(backoffMs)}ms backoff`);
|
|
97143
|
+
commitBatch(indices, indices.map(() => {
|
|
97144
|
+
return;
|
|
97145
|
+
}), undefined, {
|
|
97146
|
+
batchIndex,
|
|
97147
|
+
batchCount: textBatches.length,
|
|
97148
|
+
docCount: indices.length,
|
|
97149
|
+
requestTokens,
|
|
97150
|
+
elapsedMs: backoffMs,
|
|
97151
|
+
outcome: "retrying",
|
|
97152
|
+
reason: "timed out"
|
|
97153
|
+
});
|
|
97154
|
+
await abortableDelay(backoffMs, signal, "embedding interrupted during retry backoff");
|
|
97155
|
+
if (!dispatchAbort.signal.aborted) {
|
|
97156
|
+
return requestAndCommit(indices, batchIndex, true, timeoutAttempt);
|
|
97157
|
+
}
|
|
97158
|
+
} else if (timedOut && indices.length > 1) {
|
|
97159
|
+
const mid = Math.ceil(indices.length / 2);
|
|
97160
|
+
await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt + 1);
|
|
97161
|
+
await requestAndCommit(indices.slice(mid), batchIndex, false, timeoutAttempt + 1);
|
|
96997
97162
|
return;
|
|
96998
97163
|
}
|
|
96999
97164
|
const message = err instanceof Error ? err.message : String(err);
|
|
97000
97165
|
const reason = err instanceof ContextExceededError ? "context-window-exceeded" : "batch-request-failed";
|
|
97001
|
-
|
|
97002
|
-
|
|
97003
|
-
|
|
97004
|
-
|
|
97166
|
+
const failureKind = reason === "batch-request-failed" ? timedOut ? "timeout" : "network-error" : undefined;
|
|
97167
|
+
let stopRequested = false;
|
|
97168
|
+
for (const [k2, idx] of indices.entries()) {
|
|
97169
|
+
if (onSkip?.({
|
|
97170
|
+
index: idx,
|
|
97171
|
+
reason,
|
|
97172
|
+
message,
|
|
97173
|
+
batchStart: k2 === 0,
|
|
97174
|
+
batchSize: indices.length,
|
|
97175
|
+
failureKind
|
|
97176
|
+
}) === false)
|
|
97177
|
+
stopRequested = true;
|
|
97178
|
+
}
|
|
97179
|
+
if (stopRequested)
|
|
97180
|
+
stopDispatch();
|
|
97005
97181
|
batchEmbeddings = indices.map(() => {
|
|
97006
97182
|
return;
|
|
97007
97183
|
});
|
|
97008
|
-
|
|
97009
|
-
|
|
97184
|
+
outcome = "failed";
|
|
97185
|
+
failureReason = message;
|
|
97186
|
+
}
|
|
97187
|
+
commitBatch(indices, batchEmbeddings, responseModel, {
|
|
97188
|
+
batchIndex,
|
|
97189
|
+
batchCount: textBatches.length,
|
|
97190
|
+
docCount: indices.length,
|
|
97191
|
+
requestTokens,
|
|
97192
|
+
elapsedMs: Date.now() - requestStart,
|
|
97193
|
+
outcome,
|
|
97194
|
+
reason: failureReason
|
|
97195
|
+
});
|
|
97010
97196
|
};
|
|
97011
|
-
const runProviderBatch = async (textBatch) => {
|
|
97197
|
+
const runProviderBatch = async (textBatch, batchIndex) => {
|
|
97012
97198
|
if (textBatch.oversized) {
|
|
97013
97199
|
const idx = textBatch.indices[0];
|
|
97014
97200
|
const estTokens = estimateTokenCount(texts[idx]);
|
|
97015
97201
|
onSkip?.({
|
|
97016
97202
|
index: idx,
|
|
97017
97203
|
reason: "context-window-exceeded",
|
|
97018
|
-
message: `Document estimated at ${estTokens} tokens exceeds the ${tokenBudget}-token embedding budget; skipped
|
|
97204
|
+
message: `Document estimated at ${estTokens} tokens exceeds the ${tokenBudget}-token embedding budget; skipped.`,
|
|
97205
|
+
batchStart: true,
|
|
97206
|
+
batchSize: 1
|
|
97207
|
+
});
|
|
97208
|
+
commitBatch([idx], [undefined], undefined, {
|
|
97209
|
+
batchIndex,
|
|
97210
|
+
batchCount: textBatches.length,
|
|
97211
|
+
docCount: 1,
|
|
97212
|
+
requestTokens: estTokens,
|
|
97213
|
+
elapsedMs: 0,
|
|
97214
|
+
outcome: "failed",
|
|
97215
|
+
reason: "oversized"
|
|
97019
97216
|
});
|
|
97020
|
-
commitBatch([idx], [undefined]);
|
|
97021
97217
|
return;
|
|
97022
97218
|
}
|
|
97023
|
-
await requestAndCommit(textBatch.indices);
|
|
97219
|
+
await requestAndCommit(textBatch.indices, batchIndex);
|
|
97024
97220
|
};
|
|
97025
97221
|
const concurrency = resolveEmbeddingConcurrency(this.config);
|
|
97026
|
-
await concurrentMap(textBatches, runProviderBatch, concurrency, {
|
|
97222
|
+
await concurrentMap(textBatches, (textBatch, i) => runProviderBatch(textBatch, i + 1), concurrency, {
|
|
97223
|
+
signal: dispatchAbort.signal
|
|
97224
|
+
});
|
|
97027
97225
|
if (callerAbortListener)
|
|
97028
97226
|
signal?.removeEventListener("abort", callerAbortListener);
|
|
97029
97227
|
if (signal?.aborted) {
|
|
@@ -97034,7 +97232,7 @@ class RemoteEmbedder {
|
|
|
97034
97232
|
}
|
|
97035
97233
|
return results;
|
|
97036
97234
|
}
|
|
97037
|
-
async requestBatch(batch, headers, ollamaOpts, signal) {
|
|
97235
|
+
async requestBatch(batch, headers, ollamaOpts, timeoutMs, signal) {
|
|
97038
97236
|
const body = {
|
|
97039
97237
|
input: batch,
|
|
97040
97238
|
model: this.model
|
|
@@ -97049,9 +97247,9 @@ class RemoteEmbedder {
|
|
|
97049
97247
|
method: "POST",
|
|
97050
97248
|
headers,
|
|
97051
97249
|
body: JSON.stringify(body)
|
|
97052
|
-
},
|
|
97250
|
+
}, timeoutMs, signal);
|
|
97053
97251
|
if (!response.ok) {
|
|
97054
|
-
const respBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs:
|
|
97252
|
+
const respBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }).catch((err) => {
|
|
97055
97253
|
if (signal?.aborted)
|
|
97056
97254
|
throw err;
|
|
97057
97255
|
return "";
|
|
@@ -97062,7 +97260,7 @@ class RemoteEmbedder {
|
|
|
97062
97260
|
}
|
|
97063
97261
|
throw new Error(message);
|
|
97064
97262
|
}
|
|
97065
|
-
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs:
|
|
97263
|
+
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: timeoutMs, signal }));
|
|
97066
97264
|
if (!json.data || json.data.length !== batch.length) {
|
|
97067
97265
|
throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}.${embeddingEndpointPathHint(this.endpoint)}`);
|
|
97068
97266
|
}
|
|
@@ -97131,6 +97329,15 @@ function resolveOllamaOptions(config) {
|
|
|
97131
97329
|
function hasRemoteEndpoint(config) {
|
|
97132
97330
|
return isHttpUrl(config.endpoint);
|
|
97133
97331
|
}
|
|
97332
|
+
function describeEmbeddingCredential(apiKey) {
|
|
97333
|
+
if (!apiKey)
|
|
97334
|
+
return "none configured";
|
|
97335
|
+
if (SECRET_STORE_REFERENCE_PATTERN.test(apiKey))
|
|
97336
|
+
return `${apiKey} (store)`;
|
|
97337
|
+
if (ENV_REFERENCE_PATTERN.test(apiKey))
|
|
97338
|
+
return `${apiKey} (env)`;
|
|
97339
|
+
return "literal apiKey";
|
|
97340
|
+
}
|
|
97134
97341
|
|
|
97135
97342
|
// src/llm/embedder.ts
|
|
97136
97343
|
var embedderOverrides;
|
|
@@ -97181,9 +97388,12 @@ function deriveSemanticProviderFingerprint(embedding) {
|
|
|
97181
97388
|
}
|
|
97182
97389
|
return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}`;
|
|
97183
97390
|
}
|
|
97184
|
-
|
|
97391
|
+
function formatEmbeddingHeartbeat(storedCount, total, failedCount) {
|
|
97392
|
+
return `Still generating embeddings: ${storedCount}/${total} stored, ${failedCount} failed; waiting on embedding provider.`;
|
|
97393
|
+
}
|
|
97185
97394
|
var CANARY_SAMPLE_SIZE = 8;
|
|
97186
97395
|
var CANARY_SIMILARITY_THRESHOLD = 0.999;
|
|
97396
|
+
var CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
97187
97397
|
function decideEmbeddingCompatibility(pairs) {
|
|
97188
97398
|
if (pairs.length === 0)
|
|
97189
97399
|
return { outcome: "keep", medianSimilarity: undefined, verifiedSamples: 0 };
|
|
@@ -97271,11 +97481,24 @@ function throwIfAborted(signal) {
|
|
|
97271
97481
|
}
|
|
97272
97482
|
}
|
|
97273
97483
|
async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds, opts) {
|
|
97484
|
+
if (db.inTransaction) {
|
|
97485
|
+
throw new Error("generateEmbeddingsForDb was called with an ambient transaction already open on `db`: per-batch commits " + "would become SAVEPOINTs inside it, losing the crash-durability contract per-batch commit exists for. " + "Run the embedding phase on a connection with no open transaction.");
|
|
97486
|
+
}
|
|
97274
97487
|
throwIfAborted(signal);
|
|
97275
97488
|
if (config.semanticSearchMode === "off") {
|
|
97489
|
+
purgeEmbeddingSalvage(db);
|
|
97276
97490
|
onProgress({ phase: "embeddings", message: "Semantic search disabled; skipping embeddings." });
|
|
97277
97491
|
return { success: false, message: "Semantic search is disabled." };
|
|
97278
97492
|
}
|
|
97493
|
+
if (hasRemoteEndpoint(config.embedding ?? {})) {
|
|
97494
|
+
const endpoint = normalizeEmbeddingEndpoint(config.embedding?.endpoint ?? "");
|
|
97495
|
+
const credential = describeEmbeddingCredential(config.embedding?.apiKey);
|
|
97496
|
+
const configFileSuffix = isVerbose() ? `; config: ${getConfigPath()}` : "";
|
|
97497
|
+
onProgress({
|
|
97498
|
+
phase: "embeddings",
|
|
97499
|
+
message: `[embed] endpoint ${endpoint}, model ${config.embedding?.model ?? "unknown"}; credential: ${credential}${configFileSuffix}`
|
|
97500
|
+
});
|
|
97501
|
+
}
|
|
97279
97502
|
const vecFastPathWasReady = isVecFastPathReady(db);
|
|
97280
97503
|
const currentFingerprint = deriveSemanticProviderFingerprint(config.embedding);
|
|
97281
97504
|
const storedFingerprint = getMeta(db, "embeddingFingerprint");
|
|
@@ -97284,6 +97507,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97284
97507
|
if (opts?.forceReembed) {
|
|
97285
97508
|
db.transaction(() => {
|
|
97286
97509
|
purgeEmbeddings(db, { dropVecTable: true });
|
|
97510
|
+
purgeEmbeddingSalvage(db);
|
|
97287
97511
|
deleteMeta(db, "embeddingDim");
|
|
97288
97512
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97289
97513
|
deleteMeta(db, "embeddingIdentity");
|
|
@@ -97300,6 +97524,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97300
97524
|
if (decision.outcome === "rebuild") {
|
|
97301
97525
|
db.transaction(() => {
|
|
97302
97526
|
purgeEmbeddings(db, { dropVecTable: true });
|
|
97527
|
+
purgeEmbeddingSalvage(db);
|
|
97303
97528
|
deleteMeta(db, "embeddingDim");
|
|
97304
97529
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97305
97530
|
if (decision.identity)
|
|
@@ -97311,6 +97536,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97311
97536
|
rebuildReason = decision.reason;
|
|
97312
97537
|
} else {
|
|
97313
97538
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97539
|
+
relabelEmbeddingSalvageFingerprint(db, storedFingerprint, currentFingerprint);
|
|
97314
97540
|
if (decision.identity)
|
|
97315
97541
|
setMeta(db, "embeddingIdentity", decision.identity);
|
|
97316
97542
|
if (decision.verified) {
|
|
@@ -97321,28 +97547,66 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97321
97547
|
onProgress({ phase: "embeddings", message });
|
|
97322
97548
|
}
|
|
97323
97549
|
}
|
|
97550
|
+
} else {
|
|
97551
|
+
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97324
97552
|
}
|
|
97325
97553
|
try {
|
|
97326
97554
|
throwIfAborted(signal);
|
|
97327
97555
|
const allEntries = getAllEntriesForEmbedding(db, targetEntryIds);
|
|
97328
|
-
|
|
97556
|
+
let vecFailedCount = 0;
|
|
97557
|
+
let vecUnavailableCount = 0;
|
|
97558
|
+
const { reusedCount, remaining: candidateEntries } = reuseSalvagedEmbeddings(db, allEntries, currentFingerprint, (entry, embedding) => {
|
|
97559
|
+
const result = upsertEmbedding(db, entry.id, embedding);
|
|
97560
|
+
if (result.vec === "failed")
|
|
97561
|
+
vecFailedCount++;
|
|
97562
|
+
if (result.vec === "unavailable")
|
|
97563
|
+
vecUnavailableCount++;
|
|
97564
|
+
return result.stored;
|
|
97565
|
+
});
|
|
97566
|
+
if (reusedCount > 0) {
|
|
97567
|
+
onProgress({
|
|
97568
|
+
phase: "embeddings",
|
|
97569
|
+
message: `Reused ${reusedCount} embedding${reusedCount === 1 ? "" : "s"} from the previous generation; embedding ${candidateEntries.length} new.`
|
|
97570
|
+
});
|
|
97571
|
+
}
|
|
97572
|
+
if (candidateEntries.length === 0) {
|
|
97329
97573
|
onProgress({ phase: "embeddings", message: "Embeddings already up to date." });
|
|
97330
97574
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97331
|
-
|
|
97575
|
+
if (reusedCount > 0) {
|
|
97576
|
+
const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
|
|
97577
|
+
setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
|
|
97578
|
+
}
|
|
97579
|
+
purgeEmbeddingSalvage(db);
|
|
97580
|
+
return reusedCount > 0 ? { success: true, vecInsertFailures: vecFailedCount } : { success: true };
|
|
97581
|
+
}
|
|
97582
|
+
const maxInputTokens = config.embedding?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
97583
|
+
let truncatedCount = 0;
|
|
97584
|
+
const texts = [];
|
|
97585
|
+
const pendingEntries = [];
|
|
97586
|
+
for (const entry of candidateEntries) {
|
|
97587
|
+
const capped = capEmbeddingText(entry.searchText, maxInputTokens);
|
|
97588
|
+
if (capped.text.length === 0)
|
|
97589
|
+
continue;
|
|
97590
|
+
if (capped.truncated)
|
|
97591
|
+
truncatedCount++;
|
|
97592
|
+
pendingEntries.push(entry);
|
|
97593
|
+
texts.push(capped.text);
|
|
97594
|
+
}
|
|
97595
|
+
if (truncatedCount > 0) {
|
|
97596
|
+
const message = `[embed] ${truncatedCount} entr${truncatedCount === 1 ? "y" : "ies"} truncated to the ${maxInputTokens}-token embedding cap (embedding.maxInputTokens); rerun with a higher cap to embed the full text.`;
|
|
97597
|
+
onProgress({ phase: "embeddings", message });
|
|
97332
97598
|
}
|
|
97333
97599
|
if (rebuildReason) {
|
|
97334
|
-
const message = `[embed] Re-embedding ${
|
|
97335
|
-
warn(message);
|
|
97600
|
+
const message = `[embed] Re-embedding ${pendingEntries.length} entr${pendingEntries.length === 1 ? "y" : "ies"} because ${rebuildReason}`;
|
|
97336
97601
|
onProgress({ phase: "embeddings", message });
|
|
97337
97602
|
}
|
|
97338
97603
|
onProgress({
|
|
97339
97604
|
phase: "embeddings",
|
|
97340
|
-
message: `Generating embeddings for ${
|
|
97605
|
+
message: `Generating embeddings for ${pendingEntries.length} entr${pendingEntries.length === 1 ? "y" : "ies"}.`
|
|
97341
97606
|
});
|
|
97342
|
-
const texts = allEntries.map((entry) => entry.searchText);
|
|
97343
97607
|
if (isVerbose()) {
|
|
97344
97608
|
if (hasRemoteEndpoint(config.embedding ?? {})) {
|
|
97345
|
-
const tokenBudget = config.embedding?.maxTokens ??
|
|
97609
|
+
const tokenBudget = config.embedding?.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
97346
97610
|
const maxCount = config.embedding?.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
97347
97611
|
const batches = buildTokenBoundedBatches(texts, tokenBudget, maxCount);
|
|
97348
97612
|
const batchNumberByIndex = new Map;
|
|
@@ -97350,7 +97614,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97350
97614
|
for (const i of batch.indices)
|
|
97351
97615
|
batchNumberByIndex.set(i, batchIdx + 1);
|
|
97352
97616
|
});
|
|
97353
|
-
for (const [i, entry] of
|
|
97617
|
+
for (const [i, entry] of pendingEntries.entries()) {
|
|
97354
97618
|
const chars = entry.searchText.length;
|
|
97355
97619
|
const tokens = estimateTokenCount(entry.searchText);
|
|
97356
97620
|
const batch = batches[batchNumberByIndex.get(i) - 1];
|
|
@@ -97358,7 +97622,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97358
97622
|
warnVerbose(`[embed] ${entry.itemRef} (${chars} chars, est. ${tokens} tokens) \u2192 ${label}`);
|
|
97359
97623
|
}
|
|
97360
97624
|
} else {
|
|
97361
|
-
for (const entry of
|
|
97625
|
+
for (const entry of pendingEntries) {
|
|
97362
97626
|
warnVerbose(`[embed] ${entry.itemRef} (${entry.searchText.length} chars, est. ${estimateTokenCount(entry.searchText)} tokens)`);
|
|
97363
97627
|
}
|
|
97364
97628
|
}
|
|
@@ -97367,27 +97631,58 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97367
97631
|
let storedCount = 0;
|
|
97368
97632
|
let skippedCount = 0;
|
|
97369
97633
|
let embedFailedCount = 0;
|
|
97370
|
-
let vecFailedCount = 0;
|
|
97371
|
-
let vecUnavailableCount = 0;
|
|
97372
97634
|
let storedTokens = 0;
|
|
97373
|
-
let lastProgressBucket = 0;
|
|
97374
97635
|
try {
|
|
97375
97636
|
heartbeatTimer = setInterval(() => {
|
|
97376
97637
|
onProgress({
|
|
97377
97638
|
phase: "embeddings",
|
|
97378
|
-
message:
|
|
97639
|
+
message: formatEmbeddingHeartbeat(storedCount, pendingEntries.length, embedFailedCount)
|
|
97379
97640
|
});
|
|
97380
97641
|
}, 15000);
|
|
97381
97642
|
const skips = [];
|
|
97382
97643
|
const embedStart = Date.now();
|
|
97644
|
+
let consecutiveSingleDocFailures = 0;
|
|
97645
|
+
let consecutiveNetworkErrorFailures = 0;
|
|
97646
|
+
let circuitBreakerReason;
|
|
97647
|
+
const onSkip = (skip) => {
|
|
97648
|
+
skips.push(skip);
|
|
97649
|
+
if (!skip.batchStart)
|
|
97650
|
+
return;
|
|
97651
|
+
if (skip.reason === "context-window-exceeded") {
|
|
97652
|
+
consecutiveSingleDocFailures = 0;
|
|
97653
|
+
consecutiveNetworkErrorFailures = 0;
|
|
97654
|
+
return;
|
|
97655
|
+
}
|
|
97656
|
+
consecutiveSingleDocFailures = skip.batchSize === 1 ? consecutiveSingleDocFailures + 1 : 0;
|
|
97657
|
+
consecutiveNetworkErrorFailures = skip.failureKind === "network-error" ? consecutiveNetworkErrorFailures + 1 : 0;
|
|
97658
|
+
if (consecutiveSingleDocFailures >= CIRCUIT_BREAKER_THRESHOLD || consecutiveNetworkErrorFailures >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
97659
|
+
circuitBreakerReason = skip.message;
|
|
97660
|
+
return false;
|
|
97661
|
+
}
|
|
97662
|
+
return;
|
|
97663
|
+
};
|
|
97383
97664
|
let observedModel;
|
|
97384
97665
|
let observedVectorLen;
|
|
97385
|
-
const
|
|
97666
|
+
const reportPerBatchLine = hasRemoteEndpoint(config.embedding ?? {});
|
|
97667
|
+
const onBatch = (indices, batchEmbeddings, model, outcome) => {
|
|
97668
|
+
if (outcome?.outcome === "retrying") {
|
|
97669
|
+
if (reportPerBatchLine) {
|
|
97670
|
+
onProgress({
|
|
97671
|
+
phase: "embeddings",
|
|
97672
|
+
message: `[embed] batch ${outcome.batchIndex}/${outcome.batchCount}: ${outcome.docCount} docs, ${outcome.requestTokens.toLocaleString()} tokens \u2192 retrying after ${(outcome.elapsedMs / 1000).toFixed(1)} s`
|
|
97673
|
+
});
|
|
97674
|
+
}
|
|
97675
|
+
return;
|
|
97676
|
+
}
|
|
97386
97677
|
if (model)
|
|
97387
97678
|
observedModel = model;
|
|
97679
|
+
if (batchEmbeddings.some((embedding) => embedding !== undefined)) {
|
|
97680
|
+
consecutiveSingleDocFailures = 0;
|
|
97681
|
+
consecutiveNetworkErrorFailures = 0;
|
|
97682
|
+
}
|
|
97388
97683
|
db.transaction(() => {
|
|
97389
97684
|
for (let k2 = 0;k2 < indices.length; k2++) {
|
|
97390
|
-
const entry =
|
|
97685
|
+
const entry = pendingEntries[indices[k2]];
|
|
97391
97686
|
if (!entry)
|
|
97392
97687
|
continue;
|
|
97393
97688
|
const embedding = batchEmbeddings[k2];
|
|
@@ -97410,29 +97705,25 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97410
97705
|
vecUnavailableCount++;
|
|
97411
97706
|
}
|
|
97412
97707
|
})();
|
|
97413
|
-
|
|
97414
|
-
|
|
97415
|
-
|
|
97708
|
+
if (outcome && outcome.reason !== "oversized" && reportPerBatchLine) {
|
|
97709
|
+
const elapsedSeconds2 = (outcome.elapsedMs / 1000).toFixed(1);
|
|
97710
|
+
const outcomeLabel = outcome.outcome === "stored" ? `${outcome.docCount} stored (${elapsedSeconds2} s)` : `failed: ${outcome.reason}`;
|
|
97416
97711
|
onProgress({
|
|
97417
97712
|
phase: "embeddings",
|
|
97418
|
-
message: `
|
|
97713
|
+
message: `[embed] batch ${outcome.batchIndex}/${outcome.batchCount}: ${outcome.docCount} docs, ${outcome.requestTokens.toLocaleString()} tokens \u2192 ${outcomeLabel}`
|
|
97419
97714
|
});
|
|
97420
97715
|
}
|
|
97716
|
+
onProgress({
|
|
97717
|
+
phase: "embeddings",
|
|
97718
|
+
message: `Embedded ${storedCount}/${pendingEntries.length} entries.`
|
|
97719
|
+
});
|
|
97421
97720
|
};
|
|
97422
|
-
await embedBatch(texts, config.embedding, signal,
|
|
97721
|
+
await embedBatch(texts, config.embedding, signal, onSkip, onBatch);
|
|
97423
97722
|
throwIfAborted(signal);
|
|
97424
97723
|
const elapsedSeconds = Math.max((Date.now() - embedStart) / 1000, 0.001);
|
|
97425
97724
|
if (skippedCount > 0) {
|
|
97426
97725
|
warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
|
|
97427
97726
|
}
|
|
97428
|
-
if (embedFailedCount > 0) {
|
|
97429
|
-
const detail = skips.slice(0, 20).map((skip) => ` - ${allEntries[skip.index]?.itemRef ?? skip.index} (${skip.reason}): ${skip.message}`).join(`
|
|
97430
|
-
`);
|
|
97431
|
-
const more = skips.length > 20 ? `
|
|
97432
|
-
...and ${skips.length - 20} more` : "";
|
|
97433
|
-
warn(`[embed] ${embedFailedCount} embedding${embedFailedCount === 1 ? "" : "s"} could not be generated and ${embedFailedCount === 1 ? "was" : "were"} skipped:
|
|
97434
|
-
${detail}${more}`);
|
|
97435
|
-
}
|
|
97436
97727
|
const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
|
|
97437
97728
|
setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
|
|
97438
97729
|
if (vecFailedCount > 0) {
|
|
@@ -97440,14 +97731,39 @@ ${detail}${more}`);
|
|
|
97440
97731
|
}
|
|
97441
97732
|
const entriesPerSec = storedCount / elapsedSeconds;
|
|
97442
97733
|
const tokensPerSec = storedTokens / elapsedSeconds;
|
|
97734
|
+
const totalStored = storedCount + reusedCount;
|
|
97735
|
+
const oversizedSkips = skips.filter((skip) => skip.reason === "context-window-exceeded");
|
|
97736
|
+
const timedOutSkips = skips.filter((skip) => skip.reason === "batch-request-failed" && skip.failureKind === "timeout");
|
|
97737
|
+
const failedSkips = skips.filter((skip) => skip.reason === "batch-request-failed" && skip.failureKind !== "timeout");
|
|
97738
|
+
const throughputLine = reusedCount > 0 ? `Stored ${totalStored} embedding${totalStored === 1 ? "" : "s"} (${reusedCount} reused, ${storedCount} newly embedded) in ${elapsedSeconds.toFixed(1)}s (${entriesPerSec.toFixed(1)} entries/s, ~${Math.round(tokensPerSec)} tokens/s)` : `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"} in ${elapsedSeconds.toFixed(1)}s (${entriesPerSec.toFixed(1)} entries/s, ~${Math.round(tokensPerSec)} tokens/s)`;
|
|
97443
97739
|
onProgress({
|
|
97444
97740
|
phase: "embeddings",
|
|
97445
|
-
message:
|
|
97741
|
+
message: `${throughputLine}; ${oversizedSkips.length} oversized skipped, ${timedOutSkips.length} timed out, ${failedSkips.length} failed.`
|
|
97446
97742
|
});
|
|
97743
|
+
const printSkipList = (label, skipList) => {
|
|
97744
|
+
if (skipList.length === 0)
|
|
97745
|
+
return;
|
|
97746
|
+
const limit = isVerbose() ? skipList.length : 20;
|
|
97747
|
+
const listed = skipList.slice(0, limit).map((skip) => ` - ${pendingEntries[skip.index]?.itemRef ?? skip.index}: ${skip.message}`).join(`
|
|
97748
|
+
`);
|
|
97749
|
+
const more = skipList.length > limit ? `
|
|
97750
|
+
...and ${skipList.length - limit} more` : "";
|
|
97751
|
+
onProgress({ phase: "embeddings", message: `[embed] ${label} skipped:
|
|
97752
|
+
${listed}${more}` });
|
|
97753
|
+
};
|
|
97754
|
+
printSkipList("oversized documents", oversizedSkips);
|
|
97755
|
+
printSkipList("timed-out documents", timedOutSkips);
|
|
97756
|
+
printSkipList("failed documents", failedSkips);
|
|
97447
97757
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
97448
97758
|
const observedIdentity = deriveObservedEmbeddingIdentity(config.embedding, observedModel, observedVectorLen);
|
|
97449
97759
|
if (observedIdentity)
|
|
97450
97760
|
setMeta(db, "embeddingIdentity", observedIdentity);
|
|
97761
|
+
if (circuitBreakerReason !== undefined) {
|
|
97762
|
+
const message = `embedding provider failed ${CIRCUIT_BREAKER_THRESHOLD} consecutive batches ` + `(last: ${circuitBreakerReason}); stopped after ${storedCount} embedding${storedCount === 1 ? "" : "s"} ` + "were stored \u2014 rerun akm index when the endpoint is healthy";
|
|
97763
|
+
warn(`[embed] ${message}`);
|
|
97764
|
+
onProgress({ phase: "embeddings", message });
|
|
97765
|
+
return { success: false, message, vecInsertFailures: vecFailedCount };
|
|
97766
|
+
}
|
|
97451
97767
|
if (storedCount === 0 && embedFailedCount > 0) {
|
|
97452
97768
|
const firstMessage = skips[0]?.message ?? "All embeddings failed.";
|
|
97453
97769
|
return {
|
|
@@ -97455,6 +97771,7 @@ ${detail}${more}`);
|
|
|
97455
97771
|
message: `All ${embedFailedCount} embedding batch(es) failed: ${firstMessage}`
|
|
97456
97772
|
};
|
|
97457
97773
|
}
|
|
97774
|
+
purgeEmbeddingSalvage(db);
|
|
97458
97775
|
return { success: true, vecInsertFailures: vecFailedCount };
|
|
97459
97776
|
} finally {
|
|
97460
97777
|
if (heartbeatTimer)
|
|
@@ -97871,7 +98188,11 @@ async function indexWrittenAssets(stashDir, filePaths, options = {}) {
|
|
|
97871
98188
|
return await (async () => {
|
|
97872
98189
|
const rebuildProbe = probeLock(getIndexRebuildLockPath());
|
|
97873
98190
|
if (rebuildProbe.state === "held") {
|
|
97874
|
-
|
|
98191
|
+
const holderLabel = formatLockHolderPid({
|
|
98192
|
+
pid: rebuildProbe.holderPid,
|
|
98193
|
+
launcherPid: rebuildProbe.launcherPid ?? null
|
|
98194
|
+
});
|
|
98195
|
+
warn(`index rebuild in progress (pid ${holderLabel}); the next index pass will index ${filePaths.join(", ")}`);
|
|
97875
98196
|
return true;
|
|
97876
98197
|
}
|
|
97877
98198
|
const dbPath = getDbPath();
|
|
@@ -98845,7 +99166,7 @@ var PROPOSAL_TXN_PHASES = [
|
|
|
98845
99166
|
"committed"
|
|
98846
99167
|
];
|
|
98847
99168
|
function proposalHash(content) {
|
|
98848
|
-
return
|
|
99169
|
+
return createHash11("sha256").update(content).digest("hex");
|
|
98849
99170
|
}
|
|
98850
99171
|
function proposalFileHash(filePath) {
|
|
98851
99172
|
return proposalHash(fs55.readFileSync(filePath));
|